第5章 MCP服务器开发基础

48 阅读5分钟

第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)——包括参数验证、错误处理、性能优化等实战技巧。


延伸阅读