1. 从 Function Calling 到 MCP:Agent 工具调用为什么需要统一标准
如果你正在做 Agent 开发,大概率已经写过不止一套工具定义。给 OpenAI 写一份 JSON Schema,换到 Claude 又要调整字段格式,再换到某个国产模型平台,参数结构又变了。更麻烦的是,你在 LangChain 里注册好的工具,换到另一个框架时几乎要重写一遍注册逻辑。这不是你代码写得不好,而是整个 Agent 工具调用生态长期缺少一套统一标准。
MCP(Model Context Protocol)就是在这个背景下出现的。它是一套开放的、语言无关的通信协议,由 Anthropic 牵头推出,核心目标是让 Agent 和外部工具、数据源之间的连接方式标准化。你可以把它理解成 Agent 世界的 USB-C 接口:一端是各种工具和数据,另一端是各种大模型和 Agent 框架,中间靠 MCP 协议打通。工具只要实现一次 MCP 服务端,所有支持 MCP 的客户端都能直接调用,不需要为每个模型或框架单独适配。
这篇文章面向正在做 Agent 工具调用的开发者,尤其是被 Function Calling 碎片化折磨过的人。我会从 Function Calling 的局限切入,拆解 MCP 的协议原理和架构分层,然后给出一份可复制的 MCP 服务端配置骨架,包含 config.toml 示例和客户端接入验证动作,帮你在本地跑通一次完整的工具调用链路。过程中涉及模型调用和 API 接入的部分,我会用 TaoToken 作为统一接入层来演示,因为它同时支持模型对话和 Coding Plan,适合做 Agent 场景的验证。
2. Function Calling 的碎片化到底卡在哪里
Function Calling 本身不复杂:你定义一组工具,把名称、描述、参数 Schema 传给模型,模型决定调用哪个工具、传什么参数,你执行后把结果返回。问题出在“每个模型和框架都有自己的格式”。
我试过在一个项目里同时对接三个模型平台,工具定义写了三套。OpenAI 用functions字段,Anthropic 用tools字段且参数结构不同,某个国产平台又要求把工具描述嵌在 system prompt 里。每次新增一个工具,三个地方都要改。更头疼的是,这些工具定义和框架强绑定,LangChain 的 Tool 对象没法直接给另一个框架用。
除了格式不统一,还有几个真实痛点。工具无法复用:你写的数据库查询工具只能在当前项目用,换个 Agent 框架就要重写。上下文割裂:工具、资源、知识库分散在不同系统,每次接入都要写胶水代码。权限管控粗放:工具直接暴露给模型,没有统一的权限粒度和审计机制。
这些问题的根源是:Function Calling 只定义了“模型怎么调用工具”,没有定义“工具怎么标准化地暴露给所有客户端”。MCP 补的就是这一层。
3. MCP 的架构分层与三大核心能力
MCP 的架构非常简洁,只有两个核心角色。MCP 客户端(Client)是 Agent 侧,也就是大模型应用、Agent 框架或智能体客户端,负责向服务端发起请求,获取工具、资源、提示词,把结果喂给大模型。MCP 服务端(Server)是工具和数据侧,负责对外暴露自己有哪些工具、哪些资源、哪些提示模板,接收调用并返回结果。两端只通过标准协议交互,互相不需要知道对方的实现细节。你用 Python 写的 MCP 服务,Node.js 写的 Agent 可以直接调;你用 Go 写的工具,Claude 桌面端可以直接用。
MCP 定义了三类标准交互能力,覆盖了 Agent 上下文注入的主要场景。
工具(Tools)是可执行的动作,对应传统的 Function Calling。服务端对外暴露一组可调用的工具,每个工具包含名称、描述、参数 Schema,客户端调用后获取执行结果。和传统 Function Calling 的区别在于格式标准化,一次编写,处处可调用。
资源(Resources)是可读取的数据,这是 MCP 的特色能力。服务端对外暴露一组可读取的资源,用 URI 标识,比如file:///path/to/doc或db://user/123,客户端可以按需读取内容并自动注入到大模型上下文中。适合知识库、文件内容、数据库记录等只读数据。
提示词模板(Prompts)是可复用的指令模板。服务端可以对外提供标准化的提示词模板,客户端调用模板填入参数即可生成高质量 prompt,适合沉淀领域专家提示词和固定工作流指令。
这三者加起来,覆盖了 Agent 从拿数据到用工具再到按指令执行的全流程,并且全部标准化。
4. 可复制的 MCP 服务端配置骨架
下面给出一份最小可跑的 MCP 服务端配置骨架。我用 Python SDK 来演示,因为它的装饰器写法最直观。你不需要自己解析协议,SDK 会处理消息序列化和交互流程。
先安装依赖:
pip install mcp然后创建一个server.py,实现一个最简单的工具服务:
from mcp.server import Server from mcp.server.stdio import stdio_server from mcp.types import Tool, TextContent app = Server("demo-server") @app.list_tools() async def list_tools(): return [ Tool( name="get_weather", description="查询指定城市的天气", inputSchema={ "type": "object", "properties": { "city": {"type": "string", "description": "城市名称"} }, "required": ["city"] } ) ] @app.call_tool() async def call_tool(name: str, arguments: dict): if name == "get_weather": city = arguments["city"] return [TextContent(type="text", text=f"{city} 今天晴,25 摄氏度")] raise ValueError(f"未知工具: {name}") async def main(): async with stdio_server() as (read, write): await app.run(read, write, app.create_initialization_options()) if __name__ == "__main__": import asyncio asyncio.run(main())这份代码做了三件事:能力声明(list_tools告诉客户端有哪些工具)、请求处理(call_tool接收调用并执行逻辑)、结果返回(按标准格式返回TextContent)。剩下的协议解析全部由 SDK 搞定。
接下来是客户端侧的配置。以 Claude Desktop 为例,配置文件通常叫claude_desktop_config.json,但很多团队会用config.toml来统一管理多个 MCP 服务。下面是一份config.toml示例:
[[mcp_servers]] name = "demo-server" command = "python" args = ["/path/to/server.py"] transport = "stdio" [[mcp_servers]] name = "remote-tools" url = "https://your-mcp-server.example.com/sse" transport = "sse"这份配置定义了两个 MCP 服务端:一个本地 STDIO 模式,一个远程 SSE 模式。STDIO 模式适合本地桌面端 Agent 和开发工具,轻量、无网络开销、启动快。SSE 模式基于 HTTP 协议,适合部署在服务器上的远程工具服务。两种模式上层的消息格式完全一致,业务代码无需修改,只换传输层即可。
如果你需要让 Agent 调用大模型来完成工具选择,可以在客户端侧接入 TaoToken 的模型对话能力。TaoToken 的 API 地址是https://taotoken.net/api,支持标准的模型调用格式。你可以在 Agent 的规划环节用它来做工具选择和参数生成,然后把结果传给 MCP 客户端执行。
5. 验证请求与成功结果
配置写好后,需要验证整条链路是否跑通。最直接的方式是用 MCP 官方提供的 Inspector 工具,或者手动发一条初始化请求。
先启动服务端:
python server.py然后在另一个终端用 MCP 客户端连接。如果你用的是支持 MCP 的 Agent 框架,直接在配置里指向server.py即可。手动验证的话,可以用 Python 写一个最小客户端:
from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client async def main(): params = StdioServerParameters(command="python", args=["server.py"]) async with stdio_client(params) as (read, write): async with ClientSession(read, write) as session: await session.initialize() tools = await session.list_tools() print("可用工具:", [t.name for t in tools.tools]) result = await session.call_tool("get_weather", {"city": "北京"}) print("调用结果:", result.content[0].text) if __name__ == "__main__": import asyncio asyncio.run(main())运行后你应该看到类似输出:
可用工具: ['get_weather'] 调用结果: 北京 今天晴,25 摄氏度这说明整条链路已经跑通:客户端初始化、获取工具列表、调用工具、返回结果。整个过程没有写任何协议解析代码,全部由 SDK 处理。
如果你在 Agent 里接入 TaoToken 做模型规划,可以在工具选择环节调用模型对话接口,把 MCP 返回的工具列表作为上下文传给模型,让模型决定调用哪个工具。TaoToken 的模型对话入口在https://taotoken.net/api,接入文档里有完整的请求示例。对于长期做编码和 Agent 开发的场景,Coding Plan 会更划算,适合需要频繁调用模型做工具规划的项目。
6. 本篇常见错排查
第一个常见错误是 STDIO 模式下服务端往 stdout 打印了非协议内容。MCP 的 STDIO 模式用 stdout 传输协议消息,如果你在代码里print了调试信息,客户端会解析失败。解决办法是把调试信息输出到 stderr,或者用日志库写到文件。
第二个错误是工具参数 Schema 写错。inputSchema必须是合法的 JSON Schema,required字段要和properties里的键对应。如果模型传参时缺少必填字段,call_tool里会抛 KeyError。建议在工具函数里做参数校验,返回明确的错误信息。
第三个错误是 SSE 模式下 URL 路径不对。MCP 的 SSE 端点通常需要客户端先发一个 GET 请求建立事件流,再通过 POST 发送请求。如果你用的框架要求特定路径,检查一下是不是少了/sse后缀。
第四个错误是客户端初始化超时。STDIO 模式下如果服务端启动慢,客户端可能在初始化阶段就超时。可以在配置里增加超时时间,或者先手动启动服务端确认能正常运行。
第五个错误是把 MCP 当成框架来用。MCP 只解决工具和资源的标准化接入,不解决流程编排、记忆、规划、状态管理。这些还是需要 Agent 框架来做。MCP 和框架是互补关系,不是替代关系。
7. 接入路径与下一步动作
如果你已经跑通了上面的最小示例,下一步可以把这个模式复制到真实工具上。比如把数据库查询、文件读取、搜索引擎封装成 MCP 服务端,然后在 Agent 里通过 MCP 客户端统一调用。这样你的工具就能跨模型、跨框架复用,不用再为每个平台写适配层。
对于需要模型规划能力的场景,可以在 Agent 侧接入 TaoToken 的模型对话接口,把 MCP 返回的工具列表作为上下文传给模型做工具选择。API Keys 的获取和接入文档在https://taotoken.net/api,里面有完整的请求格式和参数说明。如果你主要做长期编码和 Agent 开发,Coding Plan 的调用方式更适合高频场景,可以在控制台里查看具体的套餐和额度。
MCP 的价值不在于技术有多高深,而在于标准化带来的生态效应。当越来越多的工具以 MCP 形式开放,Agent 开发者不需要一个个对接,插上就能用。你现在就可以从封装第一个 MCP 服务端开始,把手里重复写的工具定义收敛成一套标准实现。