MCP Python SDK 入门指南:从零构建、运行并测试你的第一个 MCP 服务器

发布时间:2026/9/21 15:50:20
MCP Python SDK 入门指南:从零构建、运行并测试你的第一个 MCP 服务器 人工智能MCP 服务MCP Clients【免费下载链接】python-sdkThe official Python SDK for Model Context Protocol servers and clients项目地址https://gitcode.com/gh_mirrors/pythonsd/python-sdk点击查看免费下载导读本指南面向 MCPModel 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也可以直接跳到需要的页面——完成入门后其余文档就从教程变成了参考手册每页均可独立阅读。安装 SDKSDK 以mcp包的形式发布在 PyPI 上要求Python 3.10 及以上。文档描述的是v2即当前的稳定主线。使用uv或pip均可安装 uvbash uv add mcp[cli] pipbash pip install mcp[cli] !!! note 从 v1 迁移 v2 是一次带破坏性变更的主版本升级迁移指南 覆盖了全部变更点。如果你的项目依赖mcp但尚未准备好迁移请保持2的上限约束例如mcp1.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 生成与校验的来源。httpx2Streamable HTTP 与 SSE 客户端传输背后的 HTTP 客户端内置 server-sent events 支持。starlette、uvicorn、sse-starlette、python-multipartHTTP 服务端传输层。jsonschema按声明的输出 Schema 校验工具的结构化输出。pyjwt[crypto]授权场景下的 OAuth 令牌处理。opentelemetry-api只引入轻量 APISDK 的追踪中间件在你不安装 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主机HostLLM 应用例如 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 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。用 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.pymcp dev会先导入你的服务器文件读取其依赖然后构造uv命令再调用npx modelcontextprotocol/inspector拉起 Inspector 并把服务器作为子进程交由其托管。如果系统找不到npx命令会明确报错提示需要 Node.js/npm 并加入 PATH。能力声明CapabilitiesInspector 里出现三个标签页客户端是怎么知道的客户端连接时服务器会声明它的能力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_noneTrue)) 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/list、tools/callresourcesresources/list、resources/templates/list、resources/readpromptsprompts/list、prompts/getMCPServer同时提供三种原语所以三者总是被声明。注意字典里没有completions参数自动补全面向资源模板和提示词需要一个你亲自编写的处理器这个服务器没有因此该能力缺席行为良好的客户端也就不会发起这类请求。这就是一切可选特性的规则注册了什么能力就出现什么Completions 一文可以印证。你没有写过什么回顾这一页你写了三个很小的 Python 函数但没有写JSON Schemaa: 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.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。下面是测试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_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的测试都会得到一条指向同一服务器的全新内存连接。为什么要raise_exceptionsTrue有两类不同的错误可能发生这个开关只影响其中一类工具内部的异常不是协议故障它会变成is_errorTrue的普通结果若是ToolError模型还能读到你的消息。raise_exceptions不改变这一点无论开关与否call_tool返回的都是同一个is_errorTrue结果。完整讲解见 处理错误。工具体外部的失败则不同。在Client(mcp)建立的连接上服务器会先把异常清理成通用的Internal server error再让客户端看到——你绝不该向远程调用方泄露意外崩溃的细节。但在测试里这恰恰是你不想要的而raise_exceptionsTrue改变的就是这一点测试看到的是真实错误消息而不是被清理过的版本。测试中请保持开启。它在生产代码中没有意义。默认跨时代中立Era-neutralClient(mcp)在进程内连接默认是时代中立的它会探测服务器并挑选合适的协议路径。如果测试要验证 legacy 专属语义采样或 elicitation 推送、message_handler请固定modelegacy并在那里去掉raise_exceptionsTruelegacy 连接本就不会清理异常该开关反而会把失败重新抛出到服务器任务内部而不是你的测试里。也正是这一行代码让文档敢于承诺示例可用每一个示例文件都由 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 uvmacOS/Linux或where uvWindows给出的绝对路径——这正是mcp install会写的内容。!!! note 本页讲的是本地场景 这里的一切都在主机所在的机器上运行你的服务器主机通过 stdio 启动你的文件。这对个人或单机工具完全正确。要把服务器交给没有你文件的人你给出的是URL而不是命令同一个mcp对象经 Streamable HTTP 提供服务。运行服务器 用一张表讲清了这一决策部署与扩展 是从那里走到真实域名的路径。而主机不过是内部带 MCP 客户端的应用所以你的 Python 也能扮演主机客户端传输 用Client(StdioServerParameters(...))把这个文件作为子进程拉起测试 则完全不启进程、在内存里连它。Claude DesktopSDK 能替你配置的唯一主机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.jsonWindows%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_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] } } }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-sdkThe official Python SDK for Model Context Protocol servers and clients项目地址https://gitcode.com/gh_mirrors/pythonsd/python-sdk点击查看免费下载相关推荐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 服务器 本指南是 MCPModel Context Protocol或本 SD人工智能MCP 服务MCP Clients上一篇如何通过Instatic视觉组件开发服务构建可重用网站模块下一篇PF_RING API详解构建自定义高性能网络应用的终极指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考