1. 为什么要把现有能力 MCP 化
你可能已经有一堆内部工具:查日志的脚本、调数据的小接口、跑批的定时任务。它们平时靠人手动执行,或者被某个固定系统调用。现在想让大模型代理直接“发现并调用”这些能力,最省事的路径不是给每个模型写一套适配层,而是把它们封装成 MCP 服务器。
MCP 全称 Model Context Protocol,它做的事情可以用一句话说清:把外部能力标准化成 JSON-RPC 2.0 接口,让 AI 客户端通过统一协议发现工具、读取资源、执行调用。它解决的是 N×M 集成问题——不用为每个模型配一个连接器,写一次 MCP Server,Claude、GPT 系客户端、各类 Agent 框架都能接。
适合 MCP 化的服务有几个共同特征:调用频率高、参数简单能用自然语言描述、有明确的输入输出结构、兼具读和写操作。反过来说,参数超过七八个、返回体巨大、需要复杂会话状态的服务,直接封装效果往往不好,得先做一层裁剪。
这篇按完整链路走:先提炼工具特征,再封装成 JSON-RPC 服务,覆盖 stdio 与 Streamable HTTP/SSE 两种传输,最后用 TaoToken 统一 Key 接入并本地验证。目标是你照着能跑通从封装到联调的闭环。
2. TaoToken 前置:统一 Key 与接入点
在封装之前先把 Key 的事情理清楚。MCP Server 本身不绑定某一家模型,但你在本地验证、或者让 Agent 调用工具时,需要一个统一的模型入口。TaoToken 在这里的角色是提供兼容 OpenAI 风格的 API 入口,一个 Key 走通对话与工具调用,省去在多个平台之间切换配置。
你需要先拿到 API Key。进入控制台创建:
https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite创建后在 API Keys 页面复制密钥,形如sk-开头。接入文档在这里,包含 base_url 与各语言示例:
https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewriteAPI 基础地址统一用:
https://taotoken.net/api注意这个地址不带任何查询参数,配置里直接写死即可。Key 的管理页面:
https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite如果你后面要做长期编码或 Agent 类任务,可以了解 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把 Key 存到环境变量,后面所有配置都引用它,避免硬编码:
export TAOTOKEN_API_KEY="sk-你的密钥" export TAOTOKEN_BASE_URL="https://taotoken.net/api"3. 从特征提炼到 JSON-RPC 封装
3.1 提炼工具特征
拿一个具体例子:你有一个内部“订单查询”接口,原本是 HTTP GET,参数是订单号和用户 ID。要 MCP 化,先做特征提炼,判断它能不能成为一个好工具。
判断维度我一般看四条:参数是否少于 5 个、是否能用一句话描述用途、返回是否结构化、是否高频。订单查询满足前三条,参数就两个,返回 JSON,适合封装。如果是一个需要传 12 个筛选条件的报表接口,就得先拆成几个语义清晰的子工具,否则代理选不准。
提炼完写成工具清单,每个工具包含 name、description、inputSchema。description 要写清楚“什么时候用”,不是“这是什么”。比如不要写“查询订单”,要写“根据订单号和用户 ID 查询订单状态与金额,用户询问订单进度时调用”。
3.2 JSON-RPC 服务骨架
MCP 底层是 JSON-RPC 2.0,核心方法有initialize、tools/list、tools/call。手写一个最小 server 能帮你理解协议,但生产里建议用官方 SDK。下面用 Python 的mcp库写一个可运行的骨架。
先装依赖:
pip install "mcp[cli]" httpx服务代码order_server.py:
import os import httpx from mcp.server.fastmcp import FastMCP mcp = FastMCP("order-service") UPSTREAM = "https://internal.example.com/order" API_KEY = os.environ["TAOTOKEN_API_KEY"] @mcp.tool() async def query_order(order_id: str, user_id: str) -> dict: """根据订单号和用户 ID 查询订单状态与金额。 当用户询问订单进度、支付状态或订单金额时调用。 参数 order_id 为订单编号,user_id 为用户标识。""" async with httpx.AsyncClient(timeout=10) as client: resp = await client.get( UPSTREAM, params={"order_id": order_id, "user_id": user_id}, headers={"Authorization": f"Bearer {API_KEY}"}, ) resp.raise_for_status() data = resp.json() return { "order_id": data["id"], "status": data["status"], "amount": data["amount"], } if __name__ == "__main__": mcp.run()这里@mcp.tool()装饰器自动把函数签名转成 JSON Schema,docstring 变成工具描述。返回体做了裁剪,只留代理需要的字段,避免把上游几十个字段全塞回去。
3.3 stdio 与 Streamable HTTP/SSE 两种传输
stdio 传输适合本地进程,客户端启动 server 子进程,通过标准输入输出通信。上面的mcp.run()默认就是 stdio。它的优点是零网络配置、启动快,适合个人开发机和桌面客户端。
Streamable HTTP 适合远程部署,客户端通过 HTTP POST 发 JSON-RPC 请求,服务端可以返回单次响应或 SSE 流。切换方式:
if __name__ == "__main__": mcp.run(transport="streamable-http", host="0.0.0.0", port=8000)启动后端点默认在http://localhost:8000/mcp。SSE 流式响应用于工具执行时间较长、需要边执行边推送进度的场景。如果你的工具是秒级返回,用普通 JSON 响应即可,不必强上 SSE。
两种传输的取舍:本地调试用 stdio,团队共享或云端部署用 Streamable HTTP。协议层一致,工具定义不用改,只换 transport 参数。
4. 可复制配置:settings.json 与 config.toml
4.1 Claude Desktop 风格 settings.json
很多客户端用 JSON 配置 MCP Server。stdio 方式:
{ "mcpServers": { "order-service": { "command": "python", "args": ["/abs/path/order_server.py"], "env": { "TAOTOKEN_API_KEY": "sk-你的密钥" } } } }Streamable HTTP 方式:
{ "mcpServers": { "order-service": { "url": "http://localhost:8000/mcp", "headers": { "Authorization": "Bearer sk-你的密钥" } } } }注意 stdio 配置里args用绝对路径,相对路径在不同客户端工作目录下容易找不到文件,这是高频踩坑点。
4.2 config.toml 风格
部分工具链用 TOML。等价配置:
[[mcp_servers]] name = "order-service" transport = "stdio" command = "python" args = ["/abs/path/order_server.py"] [mcp_servers.env] TAOTOKEN_API_KEY = "sk-你的密钥"HTTP 版本:
[[mcp_servers]] name = "order-service" transport = "streamable-http" url = "http://localhost:8000/mcp" [mcp_servers.headers] Authorization = "Bearer sk-你的密钥"提示:Key 尽量走环境变量注入,配置文件里写占位符,提交到仓库前检查一遍,避免密钥泄露。
5. 验证请求与成功结果
5.1 用 MCP Inspector 本地验证
官方 Inspector 能模拟客户端交互,最直观:
npx @modelcontextprotocol/inspector python /abs/path/order_server.py打开它给出的本地地址,在 Tools 面板点list tools,应该看到query_order及其 schema。再点call tool,填入:
{"order_id": "20250101-001", "user_id": "u_123"}成功时返回:
{ "order_id": "20250101-001", "status": "paid", "amount": 199.00 }5.2 直接发 JSON-RPC 请求验证 HTTP 传输
server 以 streamable-http 启动后,用 curl 验证:
curl -X POST http://localhost:8000/mcp \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -d '{ "jsonrpc": "2.0", "id": 1, "method": "tools/list", "params": {} }'返回里应包含result.tools数组。再调tools/call:
curl -X POST http://localhost:8000/mcp \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -d '{ "jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": { "name": "query_order", "arguments": {"order_id": "20250101-001", "user_id": "u_123"} } }'看到result.content里带文本结果,说明链路通了。注意Accept头必须同时包含application/json和text/event-stream,只写一个会被部分实现拒绝。
5.3 用 TaoToken 验证模型侧工具调用
工具通了,还要确认模型能正确选择它。用兼容 OpenAI 的调用方式,把工具 schema 传进去:
import os from openai import OpenAI client = OpenAI( api_key=os.environ["TAOTOKEN_API_KEY"], base_url="https://taotoken.net/api", ) tools = [{ "type": "function", "function": { "name": "query_order", "description": "根据订单号和用户 ID 查询订单状态与金额", "parameters": { "type": "object", "properties": { "order_id": {"type": "string"}, "user_id": {"type": "string"}, }, "required": ["order_id", "user_id"], }, }, }] resp = client.chat.completions.create( model="gpt-4o-mini", messages=[{"role": "user", "content": "帮我查下订单 20250101-001,用户 u_123"}], tools=tools, ) print(resp.choices[0].message.tool_calls)如果返回里出现tool_calls,且function.name是query_order、arguments 解析正确,说明模型侧识别成功。这一步跑通,整个闭环就成立了。
6. 本篇常见错排查
报错Method not found: tools/list:多半是客户端连到了旧版 SSE 端点,或者 server 没实现tools/list。检查 transport 是否与客户端期望一致,stdio 客户端不要连 HTTP 地址。
stdio 启动后立即退出:常见原因是脚本里有print输出污染了标准输出。stdio 传输下 stdout 只用于 JSON-RPC 消息,任何调试打印都会破坏协议。把调试信息改到 stderr,或用 logging。
HTTP 请求返回 406:Accept头没带全。Streamable HTTP 要求同时接受application/json和text/event-stream,补上即可。
工具调用参数解析失败:inputSchema 里类型写错,比如把数字写成 string。用 Pydantic 模型定义参数能自动生成正确 schema,减少手写错误。
Key 无效 401:确认 base_url 是https://taotoken.net/api,不要多加路径或参数;确认 Key 从控制台复制完整,没有多余空格。Key 管理入口:
https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite远程部署后本地能连、外部连不上:检查 host 是否绑到0.0.0.0,防火墙是否放行端口,反向代理是否透传了Accept头。
工具返回体过大导致代理卡顿:上游返回几十个字段时,在 server 里做字段裁剪,只返回代理决策需要的部分。这一步不做,后面调优会很痛苦。
7. 继续接入与下一步
到这里你已经跑通了提炼、封装、双传输配置、本地验证的完整链路。接下来如果要把这套东西接到真实客户端里长期用,建议先把 Key 和接入文档过一遍,确认 base_url 与鉴权方式:
https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite如果只是想在网页里快速验证模型对工具描述的理解,用模型对话入口最省事:
https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=model-chat&utm_campaign=rewrite要做长期编码或 Agent 类高频任务,Coding Plan 更合适:
https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite我自己的习惯是:每封装一个新工具,先用 Inspector 验证 schema,再用 curl 验证 HTTP 传输,最后用模型侧调用确认工具选择准确。三步都过,才接到生产客户端。这样出问题时能快速定位是协议层、传输层还是模型理解层,不用在一堆日志里瞎猜。