
MCP Python SDK 快速上手指南从安装到测试的完整入门路径【免费下载链接】python-sdkThe official Python SDK for Model Context Protocol servers and clients项目地址: https://gitcode.com/gh_mirrors/pythonsd/python-sdk本指南对应官方 Python SDKmcp包文档的 Get started 入门章节带你从零开始完成一条完整的开发闭环安装 SDK → 编写第一个 MCP 服务器工具、资源、提示词三大原语→ 接入 Claude Desktop / Claude Code / Cursor / VS Code 等真实主机 → 用内置的进程内客户端编写测试。读完本文你将掌握如何用几十行 Python 构建一个可运行、可验证的 MCP 服务器并理解mcp命令行工具与Client进程内连接这两个核心开发利器。一、安装 SDKMCP Python SDK 以mcp包名发布在 PyPI 上要求Python 3.10。本系列文档描述的是v2 当前稳定版本线安装时建议直接带上[cli]扩展以便获得mcp命令行工具 uvbash uv add mcp[cli] pipbash pip install mcp[cli] 从 v1 迁移过来的用户请注意v2 是包含破坏性变更的大版本迁移指南 覆盖了全部变更点。如果你的项目依赖mcp且尚未准备好迁移请保持2的上限约束例如mcp1.28,2这样未固定版本的解析结果会停留在 1.x 线。安装了哪些依赖日常使用无需了解这些细节但弄清每个依赖的用途有助于排查问题依赖策略可参考 DEPENDENCY_POLICY.md依赖用途mcp-types所有协议类型请求、结果、内容块的独立包与 SDK 同步版本。依赖mcp的代码通过mcp.types别名导入即本文所有from mcp.types import ...只有在不装 SDK、单独安装mcp-types的项目中才直接import mcp_typesanyio异步运行时。整个 SDK 基于 anyio 编写因此可运行在asyncio或trio之上pydantic每个mcp.types模型的基座同时负责全部 schema 生成与校验httpx2Streamable HTTP 与 SSE客户端传输背后的 HTTP 客户端内置 server-sent events 支持starlette、uvicorn、sse-starlette、python-multipartHTTP服务端传输jsonschema校验工具的结构化输出是否符合其声明的输出 schemapyjwt[crypto]授权场景的 OAuth token 处理opentelemetry-api仅轻量 APISDK 的追踪中间件零成本除非你自行安装 OpenTelemetry SDK 与导出器typing-extensions、typing-inspection在 Python 3.10 上提供现代类型标注能力pywin32仅 Windows用于stdio子进程管理可选扩展extrasmcp[cli]添加typer和python-dotenv为mcp命令行工具mcp dev、mcp run、mcp install提供支持。开发阶段需要它部署服务器时可能用不到。mcp[rich]添加rich提供更美观的服务器日志。二、第一个服务器三大原语在写代码之前先建立三个贯穿全文档的核心词汇主机hostLLM 应用程序如 Claude、IDE、Agent 运行时是用户直接对话的对象。客户端client寄宿在主机内部、说 MCP 语言的组件。主机为每个已连接的服务器运行一个客户端。服务器server你用本 SDK 构建的东西。它向客户端暴露能力从不直接与模型对话。你负责编写服务器主机是别人的产品。SDK 同样提供Client类——主机连服务器时用的正是这个类它可以通过 URL 连接服务器也可以把它作为子进程启动还能在测试中直接连接服务器对象。三种原语由谁决定使用来区分一个服务器恰好暴露三类东西区别它们的核心是谁来决定使用它们原语控制者是什么示例工具Tools模型模型调用来执行动作的函数调用 API、写数据库资源Resources应用程序主机加载进模型上下文的只读数据文件内容、API 响应提示词Prompts用户用户按名称调用的可复用消息模板斜杠命令、菜单项控制者是这种划分的全部意义工具因模型决定调用而执行资源因应用程序判断模型需要而被附加提示词因用户选中而被运行。如果你构建过 Web API直觉可以平移资源相当于GET加载数据、不改变状态工具相当于POST执行工作、可能有副作用提示词没有 HTTP 对应物更接近用户按名称运行的已保存查询。一个服务器三种能力下面是docs_src/first_steps/tutorial001.py文档中的server.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 fHello, {name}! mcp.prompt() def summarize(text: str) - str: Summarize a piece of text in one sentence. return fSummarize 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。从源码结构看MCPServer的tool()、resource()、prompt()装饰器以及add_tool、add_resource、add_prompt等底层注册方法定义于 src/mcp/server/mcpserver/server.py内部通过tool_manager、resource_manager、prompt_manager分门别类地管理原语函数元数据从类型标注生成 JSON Schema、校验参数、转换返回值则由 src/mcp/server/mcpserver/func_metadata.py 中的func_metadata()完成。用 MCP Inspector 试运行uv run mcp dev server.py打开命令打印的 URLmcp dev内部会调用npx modelcontextprotocol/inspector拉起 Inspector 界面因此要求npx在PATH中参见 src/mcp/cli/cli.py 中dev命令的实现。Inspector 为每种原语提供一个标签页按顺序逐个体验工具Tools唯一的条目add描述为Add two numbers.。表单有必填的整数字段a和b。填入并调用结果为3。Inspector 是从a: int, b: int生成这个表单的——其他所有客户端也是如此。资源ResourcesResources 列表为空。greeting出现在Resource Templates下因为greeting://{name}带参数在没有提供name之前不存在可列出的具体资源。给它World并读取得到Hello, World!。提示词Prompts唯一的条目summarize带一个必填的text参数。填入文本获取后你会收到一条role: user的消息内容是你的渲染字符串。提示词的全部本质就是一个构建消息的函数。Inspector 通过stdio运行了你的服务器——这是 MCP 服务器可说的传输方式之一。传输选择见 运行你的服务器。三、能力声明Capabilities是怎么来的你在 Inspector 里看到了三个标签页客户端是怎么知道有三个的当客户端连接时服务器会声明它的能力capabilities它将应答哪些请求族。客户端依据这份声明决定值得问什么。你从没写过它——MCPServer替你声明了。自己验证一下让server.py在一个终端里以 HTTP 方式运行uv run mcp run server.py --transport streamable-http然后在另一个终端用客户端指向它docs_src/first_steps/tutorial001_client.pyimport 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_noneTrue)) if __name__ __main__: anyio.run(main)python client.py{prompts: {list_changed: True}, resources: {subscribe: True, list_changed: True}, tools: {list_changed: True}}这个字典就是你服务器声明的能力是每个连接的客户端学到的第一件事能力客户端现在可以调用toolstools/list、tools/callresourcesresources/list、resources/templates/list、resources/readpromptsprompts/list、prompts/getMCPServer同时服务三种原语所以三者总是被声明。注意哪些不在其中completions资源模板和提示词的参数自动补全需要一个你编写 handler 才会出现——这个服务器没有所以该能力缺席行为良好的客户端就不会来问。这就是所有可选能力的规则注册了能力才出现Completions 页面证实了这一点。这段客户端逻辑在仓库测试 tests/docs_src/test_first_steps.py 中被逐条断言test_the_three_primitive_capabilities_are_always_declared精确地验证了上面这份能力字典并且连一个完全空的MCPServer(Empty)也会声明同样的三种能力——注册只影响可选能力。回顾你什么都没写回头看这一页。你写了三个很小的 Python 函数。你没有写JSON Schemaa: int, b: int就是add的 schema。请求处理器tools/list、resources/read、prompts/get全部由 SDK 代劳。能力声明MCPServer替你做了。任何协议代码版本协商、JSON-RPC 帧、能力交换——全部发生在mcp dev和client.py内部你从未见过。这个比例正是 SDK 的意义所在。四、把服务器接进真实主机主机是你的服务器最终栖身的应用程序Claude Desktop、Claude Code、IDE。主机内部有一个 MCP客户端把你的服务器作为子进程启动并通过该进程的 stdin/stdout 与它对话。因此接入主机其实只有一个动作把启动服务器的命令告诉它。本页的所有内容两个 CLI 命令、三个 JSON 文件都是放置同一条命令的不同位置。以docs_src/real_host/tutorial001.py文档中的server.py为例from mcp.server import MCPServer from mcp.server.mcpserver.exceptions import ToolError mcp MCPServer(Bookshop) CATALOG { Dune: Frank Herbert, Neuromancer: William Gibson, The Left Hand of Darkness: Ursula K. Le Guin, } mcp.tool() def search_books(query: str) - list[str]: Search the catalog by title or author. needle query.lower() return [title for title, author in CATALOG.items() if needle in title.lower() or needle in author.lower()] mcp.tool() def get_author(title: str) - str: Look up the author of a book in the catalog. if title not in CATALOG: raise ToolError(fNo book titled {title!r} in the catalog.) return CATALOG[title] mcp.resource(catalog://titles) def titles() - str: Every title in the catalog, one per line. return \n.join(sorted(CATALOG)) if __name__ __main__: mcp.run()这个文件中有三点对下文所有主机都至关重要mcp.run()无参数启动的是stdio服务器它阻塞运行从 stdin 读协议消息、向 stdout 写消息。这正是本页每个主机说的传输方式。主机把你的文件作为子进程启动并拥有这两条管道——这就是连接永远只是这里是命令的原因。你从不选端口也没有任何端口在监听。run()位于if __name__ __main__:之下。下文所有操作都是导入这个文件而非执行它所以不加保护的run()会在任何东西加载模块的瞬间启动服务器。服务器对象是名为mcp的模块级全局量。这正是mcp run查找的名字server和app也可以。改成别的名字就要显式指明mcp run server.py:bookshop。启动命令下面所有主机拿到的是同一条命令uv run --with mcp[cli] mcp run /absolute/path/to/server.py所有主机共用同一条命令是因为uv run --with会当场把 SDK 解析进一个全新环境它可以在任意目录工作无需项目、无需激活虚拟环境。这一点在这里比其他任何地方都重要——因为主机会从它自己的工作目录、带着近乎空的环境启动你的服务器而不是从你的 shell。它也正是mcp install写入 Claude Desktop 配置的命令见下因此你手敲的命令与工具生成的命令一致除了工具额外加的精确版本 pin。如果主机找不到uv主机用极简的PATH启动你的服务器uv可能不在其中。把裸的uv换成which uvmacOS/Linux或where uvWindows得到的绝对路径——这正是mcp install写入的内容。本页讲的是本地场景所有内容都在主机所在机器上运行你的文件通过 stdio。这对个人或单机工具完全正确。若要把服务器交给没有你文件的人给的是URL而不是命令同一个mcp对象以 Streamable HTTP 对外服务。运行你的服务器 用一张表讲清了这个抉择部署与扩展 是从那里到真实域名的路线。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版本的精确 pin。文件落在claude_desktop_config.json位于macOS~/Library/Application Support/Claude/claude_desktop_config.jsonWindows%APPDATA%\Claude\claude_desktop_config.json你也可以手写这个文件。mcp install存在的意义就是避免你犯经典错误相对路径。改完后完全退出Claude Desktop不只是关窗口再重新打开。⚠️ 若 Claude Desktop 的配置目录尚不存在mcp install会报Claude app not found。先安装 Claude Desktop 并运行一次——目录就是这么创建的。 Claude Desktop 在自己的进程里启动你的服务器所以你 shell 里的环境变量并不存在。uv run mcp install server.py -v API_KEYabc123或-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] } } }与 Claude Desktop 相同的command加args放在相同的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在运行。需要 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 或立即退出而现在你可以直接读懂它而不是隔着主机猜。一旦命令安静地挂着剩下的问题十有八九是这三类相对路径。主机从它自己的工作目录启动你的服务器而不是你注册时所在目录。server.py出现在需要/absolute/path/to/server.py的地方是最常见的失败。如果主机连uv也找不到那这个路径也得是绝对的。主机还在用旧配置。主机在启动时读取配置。Claude Desktop 尤其必须在编辑claude_desktop_config.json后完全退出不只是关窗口再重开。有东西在被接管窗口之外写到了 stdout。在 stdio 上stdout就是协议。SDK 在服务期间会把冲出的散落输出转移到 stderr但在此前冲到 stdout 的输出包装脚本的回显、无缓冲进程中的导入期print()或解释器退出时被排空的缓冲print()会把损坏的消息交给主机导致其断开连接。用默认logging配置打日志其 stderr 处理器每条记录都会刷新自定义 handler 也必须避开 stdout。详见 日志。Claude Desktop 每个服务器都有一份日志mcp-server-NAME.log是你的服务器 stderr旁边还有记录连接的mcp.log位于 macOS 的~/Library/Logs/Claude或 Windows 的%APPDATA%\Claude\logs。再深入的问题交给 故障排查。五、用进程内客户端测试服务器SDK 的Client类——就是那个连 URL、启子进程的类——也能在内存中连接把服务器对象传给它它就直接与服务器对话。没有子进程没有端口没有线路上的任何东西。这与 FastAPI 的TestClient是同一个思路。基本用法假设你有一个带单个工具的简单服务器docs_src/testing/tutorial001.pyfrom mcp.server import MCPServer mcp MCPServer(Calculator) mcp.tool() def add(a: int, b: int) - int: Add two numbers. return a b运行下面的测试需要两个额外的开发期依赖 uvbash uv add --dev pytest inline-snapshot pipbash pip install pytest inline-snapshot 本文假设你已了解pytest。inline-snapshot用于让下面的测试用一行断言整个结果对象它会把测试输出记录为你看到的snapshot(...)字面量。若不想用它去掉该导入像任何其他测试一样只断言你关心的字段result.content[0].text 3即可。现在写测试test_server.pyimport 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_exceptionsTrue) 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(typetext, text3)], structured_content{result: 3}, ) )若使用trio改为返回trio即可。细节见 anyio 文档。该 fixture 产出已连接的客户端。每个接收client的测试都会得到一条指向同一服务器的全新内存连接。这就完成了现在可以扩展测试覆盖更多场景。仓库中对应实现可见 src/mcp/client/client.pyClient的server字段接受 URL 字符串走streamable_http_client传输、StdioServerParameters用stdio_client启动子进程、Transport实例或Server/MCPServer实例进程内连接——测试用到的正是最后一种。为什么要raise_exceptionsTrue有两类不同的出错场景这个开关只影响其中之一。工具体内部抛出的异常不是协议失败它会变成is_errorTrue的正常结果若是ToolError模型读到的是你的消息。raise_exceptions不改变这一点无论开或关call_tool返回的都是同一个is_errorTrue结果。关于它有一整页错误处理。工具体之外的失败则不同。在Client(mcp)给你的连接上服务器会把它净化成通用的Internal server error之后才让客户端看到——你绝不该把意外崩溃的细节泄露给远程调用方。但在测试中这恰恰是不想要的而raise_exceptionsTrue改变的正是这一点你的测试看到真实消息而非净化后的。测试里保持开启生产代码中它没有任何意义。默认跨时代中立Client(mcp)进程内连接**默认是 era-neutral跨时代中立**的它会探测服务器并选择适当的协议路径。如果你的测试要验证 legacy 专属语义sampling 或 elicitation push、message_handler请固定modelegacy并去掉raise_exceptionsTrue——legacy 连接本来就不做净化开着这个开关会把失败重新抛到服务器任务里而不是你的测试中。这行代码也解释了为什么本文档敢承诺示例都能工作每个示例文件都被 SDK 自己的测试套件实际跑过而且几乎全部正是通过这个客户端。tests/docs_src/test_first_steps.py 与 tests/docs_src/test_testing.py 就是活证据——前者逐条断言了add工具的 name/description/input_schema、资源模板的 URI 模板、提示词参数、能力字典以及greeting://World的读取结果后者原样运行了本页展示的测试。你在文档里读到的代码就是真正在跑的代码SDK 的 CI 会在任何改动破坏示例时先于页面变红。进一步你现在有了一个能跑、有测试的服务器。把它放进真实应用Claude Desktop、IDE是 连接真实主机其他一切服务方式见 运行你的服务器。六、后续路线有了一个运行中的服务器之后其余文档是参考手册而非教程每页都可以独立阅读直接跳到需要的部分服务器暴露什么工具、资源、提示词→ Servers注册的函数内部可用什么 → Inside your handler如何把它带到客户端面前stdio、HTTP、你的既有 FastAPI 应用→ Running your server构建另一侧——一个使用MCP 服务器的应用 → Clients【免费下载链接】python-sdkThe official Python SDK for Model Context Protocol servers and clients项目地址: https://gitcode.com/gh_mirrors/pythonsd/python-sdk创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考