引言
2025-2026年是AI Agent从概念走向落地的爆发期。从Claude Code、Cursor到Visual Studio Code Copilot,几乎主流AI应用都在向"智能体"形态演进------它们不再只是问答机器人,而是能够自主调用工具、读取文件、操作数据库的真实生产力工具。
然而,这些能力背后有一个关键的标准化协议正在悄然颠覆整个生态:MCP(Model Context Protocol,模型上下文协议)。
由Anthropic推出的MCP,被业界称为"AI界的USB-C接口"。它统一了AI应用与外部数据源、工具之间的通信标准,让一次开发、到处运行成为可能。截至2026年中,MCP生态已覆盖Claude、ChatGPT、VS Code、Cursor等主流平台,注册MCP服务器数量突破数千个。
本文将从零开始手写一个完整的MCP服务器和客户端。我们不依赖任何MCP SDK,直接基于JSON-RPC协议构建,让你从底层彻底理解MCP的工作机制。
实战指南 :本文提供完整可运行的代码示例,建议边看边实践。更多AI Agent实战教程参见:DeepSeek 实战指南系列
一、MCP 核心架构解析
1.1 什么是MCP?
MCP(Model Context Protocol)是一个开放的、标准化的协议,用于在AI应用(如Claude、VS Code等)和外部数据源/工具之间建立安全的通信通道。
类比理解:
-
HTTP 让浏览器与Web服务器通信 → 互联网时代的基础设施
-
MCP 让AI应用与外部工具通信 → AI Agent时代的基础设施
MCP协议的核心使命是回答三个问题:
-
AI如何发现可用工具? → 通过
tools/list方法 -
AI如何调用工具? → 通过
tools/call方法 -
AI如何获取上下文数据? → 通过
resources/read方法
1.2 三要素角色
MCP架构由三个层次构成:
MCP主机(Host):运行AI大模型的应用,例如Claude Desktop、VS Code、Cursor等。主机负责协调多个MCP客户端。
MCP客户端(Client):与具体MCP服务器建立一对一连接的信道组件。每个MCP客户端维护与一个MCP服务器的专用连接。
MCP服务器(Server):提供工具、资源或提示模板的外部程序。可以本地运行(如文件系统服务器),也可以远程部署(如Sentry MCP服务器)。
┌─────────────────────────────────────┐
│ MCP 主机 (VS Code) │
│ ┌──────────┐ ┌──────────┐ │
│ │ Client 1 │ │ Client 2 │ │
│ └────┬─────┘ └────┬─────┘ │
└───────┼──────────────┼──────────────┘
│ │
┌───────▼──────┐ ┌─────▼────────┐
│ Server A │ │ Server B │
│ (Filesystem) │ │ (Database) │
└──────────────┘ └──────────────┘
1.3 两层协议栈
MCP协议分为两个核心层次:
数据层(Data Layer):基于JSON-RPC 2.0协议,定义了客户端与服务器之间的消息格式、生命周期管理和核心原语(工具、资源、提示模板)。数据层是MCP协议最核心的部分,也是开发者最常直接接触的层次。它定义了三种服务器端原语和三种客户端原语,分别对应AI应用与外部世界的输入输出交互。
传输层(Transport Layer):定义了通信机制和信道。传输层负责建立连接、消息封帧、安全通信等底层工作。MCP支持两种传输方式:
-
Stdio传输:通过标准输入/输出流进行本地进程间通信。这是最常用的开发调试方式,性能最优,因为不需要网络开销。服务器进程由客户端直接启动,双方通过stdin/stdout交换JSON-RPC消息。注意Stdio模式下日志必须输出到stderr,否则会破坏协议消息的完整性。
-
Streamable HTTP传输:基于HTTP POST请求发送客户端到服务器的消息,配合Server-Sent Events(SSE)实现服务端推送能力。这种传输方式支持远程服务器通信,可以使用Bearer Token、API Key等标准HTTP认证方式。Anthropic推荐使用OAuth 2.0获取认证令牌,确保远程调用的安全性。
协议版本与兼容性 :MCP当前使用的协议版本标识为 2024-11-05(以日期命名)。在初始化阶段,客户端和服务器会协商协议版本,确保双方使用兼容的协议能力。如果服务器不支持客户端请求的版本,可以在初始化响应中声明自己支持的版本,由客户端决定是否继续连接。
二、协议底层:JSON-RPC 2.0 精要
在动手写MCP之前,我们必须先理解其底层的RPC协议。MCP完全基于JSON-RPC 2.0规范,每个消息都是一个JSON对象。
2.1 请求格式
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list",
"params": {}
}
关键字段:
-
jsonrpc:固定为"2.0" -
id:请求唯一标识,响应会携带相同的id -
method:要调用的方法名 -
params:方法参数(可选)
2.2 响应格式
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"tools": [
{
"name": "calculator",
"description": "执行数学计算",
"inputSchema": {
"type": "object",
"properties": {
"expression": {"type": "string"}
},
"required": ["expression"]
}
}
]
}
}
2.3 通知格式
通知是无需响应的请求,id字段被省略:
{
"jsonrpc": "2.0",
"method": "notifications/tools/list_changed",
"params": {}
}
2.4 错误响应
当请求处理失败时,服务器返回错误响应:
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32601,
"message": "Method not found"
}
}
错误响应与成功响应的区别在于包含error字段而非result字段。错误对象包含三个属性:code(整数错误码)、message(错误描述字符串)和可选的data(附加错误信息)。
标准JSON-RPC错误码:
| 错误码 | 含义 | 说明 |
|---|---|---|
| -32700 | 解析错误 | 服务端收到无效的JSON,应检查消息格式 |
| -32600 | 无效请求 | 发送的JSON不是一个合法的请求对象 |
| -32601 | 方法未找到 | 服务端不存在请求的方法 |
| -32602 | 无效参数 | 方法参数类型或数量不合法 |
| -32603 | 内部错误 | 服务端执行方法时发生运行时错误 |
除了标准JSON-RPC错误码,MCP协议还定义了一些扩展错误码用于特定场景。理解这些错误码对于调试MCP应用程序至关重要------大部分连接问题都可以通过查看错误码来快速定位。
2.5 消息交互实例
以下是一个完整的MCP消息交互序列,展示从初始化到工具调用的全过程:
第1步:客户端发送初始化请求
--> {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"my-mcp-client","version":"1.0.0"}}}
<-- {"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2024-11-05","capabilities":{"tools":{},"resources":{}},"serverInfo":{"name":"my-mcp-server","version":"1.0.0"}}}
第2步:客户端发送初始化完成通知(无需响应)
--> {"jsonrpc":"2.0","method":"notifications/initialized"}
第3步:客户端列出可用工具
--> {"jsonrpc":"2.0","id":2,"method":"tools/list"}
<-- {"jsonrpc":"2.0","id":2,"result":{"tools":[{"name":"calculator","description":"执行数学计算","inputSchema":{"type":"object","properties":{"expression":{"type":"string"}},"required":["expression"]}}]}}
第4步:客户端调用工具
--> {"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"calculator","arguments":{"expression":"2+2"}}}
<-- {"jsonrpc":"2.0","id":3,"result":{"content":[{"type":"text","text":"计算结果: 4"}]}}
理解这三个字段(jsonrpc版本标识、id请求标识、method方法名)的交互模式,就掌握了MCP协议80%的消息交互逻辑。
三、从零构建MCP服务器(纯Python,无SDK)
本节我们将完全不依赖任何MCP SDK,只用Python标准库从底层实现一个完整的MCP服务器。这能让你真正理解MCP协议的本质。
3.1 基础架构设计
我们的MCP服务器需要处理以下核心方法:
| 方法 | 说明 |
|---|---|
initialize |
初始化连接,协商能力 |
tools/list |
列出所有可用工具 |
tools/call |
调用指定工具 |
resources/list |
列出可用资源 |
resources/read |
读取指定资源 |
notifications/initialized |
客户端初始化完成通知 |
3.2 实现JSON-RPC消息处理器
import json
import sys
import logging
from typing import Any, Callable
logging.basicConfig(level=logging.INFO, stream=sys.stderr)
class JSONRPCMessage:
"""JSON-RPC 2.0 消息封装"""
@staticmethod
def create_request(id: int, method: str, params: dict = None) -> str:
msg = {
"jsonrpc": "2.0",
"id": id,
"method": method
}
if params is not None:
msg["params"] = params
return json.dumps(msg)
@staticmethod
def create_response(id: int, result: Any) -> str:
return json.dumps({
"jsonrpc": "2.0",
"id": id,
"result": result
})
@staticmethod
def create_error(id: int, code: int, message: str, data: Any = None) -> str:
error = {"code": code, "message": message}
if data is not None:
error["data"] = data
return json.dumps({
"jsonrpc": "2.0",
"id": id,
"error": error
})
@staticmethod
def create_notification(method: str, params: dict = None) -> str:
msg = {
"jsonrpc": "2.0",
"method": method
}
if params is not None:
msg["params"] = params
return json.dumps(msg)
3.3 实现MCP服务器核心
class MCPServer:
"""从零实现的MCP服务器"""
def __init__(self, server_name: str = "my-mcp-server", version: str = "1.0.0"):
self.server_name = server_name
self.version = version
self.tools: dict[str, dict] = {}
self.tool_handlers: dict[str, Callable] = {}
self.resources: dict[str, dict] = {}
self.request_id = 0
def register_tool(self, name: str, description: str,
input_schema: dict, handler: Callable):
"""注册一个MCP工具"""
self.tools[name] = {
"name": name,
"description": description,
"inputSchema": input_schema
}
self.tool_handlers[name] = handler
def register_resource(self, uri: str, name: str,
description: str, mime_type: str = "text/plain"):
"""注册一个MCP资源"""
self.resources[uri] = {
"uri": uri,
"name": name,
"description": description,
"mimeType": mime_type
}
async def handle_message(self, raw_message: str) -> str | None:
"""处理一条JSON-RPC消息"""
try:
message = json.loads(raw_message)
except json.JSONDecodeError:
return JSONRPCMessage.create_error(0, -32700, "Parse error")
method = message.get("method", "")
msg_id = message.get("id")
params = message.get("params", {})
# 初始化阶段
if method == "initialize":
return self._handle_initialize(msg_id, params)
# 初始化完成通知(无响应)
if method == "notifications/initialized":
logging.info("Client initialized")
return None
# 工具列出
if method == "tools/list":
return JSONRPCMessage.create_response(
msg_id, {"tools": list(self.tools.values())}
)
# 工具调用
if method == "tools/call":
return await self._handle_tool_call(msg_id, params)
# 资源列出
if method == "resources/list":
return JSONRPCMessage.create_response(
msg_id, {"resources": list(self.resources.values())}
)
# 资源读取
if method == "resources/read":
return await self._handle_resource_read(msg_id, params)
# 未知方法
return JSONRPCMessage.create_error(
msg_id, -32601, f"Method not found: {method}"
)
def _handle_initialize(self, msg_id: int, params: dict) -> str:
"""处理初始化请求"""
protocol_version = params.get("protocolVersion", "2024-11-05")
client_name = params.get("clientInfo", {}).get("name", "unknown")
logging.info(f"Client connecting: {client_name} (v{protocol_version})")
# 返回服务器能力声明
return JSONRPCMessage.create_response(msg_id, {
"protocolVersion": protocol_version,
"capabilities": {
"tools": {},
"resources": {}
},
"serverInfo": {
"name": self.server_name,
"version": self.version
}
})
async def _handle_tool_call(self, msg_id: int, params: dict) -> str:
"""处理工具调用请求"""
tool_name = params.get("name", "")
arguments = params.get("arguments", {})
if tool_name not in self.tool_handlers:
return JSONRPCMessage.create_error(
msg_id, -32602, f"Unknown tool: {tool_name}"
)
try:
handler = self.tool_handlers[tool_name]
result = await handler(**arguments)
return JSONRPCMessage.create_response(msg_id, {
"content": [{"type": "text", "text": str(result)}]
})
except Exception as e:
logging.error(f"Tool execution error: {e}")
return JSONRPCMessage.create_error(
msg_id, -32603, f"Internal error: {str(e)}"
)
async def _handle_resource_read(self, msg_id: int, params: dict) -> str:
"""处理资源读取请求"""
uri = params.get("uri", "")
if uri not in self.resources:
return JSONRPCMessage.create_error(
msg_id, -32602, f"Resource not found: {uri}"
)
# 返回资源内容(简化实现)
return JSONRPCMessage.create_response(msg_id, {
"contents": [{
"uri": uri,
"mimeType": self.resources[uri]["mimeType"],
"text": f"内容来自资源: {uri}"
}]
})
3.4 Stdio传输层实现
MCP通过Stdio传输层实现进程间通信。服务器从stdin读取JSON-RPC消息,将响应写入stdout。
import asyncio
class StdioTransport:
"""Stdio传输层"""
def __init__(self, server: MCPServer):
self.server = server
async def read_line(self) -> str | None:
"""从stdin读取一行"""
loop = asyncio.get_event_loop()
line = await loop.run_in_executor(None, sys.stdin.readline)
return line.strip() if line else None
def write_message(self, message: str):
"""将消息写入stdout"""
sys.stdout.write(message + "\n")
sys.stdout.flush()
async def run(self):
"""运行事件循环"""
while True:
line = await self.read_line()
if not line:
break
response = await self.server.handle_message(line)
if response:
self.write_message(response)
3.5 注册具体工具
现在,让我们为服务器注册几个实用的工具:
import subprocess
import os
from datetime import datetime
async def calculator(expression: str) -> str:
"""执行数学计算"""
try:
# 注意: 生产环境应使用更安全的方式
result = eval(expression)
return f"计算结果: {result}"
except Exception as e:
return f"计算错误: {str(e)}"
async def get_current_time(timezone: str = "Asia/Shanghai") -> str:
"""获取当前时间"""
now = datetime.now()
return f"当前时间: {now.strftime('%Y-%m-%d %H:%M:%S')} (时区: {timezone})"
async def list_directory(path: str = ".") -> str:
"""列出目录内容"""
try:
files = os.listdir(path)
return "\n".join(files) if files else "(空目录)"
except Exception as e:
return f"读取目录失败: {str(e)}"
# 构建完整的服务器
def create_demo_server() -> MCPServer:
server = MCPServer("demo-mcp-server", "1.0.0")
server.register_tool(
name="calculator",
description="执行数学计算,支持四则运算和函数",
input_schema={
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "数学表达式,如 '2 + 2' 或 'sin(pi/2)'"
}
},
"required": ["expression"]
},
handler=calculator
)
server.register_tool(
name="get_current_time",
description="获取指定时区的当前时间",
input_schema={
"type": "object",
"properties": {
"timezone": {
"type": "string",
"description": "时区名称,如 'Asia/Shanghai'",
"default": "Asia/Shanghai"
}
}
},
handler=get_current_time
)
server.register_tool(
name="list_directory",
description="列出指定目录的文件和文件夹",
input_schema={
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "目录路径",
"default": "."
}
}
},
handler=list_directory
)
# 注册资源
server.register_resource(
uri="server://info",
name="服务器信息",
description="MCP服务器的基本信息"
)
return server
3.6 启动入口
async def main():
server = create_demo_server()
transport = StdioTransport(server)
logging.info(f"Starting MCP server: {server.server_name} v{server.version}")
await transport.run()
if __name__ == "__main__":
asyncio.run(main())
四、从零构建MCP客户端
有了服务器,我们还需要一个客户端来连接和交互。同样,我们不依赖任何SDK。
import json
import asyncio
import subprocess
from typing import Any
class MCPClient:
"""从零实现的MCP客户端"""
def __init__(self):
self.process: subprocess.Popen | None = None
self.request_id = 0
self.pending_requests: dict[int, asyncio.Future] = {}
async def connect_stdio(self, command: str, *args: str):
"""通过Stdio连接到MCP服务器"""
self.process = await asyncio.create_subprocess_exec(
command, *args,
stdin=asyncio.subprocess.PIPE,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
)
# 启动消息读取循环
asyncio.create_task(self._read_loop())
async def _read_loop(self):
"""持续读取服务器响应"""
while self.process and self.process.stdout:
line = await self.process.stdout.readline()
if not line:
break
try:
response = json.loads(line.decode().strip())
msg_id = response.get("id")
if msg_id in self.pending_requests:
future = self.pending_requests.pop(msg_id)
future.set_result(response)
except json.JSONDecodeError:
continue
async def send_request(self, method: str, params: dict = None) -> dict:
"""发送JSON-RPC请求并等待响应"""
self.request_id += 1
request_id = self.request_id
message = {
"jsonrpc": "2.0",
"id": request_id,
"method": method
}
if params is not None:
message["params"] = params
future = asyncio.get_event_loop().create_future()
self.pending_requests[request_id] = future
# 发送消息
self.process.stdin.write((json.dumps(message) + "\n").encode())
await self.process.stdin.drain()
# 等待响应
response = await future
if "error" in response:
error = response["error"]
raise Exception(f"MCP error [{error['code']}]: {error['message']}")
return response.get("result", {})
async def initialize(self):
"""初始化MCP连接"""
result = await self.send_request("initialize", {
"protocolVersion": "2024-11-05",
"capabilities": {},
"clientInfo": {
"name": "my-mcp-client",
"version": "1.0.0"
}
})
# 发送初始化完成通知
self.process.stdin.write(
(json.dumps({
"jsonrpc": "2.0",
"method": "notifications/initialized"
}) + "\n").encode()
)
await self.process.stdin.drain()
return result
async def list_tools(self) -> list[dict]:
"""列出所有可用工具"""
result = await self.send_request("tools/list")
return result.get("tools", [])
async def call_tool(self, name: str, arguments: dict = None) -> Any:
"""调用指定工具"""
params = {"name": name}
if arguments:
params["arguments"] = arguments
result = await self.send_request("tools/call", params)
return result
async def list_resources(self) -> list[dict]:
"""列出所有可用资源"""
result = await self.send_request("resources/list")
return result.get("resources", [])
async def close(self):
"""关闭连接"""
if self.process:
self.process.terminate()
await self.process.wait()
4.1 使用客户端交互
async def demo_client_interaction():
"""演示客户端与服务器的交互"""
# 1. 连接到服务器
client = MCPClient()
await client.connect_stdio("python", "mcp_server.py")
# 2. 初始化连接
init_result = await client.initialize()
print(f"已连接服务器: {init_result['serverInfo']['name']}")
print(f"协议版本: {init_result['protocolVersion']}")
print(f"能力: {list(init_result['capabilities'].keys())}")
print()
# 3. 列出所有工具
tools = await client.list_tools()
print(f"可用工具 ({len(tools)}):")
for tool in tools:
print(f" - {tool['name']}: {tool['description']}")
print()
# 4. 调用工具
print("调用 calculator 工具: 2 + 3 * 4")
result = await client.call_tool("calculator", {"expression": "2 + 3 * 4"})
print(f"结果: {result['content'][0]['text']}")
print()
print("调用 get_current_time 工具")
result = await client.call_tool("get_current_time")
print(f"结果: {result['content'][0]['text']}")
print()
# 5. 列出资源
resources = await client.list_resources()
print(f"可用资源 ({len(resources)}):")
for res in resources:
print(f" - {res['name']} ({res['uri']})")
# 6. 关闭连接
await client.close()
# 运行演示
# asyncio.run(demo_client_interaction())
五、使用官方SDK快速构建(进阶)
理解底层协议后,在实际开发中我们通常会使用MCP SDK来加速开发。MCP提供了Python、TypeScript、Java、Kotlin等多语言SDK。
5.1 安装Python SDK
pip install "mcp[cli]"
5.2 使用FastMCP构建服务器(简化版)
MCP SDK从1.2.0版本开始提供了 FastMCP 类,使用Python类型提示和docstring自动生成工具定义:
from mcp.server.fastmcp import FastMCP
import httpx
import json
# 创建MCP服务器实例
mcp = FastMCP("data-analyzer")
@mcp.tool()
async def fetch_json_data(url: str) -> str:
"""获取JSON格式的远程数据
Args:
url: 数据源的URL地址
"""
async with httpx.AsyncClient() as client:
response = await client.get(url, timeout=30.0)
response.raise_for_status()
data = response.json()
return json.dumps(data, indent=2, ensure_ascii=False)
@mcp.tool()
async def analyze_text(text: str, operation: str = "word_count") -> str:
"""分析文本的基本统计信息
Args:
text: 待分析的文本内容
operation: 分析操作: word_count(词数), char_count(字符数),
line_count(行数), all(全部)
"""
operations = {
"word_count": f"词数: {len(text.split())}",
"char_count": f"字符数: {len(text)}",
"line_count": f"行数: {len(text.splitlines())}",
"all": (
f"词数: {len(text.split())}\n"
f"字符数: {len(text)}\n"
f"行数: {len(text.splitlines())}"
)
}
return operations.get(operation, f"未知操作: {operation}")
@mcp.resource("config://app/settings")
def get_settings() -> str:
"""返回一个模拟的应用配置(资源示例)"""
return json.dumps({
"app_name": "Data Analyzer",
"version": "2.0.0",
"max_request_size": "10MB",
"enable_logging": True
}, indent=2)
if __name__ == "__main__":
# 使用Stdio传输运行
mcp.run(transport="stdio")
运行方式:
python data_analyzer_server.py
5.3 使用异步客户端SDK
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
import asyncio
async def main():
# 配置服务器参数
server_params = StdioServerParameters(
command="python",
args=["data_analyzer_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()
print("可用工具:")
for tool in tools:
print(f" {tool.name}: {tool.description}")
# 调用工具
result = await session.call_tool(
"analyze_text",
arguments={
"text": "Hello MCP! This is a test.",
"operation": "all"
}
)
print(f"\n分析结果:\n{result.content[0].text}")
# 读取资源
resource = await session.read_resource("config://app/settings")
print(f"\n配置信息:\n{resource.contents[0].text}")
asyncio.run(main())
六、MCP与DeepSeek的深度结合
到目前为止,我们构建的MCP服务器和客户端都是通用的。接下来,让我们看看如何将MCP与DeepSeek大模型结合,真正打造一个可以自主调用工具的AI Agent。
6.1 架构设计
将DeepSeek与MCP结合,本质上是构建一个ReAct(推理+行动)循环。DeepSeek大模型作为推理核心,MCP作为行动接口。
用户输入 → DeepSeek大模型 → MCP客户端 → MCP服务器 → 外部工具
↑ ↓
└─── 工具调用结果 ────────┘
核心流程分为五个步骤:
第一步:意图理解。DeepSeek接收用户的自然语言输入,通过其强大的语义理解能力解析出用户的真实意图。例如用户说"帮我看看今天的代码提交记录",DeepSeek能理解这需要调用Git工具。
第二步:工具选择 。结合MCP客户端提供的工具列表(通过tools/list获取),DeepSeek判断应该使用哪个或哪些工具来完成任务。这个过程与人类选择工具的逻辑相似------"我需要查看Git记录,所以选择'git_log'工具"。
第三步:参数生成 。DeepSeek根据用户需求生成工具所需的参数。例如git_log工具可能需要branch(分支名)、since(起始日期)等参数。DeepSeek的推理能力确保了参数生成的准确性。
第四步:工具执行。DeepSeek输出结构化的函数调用(function call),MCP客户端将其转换成JSON-RPC消息发送给MCP服务器执行。这一步完全是协议层面的通信,DeepSeek不直接参与。
第五步:结果综合。MCP服务器返回工具执行结果后,DeepSeek将结果与用户的问题上下文结合,生成自然语言的回复。例如拿到Git提交记录后,它会整理成易读的格式,甚至分析提交频率和贡献者统计。
如果一次工具调用不够,整个循环会重复进行,直到任务完成或达到预设的最大轮次。这就是ReAct循环的核心思想------推理、行动、观察、再推理。
6.2 集成代码示例
from openai import OpenAI
import json
# 配置DeepSeek API(假设使用兼容OpenAI接口的DeepSeek API)
client = OpenAI(
base_url="https://api.deepseek.com/v1",
api_key="your-deepseek-api-key"
)
class DeepSeekMCPAgent:
"""集成DeepSeek与MCP的智能体"""
def __init__(self, mcp_client: MCPClient, system_prompt: str = None):
self.mcp_client = mcp_client
self.system_prompt = system_prompt or (
"你是一个智能助手,可以使用各种工具来帮助用户完成任务。"
"当你需要调用工具时,请使用函数调用格式。"
)
self.tools = []
async def load_tools(self):
"""从MCP服务器加载工具定义"""
mcp_tools = await self.mcp_client.list_tools()
self.tools = [
{
"type": "function",
"function": {
"name": t["name"],
"description": t["description"],
"parameters": t["inputSchema"]
}
}
for t in mcp_tools
]
async def chat(self, user_message: str, max_turns: int = 5) -> str:
"""多轮对话,支持工具调用"""
messages = [
{"role": "system", "content": self.system_prompt},
{"role": "user", "content": user_message}
]
for turn in range(max_turns):
# 调用DeepSeek
response = client.chat.completions.create(
model="deepseek-chat",
messages=messages,
tools=self.tools if self.tools else None,
tool_choice="auto" if self.tools else None
)
msg = response.choices[0].message
# 如果模型决定直接回复,返回结果
if not msg.tool_calls:
return msg.content
# 添加模型响应到对话
messages.append(msg)
# 处理每个工具调用
for tool_call in msg.tool_calls:
function_name = tool_call.function.name
function_args = json.loads(tool_call.function.arguments)
# 通过MCP调用工具
try:
result = await self.mcp_client.call_tool(
function_name, function_args
)
tool_result = result["content"][0]["text"]
except Exception as e:
tool_result = f"工具调用失败: {str(e)}"
# 添加工具调用结果到对话
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": tool_result
})
return "已达到最大对话轮次"
6.3 效果演示
当用户问"帮我计算一下当前时间加上8小时是什么时候,然后列出当前目录有哪些文件"时,DeepSeek会:
- 调用
get_current_time工具获取当前时间 - 根据时间结果计算加8小时
- 调用
list_directory工具列出文件 - 综合所有结果回复用户
这种自主调用工具的能力,正是MCP赋予AI Agent的核心价值。
七、MCP的高级特性
7.1 能力协商
MCP的初始化阶段包含能力协商。服务器声明自己支持哪些功能(工具、资源、提示模板),客户端也可以声明自己的支持(如采样能力):
# 客户端声明支持采样
server_result = await client.send_request("initialize", {
"protocolVersion": "2024-11-05",
"capabilities": {
"sampling": {} # 支持采样请求
},
"clientInfo": {"name": "advanced-client", "version": "1.0.0"}
})
7.2 实时通知
当工具或资源发生变化时,服务器可以主动发送通知:
# 服务器端:通知工具列表已变更
transport.write_message(
JSONRPCMessage.create_notification(
"notifications/tools/list_changed"
)
)
7.3 进度追踪
对于长时间运行的操作,MCP支持进度追踪:
{
"jsonrpc": "2.0",
"method": "notifications/progress",
"params": {
"progressToken": "token-001",
"progress": 50,
"total": 100
}
}
7.4 任务(Experimental)
MCP的实验性功能------任务,提供持久化的执行包装器,支持异步结果检索和状态跟踪:
{
"jsonrpc": "2.0",
"method": "tasks/schedule",
"params": {
"name": "batch-process",
"uri": "tasks://batch/process-001"
}
}
八、最佳实践与生产化建议
8.1 安全性
- Stdio服务器:绝不向stdout输出日志(会破坏JSON-RPC协议),始终使用stderr或文件日志
- HTTP服务器:使用OAuth 2.0进行认证授权
- 输入验证:严格校验工具参数,避免命令注入
- 最小权限:MCP服务器只开放必要的工具和资源
8.2 日志规范
# ✅ 正确的日志方式(Stdio传输)
import sys
import logging
logging.basicConfig(level=logging.INFO, stream=sys.stderr)
# ✅ 或者使用print重定向到stderr
print("Server started", file=sys.stderr)
# ❌ 错误方式 ------ 会破坏协议
print("Server started") # 输出到stdout
8.3 错误处理
- 始终捕获工具执行异常并返回友好错误信息
- 使用标准JSON-RPC错误码
- 实现超时机制,防止工具调用挂起
- 在MCP客户端实现重试逻辑,处理瞬态网络故障
8.4 与Function Calling的对比
MCP与OpenAI提出的Function Calling模式既有相似之处,又有本质区别:
| 维度 | MCP | Function Calling |
|---|---|---|
| 标准化程度 | 开放协议,跨平台 | OpenAI专有格式 |
| 工具发现 | tools/list动态发现 |
需预配置工具定义 |
| 传输层 | Stdio/HTTP双模式 | 仅HTTP |
| 资源访问 | 原生支持Resources原语 | 无标准资源机制 |
| 生态兼容 | Claude、ChatGPT、VS Code等 | 仅OpenAI兼容API |
| 安全控制 | 内置能力协商、OAuth | 依赖应用层实现 |
简单来说,Function Calling是OpenAI API的一个特性,而MCP是一个独立的开放协议。MCP的设计目标更宏大------它要成为AI应用连接外部世界的事实标准。从生态兼容性来看,支持MCP的客户端越来越多,而Function Calling仍然局限于OpenAI生态。
8.5 与LangChain工具的对比
LangChain是另一个广泛使用的AI应用框架,它也提供了工具调用机制。与MCP相比:
- LangChain的工具:框架内嵌的抽象,需要通过LangChain的组件链来调用,耦合度较高
- MCP的工具:协议层的标准化接口,无关框架,任何支持MCP的客户端都可以调用
LangChain更像是一个全栈开发框架,而MCP专注于定义AI与工具之间的通信协议。实际项目中,两者也可以结合使用------LangChain作为协调层,MCP作为工具层的标准化接口。
8.6 测试与调试
MCP官方提供了Inspector工具用于调试MCP服务器:
# 使用MCP Inspector调试服务器
npx @modelcontextprotocol/inspector python my_server.py
Inspector提供了一个Web界面,可以查看:
-
工具列表和参数定义
-
发送测试工具调用请求
-
查看JSON-RPC消息日志
-
监控服务器资源使用情况
对于开发者来说,这是一个非常实用的调试工具,可以快速验证MCP服务器的行为是否符合预期。
8.4 部署策略
- 本地MCP服务器:适用于文件系统、本地数据库等敏感操作
- 远程MCP服务器:适用于云API、SaaS服务集成
- 建议:对每个数据源/工具使用独立的MCP服务器,便于隔离和扩展
九、MCP生态全景与未来展望
9.1 当前生态格局
截至2026年中,MCP生态已经初具规模:
主流AI客户端全面支持 :
-
Claude Desktop与Claude Code :Anthropic自家产品,对MCP的支持最原生和完善
-
ChatGPT :2025年晚些时候开始支持MCP协议连接
-
Visual Studio Code :通过内置的MCP支持,可以让Copilot直接调用外部工具
-
Cursor :作为AI原生IDE,对MCP有着深度的集成
-
JetBrains IDE:2026年初开始支持MCP
官方MCP服务器参考实现 :
Anthropic官方维护了多个参考MCP服务器实现,涵盖常见场景:
-
server-filesystem:安全的文件系统操作(读、写、遍历) -
server-github:GitHub API集成(Issue、PR、代码搜索) -
server-postgres:PostgreSQL数据库查询 -
server-sqlite:SQLite数据库交互 -
server-puppeteer:浏览器自动化 -
server-sentry:错误监控和告警查询 -
server-slack:Slack消息和工作空间管理
这些参考实现以MIT协议开源,是学习MCP服务器开发的最佳实践参考。
9.2 典型应用场景
场景一:智能代码审查
将MCP服务器连接到代码仓库(GitHub/GitLab),AI就可以直接读取PR差异、检查代码规范、自动生成审查意见。开发者只需说"帮我审查这个PR",AI自动完成全部工作。
场景二:数据分析助手
连接数据库MCP服务器,AI可以执行SQL查询、生成可视化图表、分析数据趋势。不再需要手动编写复杂的SQL语句,用自然语言就能完成数据探索。
场景三:自动化运维
连接服务器监控和告警MCP服务器,AI可以实时监听系统状态、分析日志文件、自动执行故障恢复脚本。这是一种从"被动告警"到"主动运维"的范式转变。
场景四:个人知识管理
连接本地文件系统和笔记应用的MCP服务器,AI可以像个人秘书一样管理文档、整理笔记、检索信息。
9.3 未来趋势
MCP的发展正遵循着与HTTP、USB等标准化协议相似的轨迹:从初期的碎片化集成,走向统一的标准化协议,最终成为AI Agent时代的基础设施。
几个值得关注的方向:
- MCP Registry:类似Docker Hub的MCP服务器注册中心,开发者可以发现、安装和分享MCP服务器
- MCP Gateway:企业级的MCP网关,统一管理多个MCP服务器的访问控制、限流和监控
- 多模态MCP:扩展MCP协议以支持图像、音频等非文本数据的传输
- 标准化安全认证:OAuth 2.0 + MCP的深度集成,形成企业级安全标准
结语
本文从零开始,完整地实现了一个MCP(Model Context Protocol)服务器和客户端。从JSON-RPC协议底层,到Stdio传输层,再到工具注册和调用,我们没有依赖任何SDK,完全理解了MCP的核心原理。
通过本文,你应该掌握了:
- MCP的基础架构 --- 主机、客户端、服务器三层架构
- JSON-RPC 2.0协议 --- MCP的底层通信协议
- 从零构建MCP服务器 --- 纯Python实现,理解协议本质
- 从零构建MCP客户端 --- 连接、初始化、工具调用全流程
- 官方SDK快速开发 --- FastMCP的简化用法
- 与DeepSeek集成 --- 打造真正的AI Agent
MCP正在成为AI Agent时代的基础设施。就像HTTP协议统一了Web通信、USB-C统一了设备连接一样,MCP正在统一AI应用与外部世界的交互标准。随着Claude、ChatGPT、VS Code等主流平台全面支持MCP,以及社区贡献的数千个MCP服务器生态的成熟,AI从"问答工具"到"数字助手"的进化正在加速。
未来已来,只是分布不均。掌握MCP,就是掌握AI Agent时代的"接口"能力。
实战指南 :本文配套完整代码示例。更多DeepSeek大模型与AI Agent实战教程参见:DeepSeek 实战指南系列