MCP Python SDK 快速上手:用 20 行代码构建你的第一个 MCP 服务器与客户端
本指南以官方 Python SDK 的入口文档为核心,介绍 Model Context Protocol(MCP)的基本概念、SDK 的安装方式,并带你从零创建一个同时暴露工具(Tool)与资源模板(Resource Template)的 MCP 服务器,通过 mcp dev 与 MCP Inspector 完成交互式验证。读完本文,你将理解 SDK 如何从 Python 类型注解自动生成 JSON Schema 与协议处理代码,并掌握后续深入官方文档各章节的正确路径。
MCP 是什么,这个 SDK 能做什么
Model Context Protocol(MCP) 允许应用程序以标准化方式向 LLM 提供上下文,把"提供上下文"这件事与"和 LLM 交互"这件事彻底分离。本仓库 mcp 包正是这一协议官方的 Python 实现(文档面向当前稳定分支 v2,要求 Python 3.10+)。
使用该 SDK 可以:
- 构建 MCP 服务器,向任意 MCP 主机(host)暴露工具(tools)、资源(resources)与提示词(prompts);
- 构建 MCP 客户端,连接任意 MCP 服务器;
- 支持全部标准传输方式:
stdio、Streamable HTTP 与 SSE。
在 MCP 的术语体系中,host 是用户正在对话的 LLM 应用(如 Claude、IDE、Agent 运行时);client 寄生于 host 内部,负责说 MCP 协议的话,每个 host 对应一个连接就运行一个 client;server 正是你用本 SDK 构建的东西,它向客户端暴露能力,但从不直接与模型对话。SDK 同时提供了一个 Client 类——host 正是用同一个类通过 URL 或子进程方式访问服务器,你也可以用它来测试自己的服务器。
安装:两条命令二选一
# 方式一:使用 uv
uv add "mcp[cli]"
# 方式二:使用 pip
pip install "mcp[cli]"
[cli] 这个 extra 会额外安装 typer 与 python-dotenv,带来 mcp 命令行工具(mcp dev、mcp run、mcp install),开发阶段基本必需;部署时如不需要可以只装核心包。若在服务器上希望日志输出更美观,还可选用 mcp[rich] extra。
关于每个依赖的用途,官方安装文档 docs/get-started/installation.md 有完整说明,要点如下:
mcp-types:全部协议类型(请求、结果、内容块)以独立包形式随 SDK 同步版本发布,代码中通过mcp.types别名导入;anyio:整个 SDK 的异步运行时,因此同时支持asyncio与trio;pydantic:所有mcp.types模型的基础,同时承担 schema 生成与校验;httpx2、starlette、uvicorn、sse-starlette、python-multipart:HTTP 客户端与服务端传输层;jsonschema、pyjwt[crypto]、opentelemetry-api等:分别支撑结构化输出校验、OAuth 授权与可观测性。
第一个服务器:两个函数就是一个 MCP 服务器
创建文件 server.py,内容如下(完整源码见 docs_src/index/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 服务器。它暴露:
- 一个工具
add,接收两个整数并返回它们的和; - 一个资源模板
greeting://{name},URI 中的{name}是函数参数,读取时按传入的名字返回问候语。
注意两个导入路径的区别:from mcp.server import MCPServer 用于服务器端,而客户端是 from mcp import Client——不存在 from mcp import MCPServer 这种写法。
类型注解即声明
这里没有任何一处"额外声明":工具的名称来自函数名,描述来自 docstring,参数 schema 完全来自 a: int, b: int 这类类型注解。从源码看,@mcp.tool() 装饰器最终调用 MCPServer.add_tool()(见 src/mcp/server/mcpserver/server.py),后者把函数交给 ToolManager 与 Tool.from_function 处理;核心的"函数签名 → pydantic 模型"转换发生在 func_metadata()(见 src/mcp/server/mcpserver/utilities/func_metadata.py):SDK 通过 inspect.signature 读取每个参数,调用 create_model 动态构造一个 addArguments 的 pydantic 模型,这个模型就是对外发布的 JSON Schema 来源。
同理,@mcp.resource("greeting://{name}") 装饰器会先解析 URI 模板(RFC 6570):URI 中含 {param} 即注册为模板资源,并校验模板变量与函数参数一一对应(见 src/mcp/server/mcpserver/server.py);若 URI 不含变量,则注册为静态资源,此时函数不允许再声明普通参数。
运行:mcp dev 与 MCP Inspector
uv run mcp dev server.py
这条命令会启动服务器,并打开 MCP Inspector——一个用于交互式探查服务器的可视化界面。运行后终端会打印一个 URL,用浏览器打开即可。
!!! note Inspector 是一个 Node.js 应用,因此 mcp dev 要求系统 PATH 中存在 npx。
从 CLI 源码(src/mcp/cli/cli.py)可以看到 mcp dev 的实际流程:先解析文件路径(支持 server.py:对象名 语法,默认按 mcp、server、app 三个常见变量名查找,且对象必须是 MCPServer 实例),导入模块并读取其 dependencies 属性,然后构造 uv run --with ... mcp run server.py 命令,最后通过 npx @modelcontextprotocol/inspector 拉起 Inspector 并传入该命令。CLI 还提供了 mcp run(直接以指定 transport 运行服务器)与 mcp install(安装到 Claude Desktop 应用)等子命令。
动手验证:调用工具、读取资源
调用工具 add
在 Inspector 中切换到 Tools 标签页,调用 add,参数填 a=1、b=2,返回结果:
3
Inspector 根据 a: int, b: int 的类型注解自动构建了表单——一个必填的整数字段 a,一个必填的整数字段 b。Claude 及其他所有 MCP host 也会以同样方式理解这个工具。
读取资源 greeting://World
切到 Resources 标签页,读取 greeting://World:
Hello, World!
由于 greeting://{name} 含参数,它属于"资源模板":在没有提供 name 之前,并不存在一个具体的可列举资源,所以它在 Inspector 中单独出现在 Resource Templates 列表下。
仓库中的等价验证
这套示例并非只停留在文档层面,仓库测试 tests/docs_src/test_index.py 用 Client(mcp) 把服务器对象直接交给内存客户端做了完全等价的断言:
async def test_add_tool() -> None:
async with Client(mcp) as client:
result = await client.call_tool("add", {"a": 1, "b": 2})
# 断言返回 CallToolResult,文本内容为 "3"
async def test_greeting_resource_template() -> None:
async with Client(mcp) as client:
result = await client.read_resource("greeting://World")
# 断言返回 TextResourceContents,文本为 "Hello, World!"
这正是一种不需要端口、不需要额外进程的测试方式:Client(server_object) 直接复用服务器实例,是后续编写服务器测试的起点(详见 docs/get-started/testing.md)。
回顾:SDK 替你写掉了什么
对照示例再看一遍,下面这些代码你一行都没有写:
- 没有 JSON Schema。
a: int, b: int本身就是 schema——pydantic 模型在工具注册时即生成,并随tools/list响应发布给客户端; - 没有请求解析、序列化、校验代码。参数经
arg_model.model_validate()校验后以 kwargs 形式传入函数,同步函数还会被调度到 worker 线程执行(见 src/mcp/server/mcpserver/utilities/func_metadata.py); - 完全没有协议处理。版本协商、JSON-RPC 帧封装、能力(capabilities)交换都发生在
mcp dev与Client内部。
值得一提的还有 pre_parse_json:有些客户端(如 Claude Desktop)习惯把数组、对象以 JSON 字符串形式传入,SDK 会在校验前尝试自动解析这些字符串(见 src/mcp/server/mcpserver/utilities/func_metadata.py),进一步降低了与真实客户端对接时的摩擦。
你只写了两个带类型注解和 docstring 的 Python 函数,其余全部由 SDK 完成——这正是本 SDK 的设计初衷。如果你把服务器按其他 transport 跑起来(例如 uv run mcp run server.py --transport streamable-http),会发现服务器还会自动向客户端声明自己的能力(capabilities),包括 tools、resources、prompts 三组请求族的支持情况,客户端只会请求服务器声明过的能力。
下一步:围绕示例继续深入
本示例是整个官方文档的入口,后续按需选择路径即可:
- docs/get-started/index.md:从安装到"可运行、已测试"服务器的完整起步流程,其中的 docs/get-started/first-steps.md 会用更慢的节奏讲清工具、资源、提示词三大原语的区别(工具由模型决定调用、资源由应用加载、提示词由用户触发);
- 正在构建使用 MCP 服务器的应用?从 docs/client/index.md 开始,深入
Client的能力; - 已有 FastAPI 或 Starlette 应用?docs/run/asgi.md 演示如何把 MCP 服务器挂载进现有应用;
- 在按报错原文排查问题?docs/troubleshooting.md 按逐字错误消息组织;
- 想了解 v2 相对 v1 的破坏性变化?先读 docs/whats-new.md 的五分钟总览,再对照 docs/migration.md 的迁移指南;
- 想按传输方式运行服务器?
MCPServer.run()支持stdio、sse、streamable-http三种模式(见 src/mcp/server/mcpserver/server.py),参见 docs/run/index.md。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



