從零開始的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 這一概念的火熱也讓眾多 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統一不太可能”。
現在針對不同的 AI 終端每個 MCP 支援的能力也是不盡相同的,本文說的只是在 OneDay VSC 外掛上的開發體驗;

前置學習一下
看完前面的 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";constserver 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 = newStdioServerTransport();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 = newSSEServerTransport("/messages", res);await server.connect(transport);});app.post("/messages"async (req, res) => {await transport.handlePostMessage(req, res);});app.listen(3000);

具體的開發流程如下

Roo 如何呼叫MCP
Roo 是誰,Cline 最佳化版罷了
大體上了解了 MCP SDK中的使用方式,那麼問題又來了: MCP 整合在客戶端上,客戶端是如何判斷是否需要呼叫 MCP 以及使用哪個 MCP 的?
開啟 Roo 的原始碼,AI 總結啟動…

可以看出來主要流程有意圖識別、工具識別、工具呼叫這三個主要的步驟。

意圖識別

Roo Code使用大型語言模型(LLM)來理解使用者的自然語言輸入並識別使用者的意圖。當用戶提出一個請求時,LLM會分析請求並決定使用哪些工具來完成任務。
系統提示構建:透過 generatePrompt 函式構建完整的系統提示,包括 MCP 伺服器和工具資訊。
這使 LLM 能夠了解可用的 MCP 伺服器及其功能。
這使 LLM 能夠了解可用的 MCP 伺服器及其功能// src/core/prompts/system.ts// 透過 generatePrompt 函式構建完整的系統提示,包括 MCP 伺服器和工具資訊。// 這使 LLM 能夠了解可用的 MCP 伺服器及其功能asyncfunctiongeneratePrompt(    context: vscode.ExtensionContext,    cwd: string,    supportsComputerUse: boolean,    mode: Mode,    mcpHub?: McpHub,  // MCP 集線器例項,負責管理所有 MCP 伺服器連線    diffStrategy?: DiffStrategy,    browserViewportSize?: string,// ... 其他引數): Promise<string> {// ... 前面的程式碼// 非同步獲取兩個部分:模式部分和 MCP 伺服器部分const [modesSection, mcpServersSection] = awaitPromise.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.tsexportasyncfunctiongetMcpServersSection(    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("\n\n")                        // ... 生成資源模板和直接資源資訊 ...                        // 解析伺服器配置以顯示命令資訊const config = JSON.parse(server.config)                        // 返回完整的伺服器描述,包括工具、資源模板和直接資源return (`## ${server.name} (\`${config.command}${config.args ? ${config.args.join(" ")}` : ""}\`)` +                            (tools ? `\n\n### Available Tools\n${tools}` : "") +                            (templates ? `\n\n### Resource Templates\n${templates}` : "") +                            (resources ? `\n\n### Direct Resources\n${resources}` : "")                        )                    })                    .join("\n\n")}`            : "(No MCP servers currently connected)"// 如果沒有連線伺服器,顯示此訊息// ... 返回完整部分,包括 MCP 伺服器介紹和建立指南 ...}
工具描述提供getUseMcpToolDescription 函式定義了 MCP 工具的使用方法和引數格式。包含使用示例,幫助 LLM 生成正確格式的工具呼叫。
// src/core/prompts/tools/use-mcp-tool.tsexportfunction 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.tsasyncpresentAssistantMessage() {// ... 前面的程式碼case"use_mcp_tool": {constserver_namestring | undefined = block.params.server_nameconsttool_namestring | undefined = block.params.tool_nameconstmcp_argumentsstring | undefined = block.params.argumentstry {// 處理部分工具呼叫 - 這是處理未完成的工具呼叫的機制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)awaitthis.ask("use_mcp_server", partialMessage, block.partial).catch(() => {})break            } else {// 驗證必要引數是否存在if (!server_name) {this.consecutiveMistakeCount++pushToolResult(awaitthis.sayAndCreateMissingParamError("use_mcp_tool""server_name"),                    )break                }if (!tool_name) {this.consecutiveMistakeCount++pushToolResult(awaitthis.sayAndCreateMissingParamError("use_mcp_tool""tool_name"),                    )break                }// 解析 JSON 引數(如果提供)letparsedArgumentsRecord<stringunknown> | undefinedif (mcp_arguments) {try {                        parsedArguments = JSON.parse(mcp_arguments)                    } catch (error) {// 處理 JSON 解析錯誤this.consecutiveMistakeCount++awaitthis.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 - 繼續上面的程式碼awaitthis.say("mcp_server_request_started")const toolResult = awaitthis.providerRef    .deref()    ?.getMcpHub()    ?.callTool(server_name, tool_name, parsedArguments)
// src/services/mcp/McpHub.tsasynccallTool(serverNamestring,toolNamestring,    toolArguments?: Record<stringunknown>,): Promise<McpToolCallResponse> {// 查詢對應的伺服器連線const connection = this.connections.find((conn) => conn.server.name === serverName)if (!connection) {thrownewError(`No connection found for server: ${serverName}. Please make sure to use MCP servers available under 'Connected MCP Servers'.`,        )    }// 檢查伺服器是否被停用if (connection.server.disabled) {thrownewError(`Server "${serverName}" is disabled and cannot be used`)    }// 從伺服器配置中獲取超時設定lettimeoutnumbertry {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 介面傳送請求returnawait 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-Pixelator 設計
場景分析
回到當前的 MCP 場景,目前圖生碼的鏈路已經打通,現在需要解決的問題就很是清晰了,如何把圖生碼的結果應用在本地 IDE 上。
流程如下: 
1.使用者透過 OneDay VSC 等支援 MCP 的 AI 客戶端上傳 ZIP 檔案
2.MCP 伺服器解析 ZIP 檔案,提取 AST 資料 
3.伺服器呼叫 AST 轉碼 API,將 AST 轉換為 React 程式碼 
4.根據使用者選擇,生成新專案或將元件新增到現有專案 
5.返回生成的程式碼給使用者
這一流程可以透過以下圖表直觀展示:

架構設計
基於 MCP 協議,我們的系統架構如下:

系統模組設計
McpPixelator 系統包含以下核心模組: 
1.MCP 伺服器模組負責與 AI 客戶端通訊,處理請求和響應  
2.工具註冊模組註冊和管理 MCP 工具  
3.檔案處理模組解析 ZIP 檔案,提取 AST 資料  
4.API 通訊模組與 AST 轉碼 API 進行互動  
5.錯誤處理模組處理各種異常情況
這些模組之間的關係如下圖所示: 

透過這樣的系統設計,我們構建了一個基於 MCP 協議的、能夠將設計稿 AST 轉換為 React 程式碼的服務。接下來,SHOW ME THE CODE!
核心程式碼實現
程式碼核心實現大部分基於 AI 實現,MCP 外掛透過合理的 Prompt 除錯+拆分任務維度,很容易就可以實現了。
基本結構與初始化
McpPixelatorServer 類是Ï整個系統的核心,負責初始化 MCP 伺服器、設定工具處理器、處理請求等。以下是其基本結構和初始化邏輯: 
classMcpPixelatorServer {  privateserverServer;  privatezipHandlerZipHandler;  privateapiTokenstring = "";  privateapiEndpointstring = "fake";  privateuserIdstring = "MCP_PIXELATOR" + "_" + process.env.USER_ID;  privatefromstring = 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 = newServer(        {  name"mcp-pixelator",  version"0.1.0",        },        {  capabilities: {  tools: {},          },        },      );  this.zipHandler = newZipHandler();  this.setupToolHandlers();  // 錯誤處理  this.server.onerror = (error: Error) =>console.error("[MCP Error]", error);      process.on("SIGINT"async () => {  awaitthis.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 {  thrownewMcpError(            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.argumentsas {  optionsCodeGenerationOptions;    };  // 開啟檔案選擇器  const zipFilePath = awaitthis.zipHandler.selectFile();  console.log(`處理ZIP檔案: ${zipFilePath}`);  // 讀取ZIP檔案內容  const contents = awaitthis.zipHandler.readZipFile(zipFilePath);  // 提取AST資料  const astData = this.extractAstData(contents);  console.log("已提取AST資料,開始生成程式碼");  // 直接呼叫API生成程式碼  const result = awaitthis.generateReactCode(astData, options);  // 處理結果...  }  
AST 資料提取的具體實現如下: 
privateextractAstData(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") {  thrownewError("未找到有效的 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 程式碼: 
privateasyncgenerateReactCode(  astDataAstData,  optionsCodeGenerationOptions,  ): Promise<ApiResponse> {  // 確保有可用的token  if (!this.apiToken) {  try {  this.apiToken = awaitthis.fetchToken();      } catch (error) {  thrownewError("無法獲取API Token: " + error);      }    }  console.log("開始生成 React 程式碼,輸入引數:"JSON.stringify(astData));  try {  console.log("傳送請求到 API...");  const response = awaitfetch(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) {  thrownewError(  `API 請求失敗: ${response.status}${response.statusText}`,        );      }  const data = (await response.json()) asApiServerResponse;  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 = awaitthis.generateReactCode(astData, options);  if (result.error) {  return {  content: [        {  type"text",  text`處理ZIP並生成程式碼失敗: ${result.error}`,        },      ],  isErrortrue,    };  }  // 返回結果  const responseData = result.answer  ? (JSON.parse(result.answerasApiResponseData)    : {};  return {  content: [      {  type"text",  text`已成功處理ZIP檔案並生成React ${          options.type === "create" ? "專案" : "元件"      }程式碼:\n\n${responseData.text || ""}`,      },    ],  };  
這段程式碼檢查 API 響應中是否有錯誤。如果有,則返回錯誤資訊;如果沒有,則解析響應資料並返回生成的 React 程式碼。 
伺服器啟動與執行
最後,我們需要啟動 MCP 伺服器,開始監聽請求: 
asyncrun() {  const transport = newStdioServerTransport();  awaitthis.server.connect(transport);  console.error("MCP Pixelator server 正在執行...");  }  // 主程式  const server = newMcpPixelatorServer();  server.run().catch(console.error);  
這段程式碼建立了一個標準輸入/輸出傳輸器(StdioServerTransport),並使用它連線 MCP 伺服器。伺服器成功連線後,輸出執行訊息,等待 AI 客戶端的請求。 
至此,我們已經完成了 McpPixelatorServer 的核心實現。 整合在外掛內的整體流程如下:

實戰一下
這是在本次在開發逛逛影片沉浸流的新版本場景為例,將 UI 和邏輯完全拆分後(這部分具體的前端開發理念暫且不提),直接開發 UI 相關的部分。
選中對應設計稿區域:
使用 Pixelator 外掛生成 AST

在外掛中輸入

選擇對應的 Zip 檔案

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

生成完成

程式碼被插在了一個現有的React Demo 倉庫中並找到了對應的位置

執行一下
待改進
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資料備份與高效環境搭建
基於快照提供資料保護和環境搭建,實現無代理且有效可靠的資料備份,同時可以快速克隆部署開發測試環境。    
點選閱讀原文檢視詳情。

相關文章