1. 引言:为什么需要 MCP
- 当前 AI Agent 接入外部工具/数据的痛点:接口碎片化、协议不统一、重复造轮子
- MCP(Model Context Protocol)是什么:Anthropic 于 2024 年底开源的开放协议
- MCP 的核心价值:让模型与工具、数据源之间建立标准化连接,一次接入、处处复用
- 本文目标:从零手写一个 MCP Server,并接入 AI Agent,搭建一套可用的工具链
2. MCP 核心概念与架构
- 协议分层:Client、Server、Transport、Protocol
- 核心原语:Tools、Resources、Prompts 三大能力
- 消息模型:JSON-RPC 2.0 之上的请求/响应/通知
- 传输方式对比:stdio(本地进程)与 Streamable HTTP(远程服务)
- 生命周期:initialize → 能力协商 → 工具调用 → 关闭
- 配套一张 MCP 架构图(Mermaid)
下面是 MCP 的整体架构图,清晰展示了 Client、Server、Transport、Protocol 的分层关系,以及 Tools、Resources、Prompts 三大原语与各层之间的交互流向:
图中各层职责与交互流向说明:
- Client 层:负责与 LLM 应用对接,完成 initialize 握手与能力协商,并把模型意图路由到对应的工具调用、资源读取或提示词模板。
- Transport 层:承载 Client 与 Server 之间的消息传输,本地开发用 stdio,远程部署用 Streamable HTTP。
- Protocol 层:基于 JSON-RPC 2.0 定义消息格式与 Schema 校验,保证双方按统一契约通信。
- Server 层:注册并暴露 Tools、Resources、Prompts 三大原语,分别对接外部 API、数据库、文件系统等真实数据源。
3. 环境准备与项目初始化
- 技术选型:Python + 官方 SDK(mcp 包)或 TypeScript SDK
- 环境要求:Python 3.10+ / Node 18+,uv 或 pip 安装
- 初始化项目结构:server.py、client.py、requirements.txt
- 安装 MCP SDK 并验证版本
- 最小可运行示例:一个返回 “hello” 的 MCP Server
3.1 安装依赖
# 创建虚拟环境(推荐)
python -m venv .venv
source .venv/bin/activate # Windows 下为 .venv\Scripts\activate
# 安装 MCP 官方 Python SDK
pip install "mcp[cli]"
# 验证安装
mcp --version
3.2 项目结构
mcp-toolchain/
├── server.py # MCP Server 实现
├── client.py # MCP Client 实现
├── requirements.txt # 依赖清单
└── .env # 环境变量(API Key 等)
3.3 最小可运行 Server
# server.py
from mcp.server.fastmcp import FastMCP
# 创建 MCP Server 实例
mcp = FastMCP("hello-server")
@mcp.tool()
def say_hello(name: str) -> str:
"""向用户问好"""
return f"Hello, {name}!"
if __name__ == "__main__":
mcp.run() # 默认以 stdio 模式运行
启动后,用 mcp dev server.py 即可在调试面板中看到并调用 say_hello 工具。
4. 开发第一个 MCP Server
- 创建 Server 实例并注册工具
- 用装饰器定义工具:入参 schema 与返回值约定
- 实现一个「天气查询」工具:输入城市名,返回模拟天气数据
- 工具内调用外部 HTTP API 的实践(requests/httpx)
- 错误处理与结构化返回
- 本地运行与调试:用 mcp 命令行工具或直接启动
4.1 定义带 Schema 的工具
# server.py
from typing import Any
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("weather-server")
@mcp.tool()
def get_weather(city: str, unit: str = "celsius") -> dict[str, Any]:
"""查询指定城市的天气。
Args:
city: 城市名称,如 "北京"、"Shanghai"
unit: 温度单位,celsius(摄氏)或 fahrenheit(华氏)
Returns:
包含天气信息的结构化字典
"""
# 模拟数据,实际项目中可替换为真实 API 调用
data = {
"city": city,
"temperature": 26 if unit == "celsius" else 79,
"condition": "晴",
"humidity": 45,
"wind": "东南风 3 级",
}
return data
4.2 调用外部 HTTP API
# server.py(续)
import httpx
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("weather-server")
@mcp.tool()
async def get_weather_http(city: str) -> dict[str, Any]:
"""通过外部 API 查询真实天气数据。"""
url = f"https://api.example.com/weather?city={city}"
async with httpx.AsyncClient(timeout=10) as client:
resp = await client.get(url)
resp.raise_for_status() # 非 2xx 时抛出异常
return resp.json()
4.3 错误处理与结构化返回
# server.py(续)
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("weather-server")
@mcp.tool()
def get_weather_safe(city: str) -> dict[str, Any]:
"""带错误处理的天气查询工具。"""
if not city or not city.strip():
return {"error": "城市名不能为空", "success": False}
try:
# 模拟查询逻辑
result = {"city": city, "temperature": 26, "success": True}
return result
except Exception as e:
return {"error": str(e), "success": False}
4.4 本地运行与调试
# 方式一:直接启动(stdio 模式)
python server.py
# 方式二:使用 MCP Inspector 可视化调试
mcp dev server.py
启动后,Inspector 会打开一个 Web 面板,可直观地查看工具列表、入参 Schema 并手动调用测试。
5. 接入 AI Agent:编写 MCP Client
- 用官方 SDK 编写 Client 连接 Server
- 完成 initialize 握手与能力协商
- 列出并调用 Server 暴露的工具
- 将 MCP 工具接入 LLM:以 OpenAI/DeepSeek 等兼容接口为例
- 实现「用户提问 → 模型决定调用工具 → 返回结果 → 模型总结」的完整闭环
- 配套一张 Agent 调用时序图(Mermaid)
5.1 基础 Client:连接与调用
# client.py
import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
async def main():
# 配置要连接的 Server 进程
server_params = StdioServerParameters(
command="python",
args=["server.py"],
)
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
# 1. initialize 握手与能力协商
init_result = await session.initialize()
print("协商结果:", init_result.capabilities)
# 2. 列出 Server 暴露的工具
tools = await session.list_tools()
for tool in tools.tools:
print(f"工具: {tool.name} - {tool.description}")
# 3. 调用工具
result = await session.call_tool(
"get_weather",
arguments={"city": "北京"},
)
print("调用结果:", result.content)
if __name__ == "__main__":
asyncio.run(main())
5.2 接入 LLM:完整工具调用闭环
# agent.py
import json
from openai import OpenAI
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
client = OpenAI(base_url="https://api.deepseek.com", api_key="your-key")
async def run_agent(session: ClientSession, user_query: str):
# 1. 获取工具列表并转换为 OpenAI 格式
tools_result = await session.list_tools()
tools = [
{
"type": "function",
"function": {
"name": t.name,
"description": t.description,
"parameters": t.inputSchema,
},
}
for t in tools_result.tools
]
messages = [{"role": "user", "content": user_query}]
# 2. 模型决定是否调用工具
resp = client.chat.completions.create(
model="deepseek-chat",
messages=messages,
tools=tools,
)
msg = resp.choices[0].message
messages.append(msg)
# 3. 若模型请求调用工具,则执行并回填结果
if msg.tool_calls:
for tc in msg.tool_calls:
args = json.loads(tc.function.arguments)
result = await session.call_tool(tc.function.name, arguments=args)
messages.append({
"role": "tool",
"tool_call_id": tc.id,
"content": str(result.content),
})
# 4. 模型基于工具结果生成最终回答
final = client.chat.completions.create(
model="deepseek-chat",
messages=messages,
)
return final.choices[0].message.content
return msg.content
async def main():
server_params = StdioServerParameters(command="python", args=["server.py"])
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
answer = await run_agent(session, "北京今天天气怎么样?")
print("Agent 回答:", answer)
if __name__ == "__main__":
asyncio.run(main())
5.3 Agent 调用时序图
整个闭环的核心是:模型不直接执行工具,而是通过 MCP Client 与 Server 通信,从而让工具接入标准化、可复用。
6. 进阶:Resources 与 Prompts
- Resources:暴露只读数据(文件、数据库查询、API 结果)
- Prompts:预置可复用的提示词模板
- 三者(Tools/Resources/Prompts)的适用场景对比
- 在 Server 中注册一个 Resource 与一个 Prompt 的示例
6.1 注册 Resource
# server.py(续)
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("advanced-server")
@mcp.resource("config://app")
def get_config() -> str:
"""暴露应用配置(只读数据源)"""
return "version=1.0.0\nmode=production"
@mcp.resource("db://users/{user_id}")
def get_user(user_id: str) -> str:
"""从数据库查询用户信息"""
# 实际项目中在此查询数据库
return f'{{"id": "{user_id}", "name": "Alice", "role": "admin"}}'
6.2 注册 Prompt
# server.py(续)
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("advanced-server")
@mcp.prompt()
def code_review(code: str) -> str:
"""生成代码审查提示词模板"""
return f"""请对以下代码进行审查,重点关注:
1. 潜在 Bug 与边界条件
2. 性能瓶颈
3. 代码可读性与命名规范
代码内容:
```python
{code}
“”"
@mcp.prompt()
def translate(text: str, target_lang: str = “English”) -> str:
“”“翻译提示词模板”“”
return f"请将以下内容翻译成{target_lang},保持原意与语气:\n\n{text}"
### 6.3 三者适用场景对比
| 原语 | 本质 | 适用场景 | 是否可写 |
|------|------|----------|----------|
| Tools | 可执行操作 | 查询天气、调用 API、执行计算 | 可产生副作用 |
| Resources | 只读数据 | 配置文件、数据库记录、文件内容 | 只读 |
| Prompts | 提示词模板 | 代码审查、翻译、角色扮演 | 只读模板 |
**选择建议**:需要执行动作用 Tools;只需读取数据用 Resources;需要复用固定指令用 Prompts。
## 7. 实战:搭建一个多工具 Agent 工具链
- 场景设计:一个「信息查询助手」,聚合天气、新闻、计算器三个工具
- 在一个 Server 中注册多个工具
- 用 Client 侧路由把不同工具分发给不同模型或统一模型
- 处理工具调用链:一个工具的输出作为另一个工具的输入
- 日志与可观测性:记录每次工具调用的入参、出参、耗时
### 7.1 多工具 Server
```python
# multi_server.py
import time
from typing import Any
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("info-assistant")
@mcp.tool()
def get_weather(city: str) -> dict[str, Any]:
"""查询城市天气"""
return {"city": city, "temperature": 26, "condition": "晴"}
@mcp.tool()
def get_news(topic: str = "technology") -> list[dict[str, str]]:
"""获取指定主题的新闻列表"""
return [
{"title": f"{topic} 头条新闻一", "url": "https://example.com/1"},
{"title": f"{topic} 头条新闻二", "url": "https://example.com/2"},
]
@mcp.tool()
def calculator(expression: str) -> float:
"""计算数学表达式,如 '1 + 2 * 3'"""
# 安全起见,仅允许数字与四则运算
allowed = set("0123456789+-*/(). ")
if not all(c in allowed for c in expression):
raise ValueError("表达式包含非法字符")
return eval(expression) # 生产环境请使用安全解析库
7.2 Client 侧路由与日志
# multi_client.py
import asyncio
import time
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
async def call_with_log(session: ClientSession, tool_name: str, args: dict):
"""带日志与耗时的工具调用封装"""
start = time.perf_counter()
print(f"[调用] {tool_name}({args})")
result = await session.call_tool(tool_name, arguments=args)
elapsed = (time.perf_counter() - start) * 1000
print(f"[完成] {tool_name} 耗时 {elapsed:.1f}ms,结果: {result.content}")
return result
async def main():
server_params = StdioServerParameters(command="python", args=["multi_server.py"])
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
# 路由:根据用户意图分发到不同工具
intent = "weather" # 实际项目中由 LLM 判断意图
if intent == "weather":
await call_with_log(session, "get_weather", {"city": "上海"})
elif intent == "news":
await call_with_log(session, "get_news", {"topic": "ai"})
elif intent == "calc":
await call_with_log(session, "calculator", {"expression": "(3+5)*2"})
if __name__ == "__main__":
asyncio.run(main())
7.3 工具调用链:一个工具的输出作为另一个工具的输入
# chain_client.py
import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
async def run_chain(session: ClientSession):
"""演示工具调用链:先查天气,再根据天气生成穿衣建议"""
# 第一步:查询天气
weather_result = await session.call_tool(
"get_weather", arguments={"city": "北京"}
)
weather_text = str(weather_result.content)
print("天气结果:", weather_text)
# 第二步:将天气结果作为输入,调用建议工具
# (实际项目中建议工具可在 Server 端实现)
advice = await session.call_tool(
"calculator",
arguments={"expression": "26 - 10"}, # 模拟温差计算
)
print("温差建议:", advice.content)
async def main():
server_params = StdioServerParameters(command="python", args=["multi_server.py"])
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
await run_chain(session)
if __name__ == "__main__":
asyncio.run(main())
7.4 可观测性要点
- 入参日志:记录每次工具调用的完整参数,便于复现问题
- 出参日志:记录返回结果,便于排查数据异常
- 耗时统计:记录每次调用的耗时,定位性能瓶颈
- 链路追踪:为一次 Agent 会话生成唯一 trace_id,串联所有工具调用
8. 部署与远程调用
- 本地 stdio 模式与远程 HTTP 模式的切换
- 用 Streamable HTTP 暴露 MCP Server
- 鉴权与安全:API Key、CORS、输入校验
- 部署到云服务器 / Docker 容器
- 远程 Client 连接配置示例
9. 常见问题与调试技巧
- 握手失败、能力协商不一致的排查
- 工具调用超时与重试策略
- 传输层断连与重连
- 日志级别与调试工具(mcp dev、inspector)
- 版本兼容性:SDK 与协议版本对应关系
10. 总结与展望
- 回顾从零搭建工具链的完整路径
- MCP 生态现状:官方 SDK、社区 Server、规范演进
- 下一步学习方向:自定义 Transport、多 Server 聚合、与 LangChain/Claude 深度集成
- 结语:MCP 正在成为 AI 工具互联的事实标准
188



被折叠的 条评论
为什么被折叠?



