笔记概览
本笔记围绕 Model Context Protocol (MCP) 展开,覆盖以下核心模块:
- 协议本质:MCP 的定义、解决的痛点、与传统 Function Calling 的根本区别。
- 消息格式:基于 JSON-RPC 2.0 的请求、响应、通知结构。
- 实现方式:Host-Client-Server 三层架构的解耦机制。
- 项目集成:在 Python Web 应用中用 MCP 包装外部 API(附详细代码拆解)。
- 加载时机:工具列表的一次性加载 vs 动态发现。
- 隔离方案:多项目/多 Agent 场景下的命名空间、权限、进程隔离。
- 实战补充:Claude Code 本地配置实现项目级隔离的层层递进方案。
每一部分都包含技术原理、代码示例(主要针对 Python)、架构图解,并穿插扩展思考、追问方向和实践建议。
1. 协议本质:从函数调用到上下文协议
1.1 MCP 是什么?
MCP(Model Context Protocol)是 Anthropic 提出的开放协议,旨在标准化 AI 模型与外部工具、数据源的交互方式。类比为"AI 应用的 USB-C 接口"------不同工具只要遵循同一套协议,就可以被任何支持 MCP 的模型即插即用地连接。
1.2 要解决的核心痛点
传统 Function Calling 下,每接入一个新工具,开发者需要:
- 手写适配函数、描述 JSON Schema;
- 处理认证、错误重试;
- 把函数签名硬编码为特定 AI 厂商的格式(如 OpenAI 与 Anthropic 不同)。
结果:工具集成与模型强绑定,复用性差 。
MCP 的解耦思路:工具按协议暴露一次,即可被所有 MCP 兼容的 AI 使用。
1.3 MCP 与 Function Calling 的根本不同
|------|------------------|---------------------------------|
| 对比维度 | Function Calling | MCP |
| 定义位置 | 代码中一次性注入工具列表 | 工具定义在独立进程(MCP Server)中,通过协议动态获取 |
| 交互模式 | 单次请求-响应 | 持久化连接,支持工具发现、实时推送、资源订阅 |
| 标准化 | 各厂商格式不统一 | 开放协议,不绑定模型厂商 |
| 生命周期 | 函数在一次对话上下文中存活 | 工具服务器独立运行,启动/停止与模型无关 |
通俗比喻:Function Calling 是写在纸上的固定菜单;MCP 是一家可以随时更新菜单、开放厨房参观、接收实时订单的餐厅。
【延伸】
- MCP 这种"协议驱动工具"的思想在微服务架构中早有先例(如 gRPC 服务定义)。但在 AI 领域,它可能成为类似"HTTP 之于 Web"的基础设施。
- 协议标准化可能带来的生态效应:未来会出现 MCP 工具市场,开发者直接发布、复用工具服务器。
【追问】
- 如果所有 AI 都通过 MCP 调用工具,那么工具的输入输出是否还需要适配不同模型的"偏好"?MCP 的描述规范足够消除差异吗?
2. 消息格式:基于 JSON-RPC 2.0 的统一通信
2.1 基础规范
MCP 底层使用 JSON-RPC 2.0,所有消息都是 JSON 对象,通过标准输入/输出(stdio)或 HTTP/SSE 传输。
三种消息类型:
- 请求(Request) :含有
id,必须返回响应。 - 响应(Response) :包含与请求相同的
id,携带result或error。 - 通知(Notification) :无
id,不需要响应,用于单向事件推送。
2.2 示例消息
工具列表请求与响应
// 请求
{ "jsonrpc": "2.0", "id": 1, "method": "tools/list", "params": {} }
// 响应
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"tools": [
{
"name": "get_weather",
"description": "获取指定城市的当前天气",
"inputSchema": {
"type": "object",
"properties": { "city": { "type": "string" } },
"required": ["city"]
}
}
]
}
}
工具调用请求与响应
// 请求
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "get_weather",
"arguments": { "city": "Beijing" }
}
}
// 响应
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"content": [
{ "type": "text", "text": "Beijing: 22°C, sunny." }
]
}
}
资源更新通知(推送)
{
"jsonrpc": "2.0",
"method": "notifications/resources/updated",
"params": { "uri": "file:///data/report.txt" }
}
通知机制是传统 Function Calling 不具备的,它允许工具主动向模型推送信息,而不必依赖轮询。
【延伸】
- JSON-RPC 2.0 也常用于 WebSocket 通信。MCP 选择它,可能未来会考虑 WebSocket 传输层,以获得更好的双向实时能力。
- 通知中的
resources/updated暗示 MCP 不仅是"工具协议",还可能是"资源协议",有潜力构建 AI 可感知的文件系统或知识库。
【试一试】
可以尝试实现一个简单的 MCP Client 和 Server(用 Python 的 mcp 包),通过 stdio 传递上述 JSON 消息,观察初始化和工具调用流程。
3. 实现方式:客户端-主机-服务器的三层解耦
3.1 架构图
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ MCP Host │────▶│ MCP Client │────▶│ MCP Server │
│ (AI 应用) │ │ (协议客户端) │ │ (工具实现) │
└─────────────┘ └─────────────┘ └─────────────┘
- MCP Host:真正发起调用的 AI 应用(如 Claude Desktop、你的 Web 后端)。
- MCP Client:与一个 Server 1:1 映射,负责连接、收发消息、协议翻译。
- MCP Server:轻量级服务,暴露工具、资源和能力。
3.2 解耦要点
- Host 不接触 Server 的通信细节,Client 提供统一接口。
- 一个 Host 可创建多个 Client,连接不同 Server,实现多工具组合。
- Server 可用任何语言编写,只要通过 stdio 或 HTTP 产生 JSON-RPC 输出。
3.3 连接生命周期
- 初始化:Client 发送
initialize请求,协商协议版本和能力。 - 正常操作:工具发现、调用、资源读取等。
- 关闭:连接断开,释放资源。
【延伸】
- 这种架构与微服务中的 Service Mesh 相似:数据面(Client/Server)与控制面(Host 的策略)分离。
- 可以预见到未来会出现"MCP 网关"产品,集中管理多个 Server 的认证、限流和监控。
【追问】
- 三层架构中,如果 Client 崩溃,Host 能否自动恢复连接?MCP 本身未定义故障转移机制,这是否需要应用层自己实现?
4. 项目集成:在 Python Web 应用中包装外部 API(代码详解)
4.1 场景描述
智能助手 Web 应用需要接入天气查询。我们将外部天气 API 封装为一个 MCP Server,Web 后端通过 MCP Client 与之通信。
4.2 天气服务器(weather_server.py)逐段拆解
import json, asyncio
from mcp.server import Server, NotificationOptions
from mcp.server.models import InitializationCapabilities
from mcp.server.stdio import stdio_server
import httpx
server = Server("weather-server")
asyncio:异步编程库,允许等待网络请求时不阻塞。Server:创建 MCP 服务器实例。stdio_server:基于标准输入输出的传输工具。
注册工具列表
@server.list_tools()
async def list_tools():
return [{
"name": "get_weather",
"description": "获取指定城市的当前天气",
"inputSchema": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"]
}
}]
@server.list_tools()装饰器:当客户端请求tools/list时调用此函数。- 返回一个工具定义列表,包含名称、描述和 JSON Schema 参数说明。
注册工具实现
@server.call_tool()
async def call_tool(name: str, arguments: dict):
if name == "get_weather":
city = arguments["city"]
async with httpx.AsyncClient() as client:
resp = await client.get(f"https://api.weather.com/v1/current?city={city}&key=KEY")
data = resp.json()
return {
"content": [
{"type": "text", "text": f"{city}: {data['temp']}°C, {data['condition']}"}
]
}
@server.call_tool():处理tools/call请求。- 使用
httpx.AsyncClient异步调用外部 API,await等待结果。 - 返回内容必须符合 MCP 规范:
{"content": [...]}。
启动服务器
async def main():
async with stdio_server() as (read_stream, write_stream):
await server.run(
read_stream, write_stream,
InitializationCapabilities(sampling={}, experimental={}, tools={}),
notification_options=NotificationOptions()
)
if __name__ == "__main__":
asyncio.run(main())
stdio_server()建立 stdio 通信流。server.run进入事件循环,处理请求。
4.3 Web 应用集成(app.py)及常见陷阱
原始意图代码(有坑)
from fastapi import FastAPI
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
app = FastAPI()
session = None
async def connect_to_weather_server():
server_params = StdioServerParameters(command="python", args=["weather_server.py"])
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
tools = await session.list_tools()
return session # ❌ 离开 async with 块后 session 就失效了!
@app.on_event("startup")
async def startup():
global session
session = await connect_to_weather_server() # 拿到的 session 已关闭
问题剖析
async with 块退出时会自动关闭通信流和会话,返回的 session 对象已不可用。这是初学者极易犯的错误。
修正版:每次请求临时连接(可运行但不高效)
@app.get("/ask")
async def ask(question: str):
server_params = StdioServerParameters(command="python", args=["weather_server.py"])
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
result = await session.call_tool("get_weather", {"city": "Beijing"})
return {"answer": result.content[0].text}
- 每次请求启动一个子进程,用完立即关闭。逻辑正确,但开销大。
生产环境改进方向
- 使用长连接(如 HTTP/SSE 传输),避免反复启动进程。
- 用连接池维护一个持久的
session,并在 FastAPI 后台任务中维持。
4.4 通俗比喻
Web 应用就像餐厅服务员,客人问天气。服务员不会自己查,而是打电话(stdio)给专属气象员(天气服务器),通过一套标准问答(MCP 协议)获取结果,再转达给客人。气象员是独立的,更换他只需另一个懂协议的人即可,餐厅无需改造。
【延伸】
- 这个模式本质上就是"工具即服务"(TaaS)。未来 AI 应用可以由多个这样的 MCP Server 微服务组合而成,类似"AI 原生微服务"。
- 可以思考:是否可以在 Server 侧做结果缓存、限流?MCP 协议本身不限制,但应用层完全可以加上这些能力。
【试一试】
试着将天气服务器改为连接真实的 OpenWeatherMap API,并扩展一个 get_forecast 工具,体验增加工具的便捷性。
5. 加载时机:一次性加载 vs 动态发现
5.1 启动时一次性加载
Host 启动后,Client 连接 Server 并调用 tools/list,将全部工具注入到 AI 的 system prompt 或工具注册表。此后整个会话使用该固定列表。
适用场景:工具数量固定,如企业内部几个稳定的系统。
5.2 按需动态发现
MCP 支持运行时动态改变工具列表:
- Server 可发送通知
notifications/tools/list_changed。 - Client 收到后可重新调用
tools/list获取最新工具。 - 高级用法:Host 可以根据 LLM 推理的需求,临时启动特定的 MCP Server 并查询其工具,实现"懒加载"。
比喻:固定菜单 vs 服务员告知"今日特供",你可随时追问更新。
【延伸】
- 动态发现可用于插件系统:用户安装新插件时,Server 通知 Host,AI 立即获得新能力。
- 在 Agent 协作中,子 Agent 的动态创建和销毁需要这种机制的支持。
【追问】
- 动态发现增加了系统的复杂性,对 AI 的 prompt 管理提出了挑战:工具频繁变化时,如何保证模型能正确理解当前可用的工具集?
6. 隔离方案:多项目/多 Agent 的命名与权限
6.1 命名冲突
多个 MCP Server 可能拥有同名工具(如 search)。
解决方案 :命名空间前缀。例如 finance/search_transactions、hr/search_employees。
Client 端可以在聚合工具时自动添加来源前缀:
for server_name, session in server_sessions.items():
for tool in server_tools:
tool["name"] = f"{server_name}/{tool['name']}"
6.2 权限隔离
- Agent 级隔离:不同 Agent 使用不同 Client 实例,连接各自的 Server 集合,天然隔离。
- 工具级访问控制:在 Server 内通过上下文(如认证令牌)检查权限。
- 进程/容器隔离:每个 Server 运行在独立进程或容器中,结合网络策略进一步加固。
- 传输层安全:HTTP/SSE 传输时使用 TLS 和身份验证头。
多租户架构示例
Agent A (Client A) ──── finance Server
Agent B (Client B) ──── hr Server
Agent C (Client C) ──── common Server (按需连接)
各个 Agent 仅能看到授权范围内的工具,权限不交叉。
【延伸】
- 此类权限模型可以借鉴 AWS IAM 的策略语法,定义一个 MCP 权限策略语言,以声明式控制工具和资源的访问。
- 在多 Agent 协作场景(如 AutoGen、CrewAI)中,Agent 之间的工具隔离成为安全基础。
【试一试】
尝试为你的 MCP Server 增加一个简单的认证:在 initialize 阶段传入 token,并在每个工具调用前验证。
7. 实战:Claude Code 中的项目级 MCP 配置与隔离
Claude Code 提供了一套层层递进的隔离方法,可以按需组合使用。
7.1 第一层:配置作用域隔离(划清边界)
通过不同优先级的配置文件,让不同项目使用不同的 MCP 服务器集。
配置优先级(由高到低)
- 命令行参数
--mcp-config <path>(仅当前会话) - 项目根目录
.mcp.json(团队共享,可提交 Git) - 项目本地
settings.local.json(个人敏感配置,不提交 Git) - 全局
~/.claude.json(兜底配置)
实战建议 :团队在项目根目录维护 .mcp.json,个人敏感连接字符串放在 .claude/settings.local.json。
7.2 第二层:工具与权限隔离(能力限制)
如果多个项目必须共用同一个 MCP Server(如公司数据库),可通过代理和命名空间进行细粒度控制。
工具过滤示例(使用 mcproxy)
// .mcp.json
{
"mcpServers": {
"secure-db": {
"command": "npx",
"args": ["-y", "@team-attention/mcproxy", "--", "npx", "-y", "@modelcontextprotocol/server-postgres", "postgresql://..."]
}
}
}
配合 .mcproxy.json 配置文件:
{
"servers": {
"postgres@1.0.0": {
"tools": {
"query": true,
"list_tables": true,
"insert": false,
"delete": false
}
}
}
}
这样仅允许查询,禁止增删改。
命名空间 :当多个 Server 拥有同名工具时,MCP 防火墙可自动为工具添加前缀,如 github__create_issue、jira__create_issue,避免冲突。
7.3 第三层:进程与运行环境隔离(硬隔离)
- 进程隔离:每个 MCP Server 独立进程,拥有自己的内存空间。
- 沙箱环境:限制网络访问、文件系统读写,通常用 Docker 容器或 AppArmor 实现。
- scoped-mcp:一个为复杂场景设计的 Python 包,为每个 Agent 启动专属代理进程,内置凭据隔离、工具过滤和资源分区。
7.4 第四层:企业级纵深防御与审计
建议构建多层防线:
- 应用内开关(默认禁用外部工具)
- MCP 防火墙(流量过滤、脱敏、审计)
- 子 Agent 权限裁剪(最小权限原则)
- 系统级沙箱(容器、强制访问控制)
同时建立集中审计日志,记录每一次工具调用的详细信息(谁、时间、项目、工具、参数)。
7.5 总结选择指南
- 简单省心 :用
.mcp.json或settings.local.json划分项目。 - 进阶限制 :通过
mcproxy和命名空间过滤与区分工具。 - 高安全要求 :采用沙箱和
scoped-mcp创建安全泡泡。 - 企业全面管控:部署多层防御 + 审计日志。
【延伸】
- 审计日志可对接 SIEM 系统,实现对 AI 操作的安全监控和合规性检查。
- 未来可能出现 MCP 安全标准(类似 OWASP API 安全 Top 10),专门针对 AI 工具调用的威胁建模。
【追问】
- 在高度动态的 Agent 系统中,如何确保权限策略的实时更新和分发?
- 代理方式的性能损耗有多大?是否适合高频工具调用场景?