1. 从一次 MCP 调用失败说起settings.json 到底在管什么如果你在 Cursor 里配过 MCP大概率遇到过这种场景配置文件写完了重启 Cursor工具列表里那个 server 是灰的或者干脆没出现。点开日志一看报错信息只有一行spawn npx ENOENT或者MCP error -32000: Connection closed完全不知道从哪下手。这个问题的根源在于很多人把mcpServers当成了 MCP 协议的一部分。实际上它跟 MCP 协议本身没有半点关系。mcpServers是 Cursor 作为 MCP Host 的进程启动描述它只解决一件事怎么把 MCP Server 这个进程拉起来然后通过 stdio 跟它说话。至于这个 Server 内部暴露了哪些 tools、schema 长什么样、用 Java 还是 Python 写的mcpServers一概不关心。把这两层分清楚之后配置语法就变得非常好理解了。command是可执行文件args是参数数组env是注入的环境变量三个字段各司其职。报错定位也有了方向进程起不来就看command和args进程起来了但握手失败就看 stdout 有没有被日志污染工具列表为空就看tools/list返回了什么。这篇会从settings.json的骨架开始把每个字段的含义、Cursor 对 MCP Server 的运行时假设、以及一次完整调用链的每个环节拆开讲。最后给一段可以直接复制的配置配合逐项验证动作让你在本地跑通一次从自然语言到 tool result 的完整链路。如果你在配置过程中需要确认模型侧的连通性可以用 TaoToken 模型对话 先验证一下 API 是否正常避免把模型问题和 MCP 配置问题混在一起排查。2. TaoToken 前置把模型侧和 MCP 侧解耦在拆配置语法之前先花几分钟把模型侧的接入确认掉。原因很简单MCP 调用链的最后一环是 LLM 决定要不要调 tool如果模型 API 本身不通你会在 Cursor 里看到一堆莫名其妙的超时然后误以为是 MCP Server 的问题。TaoToken 在这里的角色是提供兼容 OpenAI 协议的模型接入层。你需要在 Cursor 的模型设置里填上 API 地址和 Key让 Cursor 能把上下文和 tools 描述发给模型。具体操作是登录 TaoToken 控制台在 API Keys 页面 创建一个 Key然后在 Cursor 的 Settings → Models 里把 OpenAI API Base 改成https://taotoken.net/api填入 Key。这里有个容易踩的坑Cursor 的模型设置和 MCP 设置是两个独立的入口。模型设置管的是 LLM 请求走哪个 endpointMCP 设置管的是本地进程怎么启动。两者互不影响但调用链上又是串联的。所以排查问题时先用 TaoToken 模型对话 确认模型能正常返回再去折腾mcpServers。如果你打算长期在 Cursor 里跑 Agent 类的编码任务MCP tool 调用会非常频繁每次调用都会消耗 token。这种情况下可以看一下 Coding Plan它的计费方式对高频 tool call 场景更友好。接入细节可以参考 接入文档里面有 Cursor 的具体配置截图。3. settings.json 骨架mcpServers 的通用结构与字段语义Cursor 的 MCP 配置入口在 Settings → Tools MCP → New MCP Server点进去之后你会看到一个 JSON 编辑器。这个 JSON 的顶层结构是固定的{ mcpServers: { server-id: { command: executable, args: [arg1, arg2], env: { key: value } } } }这个结构在 Node、Python、Java、Go 之间是完全通用的。下面逐项拆开。3.1 server-id逻辑标识不参与协议server-id是 MCP Server 的逻辑 ID只用于 Cursor 内部管理、UI 展示和日志提示。它可以是任意字符串推荐用 kebab-case。关键点是这个 ID 不会传给 Server也不参与 MCP 协议。你叫它memory还是my-memory-server对 Server 进程没有任何影响。3.2 command进程入口只能是一个可执行文件command的语义是启动 MCP Server 的可执行文件。这里有一条硬性规则只能是一个可执行文件不允许带参数。参数必须拆到args里。{ command: npx, args: [-y, modelcontextprotocol/server-memory] }上面这个配置里npx是 command-y和包名是 args。如果你写成command: npx -y modelcontextprotocol/server-memoryCursor 会尝试找一个名字叫npx -y modelcontextprotocol/server-memory的可执行文件然后报ENOENT。常见的 command 值包括npx、node、python3、java、/usr/bin/go以及 Windows 上的绝对路径如C:\\Users\\wtyy\\AppData\\Local\\Programs\\WtyyHelper\\wtyyhelper-mcp.exe。3.3 args参数数组顺序严格保留args是传给 command 的参数数组等价于 shell 里的command arg1 arg2 arg3。规则是必须是数组每个元素是一个独立参数顺序严格保留Cursor 不做拼接也不做转义。{ command: java, args: [-jar, /path/to/server.jar] }{ command: python3, args: [server.py] }{ command: npx, args: [-y, playwright/mcplatest, --browser, chrome] }注意--browser和chrome是两个独立的数组元素不能合并成一个字符串。3.4 env环境变量注入会覆盖系统值env是可选字段用于在启动 MCP Server 时注入环境变量。Key 和 Value 都必须是字符串会和系统环境合并如果冲突则覆盖系统值。{ env: { MEMORY_FILE_PATH: C:\\Users\\wtyy\\.mcp-storage\\memory.json, LOG_LEVEL: debug } }在配置文件里硬编码敏感信息是有风险的。MCP 配置支持通过${}语法引用环境变量{ mcpServers: { secure-api: { type: http, url: https://api.example.com/mcp, headers: { Authorization: Bearer ${API_TOKEN}, X-API-Key: ${API_KEY:-default-key} } } } }${VAR_NAME}直接引用变量不存在会报错${VAR_NAME:-default}在变量不存在时使用默认值。这个语法在 stdio 类型的 server 里同样适用于env字段。3.5 一份完整的配置示例把上面几个字段组合起来一份包含 stdio 和 SSE 两种类型的配置长这样{ mcpServers: { memory: { command: npx, args: [-y, modelcontextprotocol/server-memory], env: { MEMORY_FILE_PATH: C:\\Users\\wtyy\\.mcp-storage\\memory.json } }, sequential-thinking: { command: npx, args: [-y, modelcontextprotocol/server-sequential-thinking] }, playwright: { command: npx, args: [-y, playwright/mcplatest, --browser, chrome] }, gitlab: { command: npx, args: [-y, zereight/mcp-gitlab], env: { GITLAB_PERSONAL_ACCESS_TOKEN: glpat-******, GITLAB_API_URL: https://git.example.com, GITLAB_READ_ONLY_MODE: false } }, local-helper: { command: C:\\Users\\wtyy\\AppData\\Local\\Programs\\WtyyHelper\\wtyyhelper-mcp.exe, args: [--mcp], env: {} }, amap-sse: { url: https://mcp.amap.com/sse?keyYOUR_AMAP_KEY } } }注意最后那个amap-sse用的是url字段而不是command这是 SSE 类型的远程 MCP Server不需要本地启动进程。4. 调用链拆解从自然语言到 tool result 的完整路径配置写对了只是第一步真正跑通需要理解 Cursor 和 MCP Server 之间的通信过程。这条链路可以拆成七个环节。4.1 Cursor 启动 MCP Server 进程Cursor 读取mcpServers配置后会为每个 server-id 启动一个独立的进程。启动命令就是commandargs拼接环境变量是系统环境加上env字段的覆盖。进程启动后Cursor 通过 stdin 写入、stdout 读取来通信。这里有一条硬性规则stdio 是唯一通道。Cursor 不支持 socket、http、grpc 作为本地 server 的通信方式Server 也不需要监听任何端口。一个 Server 对应一个进程Cursor 退出时 Server 进程结束。4.2 初始化握手initialize进程启动后Cursor 发送的第一个请求是initialize{ jsonrpc: 2.0, id: 1, method: initialize, params: { clientInfo: { name: cursor, version: x.y.z } } }MCP Server 需要返回自己的能力声明{ jsonrpc: 2.0, id: 1, result: { capabilities: { tools: {} } } }如果返回里没有capabilities.toolsCursor 会认为这个 Server 不支持 tools后续不会向它发起 tool 调用。4.3 拉取工具列表tools/list握手成功后Cursor 发送tools/list请求{ jsonrpc: 2.0, id: 2, method: tools/list }Server 返回自己暴露的所有工具{ jsonrpc: 2.0, id: 2, result: { tools: [ { name: get_user_by_id, description: 根据用户 id 查询用户信息name、email、age, inputSchema: { type: object, properties: { id: { type: integer, description: 用户唯一 ID } }, required: [id] } } ] } }description和inputSchema是 LLM 判断要不要调用这个工具的核心依据。description 写得越清楚模型命中率越高。4.4 LLM 决定是否调用 toolCursor 把当前会话的上下文、所有可用工具的 name/description/inputSchema 一起发给 LLM。LLM 根据用户的自然语言输入决定是否要调用某个 tool以及传什么参数。这里有个实际经验如果配置了很多 MCP Server而 tools 名称很相似模型可能会误调用。可以在 prompt 里明确指定比如「调用 get_git_mr_diffs 这个 mcp分析这个 MR 的改动」命中率会明显提升。4.5 Cursor 发起 tools/callLLM 返回 tool call 意图后Cursor 解析出要调用的 tool 名称和参数向对应的 MCP Server 发送{ jsonrpc: 2.0, id: 3, method: tools/call, params: { name: get_user_by_id, arguments: { id: 123 } } }4.6 MCP Server 执行并返回结果Server 收到请求后执行业务逻辑返回结果{ jsonrpc: 2.0, id: 3, result: { content: [ { type: json, json: { id: 123, name: Alice, email: aliceexample.com, age: 28 } } ] } }4.7 Cursor 把结果喂回 LLMCursor 拿到 tool result 后把它作为上下文的一部分再发给 LLMLLM 生成最终回复。至此一次完整的调用链结束。整个链路里mcpServers只负责第 1 步的进程启动后面 6 步都是 MCP 协议在管。这就是为什么配置语法和协议要分开理解。5. 常见报错定位从进程启动到握手失败的排查路径配置写完之后跑不通报错信息往往很模糊。下面按调用链的顺序把常见报错和定位方法列出来。5.1 spawn ENOENTcommand 找不到这是最常见的报错意思是 Cursor 尝试启动command指定的可执行文件但系统 PATH 里找不到。排查步骤先在终端里手动执行一遍commandargs的拼接命令看能不能跑起来。如果终端里能跑但 Cursor 里报 ENOENT通常是 PATH 环境变量的问题。Cursor 启动子进程时继承的是系统环境变量如果你用的是 nvm 管理的 nodenpx 的路径可能不在系统 PATH 里。解决办法是用绝对路径。在终端里执行which npxmacOS/Linux或where npxWindows把结果填到command里。5.2 Connection closed进程启动后立即退出这个报错说明进程起来了但很快就退出了Cursor 还没来得及完成握手。常见原因有三个一是args里的包名写错了npx 下载失败后退出二是env里缺少必要的环境变量Server 启动时校验失败三是 Server 把日志写到了 stdout污染了 JSON-RPC 通道。排查方法是把command和args拿到终端里手动执行观察输出。如果终端里能看到正常的启动日志但 Cursor 里报 Connection closed那大概率是 stdout 污染问题。5.3 工具列表为空capabilities.tools 没返回进程正常启动、握手也完成了但 Cursor 的工具列表里看不到这个 Server 的 tools。这说明initialize的返回里没有capabilities.tools或者tools/list返回了空数组。检查 Server 代码里initialize的响应确认capabilities字段里有tools。如果是用现成的 Server 包检查版本是否匹配。5.4 stdout 污染日志写错流了这是最隐蔽的问题。MCP 协议规定stdout 只能输出 MCP JSON-RPC 消息stderr 可以输出任意日志。如果 Server 把调试日志打到了 stdoutCursor 会解析失败判定 Server 异常。排查方法是手动运行 Server观察 stdout 的输出。如果看到非 JSON 的内容就是污染了。解决办法是把日志重定向到 stderr比如在 Python 里用print(..., filesys.stderr)在 Node 里用console.error。5.5 JSON-RPC 格式问题一行一个 JSONMCP 的 JSON-RPC 消息要求一行一个 JSONUTF-8 编码必须 flush。Cursor 不支持 chunked 或 streaming。如果 Server 返回的 JSON 跨了多行或者没有 flushCursor 会一直等不到完整消息。5.6 环境变量引用失败${VAR_NAME} 报错如果配置里用了${API_TOKEN}但系统环境里没有这个变量Cursor 会报错。检查方式是确认变量已经在系统环境里设置或者改用${API_TOKEN:-default}提供默认值。6. 语义一致 CTA把配置跑通之后配置跑通之后你会看到 Cursor 的工具列表里出现你配置的 MCP Server展开能看到具体的 tools。这时候可以在对话里输入「调用 get_user_by_id 查询 id 为 123 的用户」观察 Cursor 是否命中这个 tool以及返回结果是否符合预期。如果调用链在模型侧出现问题比如 LLM 一直不调用 tool或者返回超时可以回到 TaoToken 模型对话 单独验证模型是否正常。如果需要在 Cursor 里长期跑 Agent 任务Coding Plan 的计费方式更适合高频 tool call 场景。接入过程中遇到配置问题接入文档 里有 Cursor 的完整配置示例API Keys 页面 可以管理你的 Key。最后留一个实用技巧配置多个 MCP Server 时先用最小的配置跑通一个确认调用链完整之后再逐个添加。每加一个就重启 Cursor 验证一次这样出问题时能快速定位是哪个 Server 的配置有问题。