MCP 实战:用一台计算器看懂 AI 的 USB-C 接口

发布时间:2026/10/8 23:06:20
MCP 实战:用一台计算器看懂 AI 的 USB-C 接口 本次实践是一个 MCPModel Context Protocol服务器的 Hello World 实验用 FastMCP 搭一台计算器服务器注册工具、资源、提示词三类能力然后分别用内存传输、HTTP 传输、STDIO 传输三种方式各连一遍客户端最后通过 langchain-mcp-adapters 把这些工具装进 LangGraph 智能体并用 MultiServerMCPClient 同时挂载两个服务器。麻雀虽小MCP 协议的核心机制一次走全。一、从 tool 到 MCP工具为什么需要协议前面的实践中ReAct、反思智能体、BeeAI我们一直在用 LangChain 的tool装饰器给智能体装工具fromlangchain_core.toolsimporttooltooldefmultiply(a:int,b:int)-int:Multiply two numbers.returna*bprint(multiply.name)# multiplyprint(multiply.description)# Multiply two numbers.print(multiply.args)# {a: {title: A, type: integer}, ...}print(Answer: str(multiply.invoke({a:2,b:3})))# 6一个装饰器就把普通函数变成了智能体能调用的工具很方便。但它有个隐含前提工具定义和智能体跑在同一个进程里。换个应用、换个语言、换台机器这套工具就得重新对接一遍——每家框架、每个客户端都在为怎么接工具发明自己的轮子。MCP 做的事情是把工具怎么被接上这件事标准化成协议客户端与服务器之间用 JSON-RPC 通信工具如何被发现list_tools、如何被调用call_tool、参数长什么样JSON Schema全部有统一约定。这就是它常被比作AI 的 USB-C的原因——设备各不相同接口只有一个。本实验的结论是同一个add工具三种传输、两种客户端、一个智能体调用的代码语义完全一致。宿主进程 Host你的应用 / IDEJSON-RPC 请求结果 / 错误MCP ServerFastMCPTools 工具add / subtract · 主动执行Resources 资源file://documents/{name}Prompts 提示词review_code 模板LLM 智能体决策要不要调用工具MCP Clientlist_tools 发现 · call_tool 调用一台 MCP 服务器向外提供的三类能力分工明确工具Tools——主动能力AI 调用它去执行操作算加法、查数据、存文件资源Resources——被动能力AI 通过 URI读取数据像打开一个文件柜提示词Prompts——沉淀好的可复用模板让重复任务不必每次重新发明指令。二、FastMCP 服务器三件套一次备齐2.1 服务器对象与工具注册FastMCP 封装了官方 MCP SDK一台服务器的诞生只需要名字和一句自我介绍fromfastmcpimportFastMCP mcpFastMCP(nameCalculatorMCPServer,instructions This server provides data analysis tools. Call get_average() to analyze numerical data. )工具注册与 LangChain 如出一辙只是装饰器换成了mcp.tool——函数签名变成 JSON Schemadocstring 变成描述mcp.tooldefadd(a:int,b:int)-int: Add two integers together. Args: a (int): The first integer. b (int): The second integer. Returns: int: The sum of a and b. returnab2.2 资源URI 是地址不是路径资源通过 URI 模式暴露。实验特意做了两个层次来澄清一个易混概念——URI 端点是 MCP 的门牌号磁盘路径才是文件的真实位置两者相关但不相同mcp.resource(file:///endpoint/{name})defreturn_template_document(name:str)-str:Read a document by namereturnfDocument contents of{name}# 模板式不碰磁盘mcp.resource(file://endpoint2/{name})defread_document(name:str)-str:Read a document by name from the path directorytry:withopen(fpath/{name},r)asf:# 真实读取磁盘文件returnf.read()exceptFileNotFoundError:returnfDocument {name} not found in path directory{name}是命名路径参数客户端请求file://endpoint2/README.txt时自动填充。实测请求一个不存在的random.txt服务器不会抛异常炸掉而是返回一个结构化的错误资源uri: file://endpoint2/random.txt mimeType: text/plain text: Document random.txt not found in path directory2.3 提示词把领域经验沉淀成模板mcp.prompt(titleCode Review)defreview_code(code:str)-str:returnfPlease review this code:\n\n{code}客户端调用get_prompt(review_code, {code: ...})拿回的是标准的消息对象role: user 完整提示文本可以直接进对话——提示词由此成了可以版本化、可以分享的服务器资产。2.4 发现与调用协议视角下的一次工具调用客户端最基本的用法clientClient(mcp)# 内存传输直接把服务器对象交给客户端asyncdefcall_add_tool(a:int,b:int):asyncwithclient:# 异步上下文管理器管理会话生命周期resultawaitclient.call_tool(add,{a:a,b:b})returnresult两个细节是 MCP 客户端的通用姿势async with client负责打开/关闭连接无论哪种传输都一样await让进程不阻塞地等服务器响应。调用结果是一个结构化对象实测add(4, 5)CallToolResult(content[TextContent(typetext, text9, ...)], structured_content{result: 9}, data9, is_errorFalse)三种取数方式各有用途result.data反序列化后的 Python 对象、result.content[0].text文本形式、result.structured_contentJSON 字典。而list_tools()能发现服务器上所有工具每个工具自带标准化的参数契约Available tools: - add: Add two integers together. - subtract: Subtract one integer from another.{additionalProperties:false,properties:{a:{type:integer,description:The number to subtract from.},b:{type:integer,description:The number to subtract.}},required:[a,b],type:object}这份 JSON Schema 正是结构化输出博文里 Pydantic schema 的网络版——参数要过网络/要给 LLM 看就必须有机器可读的契约。MCP 同样可以有 outputSchema工具返回值的结构但并非必需纯操作型工具如写数据库返回一句确认即可。三、三种传输同一协议的三条路传输层决定客户端和服务器如何交换JSON-RPC 消息。实验把同一个计算器服务器用三种方式各接了一遍add(4, 5)三次都返回 9——协议与传输正交这是 MCP 设计里最值得体会的一点。① In-Memory 内存传输同进程对象直连Client(mcp)服务器② HTTP 流式传输POST /mcpJSON-RPCClient(transport)Web 服务器run_http_async③ STDIO 标准流传输stdin 写入stdout 读回Client(transport)子进程stdio_server.py维度内存传输HTTP 传输STDIO 传输服务器位置同一个 Python 进程独立 Web 服务run_http_async客户端拉起的子进程连接方式直接传对象Client(mcp)URL StreamableHttpTransportStdioTransport(command, args)通信通道函数调用/mcp端点上的 JSON-RPCstdin / stdout 管道适用场景本地测试、Notebook远程部署、多客户端共享本地工具、桌面客户端集成三种传输的客户端代码几乎逐字相同——只有构造Client那一行不同# ① 内存clientClient(mcp)# ② HTTP先起服务器再连 URLasyncio.create_task(mcp.run_http_async(port8010))transport_httpStreamableHttpTransport(urlhttp://127.0.0.1:8010/mcp)http_clientClient(transport_http)# ③ STDIO服务器写成独立 .py末尾 mcp.run() 监听标准流transport_stdioStdioTransport(commandpython,args[stdio_server.py])stdio_clientClient(transport_stdio)STDIO 模式下stdio_server.py与内存版的服务器代码一字不差只是末尾多了if __name__ __main__: mcp.run()。客户端会把它作为子进程启动通过标准流对话。实验还给了现实世界的对照——Cursor、Claude Desktop 的mcp.json配置文件正是这两种传输的声明式写法{mcpServers:{local_stdio_server_name:{command:npx/python/uv/uvx,args:[some absolute/relative path,some install argument]},remote_http_server_name:{url:mcp_server_url}}}看到commandargs就是 STDIO看到url就是 HTTP——理解了传输层各家客户端的配置文件就不再是黑魔法。四、接入智能体langchain-mcp-adapters最后一公里让 LangGraph 智能体用上 MCP 工具。适配层把HTTP 连接 → 会话 → 工具加载串成一条流水线streamable_http_client(url)建立连接 → (read, write, sid)ClientSession(read, write)await session.initialize()load_mcp_tools(session)MCP 工具 → LangChain 工具create_agent(model, tools)LangGraph ReAct 智能体agent.ainvoke(查询)自动调用 MCP 工具frommcpimportClientSessionfrommcp.client.streamable_httpimportstreamable_http_clientfromlangchain_mcp_adapters.toolsimportload_mcp_toolsfromlangchain.agentsimportcreate_agentasyncwithstreamable_http_client(fhttp://127.0.0.1:{PORT}/mcp)as(read,write,_sid):asyncwithClientSession(read,write)assession:awaitsession.initialize()toolsawaitload_mcp_tools(session)# 从活的会话里装载工具agentcreate_agent(modelllm,toolstools)agent_responseawaitagent.ainvoke({messages:Use the add tool to add 2 and 1 and let me know if you used a tool.})注意所有东西都包在异步上下文管理器里会话必须在整个智能体执行期间保持存活工具才能随取随用因为智能体异步运行这里用ainvoke而非invoke。实测本地 Qwentemperature0Yes, I used the add tool to add 2 and 1. The result is 3.STDIO 版本只需把streamable_http_client换成stdio_client智能体同样答出 “The result of adding 2 and 1 is 3.”。4.1 多服务器一次配置工具全收MultiServerMCPClient是适配层提供的总机同时挂 STDIO 和 HTTP 两个服务器clientMultiServerMCPClient({stdio-client:{command:python,args:[stdio_server.py],transport:stdio},http-client:{url:fhttp://127.0.0.1:{PORT}/mcp,transport:streamable_http}})toolsawaitclient.get_tools()[tool.namefortoolintools]# [add, subtract, add, subtract]因为两个服务器本质相同列表里出现了两份 add、两份 subtract——各服务器各一份。这个细节顺带说明MCP 的工具发现按服务器命名空间组织聚合多服务器时要注意重名工具的去重与选择。它还免去了手工管理ClientSession和嵌套上下文管理器的麻烦一次配置、一次get_tools()、直接建智能体。最后看智能体的一次完整消息轨迹实测输出[HUMAN] whats 8 7? use tools [AI] tool call [TOOL] [{type: text, text: 15, id: lc_f4d90c44-...}] [AI] 8 7 15四个角色一步不少人发问 → 智能体决定调工具 → MCP 服务器执行并返回 15 → 智能体综合作答。这条轨迹与 ReAct 那篇手写的推理-行动-观察循环完全同构——只是这次行动跨出了进程边界走到了另一台服务器上。五、MCP 与进程内工具怎么选维度进程内 toolMCP 服务器工具位置与应用同进程独立进程 / 远程服务复用范围单一应用任何 MCP 客户端IDE、桌面应用、智能体发现机制无代码里写死list_tools运行时发现 JSON Schema 契约升级部署改代码重启应用独立升级客户端无感复杂度成本零一个装饰器传输、会话、异步生命周期管理适用场景快速原型、私有业务逻辑团队共享工具、跨应用集成、生产部署两者不是替代关系原型期用tool快速迭代工具成熟、需要跨客户端复用时包一层 FastMCP 就是标准服务。实验里同一个add函数先是 LangChain 工具、后是 MCP 工具代码几乎没变——变的只是它的发布方式。结语一台只会加减法的计算器服务器把 MCP 的骨架走了一遍三类能力工具执行、资源读取、提示词模板定义服务器能提供什么三种传输内存、HTTP、STDIO决定消息怎么走一套 JSON-RPC JSON Schema保证无论怎么走客户端看到的接口都一样。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询