从零构建你的第一个 MCP Server:深入理解 AI Agent 工具调用机制
是 Anthropic 开发的开放协议,用于让 AI Agent 安全、标准化地调用外部工具。HTTP 传输的 MCP Server 需要服务发现机制。4. 代码实现:构建加法 MCP Server。5. 完整调用流程:解密 LLM 如何使用工具。从 "10+13" 推断 a=10, b=13。1. 前言:为什么需要 MCP Server。6.1 LLM 如何"知道"使用哪个工具?代码实现:构建加
📚 目录
-
前言:为什么需要 MCP Server
-
核心概念:MCP 协议与工具调用
-
系统架构:从用户到工具的完整路径
-
代码实现:构建加法 MCP Server
-
完整调用流程:解密 LLM 如何使用工具
-
关键洞察:LLM 推理与工具编排
-
最佳实践与常见问题
-
总结与展望
1. 前言:为什么需要 MCP Server
1.1 AI Agent 的局限性
大型语言模型(LLM)虽然强大,但存在固有局限:
|
局限 |
说明 |
示例 |
|---|---|---|
| 知识截止 |
训练数据有时间限制 |
无法获取实时股票价格 |
| 计算能力 |
推理过程中不做精确计算 |
9867 × 8734
可能计算错误 |
| 外部交互 |
无法主动访问外部系统 |
不能读取文件、调用 API |
| 工具操作 |
无法执行系统命令 |
不能运行 Python 代码、操作数据库 |
1.2 MCP:连接 AI 与工具的桥梁
MCP (Model Context Protocol) 是 Anthropic 开发的开放协议,用于让 AI Agent 安全、标准化地调用外部工具。
没有 MCP:
用户 → LLM → 文本响应 ❌ (无法执行操作)
有了 MCP:
用户 → LLM → 工具调用 → 执行 → 结果 → LLM → 响应 ✅
1.3 本文目标
通过构建一个最简单但完整的 MCP Server(加法服务),我们将深入理解:
- ✅ MCP 协议的工作原理
- ✅ HTTP 传输与 JSON-RPC 2.0 规范
- ✅ Claude Code 如何发现和调用工具
-
✅ LLM 如何"知道"该用哪个工具 ⭐ 核心问题
- ✅ 多步骤工具编排的实现机制
2. 核心概念:MCP 协议与工具调用
2.1 MCP 协议栈
┌──────────────────────────────────────┐
│ 应用层 (Application) │
│ Claude Code / 其他 AI Agent │
└─────────────┬────────────────────────┘
│
┌─────────────┴────────────────────────┐
│ 协议层 (Protocol) │
│ JSON-RPC 2.0 + MCP 扩展 │
│ • initialize │
│ • tools/list │
│ • tools/call │
└─────────────┬────────────────────────┘
│
┌─────────────┴────────────────────────┐
│ 传输层 (Transport) │
│ HTTP (本文) / stdio / WebSocket │
└─────────────┬────────────────────────┘
│
┌─────────────┴────────────────────────┐
│ 工具层 (Tools) │
│ Python / JavaScript / Bash / ... │
└──────────────────────────────────────┘
2.2 核心方法
|
方法 |
作用 |
调用时机 |
|---|---|---|
| initialize |
握手协商 |
Claude Code 启动时 |
| tools/list |
列出所有工具 |
初始化后立即调用 ⭐ |
| tools/call |
执行具体工具 |
LLM 决策需要使用工具时 |
2.3 工具定义规范
{
"name": "add", # 工具唯一标识符
"description": "Add two numbers together", # 功能描述 ⭐ LLM 看这个
"inputSchema": { # JSON Schema 定义参数
"type": "object",
"properties": {
"a": {"type": "number", "description": "第一个数"},
"b": {"type": "number", "description": "第二个数"}
},
"required": ["a", "b"]
}
}
关键点:
description是 LLM 理解工具用途的唯一信息
inputSchema定义了参数结构,LLM 根据它生成参数
- 描述越清晰,LLM 越容易正确使用
3. 系统架构:从用户到工具的完整路径
3.1 整体架构
┌─────────────────────────────────────────────────────────────────┐
│ 用户交互层 │
│ 👤 Terminal / Shell │
│ $ claude "add 10+13+12" │
└────────────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Claude Code CLI │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 会话管理 │ │ 配置加载 │ │ 权限控制 │ │
│ │ Session │ │ .mcp.json │ │ Permission │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└────────────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Agent Loop 循环 │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ 接收请求 → LLM推理 → 工具调用 → 结果处理 → 生成响应 │ │
│ └────────────────────────────────────────────────────────┘ │
└───────┬─────────────────────────────────┬───────────────────────┘
│ │
│ │
▼ ▼
┌──────────────────┐ ┌──────────────────────────────┐
│ LLM 推理层 │ │ MCP 协议层 │
│ │ │ │
│ ┌────────────┐ │ │ ┌────────────────────────┐ │
│ │Claude API │ │ │ │ HTTP Transport │ │
│ │🧠 Sonnet │ │◄───────────┤ │ JSON-RPC 2.0 │ │
│ │ 4.5 │ │ 工具响应 │ │ │ │
│ └────────────┘ │ │ │ ┌──────────────────┐ │ │
│ │ │ │ │ SSE EventStream │ │ │
│ • 分析用户意图 │ │ │ │ /sse endpoint │ │ │
│ • 决定工具调用 │ │ │ │ 握手协商 │ │ │
│ • 整合返回结果 │ │ │ └──────────────────┘ │ │
│ │ 工具调用 │ │ │ │
│ ├───────────►│ │ POST / │ │
│ │ │ │ 调用工具 │ │
└──────────────────┘ │ └────────────────────────┘ │
└────────┬─────────────────────┘
│
▼
┌───────────────────────┐
│ add-server MCP │
│ 🔧 HTTP Server │
├───────────────────────┤
│ │
│ Flask Web Server │
│ • Host: 0.0.0.0 │
│ • Port: 8080 │
│ • CORS: Enabled │
│ │
│ MCP 端点: │
│ ┌─────────────────┐ │
│ │ GET /sse │ │
│ │ POST / │ │
│ │ GET /health │ │
│ └─────────────────┘ │
│ │
│ 提供工具: │
│ ┌─────────────────┐ │
│ │ 📐 add(a, b) │ │
│ │ │ │
│ │ 输入: │ │
│ │ • a: number │ │
│ │ • b: number │ │
│ │ │ │
│ │ 输出: │ │
│ │ result = a + b │ │
│ └─────────────────┘ │
│ │
│ 实现文件: │
│ add_mcp_server.py │
└───────────────────────┘
│
▼
┌────────────────────────┐
│ Python Runtime │
│ 执行加法运算 │
└────────────────────────┘
3.2 关键组件职责
|
组件 |
职责 |
关键点 |
|---|---|---|
| Claude Code |
会话管理、配置加载 |
读取 |
| LLM (Sonnet 4.5) |
理解意图、决策工具调用 |
⭐ 不直接看代码,只看工具描述 |
| MCP 协议层 |
标准化通信 |
JSON-RPC 2.0 + HTTP/SSE |
| add-server |
实现具体工具 |
Flask 服务器 + Python 逻辑 |
4. 代码实现:构建加法 MCP Server
4.1 项目结构
add-server/
├── add_mcp_server_http.py # 主服务器代码
├── requirements.txt # 依赖: Flask, flask-cors
└── README.md # 说明文档
4.2 核心代码解析
4.2.1 服务器初始化
from flask import Flask, request, jsonify, Response
from flask_cors import CORS
app = Flask(__name__)
CORS(app) # 允许跨域(Claude Code 可能从不同域访问)
# 服务器元信息
SERVER_INFO = {
"name": "add-server",
"version": "1.0.0"
}
# 工具定义 ⭐ 这是 LLM 看到的唯一信息
TOOLS = [
{
"name": "add",
"description": "Add two numbers together and return the result",
"inputSchema": {
"type": "object",
"properties": {
"a": {"type": "number", "description": "The first number"},
"b": {"type": "number", "description": "The second number"}
},
"required": ["a", "b"]
}
}
]
关键点:
TOOLS列表定义了所有可用工具
description必须清晰,LLM 靠它理解工具用途
inputSchema使用 JSON Schema 标准
4.2.2 服务发现端点(SSE)
@app.route('/sse', methods=['GET'])
def sse_endpoint():
"""
SSE 端点 - 告诉客户端 JSON-RPC 端点的 URL
工作流程:
1. Claude Code 启动时访问 /sse
2. 服务器返回 endpoint 事件,包含 JSON-RPC URL
3. 客户端后续所有请求发送到该 URL
"""
def generate():
endpoint_data = json.dumps({"url": "http://localhost:8080"})
yieldf"event: endpoint\n"
yieldf"data: {endpoint_data}\n\n"
return Response(
generate(),
mimetype='text/event-stream',
headers={
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
}
)
为什么需要 SSE?
- HTTP 传输的 MCP Server 需要服务发现机制
- SSE 允许服务器主动推送端点信息
-
客户端只需知道
/sse即可动态发现 JSON-RPC 端点
4.2.3 主请求处理器
@app.route('/', methods=['POST'])
def handle_request():
message = request.get_json()
msg_id = message.get("id")
method = message.get("method")
params = message.get("params", {})
# ============================================================
# 方法 1: initialize - 握手
# ============================================================
if method == "initialize":
return jsonify({
"jsonrpc": "2.0",
"id": msg_id,
"result": {
"protocolVersion": "2024-11-05",
"capabilities": {"tools": {}}, # 声明支持工具
"serverInfo": SERVER_INFO
}
})
# ============================================================
# 方法 2: tools/list - 列出工具 ⭐ 关键方法
# ============================================================
elif method == "tools/list":
return jsonify({
"jsonrpc": "2.0",
"id": msg_id,
"result": {"tools": TOOLS} # 返回工具定义
})
# ============================================================
# 方法 3: tools/call - 执行工具
# ============================================================
elif method == "tools/call":
tool_name = params.get("name")
arguments = params.get("arguments", {})
if tool_name == "add":
a = arguments.get("a")
b = arguments.get("b")
result = float(a) + float(b)
return jsonify({
"jsonrpc": "2.0",
"id": msg_id,
"result": {
"content": [
{
"type": "text",
"text": f"The sum of {a} and {b} is {result}"
}
]
}
})
else:
# 工具未找到
return jsonify({
"jsonrpc": "2.0",
"id": msg_id,
"error": {
"code": -32601,
"message": f"Tool not found: {tool_name}"
}
}), 404
代码亮点:
- ✅ 标准 JSON-RPC 2.0 格式
- ✅ 清晰的错误处理
- ✅ 结构化的响应格式
4.2.4 健康检查(可选但推荐)
@app.route('/health', methods=['GET'])
def health_check():
"""简单的健康检查,便于监控和调试"""
return jsonify({
"status": "healthy",
"server": SERVER_INFO
})
4.3 启动服务器
if __name__ == '__main__':
print("🚀 Starting Add MCP Server")
print(f"📍 URL: http://localhost:8080")
print(f"🔧 Available tools: add")
app.run(host='0.0.0.0', port=8080, debug=True)
运行命令:
# 安装依赖
pip install flask flask-cors
# 启动服务器
python3 add_mcp_server_http.py
5. 完整调用流程:解密 LLM 如何使用工具
5.1 配置 Claude Code
创建或编辑 ~/.mcp.json:
{
"mcpServers": {
"add-server": {
"type": "http",
"url": "http://localhost:8080"
}
}
}
5.2 启动时的初始化流程
┌─────────────────────────────────────────────────────────────────┐
│ Claude Code 启动流程 │
└─────────────────────────────────────────────────────────────────┘
1️⃣ 读取配置文件 (.mcp.json)
↓
发现 add-server: {"type": "http", "url": "http://localhost:8080"}
2️⃣ 建立连接 - 服务发现 (SSE)
↓
GET http://localhost:8080/sse
←─────────────────────────────────
event: endpoint
data: {"url": "http://localhost:8080"}
3️⃣ 初始化连接 (initialize)
↓
POST http://localhost:8080/
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize"
}
←─────────────────────────────────
{
"result": {
"protocolVersion": "2024-11-05",
"capabilities": {"tools": {}},
"serverInfo": {
"name": "add-server",
"version": "1.0.0"
}
}
}
4️⃣ 获取工具列表 (tools/list) ⭐ 关键步骤
↓
POST http://localhost:8080/
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/list"
}
←─────────────────────────────────
{
"result": {
"tools": [
{
"name": "add",
"description": "Add two numbers together and return the result",
"inputSchema": {
"type": "object",
"properties": {
"a": {"type": "number", "description": "The first number"},
"b": {"type": "number", "description": "The second number"}
},
"required": ["a", "b"]
}
}
]
}
}
5️⃣ 工具信息传递给 LLM ⭐⭐⭐ 核心机制
↓
Claude Code 将工具信息包含在系统提示中发送给 Claude API
System Prompt (简化版):
"""
You have access to the following tools:
Tool: add
Description: Add two numbers together and return the result
Parameters:
- a (number): The first number
- b (number): The second number
When you need to add numbers, call this tool by outputting:
<tool_call>
<name>add</name>
<arguments>
{"a": 10, "b": 13}
</arguments>
</tool_call>
"""
关键理解:
-
LLM 从未见过 MCP Server 的代码
-
LLM 只看到
tools/list返回的工具描述 - 工具描述就是 LLM 的"使用说明书"
5.3 用户请求的执行流程
场景:用户输入 "add 10+13+12"
┌─────────────────────────────────────────────────────────────────┐
│ 用户请求执行流程 │
└─────────────────────────────────────────────────────────────────┘
6️⃣ 用户发送请求
↓
User: "add 10+13+12"
7️⃣ LLM 推理决策 ⭐⭐⭐ 最关键的步骤
↓
Claude API 的思考过程:
输入:
- 用户消息: "add 10+13+12"
- 可用工具: add(a, b)
- 工具描述: "Add two numbers together"
推理:
"用户要计算 10+13+12 的和
我有一个 add 工具,但它只能加两个数
所以我需要调用两次:
- 第一次: add(10, 13) → 得到 23
- 第二次: add(23, 12) → 得到最终结果"
决策:
调用 add 工具两次(串行)
8️⃣ Claude Code 执行工具调用 (第一次)
↓
POST http://localhost:8080/
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "add",
"arguments": {"a": 10, "b": 13}
}
}
←─────────────────────────────────
{
"result": {
"content": [
{"type": "text", "text": "The sum of 10 and 13 is 23.0"}
]
}
}
9️⃣ Claude Code 执行工具调用 (第二次)
↓
LLM 收到第一次结果后,继续推理:
"现在我知道 10+13=23,接下来计算 23+12"
POST http://localhost:8080/
{
"jsonrpc": "2.0",
"id": 4,
"method": "tools/call",
"params": {
"name": "add",
"arguments": {"a": 23.0, "b": 12}
}
}
←─────────────────────────────────
{
"result": {
"content": [
{"type": "text", "text": "The sum of 23.0 and 12 is 35.0"}
]
}
}
🔟 LLM 整合结果并生成最终响应
↓
Claude API 收到两次工具调用结果后:
"根据工具调用结果:
- 第一步: 10 + 13 = 23
- 第二步: 23 + 12 = 35
因此,10 + 13 + 12 = 35"
生成用户友好的响应:
"## 计算结果
我使用 add 工具进行了计算:
**步骤 1**: 10 + 13 = **23**
**步骤 2**: 23 + 12 = **35**
**最终答案**: 10 + 13 + 12 = **35** ✓"
1️⃣1️⃣ 结果返回给用户
↓
User 看到完整的计算过程和结果
6. 关键洞察:LLM 推理与工具编排
6.1 LLM 如何"知道"使用哪个工具?
这是最关键的问题。答案分为三个层次:
层次 1:工具描述是唯一依据
# ❌ LLM 看不到这些
def add(a, b):
"""这个函数计算两个数的和""" # 代码注释
return a + b
# ✅ LLM 只看到这个
{
"name": "add",
"description": "Add two numbers together and return the result", # ⭐
"inputSchema": {...}
}
实验验证:
# 测试 1: 模糊描述
{
"name": "add",
"description": "Does something with numbers", # ❌ 太模糊
}
# 结果: LLM 不确定何时使用
# 测试 2: 清晰描述
{
"name": "add",
"description": "Add two numbers together and return the sum", # ✅ 清晰
}
# 结果: LLM 正确使用
层次 2:上下文匹配算法
LLM 内部的决策过程(简化模型):
输入: "add 10+13+12"
步骤 1: 理解用户意图
→ "用户想要计算数字之和"
步骤 2: 遍历所有可用工具
tools = [
{"name": "add", "description": "Add two numbers together"},
{"name": "multiply", "description": "Multiply two numbers"},
...
]
步骤 3: 相关性评分
for tool in tools:
score = similarity(user_intent, tool.description)
结果:
add: 0.95 ⭐ 最匹配
multiply: 0.15
...
步骤 4: 参数生成
根据 inputSchema 和上下文生成参数
schema = {"a": number, "b": number}
context = "10+13+12"
推理: "需要分两步执行"
call_1: add(10, 13)
call_2: add(result_1, 12)
层次 3:多工具编排策略
LLM 具备规划能力:
场景: 用户要求 "计算 (10+5) × 3"
可用工具: add(a, b), multiply(a, b)
LLM 的执行计划:
1.add(10, 5) → result1 = 15
2. multiply(result1, 3) → result2 = 45
3. 返回 result2
关键能力:
✅ 识别依赖关系 (步骤 2 依赖步骤 1)
✅ 中间变量传递 (result1)
✅ 正确的执行顺序
6.2 为什么 LLM 能做到这些?
|
能力 |
来源 |
示例 |
|---|---|---|
| 语义理解 |
预训练语料库 |
"add" 和 "sum" 语义相近 |
| 指令遵循 |
RLHF 微调 |
理解工具调用格式 |
| 规划能力 |
Chain-of-Thought |
多步骤推理 |
| 参数推断 |
Schema 理解 + 上下文 |
从 "10+13" 推断 a=10, b=13 |
6.3 实验:改变描述的影响
# 实验对照组
EXPERIMENT_TOOLS = [
# 版本 A: 精确描述
{
"name": "add",
"description": "Add two numbers together and return their sum"
},
# 版本 B: 模糊描述
{
"name": "add",
"description": "Process numbers"# ❌ 太模糊
},
# 版本 C: 误导性描述
{
"name": "add",
"description": "Multiply two numbers"# ❌ 描述错误
}
]
# 测试结果
用户输入: "What is 5 + 3?"
版本 A: ✅ 正确调用 add(5, 3)
版本 B: ❌ 不确定,可能不调用或调用错误工具
版本 C: ❌ 可能调用其他工具或产生困惑
结论:工具描述的质量直接决定 LLM 的使用效果。
7. 最佳实践与常见问题
7.1 工具描述最佳实践
✅ 好的描述
{
"name": "send_email",
"description": """
Send an email to a recipient with a subject and body.
Use this when the user wants to:
- Send an email
- Email someone
- Compose and send a message
Note: Requires valid email address format.
"""
}
特点:
- ✅ 清晰的功能说明
- ✅ 列出使用场景
- ✅ 说明重要约束
❌ 差的描述
{
"name": "send_email",
"description": "Sends email" # ❌ 太简短,信息不足
}
7.2 参数 Schema 设计
✅ 完善的 Schema
{
"inputSchema": {
"type": "object",
"properties": {
"to": {
"type": "string",
"description": "Recipient email address (e.g., user@example.com)",
"pattern": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"
},
"subject": {
"type": "string",
"description": "Email subject line",
"minLength": 1,
"maxLength": 200
},
"body": {
"type": "string",
"description": "Email body content (plain text or HTML)"
}
},
"required": ["to", "subject", "body"]
}
}
特点:
- ✅ 详细的字段描述
- ✅ 数据验证规则
- ✅ 示例格式
7.3 错误处理
✅ 友好的错误消息
try:
result = perform_action(params)
return {
"content": [{"type": "text", "text": f"Success: {result}"}]
}
except ValueError as e:
return {
"content": [
{
"type": "text",
"text": f"Error: Invalid input - {str(e)}\n\n"
f"Please provide valid numbers."
}
],
"isError": True# 标记为错误响应
}
7.4 常见问题
Q1: LLM 没有调用我的工具?
可能原因:
-
工具描述不清晰 → 改进
description - 用户意图不明确 → 提示用户更明确的表达
- 有更匹配的其他工具 → 检查工具冲突
调试方法:
# 查看 Claude Code 日志
claude --debug "test my tool"
# 手动测试工具
curl -X POST http://localhost:8080/ \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list"
}'
Q2: 工具调用参数错误?
可能原因:
inputSchema定义不清晰
- 参数描述缺失
- 类型定义错误
解决方案:
# ✅ 添加更多上下文
"properties": {
"amount": {
"type": "number",
"description": "Amount in USD (e.g., 10.50 for $10.50)", # ⭐ 示例
"minimum": 0,
"maximum": 10000
}
}
Q3: 如何支持复杂返回类型?
# 支持多种内容类型
return {
"content": [
{"type": "text", "text": "Here are the results:"},
{"type": "image", "data": base64_image, "mimeType": "image/png"},
{"type": "resource", "uri": "file:///path/to/file"}
]
}
8. 总结与展望
8.1 核心要点回顾
|
概念 |
关键理解 |
|---|---|
| MCP 协议 |
标准化 AI 与工具的通信协议 |
| 工具描述 |
LLM 理解工具的唯一依据 ⭐ |
| JSON-RPC 2.0 |
底层通信格式 |
| 工具编排 |
LLM 具备多步骤规划能力 |
| 参数推断 |
LLM 从上下文推断工具参数 |
8.2 实现 MCP Server 的三个层次
层次 1: 最小实现 (本文示例)
├─ 单个工具
├─ 基本错误处理
└─ HTTP 传输
层次 2: 生产级实现
├─ 多个工具
├─ 完善的验证
├─ 日志和监控
├─ 认证授权
└─ 性能优化
层次 3: 企业级实现
├─ 工具发现与注册
├─ 版本管理
├─ 分布式部署
├─ 工具组合与链式调用
└─ 安全沙箱
8.3 从加法到复杂工具
渐进式学习路径:
1. add(a, b) → 理解基本原理
↓
2. calculator(expr) → 解析复杂输入
↓
3. database_query() → 外部系统集成
↓
4. code_execution() → 安全执行代码
↓
5. workflow_engine() → 多工具编排
🌟 生态系统
9. 实战练习
练习 1: 扩展加法服务器
在 add-server 基础上添加:
-
✅
subtract(a, b)- 减法 -
✅
multiply(a, b)- 乘法 -
✅
divide(a, b)- 除法(处理除零)
练习 2: 构建文件操作 MCP Server
实现:
-
✅
read_file(path)- 读取文件 -
✅
write_file(path, content)- 写入文件 -
✅
list_directory(path)- 列出目录
练习 3: API 集成 MCP Server
实现:
-
✅
get_weather(city)- 获取天气 -
✅
search_github(query)- 搜索仓库 -
✅
translate_text(text, target_lang)- 翻译
10. 资源与参考
官方文档
-
MCP 协议规范
-
Claude Code 文档
-
JSON-RPC 2.0 规范
示例代码
-
本文完整代码
-
MCP Python SDK
-
社区 MCP Server 集合
社区
-
MCP Discord
-
Claude Code 论坛
附录:完整代码清单
A.1 add_mcp_server_http.py
#!/usr/bin/env python3
"""
完整的 MCP Server 实现 - 加法服务
详细注释版本
"""
import json
from flask import Flask, request, jsonify, Response
from flask_cors import CORS
app = Flask(__name__)
CORS(app)
SERVER_INFO = {
"name": "add-server",
"version": "1.0.0"
}
TOOLS = [
{
"name": "add",
"description": "Add two numbers together and return the result",
"inputSchema": {
"type": "object",
"properties": {
"a": {"type": "number", "description": "The first number"},
"b": {"type": "number", "description": "The second number"}
},
"required": ["a", "b"]
}
}
]
@app.route('/sse', methods=['GET'])
def sse_endpoint():
def generate():
endpoint_data = json.dumps({"url": "http://localhost:8080"})
yieldf"event: endpoint\n"
yieldf"data: {endpoint_data}\n\n"
return Response(
generate(),
mimetype='text/event-stream',
headers={
'Cache-Control': 'no-cache',
'X-Accel-Buffering': 'no',
'Connection': 'keep-alive'
}
)
@app.route('/', methods=['POST'])
def handle_request():
try:
message = request.get_json()
ifnot message:
return jsonify({
"jsonrpc": "2.0",
"id": None,
"error": {"code": -32700, "message": "Parse error"}
}), 400
msg_id = message.get("id")
method = message.get("method")
params = message.get("params", {})
if method == "initialize":
return jsonify({
"jsonrpc": "2.0",
"id": msg_id,
"result": {
"protocolVersion": "2024-11-05",
"capabilities": {"tools": {}},
"serverInfo": SERVER_INFO
}
})
elif method == "tools/list":
return jsonify({
"jsonrpc": "2.0",
"id": msg_id,
"result": {"tools": TOOLS}
})
elif method == "tools/call":
tool_name = params.get("name")
arguments = params.get("arguments", {})
if tool_name == "add":
try:
a = arguments.get("a")
b = arguments.get("b")
if a isNoneor b isNone:
return jsonify({
"jsonrpc": "2.0",
"id": msg_id,
"error": {
"code": -32602,
"message": "Both 'a' and 'b' required"
}
}), 400
result = float(a) + float(b)
return jsonify({
"jsonrpc": "2.0",
"id": msg_id,
"result": {
"content": [
{
"type": "text",
"text": f"The sum of {a} and {b} is {result}"
}
]
}
})
except (ValueError, TypeError) as e:
return jsonify({
"jsonrpc": "2.0",
"id": msg_id,
"error": {
"code": -32602,
"message": f"Invalid parameters: {str(e)}"
}
}), 400
else:
return jsonify({
"jsonrpc": "2.0",
"id": msg_id,
"error": {
"code": -32601,
"message": f"Tool not found: {tool_name}"
}
}), 404
else:
return jsonify({
"jsonrpc": "2.0",
"id": msg_id,
"error": {
"code": -32601,
"message": f"Method not found: {method}"
}
}), 404
except Exception as e:
return jsonify({
"jsonrpc": "2.0",
"id": None,
"error": {
"code": -32603,
"message": f"Internal error: {str(e)}"
}
}), 500
@app.route('/health', methods=['GET'])
def health_check():
return jsonify({"status": "healthy", "server": SERVER_INFO})
if __name__ == '__main__':
print("=" * 70)
print("🚀 Starting Add MCP Server")
print("=" * 70)
print(f"📍 URL: http://localhost:8080")
print(f"📋 Server: {SERVER_INFO['name']} v{SERVER_INFO['version']}")
print(f"🔧 Available tools: {', '.join([t['name'] for t in TOOLS])}")
print("=" * 70)
print("\n🔗 Endpoints:")
print(" GET /health - Health check")
print(" GET /sse - Service discovery")
print(" POST / - JSON-RPC requests")
print("\n⏳ Starting server...\n")
app.run(host='0.0.0.0', port=8080, debug=True)
A.2 requirements.txt
Flask==3.0.0
flask-cors==4.0.0
A.3 测试脚本
#!/bin/bash
# test_mcp_server.sh
echo"🧪 Testing MCP Server..."
echo"\n1️⃣ Health Check:"
curl -s http://localhost:8080/health | jq
echo"\n2️⃣ Initialize:"
curl -s -X POST http://localhost:8080/ \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize"}' | jq
echo"\n3️⃣ Tools List:"
curl -s -X POST http://localhost:8080/ \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' | jq
echo"\n4️⃣ Call Add Tool:"
curl -s -X POST http://localhost:8080/ \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"add","arguments":{"a":10,"b":13}}}' | jq
更多推荐

所有评论(0)