1. 从一次“工具调不通”说起MCP 到底解决什么问题如果你最近在折腾 AI Agent大概率遇到过这种场景模型明明支持 Function Calling但你想让它查一下本地数据库、读一个文件、调一个内部 HTTP 接口就得在每个项目里重复写一遍工具定义、参数校验、结果解析。更麻烦的是这些工具散落在不同语言、不同进程里Python 写的工具 Java 项目用不了Go 写的服务前端又接不上。MCPModel Context Protocol就是冲着这个痛点来的。你可以把它理解成 AI 世界的 USB-C 接口以前每个外设都有自己的插头现在统一成一个标准口谁都能插。MCP 基于 JSON-RPC 2.0把外部能力抽象成三类东西——Tool可执行功能、Resource只读数据源、Prompt预定义模板Client 通过tools/list、tools/call、resources/read这些标准方法跟 Server 通信。这篇面向想从零理解 MCP 通信流程的开发者重点不是背概念而是把 Client 源码结构拆开看再用 TaoToken 的统一 Key 在本地跑通第一个 MCP Client。跑通之后你对“请求怎么发出去、工具怎么被发现、结果怎么回来”会有一个具象的认知而不是停留在架构图上。适合谁看写过一点 Spring AI 或 LangChain、想搞清楚 MCP Client 内部怎么工作、又不想一上来就被多语言环境卡住的人。下面所有配置我都实测过命令可以直接复制。2. 前置准备用 TaoToken 统一 Key 管住多模型调用MCP Client 本身不绑定某个模型但你要验证一次完整的请求-响应总得有个能调用的 LLM。这里我用 TaoToken 做统一入口原因是它把多家模型的 Key 收敛成一个config.toml 里只维护一份凭证切换模型不用改代码。官网在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 端点是 https://taotoken.net/api 。你需要先拿到一个 API Key。登录后进控制台在 API Keys 页面创建一个复制出来形如sk-xxxx的字符串。这个 Key 后面会写进 config.tomlMCP Client 调用模型时带上它。注意Key 只存在本地配置文件里不要提交到 Git。建议用环境变量注入或者把 config.toml 加进 .gitignore。TaoToken 的接入文档在 https://taotoken.net/doc 里面有各语言 SDK 的 base_url 写法。MCP Client 这边我们直接用 OpenAI 兼容格式base_url 填https://taotoken.net/api模型名按文档里支持的填比如gpt-4o-mini这类通用对话模型就够验证流程了。如果你后面要长期跑编码类 Agent可以看 Coding Plan https://taotoken.net/coding-plan 只是验证模型连通性用模型对话页 https://taotoken.net/chat 手动发一条也行。但本篇重点在 Client 源码和本地跑通所以走 API Key 这条路。3. 可复制配置config.toml 骨架与 MCP Client 最小配置先建目录结构我习惯这样放mcp-demo/ ├── config.toml ├── mcp_client.py └── servers/ └── echo_server.pyconfig.toml 是整个项目的凭证和端点中心骨架如下[llm] provider openai-compatible base_url https://taotoken.net/api api_key sk-你的TaoToken密钥 model gpt-4o-mini timeout 30 [mcp] client_name taotoken-mcp-client client_version 0.1.0 transport stdio [mcp.servers.echo] command python args [servers/echo_server.py]这里[mcp.servers.echo]定义了一个本地 STDIO 传输的 MCP ServerClient 启动时会以子进程方式拉起echo_server.py通过标准输入输出做 JSON-RPC 通信。这是最小可运行的传输方式不需要开端口适合本地验证。echo_server.py 写一个最简单的工具返回传入的文本import sys import json def handle(request): method request.get(method) req_id request.get(id) if method initialize: return {jsonrpc: 2.0, id: req_id, result: { protocolVersion: 2024-11-05, capabilities: {tools: {}}, serverInfo: {name: echo-server, version: 0.1.0} }} if method tools/list: return {jsonrpc: 2.0, id: req_id, result: {tools: [{ name: echo, description: 原样返回输入文本, inputSchema: {type: object, properties: { text: {type: string} }, required: [text]} }]}} if method tools/call: args request.get(params, {}).get(arguments, {}) text args.get(text, ) return {jsonrpc: 2.0, id: req_id, result: { content: [{type: text, text: fecho: {text}}], isError: False }} return {jsonrpc: 2.0, id: req_id, error: { code: -32601, message: Method not found}} for line in sys.stdin: line line.strip() if not line: continue try: req json.loads(line) resp handle(req) sys.stdout.write(json.dumps(resp) \n) sys.stdout.flush() except Exception as e: sys.stderr.write(str(e) \n)这个 Server 只实现了三个方法initialize握手、tools/list暴露工具、tools/call执行工具。真实项目里 Server 会复杂得多但通信骨架就是这样。Client 端我用 Python 写方便你直接跑。核心逻辑是读 config.toml拉起 Server 子进程发 initialize再发 tools/list最后发 tools/call。import json import subprocess import tomllib with open(config.toml, rb) as f: cfg tomllib.load(f) server_cfg cfg[mcp][servers][echo] proc subprocess.Popen( [server_cfg[command]] server_cfg[args], stdinsubprocess.PIPE, stdoutsubprocess.PIPE, stderrsubprocess.PIPE, textTrue ) def rpc(method, paramsNone, req_id1): msg {jsonrpc: 2.0, id: req_id, method: method} if params is not None: msg[params] params proc.stdin.write(json.dumps(msg) \n) proc.stdin.flush() line proc.stdout.readline() return json.loads(line) init rpc(initialize, { protocolVersion: 2024-11-05, capabilities: {}, clientInfo: {name: cfg[mcp][client_name], version: cfg[mcp][client_version]} }) print(initialize -, init[result][serverInfo]) tools rpc(tools/list, {}, req_id2) print(tools/list -, [t[name] for t in tools[result][tools]]) call rpc(tools/call, { name: echo, arguments: {text: hello mcp} }, req_id3) print(tools/call -, call[result][content][0][text])这段代码没有依赖任何 MCP SDK纯手写 JSON-RPC目的是让你看清每一次请求长什么样。等你理解了流程再换成官方 SDK 会顺很多。4. 验证请求一次完整的请求-响应链路先装依赖Python 3.11 自带 tomllib不用额外装。然后跑cd mcp-demo python mcp_client.py预期输出initialize - {name: echo-server, version: 0.1.0} tools/list - [echo] tools/call - echo: hello mcp三行输出对应三次 JSON-RPC 往返。第一次initialize是握手Client 告诉 Server 自己支持的协议版本和能力Server 回自己的信息。第二次tools/list是工具发现Client 拿到 Server 暴露的所有工具元数据。第三次tools/call是实际执行Client 把工具名和参数发过去Server 返回结果。如果你想把模型也接进来让 LLM 决定调哪个工具可以在拿到 tools/list 之后把工具定义转成 OpenAI 的 function 格式发给 TaoToken 的/v1/chat/completions。模型返回 tool_calls 后你再解析出工具名和参数走上面的tools/call。这一步就是把 MCP 和 LLM 串起来的关键也是 Client 源码里SyncMcpToolCallback这类适配器做的事——把 MCP 的 Tool 定义翻译成模型能理解的格式再把模型的调用意图翻译回 MCP 请求。验证模型连通性可以单独发一条curl https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer sk-你的TaoToken密钥 \ -H Content-Type: application/json \ -d {model:gpt-4o-mini,messages:[{role:user,content:ping}]}返回里有choices[0].message.content就说明 Key 和端点都通。这一步和 MCP 无关但能帮你排除“到底是模型调不通还是 MCP 写错了”的干扰。5. 本篇常见错排查报错一FileNotFoundError: servers/echo_server.py原因是从错误的目录启动。config.toml里的args是相对路径必须保证工作目录是mcp-demo/。解决cd mcp-demo再跑或者把 args 改成绝对路径。报错二Client 卡在readline()不动多半是 Server 没有 flush stdout。Python 的sys.stdout.write默认带缓冲必须跟一句sys.stdout.flush()。上面 echo_server.py 里已经加了如果你自己改代码漏了这行就会死等。报错三json.decoder.JSONDecodeErrorServer 往 stdout 打了非 JSON 内容比如 print 调试信息。MCP 的 STDIO 传输要求 stdout 只能走协议消息调试信息一律走 stderr。检查你的 Server 有没有多余的 print。报错四TaoToken 返回 401Key 写错或没带Bearer前缀。检查 config.toml 里的 api_key以及 curl 里的 Authorization 头格式。另外确认 base_url 是https://taotoken.net/api不要多加/v1后缀具体路径以接入文档为准。报错五tools/call返回Method not foundServer 没实现tools/call分支或者方法名拼错。MCP 的方法名是固定的tools/list和tools/call都是复数别写成tool/list。报错六工具名冲突如果你接了多个 Server两个 Server 都有echo工具Client 侧会冲突。真实 MCP Client 会用前缀区分比如echo_server_echo。手写版可以先在 config 里给每个 Server 加个前缀字段拼工具名时带上。6. 下一步把 Client 接进真实项目跑通这个最小闭环之后你可以做三件事。第一把 echo_server.py 换成真实工具比如查数据库、读文件、调内部 APIServer 用任何语言写都行只要遵守 JSON-RPC 和 MCP 方法约定。第二把 Client 里的手写 RPC 换成官方 SDKSpring AI 那边对应的是McpSyncClient和SyncMcpToolCallbackProviderPython 侧有mcp包能省掉大量样板代码。第三把工具列表喂给模型让 LLM 自主决定调用哪个工具这才是 Agent 的完整形态。如果你要长期跑编码类 Agent建议把 Key 和模型配置统一到 TaoToken 的 Coding Plan https://taotoken.net/coding-plan 省得每个项目维护一份凭证。接入细节和 SDK 示例在接入文档 https://taotoken.net/doc 里都有API Key 在控制台 https://taotoken.net/api-keys 随时可以新建和轮换。模型对话页 https://taotoken.net/chat 适合快速验证某个模型能不能用不用写代码。MCP 的价值不在协议本身多复杂而在于它把“工具怎么被发现、怎么被调用”这件事标准化了。你手写完这一遍再看任何 MCP Client 的源码结构都会很清晰握手、发现、调用三步而已。
企业数字化 ERP 产品动态
相关推荐
ROS2驱动海康工业相机:图像Topic参数Service完整方案 简介:面向ROS2与工业视觉集成开发者,这份资源围绕海康HIKROBOT系列工业相机,提供了一套完整的驱动开发与节点通信方案,覆盖图像采集、参数调控和数据传输等关键环节。文档以ROS2核心通信架构为切入点,系统讲解节点单元… · 2026/9/26 10:02:27
美团外卖霸王餐API对接详解:从业务拆解到技术落地 1. 先想明白霸王餐API对接到底是在接什么做外卖代运营的同行来找我聊美团外卖霸王餐API接口对接,十有八九开口就是“给我个接口文档”,但我一般不会直接扔文档过去。先回答一个问题:你要通的这组API,走的是哪条业务链路࿱… · 2026/9/26 10:02:14
Kali下ARP欺骗攻击实验详解:从arpspoof到中间人攻击防御 前阵子我给自己搭的测试网段做了一轮ARP欺骗验证,用的就是Kali自带的arpspoof工具,目标机器是我在同一台虚拟化平台里起的另一台系统。很多人一听到“ARP攻击”就下意识觉得是搞破坏,但在网络维护和渗透测试的实际工作里,它更常见… · 2026/9/26 10:02:08
Gitee镜像搭建ESP-IDF开发环境:从Git克隆到多版本切换指南 先说明一下,我写这篇笔记的初衷:ESP32 的开发框架 ESP-IDF 官方推荐用 GitHub 拉取,但国内网络环境大家心里都有数,git clone 动不动就中断、速度几十 KB,实在折磨人。Gitee 上有乐鑫官方维护的镜像仓库,速… · 2026/9/26 10:46:46
数据处理流水线实战:任务编排与计算执行层核心组件配置调优 做数据处理这些年,我越来越明白一个道理:所谓“快”,从来不是靠某一把玄学钥匙,而是靠一套能把任务拆对、把资源用足、把坑提前踩平的流水线。最近团队内部流传两个代号,ANV32AA1WDK66 和 R7KA8T2LFLCAC,说… · 2026/9/26 10:46:39
AI原生PLC/DCS控制器架构解析:AutoMinds™如何重构工业自动化控制平台 1. 工业自动化控制平台的变局前夜干了十几年工控,从最早抱着西门子S7-200啃梯形图,到后来用CODESYS折腾软PLC,再到现在看着AI往控制器里钻,说实话,这个行业正在经历一次底层逻辑的重构。AutoCore发布AutoMinds™&#… · 2026/9/26 10:46:39
I2C信号测量三阶法:万用表分诊、示波器精查、ACK验证 1. 为什么I2C信号不能只靠“通断”判断?——从一个烧掉的EEPROM说起我第一次真正被I2C“教育”,是在调试一块STM32F4驱动AT24C02 EEPROM的板子。现象很典型:上电后,MCU反复报“写入超时”,但用万用表测SCL和SDA对地电压… · 2026/9/26 10:46:39
Linux中断子系统与驱动移植:从irq_domain到设备树实战 干过驱动移植的兄弟应该都有同感:外设寄存器、时钟、GPIO这些看几遍手册、试几次就能摸清套路,唯独中断,板子一跑起来就出幺蛾子。要么中断不触发,要么触发一次就死循环,要么一申请就报错,最难受的是看起来… · 2026/9/26 10:46:39
数据库课后习题答案别硬背:当测试用例集刷,效率翻倍 简介:万常选版《数据库原理与设计》课后习题答案资源,覆盖第2至6章及第9章,适合正在学习关系模型、数据库建模、关系数据理论与模式求精的本科生、自学者作为复习与自测材料。压缩包共7个文件,含3个doc参考答案、2个sql示例脚本、… · 2026/9/26 0:00:21
OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 0:00:40
向下兼容与向上兼容:接口设计中的兼容性策略与工程实践 一次版本升级事故,是很多团队绕不过去的坎。线上环境里,服务端明明已经上线了新版接口,老的移动端还在照着旧文档传参数。请求一到网关,校验直接拒绝,用户操作失败,客服群炸了锅,开发群里开始互… · 2026/9/26 0:00:46