- 人工智能
- MCP 服务
- MCP Clients
【免费下载链接】python-sdk
The official Python SDK for Model Context Protocol servers and clients
导读
本指南面向 MCP(Model Context Protocol)新手,也面向刚接触本 SDK 的开发者,讲解如何从"什么都没有"一路走到"一个可运行、可测试的 MCP 服务器"。你将学会:安装 Python SDK、用三个装饰器写出同时暴露工具(Tool)、资源(Resource)与提示词(Prompt)的服务器、通过uv run mcp dev server.py在 MCP Inspector 中交互调试、用Client(mcp)内存客户端编写不依赖任何子进程与端口的自动化测试,并最终把它接入 Claude Desktop、Claude Code、Cursor、VS Code 等真实主机(Host)。文中所有代码示例都来自仓库 docs_src/ 目录,并且全部由 SDK 自身的测试套件实际运行验证,你可以放心复制使用。
学习路径概览
入门路线由四个步骤组成,对应 docs/get-started/index.md 的四个核心环节:
- 安装 SDK:把
mcp包装进你的 Python 环境(要求 Python 3.10+); - 创建第一个服务器:用少量 Python 代码写出一个完整服务器;
- 接入真实主机:让 Claude Desktop、IDE 等应用启动并连接你的服务器;
- 测试服务器:用内存客户端验证行为,无需起进程、无需占用端口。
如果你已经在文档其他部分见过 MCP,也可以直接跳到需要的页面——完成入门后,其余文档就从"教程"变成了"参考手册",每页均可独立阅读。
安装 SDK
SDK 以mcp包的形式发布在 PyPI 上,要求Python 3.10 及以上。文档描述的是v2,即当前的稳定主线。使用uv或pip均可安装:
=== "uv"
```bash uv add "mcp[cli]" ```=== "pip"
```bash pip install "mcp[cli]" ```!!! note "从 v1 迁移?" v2 是一次带破坏性变更的主版本升级,迁移指南 覆盖了全部变更点。如果你的项目依赖mcp但尚未准备好迁移,请保持<2的上限约束(例如mcp>=1.28,<2),让未固定版本的解析结果停留在 1.x 线。
装了些什么
不必了解每个依赖也能正常使用 SDK,但如果好奇各依赖的用途,从 docs/get-started/installation.md 可以查到完整说明:
mcp-types:所有协议类型(请求、结果、内容块)各自独立成包,与 SDK 锁步发布版本。依赖mcp的代码通过mcp.types别名导入(文档中所有from mcp.types import ...都是这样);只有在你单独安装mcp-types而不安装 SDK 的项目里,才需要直接import mcp_types。从 src/mcp/init.py 可以看到,SDK 通过from . import types as types把mcp.types子模块绑定到包上,import mcp之后mcp.types.Tool依然可用。anyio:异步运行时。整个 SDK 都基于 anyio 编写,因此既能跑在asyncio上,也能跑在trio上。pydantic:所有mcp.types模型的基础,也是全部 Schema 生成与校验的来源。httpx2:Streamable HTTP 与 SSE 客户端传输背后的 HTTP 客户端,内置 server-sent events 支持。starlette、uvicorn、sse-starlette、python-multipart:HTTP 服务端传输层。jsonschema:按声明的输出 Schema 校验工具的结构化输出。pyjwt[crypto]:授权场景下的 OAuth 令牌处理。opentelemetry-api:只引入轻量 API,SDK 的追踪中间件在你不安装 OpenTelemetry SDK 与导出器时不产生额外成本。typing-extensions与typing-inspection:为 Python 3.10 提供现代 typing 特性。pywin32:仅 Windows 使用,负责stdio子进程管理。
可选扩展
mcp[cli]:额外安装typer与python-dotenv,为mcp命令行工具(mcp dev、mcp run、mcp install)提供支持。开发阶段建议安装,部署服务器时未必需要。mcp[rich]:额外安装rich,让服务器日志更美观。
第一个服务器:三种原语与三个装饰器
在写代码之前,先厘清三个贯穿全部文档的核心角色(详见 docs/get-started/first-steps.md):
- 主机(Host):LLM 应用,例如 Claude、IDE、Agent 运行时,是用户直接对话的对象;
- 客户端(Client):寄居在主机内部、负责说 MCP 的那一半。主机为每个已连接的服务器运行一个客户端;
- 服务器(Server):你用本 SDK 构建的东西,向客户端暴露能力,从不直接与模型对话。
你编写的是服务器。主机是别人的产品。SDK 同时提供Client类——主机按 URL 连接服务器或把它作为子进程拉起时用的正是同一个类,它稍后会出现在本页,也是你测试自己服务器的工具。
三种原语:谁来决定使用它们
一个服务器恰好暴露三类事物,区分它们的核心是由谁决定使用:
| 原语(Primitive) | 控制方 | 是什么 | 示例 |
|---|---|---|---|
| 工具(Tools) | 模型 | 模型为采取行动而调用的函数 | 一次 API 调用、一次数据库写入 |
| 资源(Resources) | 应用程序 | 主机加载进模型上下文的数据 | 文件内容、API 响应 |
| 提示词(Prompts) | 用户 | 用户按名称调用的可复用消息模板 | 斜杠命令、菜单项 |
"控制方"是整个划分的意义所在:工具因模型决定调用而运行;资源因应用程序判断模型需要而附加;提示词因用户主动选择而执行。
如果你构建过 Web API,直觉上已经掌握了大部分:资源类似GET(加载数据、不改变状态),工具类似POST(执行工作、可能有副作用),提示词没有 HTTP 对应物,更接近用户按名称运行的已保存查询。
一个服务器,三种原语
下面是完整示例 docs_src/first_steps/tutorial001.py,三个普通函数、三个装饰器,每个装饰器就是一次完整注册:
from mcp.server import MCPServer mcp = MCPServer("Demo") @mcp.tool() def add(a: int, b: int) -> int: """Add two numbers.""" return a + b @mcp.resource("greeting://{name}") def greeting(name: str) -> str: """Greet someone by name.""" return f"Hello, {name}!" @mcp.prompt() def summarize(text: str) -> str: """Summarize a piece of text in one sentence.""" return f"Summarize the following text in one sentence:\n\n{text}"逐一拆解:
@mcp.tool()把add变成工具;@mcp.resource("greeting://{name}")把greeting变成资源模板:URI 中的{name}就是函数参数;@mcp.prompt()把summarize变成提示词:它返回的字符串会成为一条用户消息。
其余一切(名称、描述、参数 Schema)都由 SDK 从函数自身读取:函数名、docstring、类型注解。你从未单独声明过它们。注意两个导入路径的差异:客户端用from mcp import Client,服务器用from mcp.server import MCPServer——不存在from mcp import MCPServer。
用 MCP Inspector 试运行
用 MCP Inspector 启动它:
uv run mcp dev server.py打开命令打印出的 URL。Inspector 为每种原语各有一个标签页,按顺序逐个体验:
工具(Tools):只有一个条目
add,描述为Add two numbers.。表单包含必填的整数字段a和b,填好调用,结果是3。这个表单正是 Inspector 根据a: int, b: int生成的——其他任何客户端也会这么做。资源(Resources):资源列表为空。
greeting位于**资源模板(Resource Templates)**下,因为greeting://{name}带参数:在有人提供name之前,并不存在可列出的具体资源。填入World并读取,得到:Hello, World!提示词(Prompts):只有一个条目
summarize,带必填参数text。输入文本获取后,你会收到一条role: user、内容为渲染后字符串的消息。提示词的全部内涵就是:一个构建消息的函数。
Inspector 通过stdio运行你的服务器——这只是 MCP 服务器能说的一种传输。现在不用选,运行服务器 才是讲这个的页面。
从 CLI 源码看(src/mcp/cli/cli.py),mcp dev会先导入你的服务器文件读取其依赖,然后构造uv命令,再调用npx @modelcontextprotocol/inspector拉起 Inspector 并把服务器作为子进程交由其托管。如果系统找不到npx,命令会明确报错提示需要 Node.js/npm 并加入 PATH。
能力声明(Capabilities)
Inspector 里出现三个标签页,客户端是怎么知道的?
客户端连接时,服务器会声明它的能力(capabilities):它愿意应答哪几类请求。客户端依据这份声明决定该请求什么。你从未写过它——MCPServer替你声明了。
亲自看一下。一个终端用 HTTP 方式运行服务器:
uv run mcp run server.py --transport streamable-http另一个终端用 docs_src/first_steps/tutorial001_client.py 指向它:
import anyio from mcp import Client async def main() -> None: async with Client("http://localhost:8000/mcp") as client: print(client.server_capabilities.model_dump(exclude_none=True)) if __name__ == "__main__": anyio.run(main)python client.py{'prompts': {'list_changed': True}, 'resources': {'subscribe': True, 'list_changed': True}, 'tools': {'list_changed': True}}这个字典就是你的服务器声明的能力,也是每个连接客户端学习到的第一件事:
| 能力(Capability) | 客户端现在可以调用 |
|---|---|
tools | tools/list、tools/call |
resources | resources/list、resources/templates/list、resources/read |
prompts | prompts/list、prompts/get |
MCPServer同时提供三种原语,所以三者总是被声明。注意字典里没有completions:参数自动补全(面向资源模板和提示词)需要一个你亲自编写的处理器,这个服务器没有,因此该能力缺席,行为良好的客户端也就不会发起这类请求。这就是一切可选特性的规则:注册了什么,能力就出现什么,Completions 一文可以印证。
你没有写过什么
回顾这一页:你写了三个很小的 Python 函数,但没有写:
- JSON Schema:
a: int, b: int本身就是add的 Schema; - 请求处理器:
tools/list、resources/read、prompts/get全部由 SDK 代劳; - 能力声明:
MCPServer替你生成了; - 任何协议代码:版本协商、JSON-RPC 帧、能力交换全部发生在
mcp dev和client.py内部,你从未见过。
这个比例正是本 SDK 的意义所在。
内存客户端测试:你不需要靠猜测
SDK 的Client类——那个既能连 URL、又能拉起子进程的类——同样支持内存连接:把服务器对象直接传给它,它就直接与服务器对话(详见 docs/get-started/testing.md)。
没有子进程、没有端口、没有线路上的任何东西。这与 FastAPI 的TestClient是同一个思路。
假设你有一个只含一个工具的简单服务器(即 docs_src/testing/tutorial001.py):
from mcp.server import MCPServer mcp = MCPServer("Calculator") @mcp.tool() def add(a: int, b: int) -> int: """Add two numbers.""" return a + b运行下面的测试需要两个额外的开发依赖:
=== "uv"
```bash uv add --dev pytest inline-snapshot ```=== "pip"
```bash pip install pytest inline-snapshot ```文档假设你已了解pytest。inline-snapshot用于在一行里断言整个结果对象——它把测试输出记录成你看到的snapshot(...)字面量。如果不想用它,去掉该导入,像普通测试那样断言关心的字段即可(例如result.content[0].text == "3")。
下面是测试:
import pytest from inline_snapshot import snapshot from mcp import Client from mcp.types import CallToolResult, TextContent from server import mcp @pytest.fixture def anyio_backend(): # (1)! return "asyncio" @pytest.fixture async def client(): # (2)! async with Client(mcp, raise_exceptions=True) as c: yield c @pytest.mark.anyio async def test_call_add_tool(client: Client): result = await client.call_tool("add", {"a": 1, "b": 2}) # Drop the server identity stamp in `_meta`; it is not what this test is about. result.meta = None assert result == snapshot( CallToolResult( content=[TextContent(type="text", text="3")], structured_content={"result": 3}, ) )- 如果你使用
trio,把返回值改为"trio"即可(详见 anyio 文档中关于指定运行后端的说明)。 - 该 fixture 产出已连接的客户端。每个接收
client的测试都会得到一条指向同一服务器的全新内存连接。
为什么要raise_exceptions=True?
有两类不同的错误可能发生,这个开关只影响其中一类:
- 工具内部的异常不是协议故障,它会变成
is_error=True的普通结果(若是ToolError,模型还能读到你的消息)。raise_exceptions不改变这一点:无论开关与否,call_tool返回的都是同一个is_error=True结果。完整讲解见 处理错误。 - 工具体外部的失败则不同。在
Client(mcp)建立的连接上,服务器会先把异常清理成通用的"Internal server error"再让客户端看到——你绝不该向远程调用方泄露意外崩溃的细节。但在测试里,这恰恰是你不想要的,而raise_exceptions=True改变的就是这一点:测试看到的是真实错误消息,而不是被清理过的版本。
测试中请保持开启。它在生产代码中没有意义。
默认"跨时代中立"(Era-neutral)
Client(mcp)在进程内连接,默认是时代中立的:它会探测服务器并挑选合适的协议路径。如果测试要验证 legacy 专属语义(采样或 elicitation 推送、message_handler),请固定mode="legacy",并在那里去掉raise_exceptions=True:legacy 连接本就不会清理异常,该开关反而会把失败重新抛出到服务器任务内部而不是你的测试里。
也正是这一行代码,让文档敢于承诺示例可用:每一个示例文件都由 SDK 自身的测试套件执行,其中绝大多数正是通过这个客户端完成的。你在使用 SDK 用来测试它自己的同一套工具。
接入真实主机
主机是你的服务器最终栖身的应用:Claude Desktop、Claude Code、IDE。主机是用户对话的对象;其内部,一个 MCP客户端把你的服务器作为子进程启动,并通过子进程的标准输入/输出与它对话(详见 docs/get-started/real-host.md)。
因此接入主机其实只有一个动作:告诉它启动你服务器的命令。本页所有内容(两条 CLI 命令、三份 JSON 文件)都是同一命令的不同存放位置。
以 docs_src/real_host/tutorial001.py 那样的服务器为例,有三个要点对所有主机都成立:
- 无参
mcp.run()启动 stdio 服务器:它阻塞,从 stdin 读协议消息、向 stdout 写消息。这是本页所有主机说的传输。主机把你的文件作为子进程启动并拥有那两条管道——这就是"连接"永远只是"给出命令"的原因。你从不选端口,也没有端口在监听。 run()放在if __name__ == "__main__":之下:下文所有操作都是导入这个文件而不是执行它,未加保护的run()会在模块被加载的瞬间就启动服务器。- 服务器对象是名为
mcp的模块级全局变量:这正是mcp run查找的名字(server和app也可以)。叫别的名字就要显式指定:mcp run server.py:bookshop。
这是本页最后一行 Python。从下面起全是主机配置。
统一的启动命令
所有主机拿到的都是同一条命令:
uv run --with "mcp[cli]" mcp run /absolute/path/to/server.py对所有主机都成立,因为uv run --with会当场把 SDK 解析进一个全新环境:从任意目录都能工作,不需要项目、不需要激活虚拟环境。这在这里比任何地方都重要——主机是从它自己的工作目录、在近乎空的环境里启动你的服务器的,而不是从你的 shell。
这也是mcp install替你写进 Claude Desktop 配置的命令(见下),所以你手敲的命令与工具生成的命令是一致的(除工具额外追加的精确版本号外)。
!!! tip "主机找不到uv怎么办" 主机用极简PATH派生你的服务器,uv可能不在其中。把裸的uv换成which uv(macOS/Linux)或where uv(Windows)给出的绝对路径——这正是mcp install会写的内容。
!!! note "本页讲的是本地场景" 这里的一切都在主机所在的机器上运行你的服务器:主机通过 stdio 启动你的文件。这对个人或单机工具完全正确。要把服务器交给没有你文件的人,你给出的是URL而不是命令:同一个mcp对象经 Streamable HTTP 提供服务。运行服务器 用一张表讲清了这一决策,部署与扩展 是从那里走到真实域名的路径。而主机不过是内部带 MCP 客户端的应用,所以你的 Python 也能扮演主机:客户端传输 用Client(StdioServerParameters(...))把这个文件作为子进程拉起,测试 则完全不启进程、在内存里连它。
Claude Desktop
SDK 能替你配置的唯一主机:
uv run mcp install server.py就这么简单。mcp install导入文件读取服务器名、找到 Claude Desktop 的配置文件、把启动命令写进去;沿途还会把路径转成绝对路径,你无需操心。它写入的条目长这样:
{ "mcpServers": { "Bookshop": { "command": "/absolute/path/to/uv", "args": [ "run", "--frozen", "--with", "mcp[cli]==2.0.0", "mcp", "run", "/absolute/path/to/server.py" ] } } }相比上面的启动命令多了三处:uv的绝对路径、--frozen(让uv永不重写它碰巧附近的 lockfile)、以及你所装mcp版本的精确锁定。它落在claude_desktop_config.json中,位置是:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json
这份文件你也可以手写。mcp install存在的意义是让你避开那个经典错误(相对路径)。改完后完全退出Claude Desktop(不只是关窗口)再重新打开。
!!! warning 如果 Claude Desktop 的配置目录还不存在,mcp install会以Claude app not found失败。先安装并运行一次 Claude Desktop——正是那次运行创建了该目录。
!!! tip Claude Desktop 在自己的进程里启动你的服务器,所以 shell 里的环境变量不在其中。uv run mcp install server.py -v API_KEY=abc123(或-f .env)会把它们记录进条目的env字段。--name覆盖条目名,默认取服务器的name。
Claude Code
没有文件需要编辑。用claudeCLI 注册服务器,--之后的一切都是启动命令:
claude mcp add bookshop -- uv run --with "mcp[cli]" mcp run /absolute/path/to/server.py在 Claude Code 会话里运行/mcp确认bookshop已连接且工具已列出。
Cursor
在项目根目录创建.cursor/mcp.json:
{ "mcpServers": { "bookshop": { "command": "uv", "args": ["run", "--with", "mcp[cli]", "mcp", "run", "/absolute/path/to/server.py"] } } }command加args,放在与 Claude Desktop 相同的mcpServers键下。服务器会出现在 Cursor 的 MCP 设置中,两个工具均已列出。
VS Code
在项目根目录创建.vscode/mcp.json:
{ "servers": { "bookshop": { "type": "stdio", "command": "uv", "args": ["run", "--with", "mcp[cli]", "mcp", "run", "/absolute/path/to/server.py"] } } }与 Cursor 的文件相比只有两处不同,且仅此两处:外层键是servers而非mcpServers,每个条目声明了type。确认信任提示后,在命令面板执行MCP: List Servers就能看到bookshop正在运行。
!!! note 需要 VS Code 1.99 或更高版本,并登录GitHub Copilot扩展(Copilot Free 即可),且 Copilot Chat 必须处于Agent模式——其他模式不会调用工具。
服务器不出现怎么办
在动任何主机配置之前,先自己运行那条启动命令:
uv run --with "mcp[cli]" mcp run /absolute/path/to/server.py什么都不打印、也不返回——这种"沉默"是正确的:stdio 服务器正等待主机先在 stdin 上开口(Ctrl-C停止)。真正的 bug 是 traceback 或立即退出,而现在你能直接读到它,而不是隔着主机猜。一旦命令安静地待在那里,剩下的问题几乎总是三选一:
- 相对路径:主机从它自己的工作目录启动服务器,而不是你注册时所在的目录。需要
/absolute/path/to/server.py的地方却写成了server.py是最常见的失败。主机若也找不到uv,该路径同样必须是绝对的。 - 主机仍在用旧配置:主机在启动时读取配置。Claude Desktop 尤其要完全退出(不只是关窗口)后重新打开,编辑
claude_desktop_config.json才会生效。 - 有东西在"重定向窗口"之外写到了 stdout:在 stdio 上,stdout就是协议。SDK 在服务期间会把散落的刷新输出转向 stderr,但在那之前刷新到 stdout 的输出(包装脚本的回显、无缓冲进程导入期的
print()),或解释器退出时被冲刷的缓冲print(),都会把损坏的消息交给主机,导致连接被丢弃。请使用默认logging配置(其 stderr 处理器每条记录都会刷新);自定义处理器也必须避开 stdout。完整故事见 日志。
Claude Desktop 为每个服务器保留一份日志:mcp-server-<NAME>.log是你的服务器 stderr,旁边mcp.log记录连接,位于 macOS 的~/Library/Logs/Claude与 Windows 的%APPDATA%\Claude\logs。
再往后超出这三类,故障排查 就是那篇页面。
下一步往哪走
服务器跑起来之后,其余文档就不是课程而是参考手册了。每一页都能独立阅读,直接跳到你需要的地方:
- 服务器对外暴露什么(工具、资源、提示词)→服务器
- 你注册的函数内部能用到什么 →处理器内部
- 怎么把它送到客户端面前(stdio、HTTP、你现有的 FastAPI 应用)→运行你的服务器
- 构建另一侧,即"使用"MCP 服务器的应用 →客户端
一个简单的小结:主机是 LLM 应用,客户端是它说 MCP 的那一半,服务器是你构建的东西;工具由模型控制、资源由应用控制、提示词由用户控制;每种原语一个装饰器(@mcp.tool()、@mcp.resource(uri)、@mcp.prompt()),名称、描述、Schema 都来自函数本身;带{param}的 URI 使资源成为模板,与具体资源分开列出;服务器的能力由 SDK 代你声明,客户端只请求服务器声明过的内容;Client("http://localhost:8000/mcp")连接运行中的服务器,而把服务器对象直接交给它——Client(mcp)——就是你的测试脚手架。下一页可以继续深入 接入真实主机、测试,随后从由模型驱动的那个原语开始逐页深入:工具。
- 人工智能
- MCP 服务
- MCP Clients
【免费下载链接】python-sdk
The official Python SDK for Model Context Protocol servers and clients
相关推荐
URSA-1.7B-IBQ512 vs 主流文生图模型:1.7B轻量化模型的优势与局限
URSA 1.7B IBQ512 vs 主流文生图模型:1.7B轻量化模型的优势与局限 URSA 1.7B IBQ512是由BAAI开发的轻量级文生图模型,基于
人工智能MCP 服务MCP ClientsRustFS Rio 高性能异步 I/O 框架解析:零拷贝流式处理、AES-GCM 加密与多算法压缩
RustFS Rio 高性能异步 I/O 框架解析:零拷贝流式处理、AES GCM 加密与多算法压缩 RustFS Rio( rustfs rio )是 Rus
人工智能MCP 服务MCP ClientsMCP Python SDK 入门指南:从零开始构建并测试你的第一个 MCP 服务器
MCP Python SDK 入门指南:从零开始构建并测试你的第一个 MCP 服务器 本指南是 MCP(Model Context Protocol)或本 SD
人工智能MCP 服务MCP Clients
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考