MCP是什么?怎么实现?从function call到AI Agent的落地路径
发布时间:2026/10/2 20:26:32来源:尧图网络
1. 从 function call 到 MCP为什么工具调用需要一层协议如果你已经用 Claude 或其它大模型写过 function call大概率遇到过这种局面给 OpenAI 写的一套工具描述换到 Anthropic 的接口上要重写一遍参数结构、返回格式、错误码全都不一样。工具本身逻辑没变但为了适配不同厂商你得维护好几份“壳”。MCPModel Context Protocol想解决的正是这件事——它把 function call 从“每家一套的私有约定”抬升成“统一协议 运行框架 工具注册机制”。一句话定位MCP 是 Anthropic 推出的工具调用标准协议本质仍是 function call但规定了工具怎么描述、怎么注册、怎么被模型发现和调用。你可以把它类比成 USB-C以前每个设备一个充电口现在统一了接口工具开发者只写一份 MCP Server任何支持 MCP 的客户端Claude Desktop、Cursor 等都能挂载使用。它适合谁三类人最该关注一是正在做 AI Agent、需要让模型操作外部系统的开发者二是写了很多 function call 但被多厂商适配折磨的人三是想理解“Agent 动手能力”到底怎么落地的人。这篇不讲空概念我会带你从协议理解走到最小可运行实现写一个 MCP Server、配好客户端、发一次真实调用并看到结果。过程中用到的模型服务我会用 TaoToken 的兼容接口来演示因为它同时提供 Claude 系列和 OpenAI 兼容调用方式方便你在同一套代码里验证。先明确一个容易混淆的点MCP 不是替代 function call而是 function call 的“平台化封装”。模型侧看到的仍然是工具列表和参数 schema只是这套 schema 由 MCP Server 通过协议暴露出来客户端负责把它翻译成具体模型厂商需要的格式。理解这一层后面配置和排障就不会迷路。2. TaoToken 前置准备拿到 Base URL、Key 和 Model ID在写 MCP 代码之前先把模型调用这条链路打通。MCP 负责“工具有没有、怎么调”模型负责“判断要不要调、调哪个”两者缺一不可。我实测下来用 TaoToken 做模型侧接入比较省事因为它对 Anthropic 和 OpenAI 两种风格都兼容MCP 客户端里无论用哪种 SDK 都能对上。你需要准备三样东西我称为“三件套”Base URLhttps://taotoken.net/apiAPI Key在控制台创建形如sk-...Model ID比如 Claude 系列模型名或你本地/远端可用的模型标识获取路径很直接打开 https://taotoken.net/api-keys 创建密钥复制保存模型列表可以在 https://taotoken.net/console 里查看当前可用的 Model ID。注意 Key 只在创建时完整显示一次丢了就重新建一个。如果你用的是 Claude Code 这类工具配置通常写在一个 JSON 文件里字段就是上面三件套。下面是一个通用片段路径按你实际工具的约定放{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: sk-你的Key, ANTHROPIC_MODEL: claude-sonnet-4-5 } }如果你用的是 OpenAI 兼容风格的 SDK比如openai库或ollama库指向远端则换成{ base_url: https://taotoken.net/api, api_key: sk-你的Key, model: claude-sonnet-4-5 }这里有个坑要提前说Base URL 末尾不要多加/v1或斜杠不同 SDK 拼接路径的方式不一样多写反而 404。另外 Key 不要提交到 Git用环境变量或本地配置文件并加进.gitignore。准备完这三件套模型侧就能正常发起对话和 function call 了。接下来我们把它和 MCP Server 接起来。MCP 的传输方式常见有 stdio本地进程通信和 HTTP/SSE远端本地开发先用 stdio 最省事客户端直接以子进程方式拉起 Server 脚本。3. 可复制配置写一个最小 MCP Server 并挂到客户端这一节是全文核心目标是让你复制就能跑。MCP 官方提供 Python 和 JavaScript 的 SDKPython 侧用mcp包里的FastMCP最简洁。先装依赖pip install mcp ollama然后写一个计算器 Server保存为math_server.pyfrom mcp.server.fastmcp import FastMCP import logging logging.basicConfig(levellogging.INFO, format%(asctime)s - %(levelname)s - %(message)s) logger logging.getLogger(__name__) mcp FastMCP(Math) mcp.tool() def add(a: int, b: int) - int: Add two numbers. logger.info(fAdding {a} and {b}) return a b mcp.tool() def multiply(a: int, b: int) - int: Multiply two numbers. logger.info(fMultiplying {a} and {b}) return a * b if __name__ __main__: logger.info(Starting Math MCP service...) mcp.run(transportstdio)关键点mcp.tool()装饰器把普通函数注册成工具函数名就是工具名docstring 就是工具描述类型注解a: int会生成参数 schema。模型正是靠这些信息判断“这个工具能干什么、要传什么参数”。transportstdio表示用标准输入输出和客户端通信适合本地。接着写客户端client.py它负责拉起 Server、列出工具、把工具转成模型能识别的格式并发起调用import asyncio from contextlib import AsyncExitStack from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client from ollama import chat class MCPClient: def __init__(self): self.sessions [] self.exit_stack AsyncExitStack() async def connect_to_server(self, server_script_paths): for path in server_script_paths: is_python path.endswith(.py) command python if is_python else node params StdioServerParameters(commandcommand, args[path], envNone) transport await self.exit_stack.enter_async_context(stdio_client(params)) stdio, write transport session await self.exit_stack.enter_async_context(ClientSession(stdio, write)) await session.initialize() self.sessions.append(session) resp await session.list_tools() print(Connected, tools:, [t.name for t in resp.tools]) async def process_query(self, query: str) - str: messages [ {role: system, content: 你是一个问答助手需要计算时调用工具。}, {role: user, content: query}, ] all_tools [] for session in self.sessions: resp await session.list_tools() all_tools.extend([ {type: function, function: {name: t.name, description: t.description}} for t in resp.tools ]) response chat(modelqwen3:0.6b, messagesmessages, toolsall_tools) final_text [] if response.message.content: final_text.append(response.message.content) elif response.message.tool_calls: for call in response.message.tool_calls: name call.function.name args call.function.arguments for session in self.sessions: try: result await session.call_tool(name, args) final_text.append(f[调用 {name} 参数 {args} {result}]) break except Exception: continue return \n.join(final_text) async def chat_loop(self): print(MCP Client Started! 输入 quit 退出。) while True: query input(\nQuery: ).strip() if query.lower() quit: break print(\n await self.process_query(query)) async def cleanup(self): await self.exit_stack.aclose() async def main(): import sys if len(sys.argv) 2: print(用法: python client.py server脚本路径) sys.exit(1) client MCPClient() try: await client.connect_to_server(sys.argv[1:]) await client.chat_loop() finally: await client.cleanup() if __name__ __main__: asyncio.run(main())注意客户端里chat(modelqwen3:0.6b, ...)这行如果你要指向 TaoToken 的兼容接口把ollama的 base_url 换成https://taotoken.net/api并带上 Key 即可Model ID 换成你控制台里可用的模型。这样模型侧和 MCP 侧就串起来了MCP 提供工具清单模型决定调用哪个。4. 验证请求跑一次本地调用并看到工具结果配置写完跑起来验证。先启动客户端并挂载 Serverpython client.py math_server.py正常的话你会看到类似输出Connected, tools: [add, multiply] MCP Client Started! 输入 quit 退出。这说明客户端成功以子进程方式拉起了math_server.py并通过 MCP 协议拿到了工具列表。接下来输入一个需要计算的查询Query: 帮我算一下 128 乘以 37 等于多少如果模型判断需要调用工具你会看到类似[调用 multiply 参数 {a: 128, b: 37} metaNone content[TextContent(typetext, text4736)] isErrorFalse]同时 Server 端日志会打印Multiplying 128 and 37。这条链路完整走通了用户提问 → 模型解析意图 → 选择multiply工具 → 客户端通过 MCP 调用 Server → Server 执行并返回 4736 → 结果回填给模型。这就是从 function call 到 AI Agent 的最小闭环。再试一个不需要工具的查询比如“你好介绍一下你自己”模型应该直接回复文本而不触发工具调用。这验证了模型确实在“按需调用”而不是无脑调工具。如果你想让结果更稳定可以在 system prompt 里明确写“涉及算术必须调用工具不要心算”。实测下来小参数模型偶尔会偷懒直接算加一句约束能明显改善。另外 Server 的 docstring 要写清楚模型靠它理解工具用途Add two numbers.这种描述比空着强很多。5. 常见报错排查401、local proxy failed、reading choices跑不通是常态我把踩过的坑按报错对照列出来你对着查。401 UnauthorizedKey 错了或没带上。检查ANTHROPIC_API_KEY/api_key是否填了完整sk-开头的串Base URL 是否是https://taotoken.net/api。如果用了环境变量确认当前 shell 真的 export 了echo $ANTHROPIC_API_KEY看一眼。local proxy failed / connection refused客户端连不上模型服务。先确认网络能访问 Base URL再确认端口和路径没写错。stdio 模式下这个错通常不是 MCP 的问题而是模型 SDK 的 base_url 配错了重点查三件套里的 URL。Error reading choices / 返回结构解析失败多半是 SDK 和接口风格不匹配。用 Anthropic SDK 就指向 Anthropic 兼容端点用 OpenAI SDK 就指向 OpenAI 兼容端点别混用。TaoToken 两种都支持但你的代码得选一种。报错里出现choices说明你在用 OpenAI 风格解析却拿到了非 OpenAI 结构。OAuth / 认证跳转类报错一般是工具或客户端要求交互式登录本地脚本场景不该出现。检查是不是误用了需要 OAuth 的远端 MCP Server本地 stdio 不需要。工具调用没触发模型没选工具。检查工具描述是否清晰、参数类型是否匹配、system prompt 是否引导。可以先把tools打印出来确认 schema 正确。Server 启动即退出mcp.run(transportstdio)必须在__main__里调用且脚本不能有阻塞的顶层代码。日志里看到Starting Math MCP service...但立刻结束通常是 transport 配错或依赖缺失。排查顺序建议先单独跑 Server 看能否启动再单独测模型对话最后合起来。分而治之比一上来就调整个链路快得多。6. 把 MCP 接进你的 Agent下一步怎么走跑通最小示例后你可以往三个方向扩展。第一加更多工具把文件读写、HTTP 请求、数据库查询都包成mcp.tool()模型的能力边界就跟着扩大。第二换传输方式本地 stdio 适合开发部署时可以用 HTTP/SSE 让多个客户端共享一个 Server。第三接进真实 Agent 框架Claude Code、Cline 这类工具都支持 MCP 配置把 Server 路径写进它们的配置文件即可。如果你要长期做编码类 Agent建议直接上 Coding Plan把模型调用和工具链统一管理省得每次手动配三件套https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentcoding_plan想先验证模型对话和工具调用效果用模型对话页快速试https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentmodel_chat需要创建和管理 Key去控制台https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentapi_keys协议细节和接入文档在这里https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentdoc最后留一个实用习惯每次改完 Server 的工具定义先重启客户端再测因为工具列表是在initialize时拉取的热改不生效。这个坑我踩过不止一次。
网站建设高端定制企业官网