Anthropic SDK MCP 接入

QuibblerAgentQuibblerAgent 2026-11-01 约 21 分钟 3 次阅读

第 4 篇把工具调用循环拆到了最细:手写 schema、手写分发、手写回传,一个 get_weather 从定义到执行全在你的仓库里。这套打法对项目专属工具毫无问题,但工具世界的大头本来就不在你的仓库里——GitHub、浏览器、数据库、网页抓取,每个都值得接,每个都手写一遍就是重复造轮子。第 9 篇结尾预告的主题正对着这里:MCP 把工具从"写死在代码里"变成"即插即用的服务"——工具由独立进程自我描述、按协议调用,你的代码只负责连接与消费。

本文是《Anthropic SDK 从入门到实战》第 10 篇。先交代当前 SDK 的 MCP 官方口径(有一处旧教程陷阱必须先纠正),再走本地 stdio 路线:连接现成服务、发现工具、与普通工具混用,最后用官方 fetch server 组装一个网页摘要助手。全文按"MCP 是什么 → 两条路线 → stdio 连接 → 工具发现 → 混合消费 → 取舍判断 → 实战 → 总结"组织。

1、MCP 是什么

2024 年 11 月,Anthropic 把内部连接模型与外部工具的协议开源,就是 MCP(Model Context Protocol)——一个基于 JSON-RPC 的开放协议,规定了客户端与服务端如何握手、如何列出能力、如何调用。它如今由社区共同演进,Claude 之外的主流编程助手与各家 Agent 框架都已支持,协议官网维护着规范与实现清单。

协议规定服务端能提供三类能力:

  • 工具(tools):模型可调用的函数,如抓网页、查仓库
  • 提示(prompts):服务预置的提示词模板
  • 资源(resources):服务暴露的文档与数据

本篇聚焦第一类,它是第 4 篇工具调用在"供应链"上的升级;三类能力里工具与 Messages API 的对接也最直接,生态里的现成服务绝大多数以工具为主。手写与 MCP 两种形态对比如下:

维度 第 4 篇手写工具 MCP 工具
定义来源 代码里手写 schema 服务自我描述,发现即定义
执行位置 自己写的分发函数 服务进程内部
复用范围 单个项目独占 任意 MCP 客户端共享
新增工具 改代码重新部署 接一个新服务即可

解读:表里最后一行是关键差别。手写形态下工具的定义、执行、维护与你的项目同生共死;MCP 形态下这三件事全部移进独立进程,你的代码只剩"连接加消费"两步——换一个服务进程,模型的能力面立刻换一批,代码一行不动,这正是"即插即用"的含义。

2、两条接入路线

先纠正一个口径,避免照抄旧教程。早期版本的 Python SDK 曾把 stdio 客户端内置在 anthropic 包里:mcp_servers 参数直接传 MCPStdioServerParameters,SDK 负责拉起子进程并转换工具。

注意:这套内置客户端在当前 2.x SDK(本系列的 2.3.0)源码里已经移除,MCPStdioServerParameters 等类型不复存在,照抄 2025 年的旧教程会在导入这一步直接报错。现行口径以 SDK 的 helpers 文档为准。

现在的官方分工一句话:远程服务走连接器,本地服务走 helpers。两条路线对比如下:

维度 本地 stdio 路线 远程 connector 路线
服务位置 本机子进程 服务商托管的 URL
连接方式 mcp 包拉起进程 mcp_servers 传 URL 定义
工具执行 本地子进程内 Anthropic 服务端代办
能力范围 工具、提示、资源 工具
接口稳定性 正式能力 beta

connector 路线的代码极短,以连接 Notion 官方远程服务为例:

response = client.beta.messages.create(
    model="claude-sonnet-5-5",
    max_tokens=1024,
    mcp_servers=[                          # beta 命名空间下的连接器
        {"type": "url", "url": "https://mcp.notion.com/mcp",
         "name": "notion"}
    ],
    messages=[{"role": "user", "content": "搜索我空间里关于发布的页面"}],
)

解读:连接器把发现、调用、回传的整个 MCP 往来交给 Anthropic 服务端代办,你只出 URL,代价是只覆盖工具一类能力且仍在 beta。本地 stdio 是另一头:子进程跑在你机器上,协议往来发生在你的事件循环里,控制权与能力面都完整。官方文档的判断标准原样转述:远程 URL 且只要工具,用 mcp_servers;要本地服务、要用提示与资源、要控制连接,用 helpers。细则见官方 MCP 连接器文档。本篇主线走本地 stdio。

3、stdio 路线:拉起服务进程

本地路线要装两个包,命令如下:

pip install "anthropic[mcp]"      # SDK 附加项,带上官方 mcp 包
pip install mcp-server-fetch      # 官方 fetch server,抓网页用

解读:anthropic[mcp] 的附加项装的是 modelcontextprotocol 官方 Python SDK(要求 Python 3.10 以上),SDK 的 MCP 转换层依赖它;mcp-server-fetch 是官方 servers 仓库里的现成服务,装好后可用 python -m mcp_server_fetch 运行。连接它的三件套如下:

import asyncio, sys
from mcp import ClientSession
from mcp.client.stdio import stdio_client, StdioServerParameters

SERVER = StdioServerParameters(
    command=sys.executable,            # 用当前解释器拉起子进程
    args=["-m", "mcp_server_fetch"],   # 官方 fetch server
)

async def main():
    async with stdio_client(SERVER) as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()  # 协议握手:能力协商
            print("fetch server 已就绪")

asyncio.run(main())

解读:三层各司其职。stdio_client 按 command 加 args 拉起子进程,拿它的标准输入输出当传输通道——"stdio 路线"因此得名;ClientSession 在通道上讲协议;initialize() 是协议规定的握手,双方交换版本与能力清单。两段 async with 退出时依次关会话、关进程,不留孤儿进程。留意一个细节:StdioServerParameters 是 mcp 包的类而不是 SDK 的——连接归 mcp 包,转换归 SDK,这正是现行分工在代码里的样子。

4、list_tools 工具发现

连上之后第一件事不是调用,是问"你有什么"。协议为此规定了发现方法,一行可用:

result = await session.list_tools()   # 工具发现:列出服务的能力
for t in result.tools:
    print(f"{t.name} —— {t.description}")

一次运行的输出如下:

fetch —— Fetches a URL from the internet and extracts its contents as markdown.

解读:fetch server 只提供一个 fetch 工具,描述写明"抓取 URL 并转成 markdown"——这段描述就是给模型看的"什么时候该用我",与第 4 篇手写的 description 用途完全相同。每个工具对象还带输入 schema:url 必填;max_length 限制返回长度,默认 5000 字符;start_index 支持从指定位置续读,网页太长时模型自己分段翻页。发现即定义——list_tools 拿到的名字、描述、schema 三件套,与第 4 篇手写在 tools 参数里的字段一一对应,模型侧看不出任何差别。

5、MCP 工具与普通工具并存

消费环节 SDK 已经封好:anthropic.lib.tools.mcp 的 async_mcp_tool 把一个 MCP 工具连同它的会话包装成"可运行工具";普通函数工具用 beta_async_tool 装饰器定义,两者混进同一个 tools 列表,交给 tool_runner 跑完整个循环:

import asyncio
from anthropic import AsyncAnthropic
from anthropic.lib.tools import beta_async_tool
from anthropic.lib.tools.mcp import async_mcp_tool

client = AsyncAnthropic()

@beta_async_tool
def now() -> str:
    """返回当前时间,ISO 8601 格式。"""
    from datetime import datetime
    return datetime.now().isoformat()

async def main():
    async with stdio_client(SERVER) as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()
            tools = [async_mcp_tool(t, session)
                     for t in (await session.list_tools()).tools]
            tools.append(now)             # 两类工具同列共存
            runner = client.beta.messages.tool_runner(
                model="claude-sonnet-5-5", max_tokens=1024, tools=tools,
                messages=[{"role": "user",
                           "content": "现在几点?顺便抓取 example.com 概括。"}],
            )
            async for message in runner:
                print(message.stop_reason)  # 观察循环走到哪一步

asyncio.run(main())

解读:tool_runner 就是第 4 篇 while 骨架的官方封装——迭代它,遇到 stop_reason 为 tool_use 的消息就自动执行工具并回传,其余停止原因在产出最终消息后结束迭代。async_mcp_tool 内部做的事恰好是第 4 篇循环里手写的三步:用工具对象组装定义,模型点名时调用 session.call_tool,把 MCP 结果转成 tool_result。对模型来说两类工具毫无差别,整条消息流如下:

sequenceDiagram participant App as 你的程序 participant API as Claude API participant MCP as fetch 子进程 App->>API: 携带两类工具定义请求 API-->>App: tool_use 块点名 fetch App->>MCP: call_tool 执行 fetch MCP-->>App: 返回网页内容 App->>API: tool_result 回传 API-->>App: 最终 text 回答

解读:fetch 调用走右支进子进程,now 调用直接在本地函数执行,两条支路的结果汇进同一条回传消息——工具的产地对模型透明,透明才有组合的自由。不想引入 runner 也行:把发现的工具转成定义列表放进 tools,在执行分支里调 session.call_tool,第 4 篇的手动循环骨架一字不改照样能转。

6、何时值得上 MCP

判断只看工具的供需关系。三种情况明确值得:

  • 多应用共享:Claude Desktop、IDE 插件、你的脚本接同一个服务,工具维护一处、处处生效
  • 吃现成生态:官方 servers 仓库里 GitHub、Postgres、Playwright 等开箱即用,Notion 等厂商也自托管了服务
  • 团队统一入口:工具集中在一个服务里,权限、审计、升级统一管理

反向也同样明确:一次性专用工具参数简单、只此一家使用,第 4 篇手写 schema 加分发十行解决,起服务反而是负担;强延迟敏感的在线链路要多付一跳进程通信与协议握手,好在握手只发生一次,后续每次工具调用就是与常驻子进程的一次往返;工具定义会全部计入输入 token,服务越热闹前缀越贵——好在 async_mcp_tool 支持 cache_control 参数,给工具列表挂上第 7 篇的缓存断点,这块成本能压下去。

一句话决策:工具要"多处复用"或"别人已经写好"时上 MCP,否则手写更快。

7、实战:网页摘要助手

零件齐了,组装完整的一单:fetch 工具抓页面,模型用中文概括。完整脚本如下:

import asyncio, sys
from anthropic import AsyncAnthropic
from anthropic.lib.tools.mcp import async_mcp_tool
from mcp import ClientSession
from mcp.client.stdio import stdio_client, StdioServerParameters

client = AsyncAnthropic()
SERVER = StdioServerParameters(
    command=sys.executable, args=["-m", "mcp_server_fetch"])

async def main():
    async with stdio_client(SERVER) as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()
            tools = [async_mcp_tool(t, session)
                     for t in (await session.list_tools()).tools]
            runner = client.beta.messages.tool_runner(
                model="claude-sonnet-5-5", max_tokens=2048, tools=tools,
                messages=[{"role": "user", "content":
                    "抓取 https://modelcontextprotocol.io 首页,"
                    "中文概括它是什么、站点提供哪些内容,100 字以内。"}],
            )
            final = None
            async for message in runner:
                final = message            # 迭代停在最终回答那轮
    print(next(b.text for b in final.content if b.type == "text"))

asyncio.run(main())

一次典型运行的输出如下:

MCP 是一个开放协议,标准化 AI 应用与外部数据和工具的连接方式;
站点提供协议规范、多种语言的 SDK、教程文档与服务器示例。

解读:从连接到答案约 25 行,其中没有一行是"工具代码"。三个行为值得注意:其一,概括这种小任务模型只调一次 fetch,问题更复杂时它会带着 start_index 自己分段翻页;其二,fetch server 抓不到目标(robots.txt 拒绝、目标是非公网地址)时会把原因作为工具结果返回,模型据此作答或放弃,异常不会抛进你的脚本;其三,换任务不用换骨架——把 SERVER 换成文件系统服务(Node.js 环境下用 npx -y @modelcontextprotocol/server-filesystem),同一个脚本立刻变成目录问答助手,即插即用在此兑现。

8、总结

本篇把 MCP 接入的 2026 年 10 月口径讲全:MCP 把工具从代码搬进自我描述的服务进程;SDK 旧内置 stdio 客户端已移除,现行两条路线——远程 URL 走 beta 的 mcp_servers 连接器,本地 stdio 走 mcp 包连接加 SDK helpers 转换;list_tools 发现即定义,async_mcp_tool 包装后与普通工具混进同一个 tools 列表,tool_runner 把第 4 篇的循环自动跑完。一句话带走:工具多处复用或已有现成服务就上 MCP,一次性小工具手写更快。

留三个练习:其一,把第 7 节脚本的 SERVER 换成文件系统服务(需本机有 Node.js),让它回答"这个目录里最长的 md 文件讲了什么";其二,给第 5 节的 tools 再加一个自写的中文工具,观察模型在 MCP 工具与本地工具之间的选择依据;其三,挑一个厂商托管的远程服务,用 connector 路线重跑摘要任务,对照两条路线的代码量与能力差异。对于工具需求已经超出自己仓库的开发者,建议今天就把 fetch server 这 25 行跑起来——即插即用的体感,跑一次就明白。下一篇讲错误处理与可观测性:限流、超时、断流这些线上必遇的问题,一次讲清。

相关推荐

获取系统SDK版本、判断手机ROM
精选Code

获取系统SDK版本、判断手机ROM

Build获取系统SDK版本Android中部分API的使用,需要在特定的SDK版本之后才能使用,因此在兼容老版本SDK的时候,经常需要判断API的版本。各种Android版本的对应关系参考《Android各版本对应的SDK版本》判断手机ROM有时候需要判断手机系统的ROM,检测ROM是MIUI、EMUI还是Flyme,可以使用getprop命令,去系统build.prop文件(关于build.p

4.2k
AndroidStudio中各种中文乱码问题
精选Code

AndroidStudio中各种中文乱码问题

AndroidStudio中各种中文乱码问题1、编译Java错误信息乱码在出现这个Annotation processors must be explicitly declared now...问题的时候,正好也发现AndroidStudio Build Output错误信息都乱码。经常遇到各种问题,习惯了,现在遇到的问题,都是以后的答案。 1.1、修改项目build.gradle(无效)在整个p

2.8k
PydanticAI 详解:类型安全的 Python AI SDK
精选AI

PydanticAI 详解:类型安全的 Python AI SDK

PydanticAI 详解:类型安全的 Python AI SDK写 LLM 应用最疼的两件事:模型返回的 JSON 不合预期、换一家模型商要改一串代码。pydantic/pydantic-ai(PydanticAI)用 Pydantic 团队的老本行回答了第一件——输出用 BaseModel 定义、运行前先校验,错误从运行时提前到编码时;第二件则用"模型即字符串"回答:换个厂商只改一个参数。如今

57