
阿里妹導讀
這篇文章主要記錄了作者在開發 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 LLMs(https://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.ts
exportasyncfunctiongetMcpServersSection(
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, null, 2).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.ts
exportfunction getUseMcpToolDescription(args: ToolArgs): string | undefined {
// 如果沒有 MCP 集線器,不需要此工具描述
if (!args.mcpHub) {
return undefined
}
// 返回標準化的工具描述,包括引數說明和使用示例
return `## use_mcp_tool
Description: 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 schema
Usage:
<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.ts
asyncpresentAssistantMessage() {
// ... 前面的程式碼
case"use_mcp_tool": {
constserver_name: string | undefined = block.params.server_name
consttool_name: string | undefined = block.params.tool_name
constmcp_arguments: string | undefined = block.params.arguments
try {
// 處理部分工具呼叫 - 這是處理未完成的工具呼叫的機制
if (block.partial) {
const partialMessage = JSON.stringify({
type: "use_mcp_tool",
serverName: removeClosingTag("server_name", server_name),
toolName: removeClosingTag("tool_name", tool_name),
arguments: removeClosingTag("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 引數(如果提供)
letparsedArguments: Record<string, unknown> | undefined
if (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.ts
asynccallTool(
serverName: string,
toolName: string,
toolArguments?: Record<string, unknown>,
): 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`)
}
// 從伺服器配置中獲取超時設定
lettimeout: number
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 介面傳送請求
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 {
privateserver: Server;
privatezipHandler: ZipHandler;
privateapiToken: string = "";
privateapiEndpoint: string = "fake";
privateuserId: string = "MCP_PIXELATOR" + "_" + process.env.USER_ID;
privatefrom: string = 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 {
options: CodeGenerationOptions;
};
// 開啟檔案選擇器
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(contents: ZipContents): AstData {
// 首先嚐試找到 AST 檔案
const astFile = contents.files.find(
(f: { name: string; type: string }) =>
(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(
astData: AstData,
options: CodeGenerationOptions,
): 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",
},
body: JSON.stringify({
inputs: {
from: this.from,
options, // 新增 options 到請求中
},
query: astData.ast,
response_mode: "blocking",
conversation_id: "",
user: this.userId,
}),
});
if (!response.ok) {
thrownewError(
`API 請求失敗: ${response.status}${response.statusText}`,
);
}
const data = (await response.json()) asApiServerResponse;
console.log("API 響應資料:", JSON.stringify(data, null, 2));
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}`,
},
],
isError: true,
};
}
// 返回結果
const responseData = result.answer
? (JSON.parse(result.answer) asApiResponseData)
: {};
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資料備份與高效環境搭建
基於快照提供資料保護和環境搭建,實現無代理且有效可靠的資料備份,同時可以快速克隆部署開發測試環境。
點選閱讀原文檢視詳情。