第5章 MCP服务器开发基础
前言
从本章开始,您将进入MCP的实践世界。我们将学习如何构建自己的MCP服务器,这是成为MCP开发者的第一步。
5.1 MCP服务器的结构与组件
5.1.1 服务器的核心架构
graph TB
A["MCP服务器架构"] --> B["输入层"]
A --> C["业务逻辑层"]
A --> D["输出层"]
B --> B1["接收客户端请求"]
B --> B2["参数验证"]
B --> B3["权限检查"]
C --> C1["工具执行"]
C --> C2["资源管理"]
C --> C3["业务处理"]
D --> D1["结果序列化"]
D --> D2["错误处理"]
D --> D3["响应返回"]
C1 --> C1A["实现核心功能"]
C2 --> C2A["数据持久化"]
C3 --> C3A["业务规则"]
5.1.2 MCP服务器的生命周期
sequenceDiagram
participant Lifecycle as 生命周期
participant Init as 初始化
participant Listen as 监听
participant Handle as 处理
participant Close as 关闭
Lifecycle->>Init: 1. 创建服务器
Init->>Init: 加载配置<br/>初始化资源
Init-->>Listen: 完成
Listen->>Listen: 2. 启动监听
Listen->>Handle: 等待连接
Handle->>Handle: 3. 处理请求
Handle->>Handle: 执行工具/资源
Handle-->>Handle: 返回结果
Handle->>Handle: 循环处理
Listen->>Close: 4. 关闭信号
Close->>Close: 释放资源<br/>保存数据
Close-->>Lifecycle: 完成
5.1.3 核心组件详解
graph TB
A["MCP服务器核心组件"] --> B["传输层"]
A --> C["业务层"]
A --> D["工具层"]
A --> E["资源层"]
B --> B1["HTTP/WebSocket"]
B --> B2["stdio"]
B --> B3["自定义协议"]
C --> C1["事件处理"]
C --> C2["会话管理"]
C --> C3["错误恢复"]
D --> D1["工具注册"]
D --> D2["参数验证"]
D --> D3["执行引擎"]
E --> E1["资源列表"]
E --> E2["资源读取"]
E --> E3["资源订阅"]
关键组件说明:
| 组件 | 职责 | 示例 |
|---|---|---|
| 传输层 | 处理网络通信 | HTTP、WebSocket、stdio |
| 会话管理 | 维护客户端连接 | 连接池、状态跟踪 |
| 工具引擎 | 执行工具函数 | 参数校验、异步执行 |
| 资源管理 | 管理数据资源 | 缓存、版本控制 |
| 错误处理 | 异常恢复 | 日志、重试、降级 |
5.2 开发环境搭建
5.2.1 Python环境搭建(推荐入门)
系统要求:
- Python 3.8+
- pip或poetry包管理器
- Git版本控制
安装步骤:
# 1. 创建项目目录
mkdir my-mcp-server
cd my-mcp-server
# 2. 创建虚拟环境
python -m venv venv
# 3. 激活虚拟环境
# macOS/Linux
source venv/bin/activate
# Windows
venv\Scripts\activate
# 4. 安装MCP SDK
pip install anthropic
# 5. 创建基本项目结构
mkdir -p mcp_server/{tools,resources}
touch mcp_server/__init__.py
touch mcp_server/server.py
touch mcp_server/tools/__init__.py
touch mcp_server/resources/__init__.py
requirements.txt:
anthropic>=0.7.0
pydantic>=2.0.0
python-dotenv>=0.19.0
5.2.2 Node.js环境搭建(Web应用首选)
系统要求:
- Node.js 16+
- npm或yarn包管理器
安装步骤:
# 1. 创建项目
mkdir my-mcp-server
cd my-mcp-server
# 2. 初始化npm项目
npm init -y
# 3. 安装依赖
npm install @anthropic-ai/mcp typescript
npm install --save-dev @types/node ts-node
# 4. 创建配置文件
cat > tsconfig.json << 'EOF'
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"lib": ["ES2020"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true
}
}
EOF
# 5. 创建项目结构
mkdir -p src/{tools,resources}
touch src/server.ts
package.json:
{
"name": "my-mcp-server",
"version": "1.0.0",
"main": "dist/server.js",
"scripts": {
"build": "tsc",
"dev": "ts-node src/server.ts",
"start": "node dist/server.js"
},
"dependencies": {
"@anthropic-ai/mcp": "^0.1.0"
},
"devDependencies": {
"@types/node": "^20.0.0",
"typescript": "^5.0.0"
}
}
5.2.3 Go环境搭建(高性能场景)
系统要求:
- Go 1.18+
- go mod包管理
初始化:
# 1. 创建项目
mkdir my-mcp-server
cd my-mcp-server
# 2. 初始化模块
go mod init github.com/username/my-mcp-server
# 3. 添加依赖
go get github.com/anthropics/mcp-go
# 4. 创建项目结构
mkdir -p cmd/{server,client}
mkdir pkg/{tools,resources}
touch main.go
5.3 第一个MCP服务器:Hello World
5.3.1 最小化Python实现
# mcp_server/server.py
import asyncio
import json
from typing import Any
class SimpleMCPServer:
def __init__(self, name: str = "hello-world-server"):
self.name = name
self.tools = []
self.resources = []
def register_tool(self, tool_func):
"""注册工具"""
tool_def = {
"name": tool_func.__name__,
"description": tool_func.__doc__ or "",
"inputSchema": {
"type": "object",
"properties": {}
}
}
self.tools.append((tool_def, tool_func))
return tool_func
async def handle_initialize(self) -> dict:
"""处理initialize请求"""
return {
"protocolVersion": "2024-11-05",
"capabilities": {
"tools": {},
"resources": {}
},
"serverInfo": {
"name": self.name,
"version": "1.0.0"
}
}
async def handle_tools_list(self) -> dict:
"""列出所有工具"""
return {
"tools": [tool_def for tool_def, _ in self.tools]
}
async def handle_tool_call(self, name: str, arguments: dict) -> dict:
"""执行工具调用"""
for tool_def, tool_func in self.tools:
if tool_def["name"] == name:
try:
result = await tool_func(**arguments) if asyncio.iscoroutinefunction(tool_func) else tool_func(**arguments)
return {
"content": [
{
"type": "text",
"text": str(result)
}
]
}
except Exception as e:
return {
"content": [
{
"type": "text",
"text": f"Error: {str(e)}"
}
]
}
return {
"error": {
"code": -32601,
"message": f"Tool '{name}' not found"
}
}
async def process_message(self, message: str) -> str:
"""处理JSON-RPC消息"""
try:
request = json.loads(message)
method = request.get("method")
if method == "initialize":
response = await self.handle_initialize()
elif method == "tools/list":
response = await self.handle_tools_list()
elif method == "tools/call":
response = await self.handle_tool_call(
request["params"]["name"],
request["params"]["arguments"]
)
else:
response = {
"error": {
"code": -32601,
"message": "Method not found"
}
}
return json.dumps({
"jsonrpc": "2.0",
"id": request.get("id"),
"result": response
})
except Exception as e:
return json.dumps({
"jsonrpc": "2.0",
"error": {
"code": -32700,
"message": f"Parse error: {str(e)}"
}
})
# 主程序
server = SimpleMCPServer()
@server.register_tool
def say_hello(name: str = "World") -> str:
"""向用户问好"""
return f"Hello, {name}! Welcome to MCP."
@server.register_tool
def add_numbers(a: int, b: int) -> int:
"""加两个数字"""
return a + b
async def main():
"""服务器主循环"""
import sys
while True:
try:
# 从stdin读取JSON-RPC消息
line = await asyncio.get_event_loop().run_in_executor(
None, sys.stdin.readline
)
if not line:
break
# 处理消息
response = await server.process_message(line.strip())
# 输出响应
print(response, flush=True)
except KeyboardInterrupt:
break
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
if __name__ == "__main__":
asyncio.run(main())
5.3.2 最小化Node.js实现
// src/server.ts
import { MCPServer, Tool } from "@anthropic-ai/mcp";
const server = new MCPServer({
name: "hello-world-server",
version: "1.0.0"
});
// 注册工具
const helloTool: Tool = {
name: "say_hello",
description: "Greet someone",
inputSchema: {
type: "object",
properties: {
name: {
type: "string",
description: "Name to greet"
}
},
required: ["name"]
}
};
const addTool: Tool = {
name: "add_numbers",
description: "Add two numbers",
inputSchema: {
type: "object",
properties: {
a: { type: "number" },
b: { type: "number" }
},
required: ["a", "b"]
}
};
server.registerTool(helloTool, async (args) => {
const name = args.name as string || "World";
return `Hello, ${name}! Welcome to MCP.`;
});
server.registerTool(addTool, async (args) => {
const a = args.a as number;
const b = args.b as number;
return a + b;
});
// 启动服务器
server.start(process.stdin, process.stdout);
5.3.3 启动与调试
启动服务器(Python):
# 方式1:直接运行
python mcp_server/server.py
# 方式2:使用Claude Desktop
# 编辑 ~/.config/Claude/claude_desktop_config.json
# 添加:
# {
# "mcpServers": {
# "hello": {
# "command": "python",
# "args": ["-m", "mcp_server.server"]
# }
# }
# }
测试工具调用:
# test_server.py
import json
import subprocess
import asyncio
async def test_server():
# 启动服务器
process = subprocess.Popen(
["python", "mcp_server/server.py"],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
# 测试消息
requests = [
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {}
},
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/list",
"params": {}
},
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "say_hello",
"arguments": {"name": "Alice"}
}
}
]
# 发送请求
for request in requests:
process.stdin.write(json.dumps(request) + "\n")
process.stdin.flush()
# 读取响应
response = process.stdout.readline()
print(f"Response: {response}")
process.terminate()
asyncio.run(test_server())
5.4 MCP SDK的使用
5.4.1 SDK特性对比
graph TB
A["MCP SDK特性对比"] --> B["Python SDK"]
A --> C["Node.js SDK"]
A --> D["Go SDK"]
B --> B1["✅ 类型提示"]
B --> B2["✅ 易于学习"]
B --> B3["✅ 库丰富"]
B --> B4["✅ 同步/异步支持"]
C --> C1["✅ TypeScript优先"]
C --> C2["✅ 网络友好"]
C --> C3["✅ 事件驱动"]
C --> C4["✅ 天然异步"]
D --> D1["✅ 高性能"]
D --> D2["✅ 并发性好"]
D --> D3["✅ 部署简单"]
D --> D4["✅ 内存效率高"]
5.4.2 快速开发框架对比
Python官方SDK框架:
from anthropic.mcp import Server
from pydantic import BaseModel
class MyServer(Server):
def __init__(self):
super().__init__(name="my-server")
@self.tool()
def my_tool(self, arg1: str, arg2: int) -> str:
"""工具文档"""
return f"Result: {arg1} {arg2}"
# 运行
if __name__ == "__main__":
server = MyServer()
server.run()
Node.js官方SDK框架:
import { Server } from "@anthropic-ai/mcp";
const server = new Server({
name: "my-server"
});
server.tool("my_tool", {
description: "Tool documentation",
inputSchema: { ... }
}, async (args) => {
return `Result: ${args.arg1} ${args.arg2}`;
});
server.start();
5.5 项目结构最佳实践
5.5.1 推荐的项目布局
my-mcp-server/
├── src/ # 源代码
│ ├── __init__.py
│ ├── server.py # 主服务器
│ ├── tools/ # 工具实现
│ │ ├── __init__.py
│ │ ├── database.py
│ │ ├── api.py
│ │ └── utils.py
│ ├── resources/ # 资源实现
│ │ ├── __init__.py
│ │ └── config.py
│ └── models/ # 数据模型
│ ├── __init__.py
│ └── types.py
├── tests/ # 测试
│ ├── test_tools.py
│ ├── test_resources.py
│ └── test_server.py
├── config/ # 配置文件
│ ├── default.yaml
│ └── production.yaml
├── docs/ # 文档
│ ├── README.md
│ ├── INSTALLATION.md
│ └── USAGE.md
├── .env.example # 环境变量示例
├── requirements.txt # 依赖
├── setup.py # 安装脚本
├── pytest.ini # 测试配置
└── README.md
5.5.2 配置管理
# config.py
import os
from dotenv import load_dotenv
from dataclasses import dataclass
load_dotenv()
@dataclass
class Config:
"""服务器配置"""
DEBUG: bool = os.getenv("DEBUG", "False") == "True"
HOST: str = os.getenv("HOST", "0.0.0.0")
PORT: int = int(os.getenv("PORT", "3000"))
LOG_LEVEL: str = os.getenv("LOG_LEVEL", "INFO")
DATABASE_URL: str = os.getenv("DATABASE_URL", "sqlite:///app.db")
API_KEY: str = os.getenv("API_KEY", "")
@property
def is_production(self) -> bool:
return not self.DEBUG
config = Config()
本章总结
| 核心概念 | 关键点 |
|---|---|
| 服务器架构 | 输入层、业务层、输出层三层结构 |
| 生命周期 | 初始化→监听→处理→关闭 |
| 环境搭建 | Python/Node.js/Go三种选择 |
| Hello World | 最小化实现8工具注册到响应 |
| SDK框架 | 官方SDK提供快速开发能力 |
| 项目结构 | 模块化、配置分离、测试覆盖 |
常见问题
Q1: 初学者应该选择哪种语言? A: Python最容易上手,拥有最完整的文档和最大的社区。建议从Python开始。
Q2: 本地开发时如何调试MCP服务器? A: 使用日志打印、添加单元测试、使用Claude Desktop的调试功能,或用MCP CLI工具。
Q3: MCP服务器如何处理并发请求? A: Python可使用asyncio、Node.js天然异步、Go使用goroutine。都能很好地处理并发。
Q4: 服务器可以处理多个客户端连接吗? A: 是的。MCP服务器设计支持多客户端连接。需要在会话管理层处理。
Q5: 如何在生产环境中运行MCP服务器? A: 使用Docker容器化,配置进程管理器(systemd/supervisor),添加日志和监控。
实战建议
✅ 做好的做法
- 从simple开始,逐步增加复杂度
- 为每个工具编写单元测试
- 使用类型提示增加代码可维护性
- 添加详细的日志和错误处理
- 分离配置和代码
❌ 避免的做法
- 不要在一个文件中放置所有代码
- 不要忽视错误处理
- 不要硬编码配置信息
- 不要跳过单元测试
- 不要在生产环境直接运行Python脚本
下一章预告
第6章将深入讲解如何开发MCP工具(Tools)——包括参数验证、错误处理、性能优化等实战技巧。
延伸阅读
- Python SDK文档:github.com/anthropics/…
- Node.js SDK文档:github.com/anthropics/…
- MCP规范:spec.modelcontextprotocol.io/
- 官方示例:github.com/anthropics/…