【Claude】MCP 协议深度解析与自定义服务器开发 — 已解决
·
【Claude】MCP 协议深度解析与自定义服务器开发 — 已解决
适用版本:Claude Code v1.0.x 及以上、MCP Protocol v2024-11-05
受影响场景:自定义工具集成、外部数据源接入、企业内部 API 桥接
阅读时长:约 30 分钟
目录
1. 问题现象
1.1 典型问题表现
问题一:MCP 服务器连接失败
> 使用数据库工具查询数据
Error: MCP server "db-tools" not connected
# 配置了 MCP 服务器但无法连接
问题二:自定义 MCP 工具不出现
// .claude/mcp-servers.json
{
"my-tools": {
"command": "node",
"args": ["./mcp-server.js"]
}
}
// 但 Claude Code 中看不到自定义工具
问题三:MCP 工具调用超时
> 查询数据库
[Claude 调用 MCP 工具: query_db]
[等待...]
Error: Tool execution timeout (30s)
# MCP 工具执行时间过长
问题四:MCP 服务器开发不知道从何入手
开发者想要:
- 自定义企业内部 API 的 MCP 工具
- 集成 Jira/Confluence 的 MCP 服务器
- 自定义数据库查询工具
但不知道 MCP 协议格式和开发方式
问题五:MCP 服务器在 CI 中不稳定
# CI 环境中 MCP 服务器频繁断开
claude -p "使用 MCP 工具"
# Error: MCP connection lost
2. 原理深挖:MCP 协议架构
2.1 MCP 协议概述
Model Context Protocol (MCP) 是 Anthropic 提出的开放协议,允许外部工具服务器与 Claude(及兼容客户端)通信,提供自定义工具能力。
┌─────────────────────────────────────────────────┐
│ MCP 架构 │
├─────────────────────────────────────────────────┤
│ │
│ Claude Code (MCP Client) │
│ │ │
│ │ JSON-RPC 2.0 over stdio/SSE │
│ │ │
│ ├──→ MCP Server A (文件系统工具) │
│ ├──→ MCP Server B (数据库工具) │
│ ├──→ MCP Server C (企业 API 工具) │
│ └──→ MCP Server D (自定义工具) │
│ │
│ 每个服务器: │
│ - 独立进程 (stdio) 或 HTTP 服务 (SSE) │
│ - 通过 JSON-RPC 通信 │
│ - 提供工具 (tools) 和资源 (resources) │
│ │
└─────────────────────────────────────────────────┘
2.2 通信协议
传输方式 1: stdio (推荐)
Claude Code ←stdin/stdout→ MCP Server (子进程)
- 零网络开销
- 生命周期由 Claude Code 管理
- 适合本地工具
传输方式 2: SSE (HTTP)
Claude Code ←HTTP/SSE→ MCP Server (HTTP 服务)
- 支持远程服务器
- 需要网络配置
- 适合共享/远程工具
消息格式: JSON-RPC 2.0
请求: {"jsonrpc": "2.0", "id": 1, "method": "tools/list", "params": {}}
响应: {"jsonrpc": "2.0", "id": 1, "result": {"tools": [...]}}
通知: {"jsonrpc": "2.0", "method": "notification", "params": {...}}
2.3 MCP 生命周期
1. 初始化
Client → Server: initialize({protocolVersion, capabilities})
Server → Client: {protocolVersion, capabilities, serverInfo}
2. 工具发现
Client → Server: tools/list()
Server → Client: {tools: [{name, description, inputSchema}]}
3. 工具调用
Client → Server: tools/call({name, arguments})
Server → Client: {content: [{type, text}], isError}
4. 资源访问 (可选)
Client → Server: resources/list()
Server → Client: {resources: [{uri, name, description}]}
5. 关闭
Client 关闭 stdin → Server 检测到 EOF → 退出
2.4 工具定义格式
{
"name": "query_database",
"description": "执行 SQL 查询并返回结果",
"inputSchema": {
"type": "object",
"properties": {
"sql": {
"type": "string",
"description": "SQL 查询语句"
},
"database": {
"type": "string",
"description": "数据库名称",
"enum": ["prod", "staging", "dev"]
},
"limit": {
"type": "integer",
"description": "返回行数限制",
"default": 100
}
},
"required": ["sql", "database"]
}
}
2.5 Claude Code MCP 配置
// .claude/settings.json 或 ~/.claude/settings.json
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@anthropic-ai/mcp-filesystem", "/path/to/allowed/dir"]
},
"database": {
"command": "node",
"args": ["./mcp-servers/db-server.js"],
"env": {
"DB_HOST": "localhost",
"DB_PORT": "5432"
}
},
"remote-api": {
"url": "https://internal-company.com/mcp-server",
"headers": {
"Authorization": "Bearer ${MCP_AUTH_TOKEN}"
}
}
}
}
3. 根因分析:MCP 开发中的常见问题
3.1 根因一:协议理解不足
开发者不了解 MCP 的 JSON-RPC 消息格式,直接用 HTTP REST 风格开发,导致通信失败。
3.2 根因二:stdio 通信错误
stdio 模式下,MCP 服务器意外向 stdout 输出调试信息,干扰 JSON-RPC 消息。
3.3 根因三:工具 Schema 错误
inputSchema 不符合 JSON Schema 规范,Claude 无法正确调用工具。
3.4 根因四:超时处理缺失
长时间运行的工具没有超时处理,Claude Code 等待 30 秒后强制断开。
3.5 根因五:环境变量不传递
MCP 服务器需要的 API Key、数据库密码等环境变量未在配置中传递。
3.6 根因六:错误处理不完善
工具执行失败时返回格式不正确,Claude 无法理解错误原因。
4. 多方案解决:从开发到部署
4.1 方案一:Python MCP 服务器开发
#!/usr/bin/env python3
"""
自定义 MCP 服务器 — 企业数据库查询工具
使用 Anthropic 官方 MCP SDK
pip install mcp
"""
import json
import sys
import os
import asyncio
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import (
Tool, TextContent, ImageContent,
LoggingLevel
)
import logging
# 配置日志 (输出到 stderr,不干扰 stdout JSON-RPC)
logging.basicConfig(
stream=sys.stderr,
level=logging.INFO,
format='[MCP] %(asctime)s %(levelname)s %(message)s'
)
logger = logging.getLogger("db-mcp-server")
# 创建 MCP 服务器
server = Server("db-tools")
# 数据库连接 (示例)
DB_CONFIG = {
"host": os.environ.get("DB_HOST", "localhost"),
"port": int(os.environ.get("DB_PORT", "5432")),
"user": os.environ.get("DB_USER", "admin"),
"password": os.environ.get("DB_PASSWORD", ""),
}
# 工具定义
@server.list_tools()
async def list_tools() -> list[Tool]:
"""返回可用工具列表"""
return [
Tool(
name="query_database",
description="执行 SQL 查询并返回结果。仅支持 SELECT 语句。",
inputSchema={
"type": "object",
"properties": {
"sql": {
"type": "string",
"description": "SQL SELECT 查询语句"
},
"database": {
"type": "string",
"description": "数据库名称",
"enum": ["main", "analytics", "logs"],
"default": "main"
},
"limit": {
"type": "integer",
"description": "返回行数限制",
"default": 100,
"minimum": 1,
"maximum": 1000
}
},
"required": ["sql"]
}
),
Tool(
name="list_tables",
description="列出指定数据库中的所有表",
inputSchema={
"type": "object",
"properties": {
"database": {
"type": "string",
"enum": ["main", "analytics", "logs"],
"default": "main"
}
}
}
),
Tool(
name="describe_table",
description="显示表结构(列名、类型、注释)",
inputSchema={
"type": "object",
"properties": {
"table": {
"type": "string",
"description": "表名"
},
"database": {
"type": "string",
"enum": ["main", "analytics", "logs"],
"default": "main"
}
},
"required": ["table"]
}
)
]
# 工具调用处理
@server.call_tool()
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
"""处理工具调用"""
logger.info(f"Tool called: {name} with {arguments}")
try:
if name == "query_database":
return await handle_query(arguments)
elif name == "list_tables":
return await handle_list_tables(arguments)
elif name == "describe_table":
return await handle_describe_table(arguments)
else:
return [TextContent(
type="text",
text=f"错误: 未知工具 '{name}'"
)]
except Exception as e:
logger.error(f"Tool error: {e}")
return [TextContent(
type="text",
text=f"工具执行错误: {str(e)}"
)]
async def handle_query(args):
"""执行数据库查询"""
sql = args.get("sql", "")
database = args.get("database", "main")
limit = args.get("limit", 100)
# 安全检查: 只允许 SELECT
if not sql.strip().upper().startswith("SELECT"):
return [TextContent(
type="text",
text="错误: 仅支持 SELECT 查询"
)]
# 模拟查询 (实际使用 psycopg2/pymysql 等)
try:
# 模拟数据
results = [
{"id": 1, "name": "Alice", "email": "alice@example.com"},
{"id": 2, "name": "Bob", "email": "bob@example.com"},
]
# 格式化为表格
if results:
headers = list(results[0].keys())
table = "| " + " | ".join(headers) + " |\n"
table += "|" + "---|" * len(headers) + "\n"
for row in results[:limit]:
table += "| " + " | ".join(str(row.get(h, "")) for h in headers) + " |\n"
else:
table = "(无结果)"
return [TextContent(type="text", text=f"查询结果 ({database}):\n\n{table}")]
except Exception as e:
return [TextContent(type="text", text=f"查询失败: {e}")]
async def handle_list_tables(args):
"""列出表"""
database = args.get("database", "main")
tables = ["users", "orders", "products", "inventory"]
return [TextContent(
type="text",
text=f"数据库 {database} 的表:\n" + "\n".join(f" - {t}" for t in tables)
)]
async def handle_describe_table(args):
"""描述表结构"""
table = args.get("table", "")
# 模拟表结构
columns = [
{"name": "id", "type": "integer", "comment": "主键"},
{"name": "name", "type": "varchar(255)", "comment": "名称"},
{"name": "email", "type": "varchar(255)", "comment": "邮箱"},

{"name": "created_at", "type": "timestamp", "comment": "创建时间"},
]
desc = f"表 {table} 结构:\n\n"
desc += "| 列名 | 类型 | 说明 |\n|---|---|---|\n"
for col in columns:
desc += f"| {col['name']} | {col['type']} | {col['comment']} |\n"
return [TextContent(type="text", text=desc)]
# 主函数
async def main():
async with stdio_server() as (read_stream, write_stream):
await server.run(read_stream, write_stream)
if __name__ == "__main__":
asyncio.run(main())
4.2 方案二:TypeScript MCP 服务器开发
/**
* 自定义 MCP 服务器 — Jira 集成工具
*
* npm install @anthropic-ai/mcp-sdk
*/
import { Server } from "@anthropic-ai/mcp-sdk";
import { StdioServerTransport } from "@anthropic-ai/mcp-sdk";
import { Tool, TextContent } from "@anthropic-ai/mcp-sdk";
// 创建服务器
const server = new Server(
{ name: "jira-tools", version: "1.0.0" },
{ capabilities: { tools: {} } }
);
// Jira API 配置
const JIRA_BASE_URL = process.env.JIRA_BASE_URL || "https://company.atlassian.net";
const JIRA_TOKEN = process.env.JIRA_TOKEN || "";
// 工具列表
server.setRequestHandler("tools/list", async () => ({
tools: [
{
name: "search_issues",
description: "搜索 Jira 问题(JQL 查询)",
inputSchema: {
type: "object",
properties: {
jql: {
type: "string",
description: "JQL 查询语句,如 'project = PROJ AND status = Open'"
},
maxResults: {
type: "integer",
description: "最大返回数",
default: 50
}
},
required: ["jql"]
}
},
{
name: "get_issue",
description: "获取 Jira 问题详情",
inputSchema: {
type: "object",
properties: {
issueKey: {
type: "string",
description: "问题键,如 PROJ-123"
}
},
required: ["issueKey"]
}
},
{
name: "create_issue",
description: "创建 Jira 问题",
inputSchema: {
type: "object",
properties: {
project: { type: "string", description: "项目键" },
summary: { type: "string", description: "标题" },
description: { type: "string", description: "描述" },
issueType: {
type: "string",
enum: ["Bug", "Task", "Story", "Epic"],
description: "问题类型"
},
priority: {
type: "string",
enum: ["Highest", "High", "Medium", "Low", "Lowest"]
}
},
required: ["project", "summary", "issueType"]
}
}
]
}));
// 工具调用处理
server.setRequestHandler("tools/call", async (request) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case "search_issues":
return await searchIssues(args.jql, args.maxResults || 50);
case "get_issue":
return await getIssue(args.issueKey);
case "create_issue":
return await createIssue(args);
default:
return {
content: [{ type: "text", text: `未知工具: ${name}` }],
isError: true
};
}
} catch (error) {
return {
content: [{ type: "text", text: `错误: ${error.message}` }],
isError: true
};
}
});
// Jira API 调用
async function searchIssues(jql: string, maxResults: number) {
const response = await fetch(
`${JIRA_BASE_URL}/rest/api/2/search?jql=${encodeURIComponent(jql)}&maxResults=${maxResults}`,
{
headers: {
"Authorization": `Bearer ${JIRA_TOKEN}`,
"Accept": "application/json"
}
}
);
if (!response.ok) {
throw new Error(`Jira API: ${response.status} ${response.statusText}`);
}
const data = await response.json();
const issues = data.issues.map((issue: any) => ({
key: issue.key,
summary: issue.fields.summary,
status: issue.fields.status.name,
priority: issue.fields.priority.name,
assignee: issue.fields.assignee?.displayName || "未分配"
}));
const text = `找到 ${data.total} 个问题:\n\n` +
issues.map(i => ` ${i.key}: ${i.summary} [${i.status}] (${i.priority})`).join("\n");
return {
content: [{ type: "text", text }]
};
}
async function getIssue(issueKey: string) {
const response = await fetch(
`${JIRA_BASE_URL}/rest/api/2/issue/${issueKey}`,
{
headers: {
"Authorization": `Bearer ${JIRA_TOKEN}`,
"Accept": "application/json"
}
}
);
if (!response.ok) {
throw new Error(`Jira API: ${response.status}`);
}
const issue = await response.json();
const text = `问题: ${issue.key}
标题: ${issue.fields.summary}
状态: ${issue.fields.status.name}
类型: ${issue.fields.issuetype.name}
优先级: ${issue.fields.priority?.name || "无"}
报告人: ${issue.fields.reporter?.displayName || "未知"}
经办人: ${issue.fields.assignee?.displayName || "未分配"}
创建时间: ${issue.fields.created}
描述:
${issue.fields.description || "(无描述)"}`;
return { content: [{ type: "text", text }] };
}
async function createIssue(args: any) {
const body = {
fields: {
project: { key: args.project },
summary: args.summary,
description: args.description || "",
issuetype: { name: args.issueType },
priority: args.priority ? { name: args.priority } : undefined
}
};
const response = await fetch(
`${JIRA_BASE_URL}/rest/api/2/issue`,
{
method: "POST",
headers: {
"Authorization": `Bearer ${JIRA_TOKEN}`,
"Content-Type": "application/json"
},
body: JSON.stringify(body)
}
);
if (!response.ok) {
const error = await response.text();
throw new Error(`创建失败: ${error}`);
}
const data = await response.json();
return {
content: [{ type: "text", text: `✓ 问题已创建: ${data.key}\nURL: ${JIRA_BASE_URL}/browse/${data.key}` }]
};
}
// 启动服务器
const transport = new StdioServerTransport();
server.connect(transport);
console.error("[MCP] Jira tools server started"); // stderr, 不干扰 stdout
4.3 方案三:Claude Code 中配置 MCP 服务器
// .claude/settings.json — MCP 服务器配置
{
"mcpServers": {
// 本地 stdio 服务器
"db-tools": {
"command": "python3",
"args": ["./mcp-servers/db-server.py"],
"env": {
"DB_HOST": "localhost",
"DB_PORT": "5432",
"DB_USER": "admin",
"DB_PASSWORD": "${DB_PASSWORD}" // 从环境变量读取
}
},
// Node.js 服务器
"jira-tools": {
"command": "node",
"args": ["./mcp-servers/jira-server.js"],
"env": {
"JIRA_BASE_URL": "https://company.atlassian.net",
"JIRA_TOKEN": "${JIRA_TOKEN}"
}
},
// 远程 SSE 服务器
"remote-api": {
"url": "https://internal.company.com/mcp",
"headers": {
"Authorization": "Bearer ${MCP_AUTH_TOKEN}"
}
},
// 使用 npx 运行的官方服务器
"filesystem": {
"command": "npx",
"args": ["-y", "@anthropic-ai/mcp-filesystem", "/Users/zhubo/projects"]
}
}
}
4.4 方案四:MCP 服务器调试
#!/bin/bash
# debug-mcp-server.sh — 调试 MCP 服务器
# 1. 直接测试 MCP 服务器 (模拟 Claude Code 的请求)
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}' | \
python3 ./mcp-servers/db-server.py 2>/dev/null
# 预期响应: {"jsonrpc":"2.0","id":1,"result":{...}}
# 2. 列出工具
echo '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' | \
python3 ./mcp-servers/db-server.py 2>/dev/null
# 3. 调用工具
echo '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"list_tables","arguments":{"database":"main"}}}' | \
python3 ./mcp-servers/db-server.py 2>/dev/null
# 4. 查看 stderr 日志
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{}}}' | \
python3 ./mcp-servers/db-server.py 2>&1 >/dev/null
# stderr 输出: [MCP] 2024-01-15 INFO ...
# 5. 在 Claude Code 中检查 MCP 连接状态
# 交互模式中输入:
# /mcp
# 应显示所有已连接的 MCP 服务器和可用工具
4.5 方案五:错误处理与超时
"""
MCP 服务器的健壮错误处理和超时管理
"""
import asyncio
import signal
import sys
from functools import wraps
def with_timeout(seconds=30):
"""工具调用超时装饰器"""
def decorator(func):
@wraps(func)
async def wrapper(*args, **kwargs):
try:
return await asyncio.wait_for(
func(*args, **kwargs),
timeout=seconds
)
except asyncio.TimeoutError:
return [TextContent(
type="text",
text=f"错误: 工具执行超时 ({seconds}秒)"
)]
return wrapper
return decorator
def with_error_handling(func):
"""错误处理装饰器"""
@wraps(func)
async def wrapper(*args, **kwargs):
try:
return await func(*args, **kwargs)
except ConnectionError as e:
return [TextContent(type="text", text=f"连接错误: {e}")]
except ValueError as e:
return [TextContent(type="text", text=f"参数错误: {e}")]
except PermissionError as e:
return [TextContent(type="text", text=f"权限错误: {e}")]
except Exception as e:
logger.error(f"Unexpected error: {e}", exc_info=True)
return [TextContent(type="text", text=f"内部错误: {e}")]
return wrapper
# 使用装饰器
@server.call_tool()
@with_error_handling
async def call_tool(name: str, arguments: dict):
if name == "query_database":
return await with_timeout(30)(handle_query)(arguments)
# ...
# 优雅关闭
def setup_graceful_shutdown():
"""设置优雅关闭"""
def signal_handler(signum, frame):
logger.info("收到关闭信号,正在清理...")
# 清理数据库连接等资源
sys.exit(0)
signal.signal(signal.SIGTERM, signal_handler)
signal.signal(signal.SIGINT, signal_handler)
4.6 方案六:MCP 服务器模板
#!/usr/bin/env python3
"""
通用 MCP 服务器模板
复制此文件并修改工具定义即可快速创建新的 MCP 服务器
"""
import asyncio
import sys
import logging
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent
# 配置 stderr 日志
logging.basicConfig(
stream=sys.stderr,
level=logging.INFO,
format='[MCP:%(name)s] %(levelname)s %(message)s'
)
logger = logging.getLogger("template")
server = Server("template-server")
# ============================================
# 工具定义 (修改此处)
# ============================================
TOOLS = [
Tool(
name="example_tool",
description="示例工具描述",
inputSchema={
"type": "object",
"properties": {
"param1": {
"type": "string",
"description": "参数1说明"
}
},
"required": ["param1"]
}
),
]
# ============================================
# 工具处理 (修改此处)
# ============================================
async def handle_example_tool(args: dict) -> list[TextContent]:
param1 = args.get("param1", "")
result = f"处理结果: {param1}"
return [TextContent(type="text", text=result)]
# 工具路由
TOOL_HANDLERS = {
"example_tool": handle_example_tool,
}
# ============================================
# MCP 协议实现 (通常不需要修改)
# ============================================
@server.list_tools()
async def list_tools() -> list[Tool]:
return TOOLS
@server.call_tool()
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
logger.info(f"调用: {name}({arguments})")
handler = TOOL_HANDLERS.get(name)
if handler:
try:
return await handler(arguments)
except Exception as e:
logger.error(f"工具错误: {e}", exc_info=True)
return [TextContent(type="text", text=f"错误: {e}")]
else:
return [TextContent(type="text", text=f"未知工具: {name}")]
async def main():
async with stdio_server() as (read_stream, write_stream):
await server.run(read_stream, write_stream)
if __name__ == "__main__":
asyncio.run(main())
5. 验证回归:MCP 服务器验证
5.1 MCP 服务器测试脚本
#!/bin/bash
# test-mcp-server.sh — 测试 MCP 服务器
SERVER_CMD="$1"
if [ -z "$SERVER_CMD" ]; then
echo "用法: $0 'python3 ./mcp-servers/db-server.py'"
exit 1
fi
echo "=== MCP 服务器测试 ==="
# 测试 1: 初始化
echo -n "测试初始化... "
INIT_RESPONSE=$(echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}' | $SERVER_CMD 2>/dev/null)
if echo "$INIT_RESPONSE" | python3 -c "import sys,json; d=json.load(sys.stdin); assert 'result' in d" 2>/dev/null; then
echo "✓"
else
echo "✗ 初始化失败"
echo " 响应: $INIT_RESPONSE"
exit 1
fi
# 测试 2: 工具列表
echo -n "测试工具列表... "
LIST_RESPONSE=$(echo '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' | $SERVER_CMD 2>/dev/null)
TOOL_COUNT=$(echo "$LIST_RESPONSE" | python3 -c "
import sys,json
d=json.load(sys.stdin)
print(len(d.get('result',{}).get('tools',[])))
" 2>/dev/null)
if [ "$TOOL_COUNT" -gt 0 ]; then
echo "✓ ($TOOL_COUNT 个工具)"
else
echo "✗ 无工具"
exit 1
fi
# 测试 3: 工具调用
echo -n "测试工具调用... "
CALL_RESPONSE=$(echo '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"list_tables","arguments":{"database":"main"}}}' | $SERVER_CMD 2>/dev/null)
if echo "$CALL_RESPONSE" | python3 -c "import sys,json; d=json.load(sys.stdin); assert 'result' in d" 2>/dev/null; then
echo "✓"
else
echo "✗ 工具调用失败"
exit 1
fi
echo ""
echo "=== 所有测试通过 ==="
5.2 验证清单
| # | 验证项 | 预期 | 方法 |
|---|---|---|---|
| 1 | 服务器启动 | 无错误 | 直接运行 |
| 2 | 初始化响应 | 有 result | JSON-RPC 测试 |
| 3 | 工具列表 | tools > 0 | tools/list |
| 4 | 工具调用 | 有结果 | tools/call |
| 5 | 错误处理 | 返回错误文本 | 无效参数 |
| 6 | stdout 纯净 | 仅 JSON-RPC | 检查无其他输出 |
| 7 | stderr 日志 | 有日志 | 2>&1 >/dev/null |
| 8 | Claude Code 集成 | 工具可用 | /mcp 命令 |
6. 避坑最佳实践
6.1 MCP 开发原则
原则 1: stdout 纯净 — 只输出 JSON-RPC,日志到 stderr
原则 2: Schema 规范 — 严格遵循 JSON Schema
原则 3: 超时处理 — 工具执行设超时
原则 4: 错误友好 — 返回可读的错误文本
原则 5: 环境变量 — 密钥通过 env 传递
原则 6: 优雅关闭 — 处理 SIGTERM/SIGINT
原则 7: 异步处理 — 使用 async/await
原则 8: 最小权限 — 工具只暴露必要能力
6.2 常见陷阱
| # | 陷阱 | 后果 | 解决 |
|---|---|---|---|
| 1 | stdout 输出日志 | JSON-RPC 解析失败 | 日志到 stderr |
| 2 | Schema 不规范 | Claude 调用失败 | 遵循 JSON Schema |
| 3 | 无超时 | Claude 等待 30s 后断开 | 工具设超时 |
| 4 | 密钥硬编码 | 安全风险 | 用环境变量 |
| 5 | 同步阻塞 | 响应慢 | 用 async/await |
| 6 | 不处理 EOF | 服务器不退出 | 检测 stdin 关闭 |
| 7 | 无错误处理 | Claude 收到异常 | 返回 TextContent |
| 8 | env 不传递 | 连接失败 | settings.json 配置 env |
7. 附录:MCP 协议速查表
7.1 JSON-RPC 方法
| 方法 | 方向 | 说明 |
|---|---|---|
initialize |
Client→Server | 初始化握手 |
tools/list |
Client→Server | 列出工具 |
tools/call |
Client→Server | 调用工具 |
resources/list |
Client→Server | 列出资源 |
resources/read |
Client→Server | 读取资源 |
notifications/initialized |
Client→Server | 初始化完成通知 |
7.2 工具定义模板
{
"name": "tool_name",
"description": "工具描述",
"inputSchema": {
"type": "object",
"properties": {
"param": {
"type": "string|integer|boolean|array|object",
"description": "参数说明",
"enum": ["option1", "option2"],
"default": "option1"
}
},
"required": ["param"]
}
}
7.3 配置位置
| 配置文件 | 范围 | 用途 |
|---|---|---|
~/.claude/settings.json |
全局 | 全局 MCP 服务器 |
.claude/settings.json |
项目 | 项目 MCP 服务器 |
.claude/settings.local.json |
个人 | 个人 MCP 配置 |
结语
MCP 协议是 Claude Code 扩展能力的核心机制。通过自定义 MCP 服务器,可以集成企业内部 API、数据库、外部服务,使 Claude Code 获得超越内置工具的能力。
核心要点回顾:
- stdio 通信:MCP 服务器通过 stdin/stdout 的 JSON-RPC 通信,stdout 必须纯净
- 工具定义:严格遵循 JSON Schema 规范定义 inputSchema
- 错误处理:工具失败时返回可读的 TextContent 错误文本
- 超时管理:长操作设超时,避免 Claude Code 30s 后强制断开
- 环境变量:密钥通过 settings.json 的 env 配置传递
- 调试方法:直接用 JSON-RPC 消息测试服务器,stderr 查看日志
- 模板复用:使用通用模板快速创建新 MCP 服务器
- Claude Code 集成:在 settings.json 的 mcpServers 中配置,用 /mcp 检查状态
更多推荐

所有评论(0)