MCP示例
·
解构 MCP 协议:从理论到实战的全维解码
https://blog.csdn.net/qq_42263280/article/details/148082862
参考文章,感谢作者。
MCP 服务端代码示例
# server.py
import httpx
from mcp.server.fastmcp import FastMCP
# 1. 创建MCP服务器实例
mcp = FastMCP("Demo")
# 2. 使用 @mcp.tool() 装饰器注册工具
@mcp.tool()
def calculate_bmi(weight_kg: float, height_m: float) -> float:
"""根据体重(千克)和身高(米)计算BMI"""
return weight_kg / (height_m ** 2)
@mcp.tool()
async def fetch_weather(city: str) -> str:
"""获取指定城市的当前天气信息"""
async with httpx.AsyncClient() as client:
# 注意:此处URL为示例,你需要替换为真实的天气API
response = await client.get(f"https://api.weather.com/{city}")
return response.text
# 3. 启动服务器
if __name__ == "__main__":
mcp.run()

客户端核心业务逻辑
# client.py (核心逻辑整合)
import os, json, asyncio
from typing import Optional
from contextlib import AsyncExitStack
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
from openai import AsyncOpenAI
class MCPClient:
def __init__(self):
self.session: Optional[ClientSession] = None
self.exit_stack = AsyncExitStack()
# 初始化OpenAI客户端,通过OpenRouter网关连接LLM
self.llm_client = AsyncOpenAI(
base_url="https://openrouter.ai/api/v1",
api_key=os.getenv("OPENROUTER_API_KEY"), # 从环境变量获取API Key
)
async def connect_to_server(self, server_script_path: str):
"""建立与MCP服务器的连接"""
server_params = StdioServerParameters(
command="python",
args=[server_script_path],
env=os.environ.copy()
)
# 建立stdio通信
stdio_transport = await self.exit_stack.enter_async_context(stdio_client(server_params))
self.stdio, self.write = stdio_transport
# 创建并初始化会话
self.session = await self.exit_stack.enter_async_context(ClientSession(self.stdio, self.write))
await self.session.initialize()
# 列出可用工具
tools = await self.session.list_tools()
print(f"Connected to server with tools: {[tool.name for tool in tools]}")
async def process_query(self, query: str) -> str:
"""处理用户查询的核心逻辑"""
messages = [{"role": "user", "content": query}]
final_text = []
while True:
# 1. 获取MCP工具列表并转换为OpenAI格式
mcp_tools = await self.session.list_tools()
openai_tools = [{
"type": "function",
"function": {
"name": tool.name,
"description": tool.description,
"parameters": tool.inputSchema # 复用MCP的参数定义
}
} for tool in mcp_tools]
# 2. 调用LLM,附带工具定义
response = await self.llm_client.chat.completions.create(
model="qwen/qwen-plus", # 通过OpenRouter指定具体模型
messages=messages,
tools=openai_tools,
tool_choice="auto"
)
message = response.choices[0].message
final_text.append(message.content or "")
# 3. 处理LLM返回的工具调用请求
if not message.tool_calls:
break # 没有工具调用,直接结束
for tool_call in message.tool_calls:
tool_name = tool_call.function.name
tool_args = json.loads(tool_call.function.arguments)
# 4. 通过MCP Server执行工具
result = await self.session.call_tool(tool_name, tool_args)
final_text.append(f"[Calling tool {tool_name} with args {tool_args}]")
# 5. 将工具调用和结果添加到消息历史,继续对话
messages.append({
"role": "assistant",
"tool_calls": [{
"id": tool_call.id,
"type": "function",
"function": {
"name": tool_name,
"arguments": json.dumps(tool_args)
}
}]
})
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": str(result.content)
})
return "\n".join(final_text)
async def shutdown(self):
"""清理资源"""
await self.exit_stack.aclose()



主程序:
# run.py
import asyncio
from client import MCPClient # 假设上面的客户端代码在 client.py
async def main():
client = MCPClient()
try:
# 连接到我们第一步编写的 server.py
await client.connect_to_server("server.py")
# 测试查询
response = await client.process_query("身高1.75米、体重70公斤的BMI是多少?")
print(response)
# response = await client.process_query("查询上海的天气")
# print(response)
finally:
await client.shutdown()
if __name__ == "__main__":
asyncio.run(main())

运行日志参考:
[1] 启动客户端...
[CLIENT] 初始化MCPClient实例
[CLIENT] 配置LLM客户端: base_url=https://openrouter.ai/api/v1, model=qwen/qwen-plus
[2] 连接到MCP Server...
[CLIENT] 执行 connect_to_server("server.py")
[CLIENT] 创建 StdioServerParameters: command="python", args=["server.py"]
[CLIENT] 通过 stdio_client 建立双向通信通道
[SERVER] 启动进程: python server.py
[SERVER] MCP Server "Demo" 已启动,等待连接...
[CLIENT] 创建 ClientSession 并初始化
[CLIENT] 发送 initialize 请求 (JSON-RPC 2.0)
[SERVER] 接收 initialize 请求
[SERVER] 返回 serverInfo: {name: "Demo", version: "1.0.0"}
[SERVER] 返回 capabilities: {tools: {listChanged: false}}
[CLIENT] 初始化成功,协议版本: 2024-11-05
[3] 发现可用工具...
[CLIENT] 发送 tools/list 请求
[SERVER] 接收 tools/list 请求
[SERVER] 返回已注册的工具列表:
- name: calculate_bmi
description: "根据体重(千克)和身高(米)计算BMI"
inputSchema: {weight_kg: number, height_m: number}
- name: fetch_weather
description: "获取指定城市的当前天气信息"
inputSchema: {city: string}
[CLIENT] 成功连接,可用工具: ['calculate_bmi', 'fetch_weather']
[4] 处理用户查询: "身高1.65米、体重65公斤的BMI是多少?"
[CLIENT] 进入 process_query 循环
[CLIENT] 构建初始 messages: [{"role": "user", "content": "身高1.65米、体重65公斤的BMI是多少?"}]
[5] 第1轮LLM调用...
[CLIENT] 调用 llm_client.chat.completions.create()
[CLIENT] 请求参数:
model: qwen/qwen-plus
messages: [{"role": "user", "content": "身高1.65米、体重65公斤的BMI是多少?"}]
tools: [
{
type: "function",
function: {
name: "calculate_bmi",
description: "根据体重(千克)和身高(米)计算BMI",
parameters: {
type: "object",
properties: {
weight_kg: {type: "number"},
height_m: {type: "number"}
},
required: ["weight_kg", "height_m"]
}
}
},
{
type: "function",
function: {
name: "fetch_weather",
description: "获取指定城市的当前天气信息",
parameters: {
type: "object",
properties: {
city: {type: "string"}
},
required: ["city"]
}
}
}
]
tool_choice: auto
[6] LLM返回响应...
[LLM] 分析用户意图: 需要计算BMI
[LLM] 决策: 调用 calculate_bmi 工具
[LLM] 生成 tool_call:
id: "call_abc123"
type: "function"
function: {
name: "calculate_bmi",
arguments: '{"weight_kg": 65.0, "height_m": 1.65}'
}
[CLIENT] 收到LLM响应,choices[0].message.tool_calls 不为空
[7] 执行工具调用...
[CLIENT] 遍历 tool_calls:
[CLIENT] 工具名称: calculate_bmi
[CLIENT] 解析参数: {"weight_kg": 65.0, "height_m": 1.65}
[CLIENT] 调用 session.call_tool("calculate_bmi", {"weight_kg": 65.0, "height_m": 1.65})
[CLIENT] 发送 tools/call 请求 (JSON-RPC 2.0)
[SERVER] 接收 tools/call 请求
[SERVER] 执行 calculate_bmi(weight_kg=65.0, height_m=1.65)
[SERVER] 计算: 65.0 / (1.65 ** 2) = 65.0 / 2.7225 = 23.875...
[SERVER] 返回执行结果: {
content: [{
type: "text",
text: "23.875..."
}],
isError: false
}
[CLIENT] 收到工具执行结果: 23.875...
[8] 更新对话历史...
[CLIENT] 添加 assistant 消息: {"role": "assistant", "tool_calls": [...]}
[CLIENT] 添加 tool 消息: {"role": "tool", "tool_call_id": "call_abc123", "content": "23.875..."}
[CLIENT] final_text 追加: "[Calling tool calculate_bmi with args {'weight_kg': 65.0, 'height_m': 1.65}]"
[9] 第2轮LLM调用(将工具结果交给LLM生成最终答案)...
[CLIENT] 调用 llm_client.chat.completions.create()
[CLIENT] 请求参数:
model: qwen/qwen-plus
messages: [
{"role": "user", "content": "身高1.65米、体重65公斤的BMI是多少?"},
{"role": "assistant", "tool_calls": [...]},
{"role": "tool", "tool_call_id": "call_abc123", "content": "23.875..."}
]
tools: [...] (同上)
[10] LLM生成最终回答...
[LLM] 基于工具结果生成自然语言回答
[LLM] 输出: "根据计算,您的BMI约为23.9。这个数值属于正常体重范围(18.5-24.9)。"
[CLIENT] 收到LLM响应,content 不为空
[CLIENT] final_text 追加: "根据计算,您的BMI约为23.9。这个数值属于正常体重范围(18.5-24.9)。"
[11] 循环结束,返回最终结果...
[CLIENT] 退出 while 循环(message.tool_calls 为空)
[CLIENT] 返回完整响应:
[Calling tool calculate_bmi with args {'weight_kg': 65.0, 'height_m': 1.65}]
根据计算,您的BMI约为23.9。这个数值属于正常体重范围(18.5-24.9)。
###############################################
JSON-RPC 通信细节(完整请求/响应)
MCP Client 与 Server 之间通过 JSON-RPC 2.0 格式通信,以下是关键交互的原始数据:
// CLIENT → SERVER (initialize)
{
"jsonrpc": "2.0",
"id": 0,
"method": "initialize",
"params": {
"protocolVersion": "2024-11-05",
"capabilities": {},
"clientInfo": {
"name": "mcp-client",
"version": "1.0.0"
}
}
}
// SERVER → CLIENT (initialize 响应)
{
"jsonrpc": "2.0",
"id": 0,
"result": {
"protocolVersion": "2024-11-05",
"capabilities": {
"tools": {
"listChanged": false
}
},
"serverInfo": {
"name": "Demo",
"version": "1.0.0"
}
}
}
工具列表请求/响应
// CLIENT → SERVER (tools/list)
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list",
"params": {}
}
// SERVER → CLIENT (tools/list 响应)
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"tools": [
{
"name": "calculate_bmi",
"description": "根据体重(千克)和身高(米)计算BMI",
"inputSchema": {
"type": "object",
"properties": {
"weight_kg": {"type": "number"},
"height_m": {"type": "number"}
},
"required": ["weight_kg", "height_m"]
}
},
{
"name": "fetch_weather",
"description": "获取指定城市的当前天气信息",
"inputSchema": {
"type": "object",
"properties": {
"city": {"type": "string"}
},
"required": ["city"]
}
}
]
}
}
工具调用请求/响应
// CLIENT → SERVER (tools/call)
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "calculate_bmi",
"arguments": {
"weight_kg": 65.0,
"height_m": 1.65
}
}
}
// SERVER → CLIENT (tools/call 响应)
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"content": [
{
"type": "text",
"text": "23.875..."
}
],
"isError": false
}
}
更多推荐



所有评论(0)