
1. 认识 MCP 与 Streamable HTTP先解决三个最基础的问题MCP 这个词最近在 AI 工程圈里的存在感已经不需要多介绍。但凡你在做 AI 应用或者 Agent 类项目多多少少都会碰到一个尴尬大模型聊天很会说话但真正要查数据、调接口、操作内部系统它却使不上劲。MCP 就是专门来解决这个问题的——它把“模型和外部工具之间的对话方式”统一成一套协议让 AI 能标准地发现工具、理解工具、调用工具。而今天这篇教程的主角是 MCP 的一种远程传输方式基于 Streamable HTTP 的调用链路。绝大多数刚入门的开发者最先接触的是 stdio 模式也就是本地起一个子进程通过标准输入输出和模型侧通信这种方式适合本机部署、单机调试但一旦你想让云端的大模型调用你内网这台服务器上的工具或者你的 Agent 服务和工具服务不在同一台机器就必须走 HTTP。Streamable HTTP 就是当前 MCP 规范里推荐的 HTTP 远程方案。这篇教程会带着你从概念到落地把服务端搭建、协议级交互、SDK 接入、模型联动一整条链路完整跑通适合正在做 AI 工具接入、智能体落地或者刚开始接触 MCP 想少走弯路的开发者。1.1 为什么大家突然都在接 MCP可以把 MCP 理解成“AI 的 USB-C 接口”。早期每个模型平台都有自己的工具调用格式A 平台有 A 的 function calling 写法B 平台有 B 的插件协议各家参数结构、鉴权方式、回调机制都不一样。你做一次工具接入就得针对每个平台写一遍适配工具量一多整个工程就变成了蜘蛛网一样的胶水代码。MCP 的思路很简单粗暴定义一个中间协议工具提供方只需要实现一个 MCP Server所有支持 MCP 的客户端比如你正在用的各种 AI 助手、Agent 框架、IDE就可以用同一种方式去发现和调用它。这样工具侧只写一次客户端侧也只接入一次中间的全部标准化。这套结构里有三个角色值得你记住Host发起请求的那一方通常是 AI 客户端、Agent 编排框架或者你自己写的业务程序。ClientMCP 协议的具体客户端负责和 Server 建立连接、收发消息的库。Server暴露工具、数据资源、提示词的一方你写的工具函数就住在这里。大多数教程一上来就给你写代码但代码反而是最容易的一层。真正值得先理解的是消息格式——因为不管是 stdio 还是 Streamable HTTPMCP 的“内容语言”永远是一样的JSON-RPC 2.0。传输方式只解决“消息怎么送达”而 JSON-RPC 决定“消息长什么样、该回什么字段”。搞懂这一层后面所有调试都变得非常清爽。1.2 Streamable HTTP 相比 stdio 和老版 SSE 强在哪先看 stdio。在 stdio 模式下MCP Server 作为子进程被启动协议消息通过 stdin/stdout 的行分隔 JSON 来传递。好处是零网络开销、免配置、适合本机调试坏处也很明显进程生命周期跟着父进程走远程无法直接访问而且模型服务和工具服务一旦分离stdio 就完全使不上劲。早期的 HTTP 版本其实也走过弯路。老规范是“SSE POST 双端点”方式客户端先发一个 GET 请求到/sse服务端保持一个长时间不关闭的流式连接事件都从这个连接推出来同时客户端要另开一个 POST 端点/messages去发送具体的 JSON-RPC 请求。这个设计能让服务端主动推送但问题在于“两个端点”增加了部署复杂度反向代理、负载均衡、鉴权都要做两份配置浏览器环境下对 SSE 长连接的兼容性也很让人头疼。Streamable HTTP 把两个端点合并成了一个客户端只需向一个 URL 发送 POST 请求。请求体是 JSON-RPC 2.0 格式。响应可以是完整的 JSON 对象也可以是一个text/event-stream流。服务端通过Mcp-Session-Id响应头维护会话接下来客户端再发请求时带上这个头即可。我个人的理解是“Streamable”这个名字的精髓在于结果不一定要一次性返完。工具执行的时间可能很长比如你让模型调一个数据聚合工具跑了十几秒这时候服务端可以持续地往流里推进度流还能承载“服务端主动发起的通知”。所以它不是简单地把老做法焊在一起而是把“单次请求-响应”和“流式事件推送”合并到一个通道里让远端工具调用既灵活又简单。1.3 一次远程调用背后到底发生了什么哪怕你之后全程使用官方 SDK不直接碰底层 message我依然建议你把下面这四条消息在脑子里形成一个“基本动画”initialize客户端连上来第一件事必发initialize。双方在这里交换协议版本、能力声明比如是否支持工具列表变更通知、服务信息和客户端信息。响应成功之后协议才进入就绪状态。notifications/initialized收到 initialize 的结果后客户端立刻发一个没有 id 的通知告诉服务端“初始化已完成”。注意这种 notification 不需要响应发完即焚。tools/list客户端向服务端询问“你有哪些工具可以用”。服务端返回一个工具数组每个工具包含名称、描述和 JSON Schema 格式的参数定义。tools/call客户端挑一个工具把参数填好发出真正的调用请求。服务端执行完工具函数后返回结果内容以及一个isError标记用来区分正常结果和错误信息。真正的大模型对接流程本质上就是在第 3 步拿到工具列表后翻译成大模型平台能识别的 function calling schema模型在回答中吐出“我要调用哪个工具、传什么参数”客户端再去执行第 4 步。也就是说MCP 管的是“模型的外设连接”模型自己的推理决策并不是 MCP 协议负责的。这一点先想明白后面接 Agent 才不会迷路。2. 服务端实操十分钟搭一个 Streamable HTTP 的 MCP 服务服务端的选择其实很多你可以用官方 Python SDK 手写协议解析也可以用 Node 生态但如果是快速验证我个人强烈推荐 FastMCP 这个开源框架它把协议协商、JSON-RPC 编解码、SSE 格式处理全部封装好了你只需要用装饰器写普通函数剩下的交给框架去跟客户端周旋。本节的实操可以让你在十分钟内跑起第一个能远程访问的 MCP 服务。2.1 环境准备与库选型用 FastMCP 少写 90% 协议代码先说环境要求。Python 建议 3.10 及以上直接用 pip 安装即可pip install fastmcp如果你更想贴近官方底层一点可以同时装官方 Python SDKpip install mcp[cli]这两个可以并存。FastMCP 在底层依赖官方 SDK但它提供的是“一键暴露函数为工具”的体验比较适合工具数量多、业务逻辑简单的场景。安装后先确认版本差异早期版本的 FastMCP 默认还在走老式 HTTP 传输较新的版本支持了streamable-http。你不需要关心框架内部怎么实现只要知道transport参数会决定最终暴露出来的端点形式即可。如果后面出现 404 或 405回来后大概率要检查这里。2.2 编写工具函数docstring 写得好模型才会用MCP 的 tools/list 拿到的不是函数源码而是工具的描述信息。模型只能靠这些描述来判断“什么时候该用这个工具、参数填什么”。所以工具函数的 docstring 和参数注解重要性不亚于业务逻辑本身。我用两个典型的工具做演示一个是查询时区时间一个是安全的数学计算器。注意数学计算器不能直接eval用户传进来的字符串否则一个__import__(os).system(...)就能把你的服务端变成矿场。这里我用 Python 自带的ast模块做表达式白名单校验只允许加减乘除等少量运算符。from datetime import datetime from zoneinfo import ZoneInfo import ast import operator from mcp.server.fastmcp import FastMCP mcp FastMCP( demo-server, instructions这个服务提供当前时间查询和安全的数学表达式计算能力。 ) mcp.tool() def get_time(timezone: str Asia/Shanghai) - str: 获取指定时区的当前时间。 Args: timezone: IANA 时区名称例如 Asia/Shanghai、UTC、America/New_York。 try: now datetime.now(ZoneInfo(timezone)) return f当前时间{now.isoformat(timespecseconds)} except Exception as exc: return f无法解析时区{exc}计算器部分稍微复杂一点核心是做一个 AST 安全解析器_ALLOWED_NODES ( ast.Expression, ast.BinOp, ast.UnaryOp, ast.Constant, ast.Add, ast.Sub, ast.Mult, ast.Div, ast.Mod, ast.Pow, ) mcp.tool() def calculate(expression: str) - str: 计算一个数学表达式例如 (1 2) * 3 或 2 ** 10。 Args: expression: 纯数学表达式字符串。 try: tree ast.parse(expression, modeeval) for node in ast.walk(tree): if not isinstance(node, _ALLOWED_NODES): return 表达式包含不允许的语法 result eval(compile(tree, filename, modeeval), {__builtins__: {}}, {}) return str(result) except Exception as exc: return f计算失败{exc}这里有几个细节值得展开说。第一ast.parse之后必须ast.walk检查所有节点因为嵌套的属性访问和函数调用都可能藏在子节点里只查根节点等于没查。第二eval的参数字典里我把__builtins__置空避免表达式里访问到内建函数配合前面的节点白名单基本能挡住常见的注入玩法。第三工具内部任何异常都要自己捕获并转成字符串返回不要让它抛到协议层变成 JSON-RPC 的-32603内部错误业务错误和协议错误分开处理会让客户端那边的调试舒服很多。2.3 启动服务确认端口、路径与日志在文件末尾加上if __name__ __main__: mcp.run(transportstreamable-http)然后启动python server.pyFastMCP 默认会拉起一个 ASGI 服务底层是 Uvicorn监听0.0.0.0:8000。端点路径需要留意不同版本有差异有的挂在根路径/有的挂在/mcp/。我建议启动后先看控制台日志它会打印实际暴露的路径。为了避免在客户端侧踩 404 的坑可以先手动确认下面这个地址是可以访问的curl -v http://127.0.0.1:8000/mcp/如果返回 405 Method Not Allowed其实是正常现象因为协议要求只能 POST如果返回 404那就是路径不对。这一条“先用 curl 探路径”的习惯能帮你省下后面至少十分钟的迷茫。服务端启动之后框架其实已经在帮你处理协议协商、会话创建、SSE 流式响应这些活了。你现在拥有的就是一个“可以被远程 MCP 客户端发现和调用”的工具服务。但“可被调用”不等于你知道它背后到底怎么响应。下一节我直接徒手用 HTTP 走一遍协议让你看清每一个字节从哪里来、到哪里去。3. 协议亲手走一遍不依赖 SDK 也能调用很多开发者习惯了一上来就套 SDK协议细节完全黑盒。一旦线上出问题只会看着日志干瞪眼。我建议每个人都至少徒手用 HTTP 客户端走一遍完整流程这比读十遍规范都管用。这里我用 Python httpx 来做演示你不用装任何 MCP 客户端库。3.1 第一步握手 initialize 返回了什么MCP 的下层消息全部是 JSON-RPC 2.0。一个合法的请求至少要有jsonrpc: 2.0、method如果期待响应还要带一个唯一id。initialize 请求长这样POST /mcp/ HTTP/1.1 Host: 127.0.0.1:8000 Content-Type: application/json Accept: application/json, text/event-stream { jsonrpc: 2.0, id: 1, method: initialize, params: { protocolVersion: 2025-03-26, capabilities: {}, clientInfo: {name: demo-client, version: 0.1.0} } }注意Accept这个请求头。Streamable HTTP 规范要求客户端在请求头里同时声明application/json和text/event-stream表示两种响应格式都能接受。如果你的客户端没有带text/event-stream某些服务端实现会直接返回 406 Not Acceptable。这个问题我见了很多次属于典型的小头引发大问题。服务端正常响应时除了返回 JSON-RPC 的 result 外通常还会在响应头里带一个Mcp-Session-Id。这个值相当于你和服务端之间的一条临时会话凭证后面的请求都要带上它。result 里主要包含协议版本、服务端能力声明和服务端信息看到serverInfo: {name: demo-server}就说明握手成功。3.2 工具列表与工具调用的请求格式握手完成后立刻发一个notifications/initialized通知。这是没有id的服务端不会单独响应{jsonrpc:2.0,method:notifications/initialized}发完通知就可以问服务端有哪些工具了{jsonrpc:2.0,id:2,method:tools/list}响应里每个工具会包含三个关键字段name、description、inputSchema。inputSchema是 JSON Schema 格式的参数定义比如get_time工具会声明一个可选字符串参数timezone。模型在决定要不要调用这个工具时读的就是这三样东西。到了真正的tools/call格式变成这样{ jsonrpc: 2.0, id: 3, method: tools/call, params: { name: get_time, arguments: { timezone: Asia/Shanghai } } }工具执行成功后返回的result结构大致是{ jsonrpc: 2.0, id: 3, result: { content: [ {type: text, text: 当前时间2025-07-26T14:30:0008:00} ], isError: false } }content是工具返回的内容数组里面最常见的类型是text未来还会有图片、音频等丰富类型。isError则告诉客户端这次调用是否业务失败。3.3 用 Python httpx 完成全流程交互下面我把上面这些步骤整合成一个可直接运行的脚本关键在两个地方一是每步请求都要带同一个Mcp-Session-Id二是要处理服务端返回的两种 content-type。import json import httpx BASE_URL http://127.0.0.1:8000/mcp/ def extract_json(resp: httpx.Response) - dict: 兼容 application/json 与 text/event-stream 两种响应。 if text/event-stream in resp.headers.get(content-type, ): lines resp.text.split(\n) data_parts [] for line in lines: if line.startswith(data:): data_parts.append(line[5:].strip()) return json.loads(.join(data_parts)) return resp.json() def main(): headers { Content-Type: application/json, Accept: application/json, text/event-stream, } with httpx.Client() as client: # 1. initialize resp client.post( BASE_URL, headersheaders, json{ jsonrpc: 2.0, id: 1, method: initialize, params: { protocolVersion: 2025-03-26, capabilities: {}, clientInfo: {name: demo-client, version: 0.1.0} } } ) init_result extract_json(resp) print(initialize 返回, json.dumps(init_result, ensure_asciiFalse, indent2)) session_id resp.headers.get(mcp-session-id) if session_id: headers[mcp-session-id] session_id # 2. notifications/initialized client.post( BASE_URL, headersheaders, json{jsonrpc: 2.0, method: notifications/initialized} ) # 3. tools/list resp client.post( BASE_URL, headersheaders, json{jsonrpc: 2.0, id: 2, method: tools/list} ) tools_result extract_json(resp) print(工具列表, json.dumps(tools_result, ensure_asciiFalse, indent2)) # 4. tools/call resp client.post( BASE_URL, headersheaders, json{ jsonrpc: 2.0, id: 3, method: tools/call, params: { name: get_time, arguments: {timezone: Asia/Shanghai} } } ) call_result extract_json(resp) print(调用结果, json.dumps(call_result, ensure_asciiFalse, indent2)) if __name__ __main__: main()第一次跑完这个脚本你基本就算“亲手打通”了 Streamable HTTP 的协议链路。你甚至可以用同样的方式直接对接任何一个 MCP Server只要把BASE_URL换掉就行。不过为了演示真实业务我们不会一直手动构造这些 JSON——下一步就用官方 SDK 接手让代码更简洁。4. 接上大模型完成真正的 AI 自动调用闭环可能你已经发现了上面几步不管是用 curl 还是 httpx调用决策都是人脑做出的——“哦这里该调 get_time”。真实场景里这个决策应该由大模型来做。所以“AI 调用 MCP”的完整闭环是用户提问 → 模型看到工具列表 → 模型选择工具并生成参数 → 你的程序去调用 MCP Server → 把结果回填给模型 → 模型给出最终回答。4.1 用官方 Python SDK 替代手写协议如果你手头没有特殊的定制需求直接用官方 SDK 是最省力的。官方 Python SDK 里提供了一个针对 Streamable HTTP 的客户端入口它会自动帮你维护 session id、解析 SSE 流、处理 JSON-RPC 消息封装。import asyncio from mcp import ClientSession from mcp.client.streamable_http import streamablehttp_client SERVER_URL http://127.0.0.1:8000/mcp/ async def main(): async with streamablehttp_client(urlSERVER_URL) as (read, write, _): async with ClientSession(read, write) as session: await session.initialize() tools await session.list_tools() for tool in tools: print(tool.name, :, tool.description) result await session.call_tool( get_time, {timezone: Asia/Shanghai} ) print(result.content) asyncio.run(main())这个代码最需要留心的是版本兼容。新版 SDK 的streamablehttp_client返回三个参数但在一些旧版本里函数位于mcp.client.http且返回两个参数。所以如果你在导入或者解包时看到报错优先检查当前安装的版本再调整导入路径。这不是什么高深问题纯粹是规范迭代太快导致的不用怀疑自己。4.2 把 MCP 工具转换成模型可识别的函数描述官方 SDK 帮你搞定了通信但大模型并不懂什么叫 MCP它只认自家平台的 function calling schema。好在两者结构高度相似。MCP 的tools/list返回的inputSchema本身就是 JSON Schema而主流大模型平台要求的parameters字段恰恰就是 JSON Schema。所以转换函数非常机械def convert_tool_to_schema(tool): return { type: function, function: { name: tool.name, description: tool.description, parameters: tool.inputSchema, } }转换完之后这个列表就可以直接塞进大模型接口的tools参数里。工具名、描述和参数结构模型会在生成回答时读取用于判断是否调用工具、调用哪个工具、传什么参数。这里有一个经常被忽略的细节MCP 工具的name可能有命名空间前缀比如projectA__get_time这种而模型平台的函数名通常不允许某些特殊字符。如果你在对接时发现模型生成不了工具调用先检查一遍转换后的 schema 是否符合模型平台的命名规范。不是所有的 MCP Server 都适合原样照搬必要时候要做一层简单的 name 映射。4.3 Agent 循环骨架决策、执行、回填接下来是核心的闭环逻辑。为了不绑定具体厂商 SDK我用一个抽象的llm_client来展示骨架。你实际项目里用的任何支持 function calling 的 SDK本质上都是同一个套路。import json import asyncio from mcp import ClientSession from mcp.client.streamable_http import streamablehttp_client SERVER_URL http://127.0.0.1:8000/mcp/ def tools_to_schemas(tools): return [convert_tool_to_schema(t) for t in tools] def tool_result_to_text(result) - str: parts [] for item in result.content: if item.type text: parts.append(item.text) return \n.join(parts) async def run_agent(llm_client, user_query: str): async with streamablehttp_client(urlSERVER_URL) as (read, write, _): async with ClientSession(read, write) as session: await session.initialize() tools await session.list_tools() schemas tools_to_schemas(tools) messages [{role: user, content: user_query}] # 第一轮把工具列表交给大模型让它决定要不要调用 response llm_client.chat( modelyour-model-name, messagesmessages, toolsschemas, tool_choiceauto, ) msg response.choices[0].message # 模型决定调用工具 if msg.tool_calls: for call in msg.tool_calls: tool_name call.function.name args json.loads(call.function.arguments) print(f模型准备调用{tool_name}({args})) mcp_result await session.call_tool(tool_name, args) messages.append({ role: tool, tool_call_id: call.id, content: tool_result_to_text(mcp_result), }) # 第二轮把工具执行结果回填给模型生成最终回答 final_response llm_client.chat( modelyour-model-name, messagesmessages, ) return final_response.choices[0].message.content return msg.content这个 Agent 循环并不复杂但它是“AI 调用 MCP”的实际落地形态。你在这个骨架里可以追加很多增强比如多轮工具调用、异常重试、把工具执行结果截断后再回填给模型防止结果太大撑爆上下文、给工具调用加人工审批等。我实际项目的经验是先跑通最简单的单工具闭环再去加那些花活否则排查问题的时候根本分不清是模型的问题还是 MCP 链路的问题。如果你要做的是 Web 前端直连还需要注意 CORS。FastMCP 服务端可以配置cors_allow_origins否则浏览器端请求会被同源策略拦截。绝大多数服务端对服务端的调用没有这个麻烦但一旦引入浏览器这就是必答题。5. 常见问题与排查实录踩完这些坑你会更熟练从协议手写到 SDK 接入再到接上大模型整条链路任何一个环节出问题都会表现成各种奇怪的报错。我把这段时间自己踩过的坑、以及帮别人排查过的典型问题整理成一份速查清单基本覆盖了大多数场景。5.1 404 / 405端点和传输方式不匹配这类错误最气人因为它往往不是你代码写错了而是版本或配置差异。HTTP 状态可能原因排查方向404请求路径和服务端暴露路径不一致查看服务端启动日志里的实际路径确认是/mcp/还是/405用了 GET 请求访问工具调用端点Streamable HTTP 统一走 POSTGET 只用于健康检查场景406Accept 请求头缺少text/event-stream请求头必须同时带application/json, text/event-stream有一个判断技巧先用 curl 对同一个 URL 做 POST 空请求看返回的状态码和响应体。如果是 404说明端点不对如果是 405说明端点对了但方法不对如果是 400 或 500说明协议消息格式有问题。这一步能最快帮你定位问题层级。5.2 JSON-RPC 错误参数、方法、协议版本MCP 使用 JSON-RPC 的错误码体系服务端返回的错误结构里会有code和message字段。下面这几个是最常见的错误码含义常见场景-32700解析错误请求体不是合法 JSON通常是手写请求时少了逗号或引号-32600无效请求缺少jsonrpc或method字段或者请求格式不符合规范-32601方法不存在调了不存在的协议方法常见于大小写错误比如Tools/List-32602无效参数参数不符合工具inputSchema比如get_time传了Asia/BeiJing这种非法时区-32603内部错误工具内部抛出了未捕获的异常针对-32602我建议工具函数内部尽量做容错把可预期的非法输入转成正常返回的文本而不是让协议层返回错误码。因为大模型很多时候会凭感觉生成参数比如用户说“看一下北京现在几点”模型可能会把timezone填成北京而 IANA 时区标准里根本没有这个名字。如果工具直接报-32602模型只能看到错误码不理解为什么错如果工具返回“无法解析时区北京请使用 IANA 名称”模型下一轮就能自己修正。5.3 Session 与会话保活远程调用最容易忽视的一环Streamable HTTP 的会话机制是很多人第一次接触时会懵的点。初始化握手成功后服务端返回Mcp-Session-Id之后的请求必须带上它如果你每次请求都重新握手服务端会为每个请求创建新会话旧会话就变成垃圾数据。几个实战注意点握手后一定要保存 session id并复用到后续请求。如果一个响应里更新了Mcp-Session-Id以最新的为准。有的服务端会在特定时机轮换 session。服务端会设置会话超时。如果长时间不活动会话过期后请求会失败此时需要重新拉一次握手。如果服务端部署在多实例负载均衡后面同一个 session 的请求可能被分发到不同实例导致 session 找不到。要么开启粘性会话要么让负载均衡层按 session id 做一致性哈希。鉴权方面Streamable HTTP 的标准做法是支持自定义 HTTP 头。很多 MCP Server 会要求客户端带Authorization头做鉴权配置方式和普通 REST API 完全一样但不要把它放在 JSON-RPC 的 params 里——那不是协议层认识的东西。5.4 SSE 响应解析的隐藏雷区当工具执行时间较长或者服务端希望主动推送时响应会以text/event-stream格式返回。如果你手写解析器有几个非常容易踩的坑。第一不要用response.text.split(\n)然后逐行解析因为一个 SSE 事件可能由多行data:组成而且事件之间以空行分隔。正确的做法是按\n\n分割事件块再对每个块解析内部的data:行。第二用 curl 调试 SSE 时记得加-N参数关闭 curl 的缓冲否则你可能会觉得服务端一直没有响应其实数据早就到了只是被缓冲住了。第三服务端不一定每次都会返回 SSE。协议允许服务端在响应不特别长的情况下直接返回普通 JSON。所以客户端最好同时兼容两种 content-type否则你会发现“时好时坏”一会能跑一会报错。官方 SDK 已经把这种兼容做完了这也是我建议生产环境用 SDK 而不是手写协议的原因。5.5 模型不调用工具问题往往不在 MCP如果你已经确认 MCP 链路完全正常——手写协议能通、SDK 能通但接上大模型后模型死活不调用工具这时候问题百分之八十在 schema 那边。我见过最典型的情况有几种工具描述写得太笼统模型不知道什么时候该用。inputSchema里required字段标少了模型以为所有参数都可选结果漏传必填参数。工具数量太多模型一次要读几百个 schema注意力被稀释干脆不调用。转换工具 schema 时字段名和模型平台要求不一致比如模型要parameters你传了input_schema。排查建议是把转换后的 schema 打印出来直接喂给模型“原始”看一遍。你会惊讶地发现很多问题在人眼看一眼之后就能发现。6. 最后聊几句实操体会这套流程我反复跑过很多次之后最大的感触是MCP 本身不是一个高深的协议但它被夹在三层东西中间——协议规范、各个 SDK 的版本差异、大模型平台的函数调用格式任何一个版本的细微差别都可能变成一天的排查时间。所以我真心建议你第一次接 MCP 不要直接跳到“让模型调用”先徒手走一遍 HTTP 流程哪怕只是打印出每个响应都能帮你建立直觉。还有一个习惯我会一直保留给每个工具的函数都写清楚 docstring 和参数说明并且在开发阶段就把常见错误情况封装成自然语言返回。模型对“工具怎么用”的所有理解都来自描述文本你现在偷懒少写的每一句话都会在未来某个诡异的调试之夜还回来。最后分享一个扩展方向这套基于 Streamable HTTP 的链路不只适用于单个工具服务。你可以把它当做一个网关后端再接内存数据库、向量检索、内部 API 等等。MCP 的价值不是帮你省一次两次接口对接而是把“模型和外部世界的边界”标准化让工具接入这件事从点对点的定制开发变成了按标准拼装积木。这个趋势在未来很长一段时间都会是 AI 工程的核心主线。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。