跳到主要内容

MCP Python SDK — 架构与原理

30 秒导读: 这是 Model Context Protocol(MCP,模型上下文协议)的官方 Python 实现。它让你用两个带类型标注的普通函数就搭出一个能被任何 LLM 应用调用的服务端——不写 JSON Schema、不写请求解析、不碰协议细节。同一个包同时是客户端。本文档讲的是 v2(预发布线,架构大改)。


1. 这是什么(零基础也能懂)

一句话定义

MCP 是「给 LLM 用的 API 标准」。这个 SDK 是它的 Python SDK:服务端把工具 / 资源 / 提示词暴露给 LLM,客户端去连接并调用它们。

解决什么问题 / 给谁用

假设你在做一个 AI 助手,想让模型能「查你公司数据库」「读某个文件」「调某个内部接口」。

  • 没有标准时:每接一个能力,你都要自己定义调用格式、写解析、写校验、把结果塞回模型的上下文——每家做法都不一样。
  • 有了 MCP:能力提供方按统一协议开一个 MCP server;任何 MCP host(Claude Desktop、IDE、你自己的 agent)用统一方式发现并调用它。像 USB 接口之于外设。

给谁用:想把某种能力接给 LLM 的开发者(写 server),以及想让自己的应用能接入任意 MCP 能力的开发者(写 client)。

它能做什么

能力说明
Tools(工具)模型可调用的函数,会产生动作/副作用(查库、发请求)。
Resources(资源)模型可读取的数据,按 URI 寻址(greeting://{name}),类似 GET。
Prompts(提示词)预置的可复用对话模板。
三种传输stdio(子进程)、Streamable HTTP、SSE(旧)。
双向server 也能反向找 client 要东西(采样、elicitation 追问、roots)。

用起来什么样

这是一个完整的 MCP server——一个工具、一个模板资源(docs_src/index/tutorial001.py,README 引用):

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}!"

注意你没写的东西:没有 JSON Schema(a: int, b: int 本身就是 schema)、没有请求解析、没有校验代码、没有协议处理。两个带类型标注的函数 + 一句 docstring。

客户端连它也只要几行(from mcp import Client):

import asyncio
from mcp import Client
from server import mcp

async def main() -> None:
async with Client(mcp) as client: # 直接连内存里的 server 对象,无需传输
result = await client.call_tool("add", {"a": 1, "b": 2})
print(result) # 拿到 3

asyncio.run(main())

一句话直觉

Python 的类型系统当协议的 schema 语言:你写函数签名,SDK 用 pydantic 把它翻成 JSON Schema 发给模型;模型回来的参数再用同一个 schema 校验后喂进你的函数。你只管写业务,协议的「翻译」全自动。


2. 顶层全景(它大概怎么转)

分层栈:从你写的函数,到线上的字节

v2 最重要的架构决定是把「MCP 语义」和「传输编码」用一层 Dispatcher 彻底隔开。看这张纵向的栈(上层最贴近你,下层最贴近网络):

你写的代码: @mcp.tool() def add(a:int,b:int)->int


┌─────────────────────────────────────────────┐
│ MCPServer 好用的装饰器层 │ server/mcpserver/server.py
│ · 类型标注 → JSON Schema · 注册 tool/资源 │
├─────────────────────────────────────────────┤
│ Server (lowlevel) 方法名 → handler 表 │ server/lowlevel/server.py
│ · 能力协商 · 中间件链 │
├─────────────────────────────────────────────┤
│ ServerRunner 每连接的「内核」 │ server/runner.py
│ · 校验参数 · 跑中间件 · 派发 · 序列化结果 │
├─────────────────────────────────────────────┤
│ Dispatcher (协议) 收发通道,★不懂 MCP★ │ shared/dispatcher.py
│ ├ JSONRPCDispatcher 走 JSON-RPC 消息流 │ shared/jsonrpc_dispatcher.py
│ └ DirectDispatcher 内存直调,不序列化 │ shared/direct_dispatcher.py
├─────────────────────────────────────────────┤
│ Transport 真正搬字节 │ stdio / streamable_http / 内存
└─────────────────────────────────────────────┘


对端(client 或 server)

怎么读这张图: 从上到下是「越来越不懂业务、越来越懂网络」。中间那条 Dispatcher 线是全书主角——它上面全是 MCP 类型和语义,它下面全是线格编码;它自己只认字符串方法名和 dict

部件一句话职责

部件干什么在哪个文件
MCPServer面向人的好用接口:装饰器注册、自动 schema、跑传输。server/mcpserver/server.py
Server(lowlevel)维护「方法名 → handler」表、能力、中间件列表。server/lowlevel/server.py
ServerRunner每个客户端连接一个实例;做校验、中间件、派发、序列化。server/runner.py
Dispatcher(Protocol)收发抽象:send_raw_request / notify + run 收循环。不含 MCP 知识shared/dispatcher.py
JSONRPCDispatcher生产实现:管请求 id 关联、收循环、每请求独立任务、取消/进度。shared/jsonrpc_dispatcher.py
Connection / ServerSession每连接状态(对端信息、协议版本、反向通道)。server/connection.pyserver/session.py
Client / ClientSession客户端:连传输、发请求、驱动多轮追问。client/client.pyclient/session.py
mcp-types线格数据模型 + 分版本方法表(2025-11-25 / 2026-07-28)。src/mcp-types/mcp_types/

主线走一遍(高层,不进代码)

以「客户端调用 add(a=1,b=2)」为例,一次请求横穿整个栈:

client server
────── ──────
call_tool("add",{a,b})
│ 打包 tools/call 请求

Dispatcher.send_raw_request ──JSON-RPC──► Dispatcher 收循环
│ 起一个新任务

ServerRunner._on_request
│ ① 按版本校验参数
│ ② 跑中间件链
│ ③ 查 handler 表 → tools/call

MCPServer._handle_call_tool
│ 用 add 的 schema 校验 {a,b}
│ 调 add(1,2) → 3
│ 把 3 转成 CallToolResult

序列化结果 ──JSON-RPC──►
◄────────────────────────────────────── 回给 client 的 send_raw_request
拿到结果 3

目标:先看懂「大盘」——请求怎么进、经过哪几层、结果怎么回。每一层的原理在后续章节展开。


3. 往下读:阅读地图

这个项目大(源码约 60+ 文件、核心文件上千行),所以拆成 5 章,由浅入深:

顺序章节讲什么什么时候读
101-mcpserver-and-tools.md顶层好用层:@mcp.tool 怎么工作,类型标注怎么自动变 JSON Schema(func_metadata 的魔法)。想写 server、想懂「零 schema」怎么做到的。
202-kernel-and-dispatcher.md全书主线:Dispatcher 抽象为什么这么设计;ServerRunner 内核如何处理一次请求的完整生命周期(校验、中间件、派发、取消、进度)。想真正懂 v2 架构、想扩展协议。
303-transports-and-sessions.md三种传输(stdio / Streamable HTTP / 内存直连)、每连接状态 Connection、有状态 vs 无状态 HTTP。关心部署、HTTP、会话、可恢复流。
404-client-and-wire-types.mdClient 客户端、mcp-types 线格层、协议版本协商(握手纪元 vs 2026 无握手纪元)。想写 client、想懂版本兼容怎么做的。
505-deep-dive-and-map.md巧妙之处(Resolve 依赖注入、elicitation 多轮追问、缓存、扩展)、边界局限、横向对比、代码地图想抄设计、想知道它在哪会崩、要跳源码。

给 AI agent 的提示: 每章开头有 essence + keyTopics,可先低成本判断相关性再决定下钻。所有源码引用都带符号名(不只行号),行号漂移时可按符号 grep 重新定位。本文档锁定 commit 53117cb