MCP Python SDK 入门指南:从零构建、运行并测试你的第一个 MCP 服务器
2026/9/21 15:50:10 网站建设 项目流程
  • 人工智能
  • MCP 服务
  • MCP Clients

【免费下载链接】python-sdk

The official Python SDK for Model Context Protocol servers and clients

项目地址:https://gitcode.com/gh_mirrors/pythonsd/python-sdk
点击查看免费下载

导读

本指南面向 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 的四个核心环节:

  1. 安装 SDK:把mcp包装进你的 Python 环境(要求 Python 3.10+);
  2. 创建第一个服务器:用少量 Python 代码写出一个完整服务器;
  3. 接入真实主机:让 Claude Desktop、IDE 等应用启动并连接你的服务器;
  4. 测试服务器:用内存客户端验证行为,无需起进程、无需占用端口。

如果你已经在文档其他部分见过 MCP,也可以直接跳到需要的页面——完成入门后,其余文档就从"教程"变成了"参考手册",每页均可独立阅读。

安装 SDK

SDK 以mcp包的形式发布在 PyPI 上,要求Python 3.10 及以上。文档描述的是v2,即当前的稳定主线。使用uvpip均可安装:

=== "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 typesmcp.types子模块绑定到包上,import mcp之后mcp.types.Tool依然可用。
  • anyio:异步运行时。整个 SDK 都基于 anyio 编写,因此既能跑在asyncio上,也能跑在trio上。
  • pydantic:所有mcp.types模型的基础,也是全部 Schema 生成与校验的来源。
  • httpx2:Streamable HTTP 与 SSE 客户端传输背后的 HTTP 客户端,内置 server-sent events 支持。
  • starletteuvicornsse-starlettepython-multipart:HTTP 服务端传输层。
  • jsonschema:按声明的输出 Schema 校验工具的结构化输出。
  • pyjwt[crypto]:授权场景下的 OAuth 令牌处理。
  • opentelemetry-api:只引入轻量 API,SDK 的追踪中间件在你不安装 OpenTelemetry SDK 与导出器时不产生额外成本。
  • typing-extensionstyping-inspection:为 Python 3.10 提供现代 typing 特性。
  • pywin32:仅 Windows 使用,负责stdio子进程管理。

可选扩展

  • mcp[cli]:额外安装typerpython-dotenv,为mcp命令行工具(mcp devmcp runmcp 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.。表单包含必填的整数字段ab,填好调用,结果是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)客户端现在可以调用
toolstools/listtools/call
resourcesresources/listresources/templates/listresources/read
promptsprompts/listprompts/get

MCPServer同时提供三种原语,所以三者总是被声明。注意字典里没有completions:参数自动补全(面向资源模板和提示词)需要一个你亲自编写的处理器,这个服务器没有,因此该能力缺席,行为良好的客户端也就不会发起这类请求。这就是一切可选特性的规则:注册了什么,能力就出现什么,Completions 一文可以印证。

你没有写过什么

回顾这一页:你写了三个很小的 Python 函数,但没有写:

  • JSON Schemaa: int, b: int本身就是add的 Schema;
  • 请求处理器tools/listresources/readprompts/get全部由 SDK 代劳;
  • 能力声明MCPServer替你生成了;
  • 任何协议代码:版本协商、JSON-RPC 帧、能力交换全部发生在mcp devclient.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 ```

文档假设你已了解pytestinline-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}, ) )
  1. 如果你使用trio,把返回值改为"trio"即可(详见 anyio 文档中关于指定运行后端的说明)。
  2. 该 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查找的名字(serverapp也可以)。叫别的名字就要显式指定: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"] } } }

commandargs,放在与 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

项目地址:https://gitcode.com/gh_mirrors/pythonsd/python-sdk
点击查看免费下载

相关推荐

上一篇:如何通过Instatic视觉组件开发服务构建可重用网站模块
下一篇:PF_RING API详解:构建自定义高性能网络应用的终极指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询