从零开始的 MCP 开发


从零开始的 MCP 开发

阿里妹导读


这篇文章主要记录了作者在开发 MCP 插件的过程中的学习路径,以及是如何从零用 AI 开发一个小插件的。

前言:我们迎来万能插头?

在 AI 提效上,我们小组的每个人都有自己的独特方式,作为一个沉醉在业务开发+业务样式改版的终端开发,再加上我的 CSS 功底基本上样式就是靠试,每次在 UI 还原部分都是很是痛苦。这样,在团队内部同学完成了 Done 插件转React 代码并完成 OneDay Web 端落地后,我就在想,是否可以在插件端实现一样的能力,就这样 MCP 的能力自然就进入我的视野了。

先看一下效果:

这个小玩具是通过 MCP 协议进行开发的,并且集成在了 OneDay插件中。这篇文章,主要记录了自己在开发 MCP 插件的过程中的学习路径,以及是如何从零用 AI 开发一个小插件的。最后,也是趁着业务大改版的机会,将这个插件结合在我的开发流程中。

MCP 协议简介:AI 的"万能插头"

2024 年 11 月,Anthropic 推出了 Model Context Protocol (MCP),这一开放协议旨在解决 LLM 与外部工具集成的标准化问题。MCP 提供了一种统一的方式,使 AI 模型能够与各种数据源和工具进行交互,被官方形象地称为 AI 应用的"USB-C 端口"。 

MCP 的本质与价值

MCP的核心价值在于提供一种标准化的方式,让 AI 模型与外部世界进行交互。在 MCP 出现之前,开发者需要为每个 AI 集成创建定制化的解决方案,这导致了严重的碎片化问题。 

MCP 解决了这些问题,它提供了以下关键价值: 

  • 统一集成标准一个协议对接所有集成,降低开发难度  

  • 实时数据更新支持动态数据交互而非静态连接  

  • 自动工具发现支持动态工具发现和上下文处理  

  • 隐私保护数据和工具不需上传远端,保护数据隐私  

  • 开发效率显著减少开发时间,提高系统可靠性

核心能力

根据 MCP 协议规范,服务器可以提供三种核心对象: 

从零开始的 MCP 开发

支持程度

目前 MCP 这一概念的火热也让众多 IDE 和框架积极投身在这一领域,其中Claude桌面应用和Continue提供了最全面的MCP支持,包括资源、提示模板和工具集成,使其能够深度整合本地工具和数据源。众多代码编辑器和IDE(如Cursor、Zed、Windsurf Editor和Theia IDE)通过MCP增强了开发工作流程,提供如智能代码生成、AI辅助编码等功能。

在官网的示例中(https://modelcontextprotocol.io/examples),可以发现,越来越多的公司、组织开始积极拥抱 MCP,目前通过 MCP 可以进行本地文件、云端文件的修改Git 相关仓库的阅读与更改基于Puppeteer 进行浏览器自动化和网页抓取,甚至通过EverArt的相关服务可以进行图像生成

一些更抽象的 MCP 服务可以在这里看一看(https://github.com/punkpeye/awesome-mcp-servers

真的是大一统么?

文档上说的很好,MCP 是AI 届的USB-C,使用了 MCP 就意味着你的协议可以在所有的 AI 应用上使用了。

但是,强如 USB-C 现在也没有办法做到真正的大一统,不同厂商之间还是存在着不同。
从零开始的 MCP 开发

所以,“MCP 可能统一,但是 MCP统一不太可能”。

现在针对不同的 AI 终端每个 MCP 支持的能力也是不尽相同的,本文说的只是在 OneDay VSC 插件上的开发体验;

从零开始的 MCP 开发

前置学习一下

看完前面的 MCP 具体协议相关的文档之后,理解能力比较强的老师可能已经知道 MCP 是在干啥了,像我这种 AI 知识早就还给 CV、ML 老师了的同学来说,还是不是很清楚 MCP 具体是咋被调用的。

为了搞清楚MCP 的运作方式,我准备学习一下开源的工具以及 SDK 是如何运作的,作为一个练习时长2 坤年的终端开发,我选择的开源仓库是 Roo 和 MCP Typescript 的 SDK。

如何使用MCP TS进行开发

在 MCP 官网上,赫然写着Building MCP with LLMshttps://modelcontextprotocol.io/tutorials/building-mcp-with-llms),但是本着尊重 AI 的劳动成果的原则还是要学习一下里面具体的内容的。

这部分不太详细展开,具体的 MCP 开发还是参考官网的文档好了。

Client

负责与 MCP 服务器建立连接并发送请求,主要的方法有:

  • connect(transport):连接到服务器

  • request(request, schema, options):发送请求并等待响应

  • close():关闭连接

import { Client } from "@modelcontextprotocol/sdk/client/index.js";

McpServer

提供一个高级 API 来创建 MCP 服务器,主要的方法有:

  • tool(name, schema, handler):注册一个工具

  • resource(name, template, handler):注册一个资源

  • connect(transport):连接到传输层

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";const server new McpServer({  name"我的MCP服务",  version"1.0.0"});

Server

一个低级类,也是本文采用的一个类,低级开发用低级类(bushi

  • setRequestHandler(schema, handler):为特定请求类型设置处理程序

  • connect(transport):连接到传输层

import { Server } from "@modelcontextprotocol/sdk/server/index.js";

传输接口(Transport)

MCP 支持多种传输方式,用于与客户端通信,主要是通过:stdio 传输(命令行应用)和SSE 传输(Web服务器)。

import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
const transport = new StdioServerTransport();await server.connect(transport);
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";import express from "express";
const app = express();
app.get("/sse"async (req, res) => {  const transport = new SSEServerTransport("/messages", res);  await server.connect(transport);});
app.post("/messages"async (req, res) => {  await transport.handlePostMessage(req, res);});
app.listen(3000);

具体的开发流程如下

从零开始的 MCP 开发

Roo 如何调用MCP

Roo 是谁,Cline 优化版罢了

大体上了解了 MCP SDK中的使用方式,那么问题又来了: MCP 集成在客户端上,客户端是如何判断是否需要调用 MCP 以及使用哪个 MCP 的?

打开 Roo 的源码,AI 总结启动...

从零开始的 MCP 开发

可以看出来主要流程有意图识别、工具识别、工具调用这三个主要的步骤。

意图识别

Roo Code使用大型语言模型(LLM)来理解用户的自然语言输入并识别用户的意图。当用户提出一个请求时,LLM会分析请求并决定使用哪些工具来完成任务。

系统提示构建:通过 generatePrompt 函数构建完整的系统提示,包括 MCP 服务器和工具信息。

这使 LLM 能够了解可用的 MCP 服务器及其功能。

这使 LLM 能够了解可用的 MCP 服务器及其功能// src/core/prompts/system.ts// 通过 generatePrompt 函数构建完整的系统提示,包括 MCP 服务器和工具信息。// 这使 LLM 能够了解可用的 MCP 服务器及其功能async function generatePrompt(    context: vscode.ExtensionContext,    cwd: string,    supportsComputerUse: boolean,    mode: Mode,    mcpHub?: McpHub,  // MCP 集线器实例,负责管理所有 MCP 服务器连接    diffStrategy?: DiffStrategy,    browserViewportSize?: string,    // ... 其他参数): Promise<string> {    // ... 前面的代码        // 异步获取两个部分:模式部分和 MCP 服务器部分    const [modesSection, mcpServersSection] = await Promise.all([        getModesSection(context),        // 仅当当前模式包含 mcp 组时才加载 MCP 服务器部分        modeConfig.groups.some((groupEntry) => getGroupName(groupEntry) === "mcp")            ? getMcpServersSection(mcpHub, effectiveDiffStrategy, enableMcpServerCreation)            : Promise.resolve(""),    ])
    // 构建完整的系统提示,包括多个部分    const basePrompt = `${roleDefinition}
${getSharedToolUseSection()}
${getToolDescriptionsForMode(  // 这里会包含 MCP 相关工具的描述    mode,    cwd,    supportsComputerUse,    effectiveDiffStrategy,    browserViewportSize,    mcpHub,    customModeConfigs,    experiments,)}
${getToolUseGuidelinesSection()}
${mcpServersSection}  // 这部分包含所有可用的 MCP 服务器及其工具信息
// ... 其他部分    `
    return basePrompt}

MCP 服务器信息生成getMcpServersSection 方法收集并格式化已连接的 MCP 服务器信息:提供服务器名称、可用工具及其参数架构,让 LLM 知道如何使用它们。

// src/core/prompts/sections/mcp-servers.tsexport async function getMcpServersSection(    mcpHub?: McpHub,    diffStrategy?: DiffStrategy,    enableMcpServerCreation?: boolean,): Promise<string> {    if (!mcpHub) {        return ""    }
    // 构建已连接服务器的信息字符串    const connectedServers =        mcpHub.getServers().length > 0            ? `${mcpHub                    .getServers()                    .filter((server) => server.status === "connected")  // 只显示已连接的服务器                    .map((server) => {                        // 为每个服务器生成其工具列表信息                        const tools = server.tools                            ?.map((tool) => {                                // 为每个工具包含输入模式(如果有)                                const schemaStr = tool.inputSchema                                    ? `    Input Schema:    ${JSON.stringify(tool.inputSchema, null2).split("n").join("n    ")}`                                    : ""
                                return `- ${tool.name}${tool.description}n${schemaStr}`                            })                            .join("nn")
                        // ... 生成资源模板和直接资源信息 ...                                                // 解析服务器配置以显示命令信息                        const config = JSON.parse(server.config)
                        // 返回完整的服务器描述,包括工具、资源模板和直接资源                        return (                            `## ${server.name} (`${config.command}${config.args ? ${config.args.join(" ")}` : ""}`)` +                            (tools ? `nn### Available Toolsn${tools}` : "") +                            (templates ? `nn### Resource Templatesn${templates}` : "") +                            (resources ? `nn### Direct Resourcesn${resources}` : "")                        )                    })                    .join("nn")}`            : "(No MCP servers currently connected)"  // 如果没有连接服务器,显示此消息
    // ... 返回完整部分,包括 MCP 服务器介绍和创建指南 ...}

工具描述提供getUseMcpToolDescription 函数定义了 MCP 工具的使用方法和参数格式。包含使用示例,帮助 LLM 生成正确格式的工具调用。

// src/core/prompts/tools/use-mcp-tool.tsexport function getUseMcpToolDescription(args: ToolArgs): string | undefined {    // 如果没有 MCP 集线器,不需要此工具描述    if (!args.mcpHub) {        return undefined    }        // 返回标准化的工具描述,包括参数说明和使用示例    return `## use_mcp_toolDescription: Request to use a tool provided by a connected MCP server. Each MCP server can provide multiple tools with different capabilities. Tools have defined input schemas that specify required and optional parameters.Parameters:- server_name: (required) The name of the MCP server providing the tool- tool_name: (required) The name of the tool to execute- arguments: (required) A JSON object containing the tool's input parameters, following the tool's input schemaUsage:<use_mcp_tool><server_name>server name here</server_name><tool_name>tool name here</tool_name><arguments>{  "param1""value1",  "param2""value2"}</arguments></use_mcp_tool>
Example: Requesting to use an MCP tool
<use_mcp_tool><server_name>weather-server</server_name><tool_name>get_forecast</tool_name><arguments>{  "city""San Francisco",  "days": 5}</arguments></use_mcp_tool>`}

工具识别&调用

首先通过 use_mcp_tool 工具来解析 LLM 返回的工具调用并验证参数。

// src/core/Cline.tsasync presentAssistantMessage() {    // ... 前面的代码        case "use_mcp_tool": {        const server_namestring | undefined = block.params.server_name        const tool_namestring | undefined = block.params.tool_name        const mcp_argumentsstring | undefined = block.params.arguments        try {            // 处理部分工具调用 - 这是处理未完成的工具调用的机制            if (block.partial) {                const partialMessage = JSON.stringify({                    type"use_mcp_tool",                    serverNameremoveClosingTag("server_name", server_name),                    toolNameremoveClosingTag("tool_name", tool_name),                    argumentsremoveClosingTag("arguments", mcp_arguments),                } satisfies ClineAskUseMcpServer)                await this.ask("use_mcp_server", partialMessage, block.partial).catch(() => {})                break            } else {                // 验证必要参数是否存在                if (!server_name) {                    this.consecutiveMistakeCount++                    pushToolResult(                        await this.sayAndCreateMissingParamError("use_mcp_tool""server_name"),                    )                    break                }                if (!tool_name) {                    this.consecutiveMistakeCount++                    pushToolResult(                        await this.sayAndCreateMissingParamError("use_mcp_tool""tool_name"),                    )                    break                }                                // 解析 JSON 参数(如果提供)                let parsedArgumentsRecord<stringunknown> | undefined                if (mcp_arguments) {                    try {                        parsedArguments = JSON.parse(mcp_arguments)                    } catch (error) {                        // 处理 JSON 解析错误                        this.consecutiveMistakeCount++                        await this.say(                            "error",                            `Roo tried to use ${tool_name} with an invalid JSON argument. Retrying...`,                        )                        pushToolResult(                            formatResponse.toolError(                                formatResponse.invalidMcpToolArgumentError(server_name, tool_name),                            ),                        )                        break                    }                }

 然后通过McpHub.callTool方法来实现 MCP 工具的调用。

// src/core/Cline.ts - 继续上面的代码await this.say("mcp_server_request_started")const toolResult = await this.providerRef    .deref()    ?.getMcpHub()    ?.callTool(server_name, tool_name, parsedArguments)
// src/services/mcp/McpHub.tsasync callTool(    serverNamestring,    toolNamestring,    toolArguments?: Record<stringunknown>,): Promise<McpToolCallResponse> {    // 查找对应的服务器连接    const connection = this.connections.find((conn) => conn.server.name === serverName)    if (!connection) {        throw new Error(            `No connection found for server: ${serverName}. Please make sure to use MCP servers available under 'Connected MCP Servers'.`,        )    }    // 检查服务器是否被禁用    if (connection.server.disabled) {        throw new Error(`Server "${serverName}" is disabled and cannot be used`)    }
    // 从服务器配置中获取超时设置    let timeoutnumber    try {        const parsedConfig = ServerConfigSchema.parse(JSON.parse(connection.server.config))        timeout = (parsedConfig.timeout ?? 60) * 1000  // 默认 60 秒    } catch (error) {        console.error("Failed to parse server config for timeout:", error)        // 解析失败时使用默认值        timeout = 60 * 1000    }
    // 使用 MCP SDK 的 Client 接口发送请求    return await connection.client.request(        {            method"tools/call",            params: {                name: toolName,                arguments: toolArguments,            },        },        CallToolResultSchema,  // 用于验证响应的模式        {            timeout,  // 应用从配置获取的超时值        },    )}

callTool 里面最后的调用还是落在我们创建 Server 时使用的 connection。

connection.client.request(        {            method"tools/call",            params: {                name: toolName,                arguments: toolArguments,            },        },        CallToolResultSchema,        {            timeout,        },    )

问题的答案

讲到这里我们终于可以给之前疑问画上一个句号了,具体的 MCP被调用的链路如下:

1.初始化连接:

  • McpHub 实例化各种 McpConnection

  • 每个连接包含 Client 和 StdioClientTransport/SSEClientTransport

2.工具调用:

  • McpHub.callTool 找到合适的 McpConnection

  • 使用 connection.client.request 发送请求

  • 请求通过 transport 发送到 MCP 服务器

3.服务端处理:

  • McpServer 接收请求

  • 找到对应的工具处理函数

  • 验证参数并执行处理函数

  • 返回结果

4.结果处理:

  • 结果通过 transport 返回

  • Client 解析响应并将其返回给 McpHub

  • McpHub 处理结果并返回给调用者

接下来又到了 AI 画图时间,具体的关系如下:

从零开始的 MCP 开发

MCP-Pixelator 设计

场景分析

回到当前的 MCP 场景,目前图生码的链路已经打通,现在需要解决的问题就很是清晰了,如何把图生码的结果应用在本地 IDE 上。

流程如下: 

1.用户通过 OneDay VSC 等支持 MCP 的 AI 客户端上传 ZIP 文件 

2.MCP 服务器解析 ZIP 文件,提取 AST 数据 

3.服务器调用 AST 转码 API,将 AST 转换为 React 代码 

4.根据用户选择,生成新项目或将组件添加到现有项目 

5.返回生成的代码给用户

这一流程可以通过以下图表直观展示:

 从零开始的 MCP 开发

架构设计

基于 MCP 协议,我们的系统架构如下:

 从零开始的 MCP 开发

系统模块设计

McpPixelator 系统包含以下核心模块: 

1.MCP 服务器模块负责与 AI 客户端通信,处理请求和响应  

2.工具注册模块注册和管理 MCP 工具  

3.文件处理模块解析 ZIP 文件,提取 AST 数据  

4.API 通信模块与 AST 转码 API 进行交互  

5.错误处理模块处理各种异常情况

这些模块之间的关系如下图所示: 

从零开始的 MCP 开发

通过这样的系统设计,我们构建了一个基于 MCP 协议的、能够将设计稿 AST 转换为 React 代码的服务。接下来,SHOW ME THE CODE!

核心代码实现

代码核心实现大部分基于 AI 实现,MCP 插件通过合理的 Prompt 调试+拆分任务维度,很容易就可以实现了。

基本结构与初始化

McpPixelatorServer 类是Ï整个系统的核心,负责初始化 MCP 服务器、设置工具处理器、处理请求等。以下是其基本结构和初始化逻辑: 

class McpPixelatorServer {    private serverServer;    private zipHandlerZipHandler;    private apiTokenstring = "";    private apiEndpointstring = "fake";    private userIdstring = "MCP_PIXELATOR" + "_" + process.env.USER_ID;    private fromstring = process.env.FROM || "unknown";  
  constructor() {      // 初始化token      this.fetchToken()        .then((token) => {          this.apiToken = token;          console.log("Token已更新");        })        .catch((error) => {          console.error("初始化token失败:", error);        });  
    // 初始化MCP服务器      this.server = new Server(        {          name"mcp-pixelator",          version"0.1.0",        },        {          capabilities: {            tools: {},          },        },      );  
    this.zipHandler = new ZipHandler();      this.setupToolHandlers();  
    // 错误处理      this.server.onerror = (error: Error) => console.error("[MCP Error]", error);      process.on("SIGINT"async () => {        await this.server.close();        process.exit(0);      });    }      // 其他方法...  }  

在构造函数中,我们首先初始化 API Token,然后创建 MCP 服务器实例,设置基本配置和能力。接着初始化 ZIP 文件处理器,并设置工具处理器。最后,我们配置错误处理逻辑和进程退出处理。 

工具注册与处理

MCP 协议的核心是工具(Tools)的注册和处理。以下是我们注册工具的代码: 

private setupToolHandlers() {    // 注册工具列表    this.server.setRequestHandler(ListToolsRequestSchema, async () => ({      tools: [        {          name"process_done_zip_and_generate",          description"读取 Done Zip文件并直接生成 React 代码",          inputSchema: {            type"object",            properties: {              options: {                type"object",                properties: {                  type: {                    type"string",                    enum: ["create""add"],                    description"生成代码的类型:create - 创建新项目,add - 添加到现有项目",                    default"create",                  },                  projectPath: {                    type"string",                    description"当 type 为 add 时,需要提供项目路径",                  },                },                required: ["type"],              },            },            required: ["options"],          },        },      ],    }));      // 处理工具调用请求    this.server.setRequestHandler(CallToolRequestSchema, async (request) => {      try {        console.log("收到工具调用请求:", {          工具名称: request.params.name,          参数: request.params.arguments,        });  
      if (request.params.name === "process_done_zip_and_generate") {          // 处理逻辑...        } else {          throw new McpError(            ErrorCode.MethodNotFound,            `未知工具: ${request.params.name}`,          );        }      } catch (error) {        // 错误处理...      }    });  

这段代码首先注册了一个名为 process_done_zip_and_generate 的工具,用于读取 ZIP 文件并生成 React 代码。该工具接受一个 options 参数,包含 type(创建新项目或添加到现有项目)和可选的 projectPath(当 type 为 add 时的项目路径)。

然后,我们设置了处理工具调用的逻辑,根据工具名称执行相应的操作。如果请求的是未知工具,则抛出 MethodNotFound 错误。  

ZIP 文件处理与 AST 提取

当接收到工具调用请求后,我们需要处理 ZIP 文件并提取 AST 数据: 

// 在 CallToolRequestSchema 处理函数中  if (request.params.name === "process_done_zip_and_generate") {    // 获取参数    const { options } = request.params.arguments as {      optionsCodeGenerationOptions;    };  
  // 打开文件选择器    const zipFilePath = await this.zipHandler.selectFile();  
  console.log(`处理ZIP文件: ${zipFilePath}`);  
  // 读取ZIP文件内容    const contents = await this.zipHandler.readZipFile(zipFilePath);  
  // 提取AST数据    const astData = this.extractAstData(contents);  
  console.log("已提取AST数据,开始生成代码");  
  // 直接调用API生成代码    const result = await this.generateReactCode(astData, options);  
  // 处理结果...  }  

AST 数据提取的具体实现如下: 

private extractAstData(contentsZipContents): AstData {    // 首先尝试找到 AST 文件    const astFile = contents.files.find(      (f: { name: stringtypestring }) =>        (f.name === "ast.json" ||          f.name === "ast.txt" ||          f.name.endsWith(".ast")) &&        (f.type === "json" || f.type === "text"),    );  
  if (!astFile || typeof astFile.content !== "string") {      throw new Error("未找到有效的 AST 文件");    }  
  try {      // 尝试解析 JSON      const astContent = JSON.parse(astFile.content);      return { ast: astContent };    } catch (e) {      // 如果解析失败,直接使用原始字符串      console.log("AST 文件解析为 JSON 失败,使用原始字符串");      return { ast: astFile.content };    }  }  

这段代码首先在 ZIP 内容中查找 AST 文件(名为 "ast.json"、"ast.txt" 或以 ".ast" 结尾),然后尝试将其解析为 JSON 对象。如果解析失败,则使用原始字符串。

调用 AST 转码 API

提取 AST 数据后,我们调用外部 API 将其转换为 React 代码: 

private async generateReactCode(    astDataAstData,    optionsCodeGenerationOptions,  ): Promise<ApiResponse> {    // 确保有可用的token    if (!this.apiToken) {      try {        this.apiToken = await this.fetchToken();      } catch (error) {        throw new Error("无法获取API Token: " + error);      }    }  
  console.log("开始生成 React 代码,输入参数:"JSON.stringify(astData));    try {      console.log("发送请求到 API...");      const response = await fetch(this.apiEndpoint, {        method"POST",        headers: {          Authorization`Bearer ${this.apiToken}`,          "Content-Type""application/json",        },        bodyJSON.stringify({          inputs: {            fromthis.from,            options, // 添加 options 到请求中          },          query: astData.ast,          response_mode"blocking",          conversation_id"",          userthis.userId,        }),      });  
    if (!response.ok) {        throw new Error(          `API 请求失败: ${response.status} ${response.statusText}`,        );      }  
    const data = (await response.json()) as ApiServerResponse;      console.log("API 响应数据:"JSON.stringify(data, null2));      return {        answer: data?.answer || undefined,        error: data?.error || data?.message,      };    } catch (error) {      console.error("请求失败:", error);      throw error;    }  }  

这段代码首先确保有可用的 API Token,然后向 AST 转码 API 发送请求,将 AST 数据、用户选项等信息包含在请求体中。如果请求成功,则解析响应数据并返回;如果失败,则抛出相应的错误。

返回结果处理

最后,我们需要处理 API 响应并将结果返回给 AI 客户端: 

// 在 CallToolRequestSchema 处理函数中  const result = await this.generateReactCode(astData, options);  
if (result.error) {    return {      content: [        {          type"text",          text`处理ZIP并生成代码失败: ${result.error}`,        },      ],      isErrortrue,    };  }  
// 返回结果  const responseData = result.answer    ? (JSON.parse(result.answeras ApiResponseData)    : {};  return {    content: [      {        type"text",        text`已成功处理ZIP文件并生成React ${          options.type === "create" ? "项目" : "组件"        }代码:nn${responseData.text || ""}`,      },    ],  };  

这段代码检查 API 响应中是否有错误。如果有,则返回错误信息;如果没有,则解析响应数据并返回生成的 React 代码。

服务器启动与运行

最后,我们需要启动 MCP 服务器,开始监听请求: 

async run() {    const transport = new StdioServerTransport();    await this.server.connect(transport);    console.error("MCP Pixelator server 正在运行...");  }  
// 主程序  const server = new McpPixelatorServer();  server.run().catch(console.error);  

这段代码创建了一个标准输入/输出传输器(StdioServerTransport),并使用它连接 MCP 服务器。服务器成功连接后,输出运行消息,等待 AI 客户端的请求。 

至此,我们已经完成了 McpPixelatorServer 的核心实现。 集成在插件内的整体流程如下:

从零开始的 MCP 开发

实战一下

这是在本次在开发逛逛视频沉浸流的新版本场景为例,将 UI 和逻辑完全拆分后(这部分具体的前端开发理念暂且不提),直接开发 UI 相关的部分。

选中对应设计稿区域:

从零开始的 MCP 开发

使用 Pixelator 插件生成 AST

从零开始的 MCP 开发

从零开始的 MCP 开发

在插件中输入

从零开始的 MCP 开发

选择对应的 Zip 文件

从零开始的 MCP 开发

等待生成(一般耗时 60s 左右)

从零开始的 MCP 开发

生成完成

从零开始的 MCP 开发

代码被插在了一个现有的React Demo 仓库中并找到了对应的位置

从零开始的 MCP 开发

运行一下

从零开始的 MCP 开发

待改进

1.现在还是比较粗暴的方式,用一个 LLM 去调用 MCP,然后用 MCP 调用相关接口(但是这个接口里面也是一个 LLM),再把返回的代码结构吐回来,再来判断是否要新增还是补全;这个有点不够优雅,后续会考虑直接融合在一个对话内。

2.现在还是全 Web 相关代码,在移动端上的适配还是有点弱,针对插件侧的产出,可以和移动端、大屏、Weex相关的内容结合,这样需要二次创作的东西会少一点。

3.现在 D2C 图生码的时间会比较长,可能会存在超时被插件强制超时处理的情况(Roo 的默认超时时间是60s可以配置,OneDay VSC 默认还是 60s 但是并不能配置),这个后面考虑和插件开发同学一道,完成。

一些槽点记录

文件选择器

在直接用 LLM 生成我的诉求的时候,初始的诉求是打开一个文件选择器,并且可以支持用户选择 Zip 文件,LLM 一直在尝试安装 electron 来实现这个文件选择器,矫正了几次都是一直在使用这个,同时还试了一下 inquiry 这个社区包,但是也是没能实现,后面发现了个神奇的脚本,解决了这个问题,也算是有所收获。

osascript -e 'choose file with prompt "选择 ZIP 文件" of type {"ZIP"}' 2>/dev/null

任务之间的衔接

一个大的任务拆成多个子任务时,比如说代码生成任务拆成选择 Zip 并解析和生成 Code 两步后,负责衔接两步任务的是 LLM,因此遇到了很多次,JSON 数据要么 String 了一下 要么直接给我套上了个双引号,总之质疑了自己好几次,最后还是选择了将两个任务合二为一了...

调试

调试是非常之困难...学习了一下官方提供的 inspector,但是对于这个使用骚操作拉起来选择框的情况,是没有办法直接使用 inspector 的只能再魔改...

同时如果想要看 mcp 插件具体返回了什么,还要 console 打印/写文件里面,又是一堆 token 浪费...

参考:

1.mcp 文档:https://modelcontextprotocol.io/introduction

2.Roo 源码:https://github.com/RooVetGit/Roo-Code?tab=readme-ov-file

3.MCP Typescript SDK:https://github.com/modelcontextprotocol/typescript-sdk

4.https://www.linkedin.com/pulse/ai-data-connection-why-anthropics-mcp-matters-chandrakumar-r-pillai-lvepe

5.https://blog.bytebytego.com/p/ep154-what-is-mcp

6.https://www.ali213.net/news/html/2025-1/896671.html

7.Sider DeepResearch

无代理ECS数据备份与高效环境搭建


基于快照提供数据保护和环境搭建,实现无代理且有效可靠的数据备份,同时可以快速克隆部署开发测试环境。    


点击阅读原文查看详情。

版权声明:charles 发表于 2025年4月12日 am9:51。
转载请注明:从零开始的 MCP 开发 | AI工具大全&导航

相关文章