MCP的个人理解
笔记概览
本笔记围绕 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 系统中,如何确保权限策略的实时更新和分发?
- 代理方式的性能损耗有多大?是否适合高频工具调用场景?
