1. 为什么你配好了 MCP 服务器,AI 还是调不动工具
很多人第一次接触 MCP(Model Context Protocol)时,卡点不在"装没装服务器",而在"握手之后到底发生了什么"。你照着文档把服务器进程拉起来了,配置文件也写了,结果 AI 应用里工具列表是空的,或者调用时报Method not found,甚至连接直接断掉。这类问题九成出在对 MCP 客户端-服务器架构和 JSON-RPC 全链路的理解断层上。
MCP 是什么?一句话:它是一套让 AI 应用(Host)通过标准化协议去发现和调用外部能力的规范。能做什么?把文件读写、数据库查询、API 调用这些操作,包装成 AI 可以动态发现的"工具""资源""提示词"。适合谁?正在给 AI 工具接入本地或远程能力、又不想为每个模型单独写适配层的开发者。
这一篇聚焦从握手到执行的完整链路:Host 怎么创建 Client、Client 怎么和 Server 完成initialize能力协商、tools/list怎么发现原语、tools/call怎么执行并回传结果。同时我会用 TaoToken 统一 Key 和 API 通道,把模型侧和 MCP 侧串起来,交付可复制的config.toml与settings.json骨架,并给出一次真实握手请求与执行响应的验证动作。读完你应该能自己判断:问题出在传输层、数据层,还是能力协商阶段。
2. 先把 TaoToken 的 Key 和通道准备好
MCP 本身不负责模型推理,它只管"上下文和工具怎么传"。但你在本地跑通全链路时,总得有个 LLM 来触发工具调用,否则tools/call永远不会被发起。所以第一步是把模型通道统一掉,避免一会儿换一个 Key、一会儿改一个 base_url。
TaoToken 在这里的角色是统一入口:一个 Key 覆盖多家模型,API 地址固定,省得你在多个配置文件里来回粘贴不同的凭证。注册和拿 Key 的入口在官网 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,登录后进控制台创建 API Key,地址是 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite 。API 基址统一用 https://taotoken.net/api ,注意这个地址不带任何查询参数。
拿到 Key 之后,先别急着写 MCP 配置。用一条最朴素的请求确认通道是通的,这一步能帮你排除掉后面 80% 的"到底是 MCP 问题还是模型问题"的扯皮。
curl https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "gpt-4o-mini", "messages": [{"role": "user", "content": "reply with ok"}] }'返回里能看到choices[0].message.content就说明模型通道没问题。把TAOTOKEN_API_KEY写进环境变量,别硬编码进配置文件,后面 MCP 服务器和客户端都会读它。
注意:MCP 的传输层和模型 API 是两条独立的通道。模型通道断了,表现是 AI 不回复;MCP 通道断了,表现是工具列表为空或调用超时。排障时先分清是哪条。
3. 拆开架构:Host、Client、Server 与两层协议
在写配置之前,得先把角色理清楚,否则你会在"我到底该配哪个文件"上浪费半小时。
MCP 采用客户端-服务器架构,三个角色分工明确:Host 是 AI 应用本体,管全局,负责协调一个或多个 Client;Client 是 Host 为每个 Server 创建的专属连接对象,一条连接对应一个 Server,互不干扰;Server 提供能力,暴露工具、资源、提示词。一句话概括就是:Host 管全局,Client 管连接,Server 管能力。
按运行位置,Server 分两种。本地服务器走 STDIO 传输,通过标准输入输出流通信,零网络开销,服务单个 Client;远程服务器走 Streamable HTTP 传输,可同时服务多个 Client,兼容 bearer 令牌、API Key、自定义请求头等认证方式。
协议本身分两层。数据层定义基于 JSON-RPC 2.0 的通信协议,管"说什么",包含生命周期管理、工具/资源/提示词等核心元素、以及通知机制;传输层定义通信机制和通道,管"怎么送达",包含连接建立、消息帧和授权。这个分层最妙的地方在于:不管底层是 STDIO 还是 HTTP,上层 JSON-RPC 消息格式完全一致。所以你调试时抓到的报文,换传输方式后结构不变。
数据层里最核心的概念是原语(Primitives)。服务器可以暴露三个核心原语:工具(Tools)是可执行函数,资源(Resources)是上下文数据源,提示词(Prompts)是可复用模板。每个原语都关联发现(*/list)、检索(*/get)以及执行(tools/call)方法。工作流是:先*/list发现,再按需调用。这个设计让工具列表可以动态更新,而不是写死的。
4. 可复制的 config.toml 与 settings.json 骨架
现在进入实操。下面这份config.toml是 MCP 服务器侧的配置骨架,我把它放在项目根目录,用 STDIO 传输启动一个本地 Server。
# config.toml - MCP Server 侧配置骨架 [server] name = "local-tools" version = "1.0.0" protocol_version = "2025-06-18" [transport] type = "stdio" # 本地用 stdio,远程改 "streamable-http" command = "python" args = ["-m", "my_mcp_server"] [capabilities] tools = { listChanged = true } # 声明支持工具原语且列表可变 resources = {} # 声明支持资源原语 prompts = {} # 声明支持提示词原语 [model] # 模型侧统一走 TaoToken,一个 Key 覆盖多模型 base_url = "https://taotoken.net/api" api_key_env = "TAOTOKEN_API_KEY" default_model = "gpt-4o-mini"对应的settings.json是 Host 侧的客户端配置,告诉 AI 应用去哪里找 Server、怎么连。
{ "mcpServers": { "local-tools": { "command": "python", "args": ["-m", "my_mcp_server"], "env": { "TAOTOKEN_API_KEY": "${TAOTOKEN_API_KEY}" }, "transport": "stdio" }, "remote-tools": { "url": "https://taotoken.net/api/mcp", "transport": "streamable-http", "headers": { "Authorization": "Bearer ${TAOTOKEN_API_KEY}" } } }, "model": { "base_url": "https://taotoken.net/api", "api_key_env": "TAOTOKEN_API_KEY" } }几个关键点值得单独说。capabilities里的listChanged = true决定了服务器后续能不能主动推送notifications/tools/list_changed,如果你没声明却指望收到通知,那是收不到的。transport字段决定走 STDIO 还是 HTTP,本地调试优先 STDIO,因为不涉及网络和认证,出问题好定位。env里用${TAOTOKEN_API_KEY}引用环境变量,避免把 Key 写进版本库。
提示:远程 Server 的
url和模型 API 的base_url是两个不同的端点,别混用。模型走/api/v1/chat/completions,MCP 走它自己的路径。
5. 一次握手请求与执行响应的完整验证
配置写完了,怎么确认链路真的通了?别只看 AI 有没有回复,要抓 JSON-RPC 报文。下面按顺序走一遍。
第一步,握手。Client 向 Server 发initialize,协商协议版本和能力。
{ "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "protocolVersion": "2025-06-18", "capabilities": { "elicitation": {} }, "clientInfo": { "name": "example-client", "version": "1.0.0" } } }Server 的响应会亮出它的底牌:
{ "jsonrpc": "2.0", "id": 1, "result": { "protocolVersion": "2025-06-18", "capabilities": { "tools": { "listChanged": true }, "resources": {} }, "serverInfo": { "name": "example-server", "version": "1.0.0" } } }看到capabilities.tools存在,说明服务器支持工具原语。握手成功后,Client 必须补发一个通知,否则 Server 不会进入就绪状态:
{ "jsonrpc": "2.0", "method": "notifications/initialized" }第二步,发现工具。发tools/list,不需要参数。
{ "jsonrpc": "2.0", "id": 2, "method": "tools/list" }响应里的tools数组就是可用工具清单,每个工具带name、description、inputSchema。inputSchema是 JSON Schema 格式,标注了哪些参数必填、哪些可选。这一步拿到的name是后面调用的"主键",必须精确匹配。
第三步,执行工具。用发现阶段拿到的完整名称发起tools/call。
{ "jsonrpc": "2.0", "id": 3, "method": "tools/call", "params": { "name": "weather_current", "arguments": { "location": "San Francisco", "units": "imperial" } } }响应是内容对象数组,type字段标识内容类型:
{ "jsonrpc": "2.0", "id": 3, "result": { "content": [ { "type": "text", "text": "Current weather in San Francisco: 68F, partly cloudy." } ] } }到这里,从握手到执行的链路就闭环了。如果你在 AI 应用里操作,Host 会自动完成这些步骤:初始化时逐一连接 Server 并缓存能力,把各 Server 的工具汇总成统一注册表交给 LLM,LLM 决定调用后 Host 拦截请求、路由到对应 Server、把结果追加回对话流。
第四步,验证通知。如果你的 Server 声明了listChanged = true,当工具列表变动时它会主动推送:
{ "jsonrpc": "2.0", "method": "notifications/tools/list_changed" }注意这条消息没有id字段,遵循 JSON-RPC 2.0 的通知语义——发了就完事,不等回复。Client 收到后应该立即重新发一次tools/list刷新本地注册表,形成"通知到刷新"的闭环。
6. 本篇常见错排查
报错一:Method not found。最常见的原因是握手没完成就发tools/list。MCP 是有状态协议,必须先initialize再发notifications/initialized,之后才能调其他方法。顺序错了,Server 会直接拒绝。
报错二:工具列表为空。先确认initialize响应里有没有capabilities.tools。如果服务器压根没声明工具能力,tools/list返回空数组是正常的。再检查settings.json里的command和args能不能手动跑起来,进程起不来自然没工具。
报错三:tools/call报参数校验失败。对照tools/list返回的inputSchema检查参数名和类型。required数组里的字段一个都不能少,enum字段的值必须在允许范围内。工具名也要精确匹配,weather_current不能写成weather。
报错四:连接建立后立刻断开。多半是协议版本不匹配。protocolVersion字段如果双方不一致,MCP 会直接终止连接,绝不含糊。把 Client 和 Server 的版本对齐到同一个日期版本。
报错五:收不到list_changed通知。检查初始化时服务器有没有声明"listChanged": true。没声明就不会发,这是能力协商决定的,不是 bug。
报错六:模型不触发工具调用。这通常是模型侧问题,不是 MCP 问题。确认 TaoToken 通道正常、模型支持 function calling、工具描述写得够清楚。工具描述太模糊,LLM 不知道该在什么时候调用。
排障时建议按"传输层到数据层"的顺序查:先确认进程/网络通不通,再确认 JSON-RPC 报文格式对不对,最后确认能力协商和原语调用。抓包看报文是最快的定位方式,因为不管 STDIO 还是 HTTP,上层消息格式完全一致。
7. 把链路跑通之后,下一步做什么
链路跑通只是起点。真正让 MCP 好用的,是理解原语的动态性:工具列表可以随服务状态、外部依赖、用户权限变化而增减,通知机制让 Client 无需轮询就能保持同步。你可以试着给 Server 加一个工具,观察list_changed通知怎么触发 Client 刷新注册表,这个体感比读十遍文档都强。
如果你要长期跑编码类或 Agent 类任务,建议把模型通道固定下来,用 TaoToken 的 Coding Plan 统一管理额度,入口在 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite 。想先在网页里验证模型对工具调用的理解,可以直接用模型对话 https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite 。接入细节和字段说明看文档 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,Key 管理在控制台 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite 。Claude Code 相关的接入配置参考 https://taotoken.net/ClaudeCodeAnthropic?utm_source=taotoken_aicg_blog_end&utm_content=claudecode&utm_campaign=rewrite 。
最后留一个我踩过的坑:STDIO 传输下,Server 往标准输出里打任何非 JSON-RPC 的日志,都会污染消息流导致解析失败。调试信息一律走标准错误,别走标准输出。这个细节不写进文档,但能让你少熬一个晚上。