1. 从“只会聊天”到“动手做事”,中间差了什么
大语言模型能写诗、能改代码、能陪你聊一整天,但你让它“帮我查一下数据库里昨天的订单量”,它只能礼貌地告诉你它做不到。原因不复杂:模型训练完之后,知识就冻结在参数里了,它没法主动去读你的文件、调你的接口、碰你的数据库。Function Calling 在 2023 年补上了这块短板——你写一个函数,把函数名、参数格式、描述打包发给模型,模型判断需要调用时吐出结构化 JSON,你的程序执行完再把结果塞回去,模型据此生成自然语言回答。这套流程跑通之后,模型确实“能动手”了。
但真把它放进项目里,问题就来了。Function Calling 的函数定义通常跟具体模型服务绑死,换一家模型供应商,工具描述格式可能就得重写;三个项目都要用同一个“查天气”函数,你得复制三份实现,语言不一致还得翻译一遍;团队里 A 用 Python 写工具、B 用 TypeScript 写工具,调用方式各玩各的,代码库碎成一地。MCP(Model Context Protocol,模型上下文协议)就是冲着这些痛点来的:它把工具调用从“写死在应用里”解耦成“独立服务”,用一套开放协议规定好 Server 怎么暴露能力、Client 怎么发现和调用,做到一次开发、多处复用。这篇就带你从零把 MCP 服务端和客户端配起来,跑通一条真实的工具调用链路,让你手里的模型从“能聊天”升级成“能干活”。
2. 前置准备:TaoToken 接入与 MCP 运行环境
MCP 本身是协议层的东西,它不绑定任何一家模型服务。但你要验证“模型能不能正确发起工具调用”,就得有一个能响应 Function Calling / Tool Use 的模型端点。我这边用 TaoToken 来做模型接入层,原因是它同时提供 OpenAI 兼容接口和 Claude Code 兼容接口,MCP 客户端配置里改个 base_url 就能切换,省得为了测一个协议去折腾多套鉴权。
先拿 Key。打开 https://taotoken.net/api-keys ,登录后创建一个 API Key,复制出来存好,后面客户端配置里要用。注意这个 Key 只在创建时完整显示一次,关掉页面就看不到了,建议直接贴进密码管理器。
模型对话调试入口在 https://taotoken.net/model-chat ,你可以在网页里先发一条带工具定义的请求,确认模型能吐出 tool_calls 结构,再去配本地 MCP 客户端,这样排障时能快速区分是“模型不响应工具调用”还是“MCP 链路配置错了”。
如果你打算长期跑编码类 Agent(比如让模型通过 MCP 读写本地文件、执行命令),可以看下 Coding Plan:https://taotoken.net/coding-plan ,它针对高频编码场景做了额度优化,比按量计费更适合天天跑 Agent 的人。
接入文档在 https://taotoken.net/doc ,里面列了 OpenAI 兼容端点和 Anthropic 兼容端点的具体路径差异,配 MCP 客户端时对着改就行。API 基础地址统一用 https://taotoken.net/api ,不要加多余路径,具体端点拼接方式文档里有表格。
环境方面,你需要:
- Node.js 18+(大部分 MCP Server 是 npm 包,用 npx 直接跑)
- Python 3.10+(如果你要写 Python 版 MCP Server)
- 一个支持 MCP 的客户端,比如 Claude Desktop、Cursor,或者自己写一个基于 mcp SDK 的 Client
3. 可复制配置:MCP 服务端 config.toml 骨架
MCP Server 的职责是暴露工具。下面这个 config.toml 骨架定义了一个本地文件读取 Server,提供两个工具:read_file 和 list_dir。你可以直接复制,改掉 root 路径就能用。
# mcp-server-filesystem/config.toml [server] name = "local-filesystem" version = "0.1.0" description = "提供本地文件读取与目录列举能力的 MCP Server" [transport] # stdio 模式:客户端通过标准输入输出与 Server 通信,适合本地进程 type = "stdio" command = "npx" args = ["-y", "@modelcontextprotocol/server-filesystem", "/Users/yourname/workspace"] [capabilities] tools = true resources = true prompts = false [[tools]] name = "read_file" description = "读取指定路径的文本文件内容,返回 UTF-8 字符串" [tools.inputSchema] type = "object" properties.path = { type = "string", description = "相对于 workspace 根目录的文件路径,例如 docs/readme.md" } required = ["path"] [[tools]] name = "list_dir" description = "列出指定目录下的文件和子目录名称" [tools.inputSchema] type = "object" properties.path = { type = "string", description = "相对于 workspace 根目录的目录路径,例如 src" } required = ["path"] [limits] max_file_size_kb = 512 allowed_extensions = [".md", ".txt", ".json", ".toml", ".py", ".ts"]几个关键点解释一下。transport.type 选 stdio 是因为本地开发最省事,客户端拉起 Server 进程后直接走管道通信,不用开端口。command 和 args 是客户端启动 Server 时执行的命令,这里用 npx 拉官方 filesystem server,最后一个参数是允许访问的根目录,务必改成你自己的路径,别写/或者用户主目录,否则模型能读到你所有文件。capabilities 里 tools = true 表示这个 Server 暴露工具能力,resources = true 表示还暴露资源(比如可以直接把某个文件作为 context 注入),prompts = false 表示不提供预置提示词模板。limits 是我自己加的约束段,官方 server 不一定认这个字段,但你在自研 Server 里可以实现它,用来限制单文件大小和允许的扩展名,防止模型一口气读个几百 MB 的日志把上下文撑爆。
如果你要写自己的 MCP Server,Python 侧最小骨架长这样:
# my_mcp_server.py from mcp.server import Server from mcp.server.stdio import stdio_server from mcp.types import Tool, TextContent app = Server("my-tools") @app.list_tools() async def list_tools(): return [ Tool( name="get_order_count", description="查询指定日期的订单数量", inputSchema={ "type": "object", "properties": { "date": {"type": "string", "description": "日期,格式 YYYY-MM-DD"} }, "required": ["date"] } ) ] @app.call_tool() async def call_tool(name: str, arguments: dict): if name == "get_order_count": date = arguments["date"] # 这里替换成你真实的数据库查询 count = 1024 return [TextContent(type="text", text=f"{date} 的订单量为 {count}")] 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())装依赖用pip install mcp,跑起来用python my_mcp_server.py,它就会在 stdio 上等客户端发初始化请求。
4. 客户端 settings.json 配置与工具调用链路验证
客户端这边以 Claude Desktop 风格的 settings.json 为例,其他 MCP 客户端字段名可能略有差异,但结构一致。
{ "mcpServers": { "local-filesystem": { "command": "npx", "args": [ "-y", "@modelcontextprotocol/server-filesystem", "/Users/yourname/workspace" ], "env": { "MCP_LOG_LEVEL": "info" } }, "my-order-tools": { "command": "python", "args": ["/Users/yourname/mcp-servers/my_mcp_server.py"], "env": { "DB_HOST": "127.0.0.1", "DB_PORT": "5432" } } }, "model": { "provider": "openai-compatible", "baseUrl": "https://taotoken.net/api", "apiKey": "sk-你的TaoToken密钥", "modelName": "claude-sonnet-4-20250514" } }mcpServers 下每个键是一个 Server 实例名,客户端启动时会并行拉起这些进程。command + args 跟服务端 config.toml 里写的启动命令对应。env 可以传环境变量,比如数据库连接信息,注意别把密钥硬编码进 args 里,放 env 相对安全一点,但生产环境还是建议走密钥管理服务。model 段是模型接入配置,baseUrl 填 https://taotoken.net/api ,apiKey 填你刚才创建的 Key,modelName 填你要用的模型标识。如果你的客户端走 Anthropic 兼容协议,baseUrl 和鉴权头格式按接入文档调整。
配好之后重启客户端,它会在启动时向每个 Server 发送 initialize 请求,Server 返回自己支持的能力列表。你可以在客户端日志里看到类似这样的输出:
[mcp] server local-filesystem initialized, capabilities: tools, resources [mcp] server my-order-tools initialized, capabilities: tools [mcp] discovered 3 tools: read_file, list_dir, get_order_count接下来做一次真实调用验证。在对话里输入:“帮我看看 workspace 下 docs 目录里有哪些文件,然后读一下 readme.md 的前 200 个字。”
模型收到请求后,会先发起 list_dir 调用:
{ "type": "tool_use", "id": "toolu_01ABC", "name": "list_dir", "input": { "path": "docs" } }客户端拦截到这个 tool_use,转发给 local-filesystem Server,Server 执行后返回:
{ "type": "tool_result", "tool_use_id": "toolu_01ABC", "content": [{ "type": "text", "text": "readme.md\napi.md\nchangelog.md" }] }客户端把结果回传给模型,模型接着发起 read_file 调用,拿到内容后生成最终回答。整条链路跑通,说明 MCP 配置没问题。如果模型只回“我无法访问文件系统”,那大概率是 Server 没启动成功或者工具列表没被发现,去看客户端日志里有没有 initialize 失败的报错。
5. 本篇常见错排查
Server 启动即退出,日志显示 command not found。客户端拉起 Server 时用的 PATH 可能跟你终端里不一样。npx 找不到就写绝对路径,比如/usr/local/bin/npx。Python 同理,用which python查出来填进去。
工具列表为空,但 Server 进程活着。检查 Server 的 capabilities 声明。有些 Server 默认不暴露 tools,需要在启动参数里加--enable-tools之类的 flag。另外确认客户端版本支持 MCP,老版本可能只认 resources 不认 tools。
模型不发起工具调用,直接编答案。两个原因:一是模型本身对 tool_use 支持不好,换个支持 Function Calling 的模型试;二是工具 description 写得太模糊,模型判断不出什么时候该用。把 description 写具体,比如“查询指定日期的订单数量”比“查订单”好得多,参数 description 也要写清楚格式示例。
调用返回 401 或 403。检查 apiKey 有没有多余空格,baseUrl 是不是写成了https://taotoken.net/api/带尾斜杠,有些客户端对尾斜杠敏感。另外确认 Key 没有过期或被禁用,去 console 里看一眼状态。
stdio 通信卡死,请求发出去没响应。常见于 Server 往 stdout 打了非协议内容,比如 print 调试信息。MCP 走 stdio 时 stdout 只能传协议 JSON,调试日志必须走 stderr。检查你的 Server 代码里有没有裸 print。
文件读取报 permission denied。Server 启动时传入的根目录参数决定了它能访问的范围,模型请求的路径如果解析后超出这个范围会被拒绝。确认你传的根目录包含了目标文件,并且路径拼接时没有../逃逸。
6. 把 MCP 接进你的日常工具链
配通一次之后,后面就是复制粘贴改路径的事。我自己的做法是把常用的 MCP Server 分成两类:一类是通用能力(文件系统、HTTP 请求、SQLite 查询),直接跑官方实现;另一类是业务专属(查内部订单、读配置中心),自己写 Server 暴露成工具。客户端 settings.json 里把这两类都挂上,模型就能在一个对话里同时调文件、查库、发请求。
想让模型侧调试更顺手,可以去 https://taotoken.net/model-chat 里手动构造带 tools 的请求,观察模型返回的 tool_calls 结构是否符合预期,确认没问题再写进客户端配置。接入细节和端点差异查 https://taotoken.net/doc ,Key 管理在 https://taotoken.net/api-keys ,长期跑编码 Agent 的话 Coding Plan 在 https://taotoken.net/coding-plan 。Claude Code 相关的 Anthropic 兼容配置参考 https://taotoken.net/claude-code-anthropic ,控制台入口在 https://taotoken.net/console 。
一个实用技巧:给每个 MCP Server 的日志加个前缀,比如[fs]、[order],客户端日志混在一起时能快速定位是哪个 Server 出的问题。另外工具数量别一次挂太多,模型在几十个工具里选容易选错,按场景分组、用的时候再启用对应的 Server,准确率会高不少。