. 总览:从配置到可用的完整时间线
1 2 3 4 5 6 7 8
| 时间线 ──────────────────────────────────────────────────────────────────────▶
┌─────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ 读取 │ │ 拉起 │ │ MCP │ │ 工具 │ │ 等待 │ │ 调用 │ │ mcpServers│→│ 子进程 │→ │ 握手 │→ │ 注入 │→ │ 用户 │→ │ 工具 │ │ 配置 │ │ │ │ │ │ 模型 │ │ 提问 │ │ │ └─────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘ 阶段一 阶段二 阶段三 阶段四 阶段五 阶段五
|
每个阶段的核心问题:
| 阶段 |
核心问题 |
答案 |
| 一 |
从哪里读配置? |
各客户端有固定的配置文件路径 |
| 二 |
怎么拉起 MCP Server? |
执行配置中的 command + args |
| 三 |
怎么知道 Server 可用? |
通过 initialize + tools/list 握手 |
| 四 |
模型怎么知道有工具? |
客户端把工具列表注入模型上下文 |
| 五 |
模型怎么决定调不调用? |
根据用户意图 + 工具描述匹配 |
| 六 |
会话结束后呢? |
客户端杀死子进程 |
2. 阶段一:AI 客户端读取 mcpServers 配置
2.1 配置文件在哪里
当 AI 客户端(如 Claude Code、Cursor、Cline 等)启动时,它会去固定路径读取配置文件。
主流客户端配置路径:
| 客户端 |
macOS 路径 |
Windows 路径 |
| Claude Code |
~/.claude.json |
%USERPROFILE%\.claude.json |
| Claude Desktop |
~/Library/Application Support/Claude/claude_desktop_config.json |
%APPDATA%\Claude\claude_desktop_config.json |
| Cursor |
~/.cursor/mcp.json |
%USERPROFILE%\.cursor\mcp.json |
| Cline (VSCode) |
~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json |
%APPDATA%\Code\User\globalStorage\saoudrizwan.claude-dev\settings\cline_mcp_settings.json |
| Roo Code |
~/Library/Application Support/Code/User/globalStorage/rooveterinaryinc.roo-cline/settings/cline_mcp_settings.json |
同上结构 |
| Trae |
~/.trae/mcp.json |
%USERPROFILE%\.trae\mcp.json |
| Trae CN |
~/.trae-cn/mcp.json |
%USERPROFILE%\.trae-cn\mcp.json |
| Windsurf |
~/.codeium/windsurf/mcp_config.json |
%USERPROFILE%\.codeium\windsurf\mcp_config.json |
| Gemini CLI |
~/.gemini/mcp.json |
%USERPROFILE%\.gemini\mcp.json |
| GitHub Copilot CLI |
~/.config/github-copilot/mcp.json |
%USERPROFILE%\.config\github-copilot\mcp.json |
| VSCode GitHub Copilot |
~/Library/Application Support/Code/User/globalStorage/github.copilot/mcp.json |
同上结构 |
| Kiro |
~/.kiro/mcp.json |
%USERPROFILE%\.kiro\mcp.json |
| Zed |
~/.config/zed/mcp.json |
%USERPROFILE%\.config\zed\mcp.json |
| Cherry Studio |
~/Library/Application Support/cherry-studio/mcp.json |
%APPDATA%\cherry-studio\mcp.json |
| Codex |
~/.codex/mcp.json |
%USERPROFILE%\.codex\mcp.json |
| OpenCode |
~/.opencode/mcp.json |
%USERPROFILE%\.opencode\mcp.json |
| Qwen Code |
~/.qwen/mcp.json |
%USERPROFILE%\.qwen\mcp.json |
| Kilo Code |
~/.kilo/mcp.json |
%USERPROFILE%\.kilo\mcp.json |
| CodeBuddy CLI |
~/.codebuddy/mcp.json |
%USERPROFILE%\.codebuddy\mcp.json |
2.2 配置内容解析
配置文件的标准格式:
1 2 3 4 5 6 7 8
| { "mcpServers": { "mcp-bridge": { "command": "node", "args": ["/Users/yourname/project/packages/mcp-bridge-main/dist/mcp-proxy.js"] } } }
|
各字段含义:
1 2 3 4 5 6 7 8 9 10
| { "mcpServers": { "mcp-bridge": { "command": "node", "args": [ "/absolute/path/to/dist/mcp-proxy.js" ] } } }
|
本质上等价于在终端执行:
1
| node /absolute/path/to/dist/mcp-proxy.js
|
只不过这个命令不是你手动执行,而是 AI 客户端在启动时自动执行。
2.3 多个 MCP Server 共存
一个配置文件中可以同时配置多个 MCP Server:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| { "mcpServers": { "mcp-bridge": { "command": "node", "args": ["/path/to/mcp-bridge/dist/mcp-proxy.js"] }, "filesystem": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/yourname/Documents"] }, "github": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-github"] } } }
|
AI 客户端会逐个启动所有配置的 Server,每个 Server 独立握手,工具列表合并后一起暴露给模型。
3. 阶段二:拉起子进程
3.1 子进程是怎么启动的
AI 客户端读取到配置后,使用类似 Node.js child_process.spawn() 的方式启动子进程:
1 2 3 4 5 6 7 8 9 10 11 12
| AI 客户端内部伪代码:
for (const [name, config] of Object.entries(mcpServers)) { const child = spawn(config.command, config.args, { stdio: ['pipe', 'pipe', 'pipe'] // stdin stdout stderr });
// child.stdin → 客户端可以往里写数据(发请求) // child.stdout → 客户端可以从里读数据(收响应) // child.stderr → 调试日志,客户端通常忽略或记录 }
|
关键点: stdio: ['pipe', 'pipe', 'pipe'] 意味着父进程(AI 客户端)和子进程(mcp-proxy.js)之间通过管道直连。
3.2 进程的父子关系
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| ┌─────────────────────────────────────────────┐ │ AI 客户端进程(父进程) │ │ │ │ ┌─────────────────────────────────────┐ │ │ │ MCP Manager │ │ │ │ │ │ │ │ ┌──────────┐ ┌──────────────┐ │ │ │ │ │ Server A │ │ Server B │ │ │ │ │ │ stdin ──▶│ │ stdin ──▶ │ │ │ │ │ │ ◀── stdout│ │ ◀── stdout │ │ │ │ │ └──────────┘ └──────────────┘ │ │ │ │ node mcp-proxy node other-server │ │ │ └─────────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────────┐ │ │ │ AI 模型 │ │ │ │ 可以使用 Server A + B 的所有工具 │ │ │ └─────────────────────────────────────┘ │ └─────────────────────────────────────────────┘
|
- 子进程的生命周期完全由父进程管理
- 父进程退出时,子进程也会被终止
- 每个 MCP Server 是独立的子进程,互不影响
3.3 启动失败的情况
| 失败原因 |
表现 |
排查方法 |
command 不存在 |
进程无法启动 |
确认 node 在 PATH 中 |
args 路径错误 |
进程启动后立刻崩溃 |
检查 mcp-proxy.js 路径是否正确 |
| 权限不足 |
进程无法启动 |
检查文件执行权限 |
| 依赖缺失 |
进程启动后报错退出 |
先执行 npm install && npm run build |
| 端口已被非 Cocos 占用 |
进程启动成功但无法连接 Cocos |
检查 3456-3466 端口占用 |
4. 阶段三:MCP 协议握手
子进程成功启动后,AI 客户端和 MCP Server 之间开始 JSON-RPC 握手。
4.1 initialize 初始化
AI 客户端发送(写入子进程 stdin):
1 2 3 4 5 6 7 8 9 10 11 12 13
| { "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "protocolVersion": "2024-11-05", "capabilities": {}, "clientInfo": { "name": "claude-code", "version": "1.0.0" } } }
|
mcp-proxy.js 回复(写入 stdout):
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| { "jsonrpc": "2.0", "id": 1, "result": { "protocolVersion": "2024-11-05", "capabilities": { "tools": {} }, "serverInfo": { "name": "cocos-bridge", "version": "1.0.0" } } }
|
这一步的含义:
- 客户端问:你是 MCP Server 吗?你支持什么?
- 服务端答:是的,我支持
tools 能力,协议版本是 2024-11-05
对应源码位置: src/mcp-proxy.ts 第 82-93 行
1 2 3 4 5 6 7 8 9 10 11 12
| if (method === "initialize") { sendToAI({ jsonrpc: "2.0", id: id, result: { protocolVersion: "2024-11-05", capabilities: { tools: {} }, serverInfo: { name: "cocos-bridge", version: "1.0.0" }, }, }); return; }
|
AI 客户端发送:
1 2 3 4 5 6
| { "jsonrpc": "2.0", "id": 2, "method": "tools/list", "params": {} }
|
mcp-proxy.js 的处理流程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 收到 tools/list │ ├─ 1. 扫描本机 3456-3466 端口 │ 对每个端口发 GET /mcp-status │ 收集所有在线 Cocos 实例信息 │ ├─ 2. 选择目标实例端口 │ 已绑定 → 用绑定的端口 │ 未绑定且只有一个实例 → 用那个实例 │ 未绑定且没有实例 → 只返回管理工具 │ ├─ 3. 向目标 Cocos 实例请求内部工具列表 │ GET http://127.0.0.1:{port}/list-tools │ ├─ 4. 拼接管理工具 │ get_active_instances + set_active_instance │ └─ 5. 返回完整工具列表给 AI 客户端
|
返回示例(简化):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| { "jsonrpc": "2.0", "id": 2, "result": { "tools": [ { "name": "get_scene_hierarchy", "description": "获取当前场景的节点树结构", "inputSchema": { "type": "object", "properties": { "depth": { "type": "number", "description": "遍历深度,默认 2" } } } }, { "name": "create_node", "description": "在当前场景中创建一个新节点", "inputSchema": { "type": "object", "properties": { "name": { "type": "string" }, "type": { "type": "string", "enum": ["empty", "sprite", "label", "button"] } }, "required": ["name"] } }, { "name": "get_active_instances", "description": "Scan local ports (3456-3466) to find all running Cocos Creator instances", "inputSchema": { "type": "object", "properties": {} } }, { "name": "set_active_instance", "description": "Manually bind the MCP client to a specific Cocos Creator instance's port", "inputSchema": { "type": "object", "properties": { "port": { "type": "number" } }, "required": ["port"] } } ] } }
|
对应源码位置: src/mcp-proxy.ts 第 96-129 行
4.3 握手完成标志
当 AI 客户端成功收到 initialize 和 tools/list 的响应后:
1 2 3
| ✅ MCP Server "mcp-bridge" 握手成功 ✅ 发现 N 个可用工具 ✅ 标记为"已连接"状态
|
如果任一步骤失败(超时、进程崩溃、JSON 格式错误):
1 2
| ❌ MCP Server "mcp-bridge" 连接失败 ❌ 标记为"不可用"状态
|
5. 阶段四:工具注入模型上下文
5.1 模型看到了什么
握手完成后,AI 客户端把工具列表转化为模型能理解的格式,注入到模型的系统提示(system prompt)或工具定义中。
模型接收到的信息(概念性表示):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| 你现在可以使用以下工具:
工具 1: get_scene_hierarchy 描述: 获取当前场景的节点树结构 参数: - depth (number, 可选): 遍历深度,默认 2 - nodeId (string, 可选): 指定根节点 UUID - includeDetails (boolean, 可选): 是否包含详情
工具 2: create_node 描述: 在当前场景中创建一个新节点 参数: - name (string, 必需): 节点名称 - type (string, 可选): empty/sprite/label/button - parentId (string, 可选): 父节点 UUID
工具 3: update_node_transform 描述: 修改节点的坐标、缩放、颜色或显隐状态 参数: - id (string, 必需): 节点 UUID - x, y, width, height, scaleX, scaleY... (可选)
... 更多工具 ...
|
5.2 description 的重要性
工具的 description 字段是模型决定是否调用工具的最关键依据。
本项目在 description 中嵌入了 AI 安全守则:
1 2 3 4 5
| 【AI 安全守则】: 1. 执行任何写操作前必须先通过 get_scene_hierarchy 验证主体存在 2. 严禁基于假设盲目猜测属性名 3. 资源属性必须通过 UUID 进行赋值 4. 严禁频繁刷新全局资源
|
这些规则会影响模型的行为,比如模型在创建节点前会先调用 get_scene_hierarchy 确认父节点存在。
6. 阶段五:用户提问与工具调用
6.1 模型如何判断是否调用工具
模型的决策过程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| 用户输入 │ ▼ 模型理解用户意图 │ ├─ 是否需要外部信息或操作? │ │ │ ├─ 不需要(知识问答、闲聊等) │ │ → 直接回答,不调用任何工具 │ │ │ └─ 需要(读取场景、创建节点等) │ │ │ ▼ │ 扫描可用工具列表 │ │ │ ├─ 有匹配的工具 │ │ → 构造参数,发起工具调用 │ │ │ └─ 没有匹配的工具 │ → 告诉用户"我无法执行这个操作" │ ▼ 生成响应
|
具体示例:
| 用户说的话 |
模型判断 |
匹配工具 |
原因 |
| “MCP 是什么?” |
不调用 |
无 |
通用知识,直接回答 |
| “看看场景里有哪些节点” |
调用 |
get_scene_hierarchy |
需要读取 Cocos 实时状态 |
| “创建一个叫 Player 的精灵节点” |
调用 |
create_node |
需要操作 Cocos 编辑器 |
| “把 Canvas 移到 (100, 200)” |
先调用 get_scene_hierarchy 再调用 update_node_transform |
两个工具 |
先确认节点存在,再修改 |
| “帮我写一个 TypeScript 工具函数” |
不调用 MCP |
可能用文件工具 |
不涉及 Cocos 操作 |
| “保存当前场景” |
调用 |
save_scene |
需要操作编辑器 |
6.2 调用工具时的数据流
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
| 用户: "在 Canvas 下创建一个叫 StartButton 的按钮"
模型决策: 需要调用 create_node
┌─ AI 客户端 ──────────────────────────────────────────────────┐ │ │ │ 模型输出: │ │ { │ │ "tool": "create_node", │ │ "arguments": { │ │ "name": "StartButton", │ │ "type": "button", │ │ "parentId": "xxxx-xxxx-xxxx" │ │ } │ │ } │ │ │ │ 客户端将其转为 MCP JSON-RPC: │ │ {"jsonrpc":"2.0","id":5,"method":"tools/call", │ │ "params":{"name":"create_node", │ │ "arguments":{"name":"StartButton", │ │ "type":"button", │ │ "parentId":"xxxx"}}} │ │ │ │ 写入 mcp-proxy.js 的 stdin ──────────────────────────────▶ │ └──────────────────────────────────────────────────────────────┘ │ │ stdin 管道 ▼ ┌─ mcp-proxy.js ───────────────────────────────────────────────┐ │ │ │ process.stdin.on("data") 收到 JSON │ │ handleRequest() 识别 method === "tools/call" │ │ │ │ 发起 HTTP 请求: │ │ POST http://127.0.0.1:3456/call-tool │ │ Body: {"name":"create_node", │ │ "arguments":{"name":"StartButton", │ │ "type":"button", │ │ "parentId":"xxxx"}} │ │ │ └──────────────────────────┬───────────────────────────────────┘ │ HTTP ▼ ┌─ Cocos 插件主进程 ────────────────────────────────────────────┐ │ │ │ McpRouter.handleRequest() │ │ → url === "/call-tool" │ │ → CommandQueue.enqueue() │ │ → ToolDispatcher.handleMcpCall("create_node", args, cb) │ │ → Editor.Scene.callSceneScript("mcp-bridge", │ │ "create-node", args, callback) │ │ │ └──────────────────────────┬───────────────────────────────────┘ │ IPC (Editor.Scene.callSceneScript) ▼ ┌─ Cocos 场景进程 ──────────────────────────────────────────────┐ │ │ │ scene-script.ts ["create-node"] │ │ │ │ const node = new cc.Node("StartButton"); │ │ node.parent = findNode("xxxx"); // 挂到 Canvas 下 │ │ // 添加 Button 组件、设置尺寸等 │ │ │ │ Editor.Ipc.sendToMain("scene:dirty"); // 标记需保存 │ │ Editor.Ipc.sendToAll("scene:node-changed"); // 刷新面板 │ │ │ │ event.reply(null, node.uuid); // 返回新节点 UUID │ │ │ └──────────────────────────┬───────────────────────────────────┘ │ ▼ 逐层返回 scene-script → 主进程 callback → HTTP 200 JSON → mcp-proxy.js → stdout 写出 MCP JSON-RPC → AI 客户端接收 → 模型拿到结果 → 组织回答给用户
用户看到: "已在 Canvas 下创建按钮节点 StartButton (UUID: xxxx)"
|
6.3 工具结果如何返回给用户
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| mcp-proxy.js 写入 stdout:
{ "jsonrpc": "2.0", "id": 5, "result": { "content": [ { "type": "text", "text": "{\"uuid\":\"xxxx-xxxx\",\"name\":\"StartButton\"}" } ] } }
|
AI 客户端收到后交给模型。模型根据工具返回的原始数据,组织成自然语言回复给用户。
7. 阶段六:会话结束与进程退出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 用户关闭 AI 客户端 / 结束会话 │ ▼ AI 客户端向子进程发送终止信号 │ ├─ 方式 1: 关闭 stdin 管道 → 子进程检测到 stdin 关闭后自行退出 ├─ 方式 2: 发送 SIGTERM 信号 → 子进程收到信号后退出 └─ 方式 3: 发送 SIGKILL 信号 → 强制杀死子进程 │ ▼ mcp-proxy.js 进程退出 │ ▼ 注意: Cocos Creator 插件的 HTTP 服务不受影响 它仍然在运行,等待下次 AI 客户端连接
|
重要区别:
| 组件 |
生命周期 |
mcp-proxy.js |
随 AI 客户端启停,每次会话重新启动 |
| Cocos 插件 HTTP 服务 |
随 Cocos Creator 编辑器启停,持续运行 |
8. 故障排查指南
8.1 MCP Server 显示不可用
症状: AI 客户端中 mcp-bridge 显示为断开/不可用状态。
排查步骤:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 1. 检查配置文件路径是否正确 → 打开对应客户端的配置文件,确认 mcpServers.mcp-bridge 存在
2. 检查 mcp-proxy.js 是否存在 → ls /你的路径/dist/mcp-proxy.js → 如果不存在,执行 npm run build
3. 手动测试子进程能否启动 → 在终端执行: node /你的路径/dist/mcp-proxy.js → 输入: {"jsonrpc":"2.0","id":1,"method":"initialize","params":{}} → 应该看到 JSON 响应输出
4. 检查 Node.js 版本 → node --version → 需要 Node.js 14+
|
8.2 工具列表为空
症状: MCP Server 已连接,但没有任何 Cocos 相关工具。
排查步骤:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 1. 检查 Cocos Creator 是否已打开 → 必须先打开 Cocos Creator 编辑器
2. 检查 MCP 服务是否已启动 → 在 Cocos Creator 菜单: MCP 桥接器 → 开启MCP设置面板 → 确认服务状态为"运行中"
3. 检查端口是否可达 → 在终端执行: curl http://127.0.0.1:3456/mcp-status → 应该返回项目信息 JSON
4. 如果端口不是 3456 → 查看 MCP 设置面板中显示的实际端口 → 或者 curl 尝试 3457、3458 等
|
8.3 工具调用超时
症状: 调用工具后长时间无响应,最终返回超时错误。
可能原因:
| 原因 |
解决方案 |
| Cocos 编辑器正在执行耗时操作 |
等待编辑器空闲后重试 |
| 场景脚本未加载 |
重新打开场景或重启插件 |
| 指令队列堆积 |
等待队列消化,或重启 MCP 服务 |
| 编辑器正在切换场景 |
等待场景加载完成 |
8.4 多实例冲突
症状: 打开多个 Cocos 项目后,MCP 操作了错误的项目。
解决方案:
1 2 3 4 5 6 7
| 1. 调用 get_active_instances 查看所有在线实例 → 返回每个实例的端口和项目路径
2. 调用 set_active_instance 绑定目标实例 → 参数: {"port": 3457} (填你要操作的项目端口)
3. 之后的所有操作都会发到绑定的端口
|