<!-- title: MCP服务器TypeScript与Python双轨开发实战 -->

# MCP服务器TypeScript与Python双轨开发实战

## 概述

本教程面向希望为Claude Code构建自定义MCP（Model Context Protocol）服务器的开发者。你将学会用 **TypeScript SDK** 和 **Python FastMCP** 两种方案，从零开发一个功能完整的MCP服务器，涵盖Tools、Resources、Prompts三大能力原语，并掌握stdio与HTTP两种传输方式的配置部署。适合有基础编程经验、想让Claude Code连接自己工具链的开发者。

## 前置条件

| 项目 | 要求 |
|------|------|
| Claude Code | 已安装并配置（`npm install -g @anthropic-ai/claude-code`） |
| Node.js | v20+（TypeScript方案） |
| Python | 3.10+（Python方案） |
| 基础知识 | TypeScript或Python、JSON配置、命令行操作 |
| 操作系统 | macOS / Linux / Windows（WSL2） |

---

## 第一章：MCP协议核心概念

### 1.1 什么是MCP

Model Context Protocol（MCP）是Anthropic推出的开放协议，标准化了AI应用与外部工具之间的通信方式。其核心思想类似USB-C——一个通用接口连接所有设备。

MCP采用 **Client-Server架构**，Claude Code作为Client，你开发的工具服务作为Server。通信基于JSON-RPC 2.0协议。

### 1.2 三大能力原语

MCP服务器可以暴露三种类型的 capabilities：

```
┌─────────────────────────────────────────────────────┐
│                  MCP Server Capabilities             │
├──────────────┬──────────────┬───────────────────────┤
│   Tools      │  Resources   │      Prompts          │
├──────────────┼──────────────┼───────────────────────┤
│ 可执行函数    │ 只读数据源    │ 可复用的提示模板       │
│ 有副作用      │ 无副作用      │ 参数化生成消息         │
│ 如：查询数据库 │ 如：读取文件  │ 如：代码审查模板       │
│ 发送邮件      │ 获取配置      │ 文档生成模板           │
└──────────────┴──────────────┴───────────────────────┘
```

- **Tools（工具）**：LLM可以调用的函数，接受参数并返回结果。适合执行操作、查询API、处理数据。
- **Resources（资源）**：向LLM暴露的结构化数据，类似文件系统。Client可以按URI读取。
- **Prompts（提示）**：预定义的提示模板，支持参数化。帮助用户快速执行常见任务。

### 1.3 传输方式

MCP支持两种传输方式：

| 传输方式 | 适用场景 | 特点 |
|----------|----------|------|
| **stdio** | 本地命令行工具 | 进程间管道通信，无需网络端口 |
| **HTTP Streamable** | 远程服务、多Client | 基于HTTP POST + SSE，支持无状态部署 |

### 1.4 整体架构

```mermaid
graph TB
    subgraph Client["Claude Code (MCP Client)"]
        A[用户请求] --> B[LLM推理]
        B --> C{需要外部工具?}
        C -->|是| D[MCP Client层]
        C -->|否| E[直接回复]
    end

    subgraph Transport["传输层"]
        F[stdio管道]
        G[HTTP Streamable]
    end

    subgraph Server["MCP Server"]
        H[Tools<br/>可执行函数]
        I[Resources<br/>只读数据]
        J[Prompts<br/>提示模板]
    end

    D --> F
    D --> G
    F --> H
    F --> I
    F --> J
    G --> H
    G --> I
    G --> J
    H --> K[数据库/API/文件系统]
    I --> K
```

---

## 第二章：TypeScript方案——从零构建MCP服务器

### 2.1 项目初始化

```bash
# 创建项目目录
mkdir my-mcp-server && cd my-mcp-server

# 初始化Node.js项目
npm init -y

# 安装依赖
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node

# 创建TypeScript配置
cat > tsconfig.json << 'EOF'
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "./build",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "declaration": true
  },
  "include": ["src/**/*"]
}
EOF
```

### 2.2 最小可运行服务器

创建 `src/index.ts`：

```typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

// 创建服务器实例
const server = new McpServer({
  name: "my-tools-server",
  version: "1.0.0",
});

// 注册一个Tool
server.tool(
  "greet",                              // 工具名称
  "向指定用户发送问候",                    // 工具描述（LLM根据此决定是否调用）
  { name: z.string().describe("用户名") }, // 参数Schema（Zod定义）
  async ({ name }) => {                   // 处理函数
    return {
      content: [{ type: "text", text: `你好，${name}！欢迎使用MCP服务器。` }],
    };
  }
);

// 启动服务器
async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error("MCP服务器已启动（stdio模式）");
}

main().catch(console.error);
```

> **关键说明**：`console.error` 而非 `console.log`——stdio模式下，stdout被MCP协议占用作为通信管道，任何写入stdout的内容都会破坏协议。调试信息必须用stderr。

### 2.3 配置package.json

```json
{
  "name": "my-mcp-server",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "build": "tsc",
    "start": "node build/index.js"
  },
  "bin": {
    "my-mcp-server": "./build/index.js"
  }
}
```

构建并测试：

```bash
npm run build
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}' | node build/index.js
```

**预期输出**（格式化后）：
```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2025-03-26",
    "capabilities": { "tools": {} },
    "serverInfo": { "name": "my-tools-server", "version": "1.0.0" }
  }
}
```

### 2.4 实战：构建一个代码分析服务器

下面构建一个更实用的服务器，包含Tools、Resources和Prompts三种能力。

创建 `src/code-analyzer/index.ts`：

```typescript
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import * as fs from "fs/promises";
import * as path from "path";

const server = new McpServer({
  name: "code-analyzer",
  version: "1.0.0",
});

// ============ Tools ============

// Tool 1: 统计代码行数
server.tool(
  "count_lines",
  "统计指定目录下源代码文件的行数，按文件类型分类汇总",
  {
    directory: z.string().describe("要分析的目录路径"),
    extensions: z.array(z.string()).optional()
      .describe("文件扩展名过滤，如 ['ts', 'py']，默认全部"),
  },
  async ({ directory, extensions }) => {
    try {
      const stats: Record<string, { files: number; lines: number }> = {};
      
      async function walk(dir: string) {
        const entries = await fs.readdir(dir, { withFileTypes: true });
        for (const entry of entries) {
          if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
          const fullPath = path.join(dir, entry.name);
          if (entry.isDirectory()) {
            await walk(fullPath);
          } else {
            const ext = path.extname(entry.name).slice(1);
            if (extensions && extensions.length > 0 && !extensions.includes(ext)) continue;
            const content = await fs.readFile(fullPath, "utf-8");
            const lineCount = content.split("\n").length;
            if (!stats[ext]) stats[ext] = { files: 0, lines: 0 };
            stats[ext].files++;
            stats[ext].lines += lineCount;
          }
        }
      }
      
      await walk(directory);
      
      const summary = Object.entries(stats)
        .map(([ext, s]) => `.${ext}: ${s.files} 文件, ${s.lines} 行`)
        .join("\n");
      
      return {
        content: [{ type: "text", text: `代码统计结果：\n${summary}` }],
      };
    } catch (error: any) {
      return {
        content: [{ type: "text", text: `错误: ${error.message}` }],
        isError: true,
      };
    }
  }
);

// Tool 2: 搜索代码内容
server.tool(
  "search_code",
  "在指定目录中搜索包含关键词的代码文件",
  {
    directory: z.string().describe("搜索目录"),
    keyword: z.string().describe("搜索关键词"),
    max_results: z.number().optional().default(20).describe("最大结果数"),
  },
  async ({ directory, keyword, max_results }) => {
    const results: string[] = [];
    
    async function search(dir: string) {
      if (results.length >= max_results) return;
      const entries = await fs.readdir(dir, { withFileTypes: true });
      for (const entry of entries) {
        if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
        const fullPath = path.join(dir, entry.name);
        if (entry.isDirectory()) {
          await search(fullPath);
        } else {
          try {
            const content = await fs.readFile(fullPath, "utf-8");
            const lines = content.split("\n");
            lines.forEach((line, i) => {
              if (line.toLowerCase().includes(keyword.toLowerCase()) && results.length < max_results) {
                results.push(`${fullPath}:${i + 1}: ${line.trim()}`);
              }
            });
          } catch { /* 跳过二进制文件 */ }
        }
      }
    }
    
    await search(directory);
    
    return {
      content: [{
        type: "text",
        text: results.length > 0 
          ? `找到 ${results.length} 个匹配：\n${results.join("\n")}`
          : `未找到包含 "${keyword}" 的代码`,
      }],
    };
  }
);

// ============ Resources ============

// Resource: 项目结构
server.resource(
  new ResourceTemplate("project://structure/{path}", { list: undefined }),
  async (uri, { path: dirPath }) => {
    async function buildTree(dir: string, prefix = ""): Promise<string> {
      const entries = await fs.readdir(dir, { withFileTypes: true });
      let result = "";
      const filtered = entries.filter(e => !e.name.startsWith(".") && e.name !== "node_modules");
      for (let i = 0; i < filtered.length; i++) {
        const entry = filtered[i];
        const isLast = i === filtered.length - 1;
        result += `${prefix}${isLast ? "└── " : "├── "}${entry.name}\n`;
        if (entry.isDirectory()) {
          const subPath = path.join(dir, entry.name);
          result += await buildTree(subPath, `${prefix}${isLast ? "    " : "│   "}`);
        }
      }
      return result;
    }
    
    const tree = await buildTree(dirPath as string);
    return {
      contents: [{
        uri: uri.href,
        text: tree,
      }],
    };
  }
);

// ============ Prompts ============

// Prompt: 代码审查模板
server.prompt(
  "code_review",
  "生成代码审查提示模板",
  { 
    file_path: z.string().describe("要审查的文件路径"),
    focus: z.enum(["security", "performance", "readability", "all"])
      .default("all").describe("审查重点"),
  },
  async ({ file_path, focus }) => {
    const focusInstructions: Record<string, string> = {
      security: "重点关注安全漏洞、输入验证、权限控制",
      performance: "重点关注性能瓶颈、算法效率、内存使用",
      readability: "重点关注命名规范、代码结构、注释质量",
      all: "全面审查安全性、性能、可读性和最佳实践",
    };
    
    return {
      messages: [{
        role: "user",
        content: {
          type: "text",
          text: `请对以下文件进行代码审查。\n\n文件: ${file_path}\n审查重点: ${focusInstructions[focus]}\n\n请给出具体的改进建议和评分。`,
        },
      }],
    };
  }
);

// 启动
async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error("Code Analyzer MCP Server 已启动");
}

main().catch(console.error);
```

### 2.5 注册到Claude Code

**方式一：命令行注册（推荐）**

```bash
# stdio模式——最常用的本地开发方式
claude mcp add code-analyzer -- node /path/to/my-mcp-server/build/code-analyzer/index.js

# 限定作用域（仅在当前项目生效）
claude mcp add code-analyzer -s project -- node /path/to/my-mcp-server/build/code-analyzer/index.js

# 限定作用域（仅在用户级别生效）
claude mcp add code-analyzer -s user -- node /path/to/my-mcp-server/build/code-analyzer/index.js
```

**方式二：JSON配置文件**

项目级别 `.mcp.json`（放在项目根目录）：

```json
{
  "mcpServers": {
    "code-analyzer": {
      "type": "stdio",
      "command": "node",
      "args": ["/absolute/path/to/my-mcp-server/build/code-analyzer/index.js"],
      "env": {}
    }
  }
}
```

用户级别 `~/.claude.json`：

```json
{
  "mcpServers": {
    "code-analyzer": {
      "type": "stdio",
      "command": "node",
      "args": ["/path/to/my-mcp-server/build/code-analyzer/index.js"]
    }
  }
}
```

**方式三：带环境变量**

```bash
claude mcp add-json code-analyzer '{
  "type": "stdio",
  "command": "node",
  "args": ["./build/code-analyzer/index.js"],
  "env": {
    "ANALYZER_MAX_DEPTH": "10",
    "ANALYZER_EXCLUDE": "node_modules,.git,dist"
  }
}'
```

### 2.6 验证与调试

```bash
# 列出已注册的MCP服务器
claude mcp list

# 查看特定服务器详情
claude mcp get code-analyzer

# 启动Claude Code并测试
claude
> 请用count_lines工具统计当前项目的代码行数
> 用search_code搜索包含"export"的代码
```

**调试技巧**——在服务器代码中加入日志：

```typescript
import * as fs from "fs";

// 写入调试日志到文件（不影响stdio通信）
function debugLog(message: string) {
  fs.appendFileSync("/tmp/mcp-debug.log", `${new Date().toISOString()} ${message}\n`);
}

// 在Tool handler中使用
server.tool("my_tool", "描述", { /* schema */ }, async (params) => {
  debugLog(`收到调用: ${JSON.stringify(params)}`);
  // ... 业务逻辑
  debugLog(`返回结果: ${result}`);
  return { content: [{ type: "text", text: result }] };
});
```

---

## 第三章：Python方案——FastMCP极速开发

### 3.1 FastMCP简介

FastMCP是MCP的官方Python SDK，以装饰器为核心的API让服务器开发极为简洁。对于数据科学、API对接、运维脚本等场景，Python方案往往更高效。

### 3.2 项目初始化

```bash
mkdir my-fastmcp-server && cd my-fastmcp-server

# 创建虚拟环境
python -m venv .venv
source .venv/bin/activate  # Windows: .venv\Scripts\activate

# 安装FastMCP
pip install fastmcp httpx

# 验证安装
fastmcp --version
```

### 3.3 最小可运行服务器

创建 `server.py`：

```python
from fastmcp import FastMCP

mcp = FastMCP("my-python-tools")

@mcp.tool
def add(a: int, b: int) -> int:
    """两数相加"""
    return a + b

@mcp.tool
def word_count(text: str) -> dict:
    """统计文本的词数和字符数"""
    words = len(text.split())
    chars = len(text)
    chars_no_space = len(text.replace(" ", ""))
    return {
        "words": words,
        "characters": chars,
        "characters_no_space": chars_no_space,
    }

if __name__ == "__main__":
    mcp.run()  # 默认stdio模式
```

**FastMCP的魔法**：函数签名中的类型注解自动生成参数Schema，docstring自动成为工具描述。无需手写Zod Schema！

运行测试：

```bash
# 方式一：直接运行
python server.py

# 方式二：用fastmcp CLI
fastmcp run server.py

# 方式三：开发模式（带热重载 + Inspector UI）
fastmcp dev server.py
```

### 3.4 实战：构建一个API聚合服务器

创建 `api_hub_server.py`：

```python
import httpx
import json
from datetime import datetime
from fastmcp import FastMCP

mcp = FastMCP(
    "api-hub",
    instructions="这是一个API聚合服务器，提供天气查询、IP查询和汇率转换功能。"
)

# ============ Tools ============

@mcp.tool
async def get_weather(city: str) -> str:
    """查询指定城市的当前天气信息
    
    Args:
        city: 城市名称（中文或英文），如"北京"或"Beijing"
    """
    async with httpx.AsyncClient(timeout=10) as client:
        # 使用wttr.in免费天气API
        resp = await client.get(
            f"https://wttr.in/{city}",
            params={"format": "j1"}
        )
        if resp.status_code != 200:
            return f"查询失败：HTTP {resp.status_code}"
        
        data = resp.json()
        current = data["current_condition"][0]
        
        return json.dumps({
            "城市": city,
            "温度": f"{current['temp_C']}°C",
            "体感温度": f"{current['FeelsLikeC']}°C",
            "湿度": f"{current['humidity']}%",
            "天气描述": current["weatherDesc"][0]["value"],
            "风速": f"{current['windspeedKmph']} km/h",
            "能见度": f"{current['visibility']} km",
        }, ensure_ascii=False, indent=2)


@mcp.tool
async def get_ip_info(ip: str = "") -> str:
    """查询IP地址的地理位置信息
    
    Args:
        ip: 要查询的IP地址，留空查询当前出口IP
    """
    async with httpx.AsyncClient(timeout=10) as client:
        url = f"https://ipapi.co/{ip}/json/" if ip else "https://ipapi.co/json/"
        resp = await client.get(url)
        if resp.status_code != 200:
            return f"查询失败：HTTP {resp.status_code}"
        
        data = resp.json()
        return json.dumps({
            "IP": data.get("ip"),
            "城市": data.get("city"),
            "地区": data.get("region"),
            "国家": data.get("country_name"),
            "时区": data.get("timezone"),
            "ISP": data.get("org"),
            "经纬度": f"{data.get('latitude')}, {data.get('longitude')}",
        }, ensure_ascii=False, indent=2)


@mcp.tool
async def convert_currency(
    amount: float, 
    from_currency: str = "USD", 
    to_currency: str = "CNY"
) -> str:
    """货币汇率转换
    
    Args:
        amount: 金额
        from_currency: 源货币代码，如USD、EUR、CNY
        to_currency: 目标货币代码
    """
    async with httpx.AsyncClient(timeout=10) as client:
        resp = await client.get(
            f"https://open.er-api.com/v6/latest/{from_currency.upper()}"
        )
        if resp.status_code != 200:
            return f"查询失败：HTTP {resp.status_code}"
        
        rates = resp.json()["rates"]
        target = to_currency.upper()
        
        if target not in rates:
            available = ", ".join(sorted(rates.keys())[:20])
            return f"不支持的货币: {target}。可用货币: {available}..."
        
        result = amount * rates[target]
        return f"{amount} {from_currency.upper()} = {result:.2f} {target}"


# ============ Resources ============

@mcp.resource("config://api-hub/status")
def get_server_status() -> str:
    """服务器状态资源"""
    return json.dumps({
        "server": "api-hub",
        "version": "1.0.0",
        "status": "running",
        "uptime_check": datetime.now().isoformat(),
        "available_tools": ["get_weather", "get_ip_info", "convert_currency"],
    }, ensure_ascii=False, indent=2)


@mcp.resource("config://api-hub/supported-currencies")
def get_supported_currencies() -> str:
    """支持的货币列表"""
    return json.dumps({
        "major_currencies": {
            "USD": "美元",
            "EUR": "欧元",
            "GBP": "英镑",
            "JPY": "日元",
            "CNY": "人民币",
            "KRW": "韩元",
            "HKD": "港币",
        },
        "total": "150+",
    }, ensure_ascii=False, indent=2)


# ============ Prompts ============

@mcp.prompt
def travel_planner(destination: str, budget_usd: float = 1000) -> list:
    """旅行规划提示模板
    
    Args:
        destination: 目的地城市
        budget_usd: 预算（美元）
    """
    return [
        {
            "role": "user",
            "content": {
                "type": "text",
                "text": (
                    f"我要去{destination}旅行，预算{budget_usd}美元。\n\n"
                    "请帮我：\n"
                    "1. 查询目的地当前天气\n"
                    "2. 将预算转换为当地货币\n"
                    "3. 根据天气和预算给出旅行建议\n"
                ),
            },
        }
    ]


@mcp.prompt
def currency_comparison(amount: float = 100) -> list:
    """多货币汇率对比提示模板"""
    currencies = ["USD", "EUR", "GBP", "JPY", "CNY"]
    currency_list = "、".join(currencies)
    
    return [
        {
            "role": "user",
            "content": {
                "type": "text",
                "text": (
                    f"请将 {amount} USD 分别转换为 {currency_list}，\n"
                    "并对比各汇率，给出分析。"
                ),
            },
        }
    ]


if __name__ == "__main__":
    mcp.run()
```

### 3.5 注册到Claude Code

```bash
# stdio模式
claude mcp add api-hub -- /path/to/my-fastmcp-server/.venv/bin/python /path/to/my-fastmcp-server/api_hub_server.py

# 或使用fastmcp命令
claude mcp add api-hub -- fastmcp run /path/to/my-fastmcp-server/api_hub_server.py

# 验证
claude mcp list
```

也可以用 `.mcp.json` 配置：

```json
{
  "mcpServers": {
    "api-hub": {
      "type": "stdio",
      "command": "/path/to/.venv/bin/python",
      "args": ["/path/to/api_hub_server.py"],
      "env": {
        "PYTHONUNBUFFERED": "1"
      }
    }
  }
}
```

> **注意**：`PYTHONUNBUFFERED=1` 确保Python输出不被缓冲，避免stdio通信延迟。

---

## 第四章：HTTP Streamable传输——远程部署

### 4.1 何时选择HTTP模式

| 场景 | 推荐传输 |
|------|----------|
| 个人本地开发 | stdio |
| 团队共享服务器 | HTTP Streamable |
| CI/CD管道中运行 | stdio |
| 需要服务发现/负载均衡 | HTTP Streamable |
| 同时服务多个Client | HTTP Streamable |

### 4.2 TypeScript HTTP服务器

```typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import express from "express";
import { z } from "zod";
import { randomUUID } from "crypto";

const app = express();
app.use(express.json());

const server = new McpServer({
  name: "remote-tools",
  version: "1.0.0",
});

// 注册工具...
server.tool(
  "echo",
  "回显输入文本",
  { message: z.string() },
  async ({ message }) => ({
    content: [{ type: "text", text: `Echo: ${message}` }],
  })
);

// MCP协议处理器
app.post("/mcp", async (req, res) => {
  const transport = new StreamableHTTPServerTransport({
    sessionIdGenerator: randomUUID,
  });
  await server.connect(transport);
  await transport.handleRequest(req, res, req.body);
});

// SSE通道（用于服务器主动推送）
app.get("/mcp", async (req, res) => {
  // StreamableHTTP需要处理GET请求用于SSE连接
});

// 删除会话
app.delete("/mcp", async (req, res) => {
  // 处理会话清理
});

app.listen(3000, () => {
  console.log("MCP HTTP Server running on port 3000");
});
```

### 4.3 FastMCP HTTP模式

FastMCP切换HTTP模式极其简单：

```python
from fastmcp import FastMCP

mcp = FastMCP("remote-api-hub")

# ... 注册tools/resources/prompts ...

# 关键：只需改一行
mcp.run(transport="streamable-http", host="0.0.0.0", port=8000)
```

或通过CLI指定：

```bash
# 启动HTTP服务器
fastmcp run server.py --transport streamable-http --port 8000

# 开发模式
fastmcp dev server.py --transport streamable-http --port 8000
```

### 4.4 在Claude Code中配置HTTP连接

```bash
# 通过命令行
claude mcp add remote-tools --transport http http://localhost:8000/mcp

# 通过JSON配置
claude mcp add-json remote-tools '{
  "type": "http",
  "url": "http://your-server:8000/mcp"
}'
```

`.mcp.json` 配置：

```json
{
  "mcpServers": {
    "remote-tools": {
      "type": "http",
      "url": "http://your-server:8000/mcp"
    }
  }
}
```

---

## 第五章：高级模式与生产实践

### 5.1 错误处理与重试

```python
from fastmcp import FastMCP
import httpx
import asyncio

mcp = FastMCP("robust-server")

async def fetch_with_retry(url: str, max_retries: int = 3) -> dict:
    """带重试的HTTP请求"""
    for attempt in range(max_retries):
        try:
            async with httpx.AsyncClient(timeout=10) as client:
                resp = await client.get(url)
                resp.raise_for_status()
                return resp.json()
        except httpx.HTTPError as e:
            if attempt == max_retries - 1:
                raise
            wait = 2 ** attempt  # 指数退避
            await asyncio.sleep(wait)

@mcp.tool
async def reliable_query(query: str) -> str:
    """可靠查询，内置重试机制"""
    try:
        data = await fetch_with_retry(f"https://api.example.com/search?q={query}")
        return json.dumps(data, ensure_ascii=False)
    except Exception as e:
        # 返回结构化错误信息，而非抛出异常
        return f"查询失败: {str(e)}。请稍后重试。"
```

### 5.2 进度通知

长时间运行的操作应发送进度通知：

```typescript
server.tool(
  "process_large_file",
  "处理大文件",
  { file_path: z.string(), chunk_size: z.number().default(1000) },
  async ({ file_path, chunk_size }, { sendNotification }) => {
    const content = await fs.readFile(file_path, "utf-8");
    const lines = content.split("\n");
    const total = lines.length;
    let processed = 0;
    
    for (let i = 0; i < total; i += chunk_size) {
      // 处理这一批数据...
      processed = Math.min(i + chunk_size, total);
      
      // 发送进度通知
      await sendNotification({
        method: "notifications/progress",
        params: {
          progress: processed,
          total: total,
          progressToken: "file-processing",
        },
      });
    }
    
    return {
      content: [{ type: "text", text: `处理完成：${total} 行` }],
    };
  }
);
```

### 5.3 服务器指令（Instructions）

通过 `instructions` 向LLM提供使用指南：

```python
mcp = FastMCP(
    "enterprise-tools",
    instructions="""
    这是一个企业级工具服务器。使用指南：
    
    1. 查询数据库时，优先使用 read_only 模式
    2. 批量操作每次不超过100条
    3. 涉及敏感数据时使用 mask_pii 参数
    4. 所有时间使用UTC时区
    
    依赖关系：
    - generate_report 依赖 query_database 的结果
    - send_notification 需要先调用 get_user_info
    """
)
```

### 5.4 返回图片和多媒体内容

```typescript
server.tool(
  "generate_chart",
  "生成图表并返回图片",
  {
    data: z.array(z.number()).describe("数据点"),
    title: z.string().describe("图表标题"),
  },
  async ({ data, title }) => {
    // 假设生成了一张PNG图表
    const imageBuffer = await generateChartPNG(data, title);
    
    return {
      content: [
        {
          type: "image",
          data: imageBuffer.toString("base64"),
          mimeType: "image/png",
        },
        {
          type: "text",
          text: `已生成图表"${title}"，包含${data.length}个数据点。`,
        },
      ],
    };
  }
);
```

### 5.5 配置层级与作用域

Claude Code的MCP配置遵循以下优先级（从高到低）：

```
项目级 .mcp.json  >  用户级 ~/.claude.json  >  企业级配置
```

```mermaid
graph TD
    A[企业级策略配置<br/>由管理员统一管理] --> B[用户级配置<br/>~/.claude.json]
    B --> C[项目级配置<br/>.mcp.json]

    D[claude mcp add -s enterprise] --> A
    E[claude mcp add -s user] --> B
    F[claude mcp add -s project] --> C

    style A fill:#ff9999
    style B fill:#ffcc99
    style C fill:#99ff99
```

**多环境配置实践**：

```bash
# 开发环境：项目级配置（指向本地服务器）
claude mcp add api-hub -s project -- python ./dev_server.py

# 生产环境：用户级配置（指向远程服务器）
claude mcp add api-hub -s user --transport http https://api.example.com/mcp

# 删除不再需要的服务器
claude mcp remove api-hub -s project
```

---

## 第六章：完整实战项目——智能文档助手

将前面所有知识整合为一个完整项目。这个MCP服务器提供文档索引、搜索、摘要和翻译功能。

### 6.1 项目结构

```
doc-assistant-mcp/
├── package.json
├── tsconfig.json
├── src/
│   ├── index.ts          # 入口文件
│   ├── tools/
│   │   ├── search.ts     # 文档搜索
│   │   ├── summarize.ts  # 文档摘要
│   │   └── translate.ts  # 文档翻译
│   ├── resources/
│   │   └── docs.ts       # 文档资源
│   └── prompts/
│       └── templates.ts  # 提示模板
├── docs/                 # 示例文档目录
│   ├── architecture.md
│   └── api-reference.md
└── .mcp.json             # 项目MCP配置
```

### 6.2 入口文件 src/index.ts

```typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { registerSearchTool } from "./tools/search.js";
import { registerSummarizeTool } from "./tools/summarize.js";
import { registerTranslateTool } from "./tools/translate.js";
import { registerDocResources } from "./resources/docs.js";
import { registerPrompts } from "./prompts/templates.js";

const server = new McpServer({
  name: "doc-assistant",
  version: "1.0.0",
  instructions: `
    文档助手服务器。可以搜索、摘要和翻译Markdown文档。
    文档存放在 ./docs/ 目录中。
    搜索时使用 search_docs 工具。
    摘要时使用 summarize_doc 工具。
  `,
});

// 注册所有功能模块
registerSearchTool(server);
registerSummarizeTool(server);
registerTranslateTool(server);
registerDocResources(server);
registerPrompts(server);

async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error("Doc Assistant MCP Server 已启动");
}

main().catch(console.error);
```

### 6.3 工具模块示例 src/tools/search.ts

```typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import * as fs from "fs/promises";
import * as path from "path";

const DOCS_DIR = path.resolve(process.env.DOCS_DIR || "./docs");

export function registerSearchTool(server: McpServer) {
  server.tool(
    "search_docs",
    "在文档目录中全文搜索关键词",
    {
      query: z.string().describe("搜索关键词"),
      case_sensitive: z.boolean().default(false).describe("是否区分大小写"),
      max_results: z.number().default(10).describe("最大返回结果数"),
    },
    async ({ query, case_sensitive, max_results }) => {
      const results: Array<{
        file: string;
        line: number;
        content: string;
        score: number;
      }> = [];

      async function scanFile(filePath: string) {
        const content = await fs.readFile(filePath, "utf-8");
        const lines = content.split("\n");
        const q = case_sensitive ? query : query.toLowerCase();

        for (let i = 0; i < lines.length; i++) {
          const line = lines[i];
          const target = case_sensitive ? line : line.toLowerCase();

          if (target.includes(q)) {
            // 简单评分：标题行权重更高
            const isHeading = line.trimStart().startsWith("#");
            const score = isHeading ? 2 : 1;
            results.push({
              file: path.relative(DOCS_DIR, filePath),
              line: i + 1,
              content: line.trim(),
              score,
            });
          }
        }
      }

      async function walkDir(dir: string) {
        const entries = await fs.readdir(dir, { withFileTypes: true });
        for (const entry of entries) {
          const fullPath = path.join(dir, entry.name);
          if (entry.isDirectory()) {
            await walkDir(fullPath);
          } else if (entry.name.endsWith(".md") || entry.name.endsWith(".txt")) {
            await scanFile(fullPath);
          }
        }
      }

      try {
        await walkDir(DOCS_DIR);
        results.sort((a, b) => b.score - a.score);
        const top = results.slice(0, max_results);

        const output = top.length > 0
          ? top.map(r => `📄 ${r.file}:${r.line}\n   ${r.content}`).join("\n\n")
          : `未找到包含"${query}"的文档`;

        return {
          content: [{
            type: "text",
            text: `搜索"${query}"的结果（${top.length}/${results.length}）：\n\n${output}`,
          }],
        };
      } catch (error: any) {
        return {
          content: [{ type: "text", text: `搜索失败: ${error.message}` }],
          isError: true,
        };
      }
    }
  );
}
```

### 6.4 项目配置文件 .mcp.json

```json
{
  "mcpServers": {
    "doc-assistant": {
      "type": "stdio",
      "command": "node",
      "args": ["./build/index.js"],
      "env": {
        "DOCS_DIR": "./docs",
        "NODE_ENV": "development"
      }
    }
  }
}
```

### 6.5 测试全流程

```bash
# 构建
npm run build

# 在项目目录中启动Claude Code
claude

# 测试对话
> 搜索文档中所有提到"API"的内容
> 搜索"authentication"相关文档，不区分大小写
> 读取文档目录的结构
> 使用code_review模板审查src/tools/search.ts
```

---

## 第七章：TypeScript与Python方案对比

### 7.1 功能特性对比

| 特性 | TypeScript SDK | Python FastMCP |
|------|---------------|----------------|
| Schema定义 | Zod（手动） | 类型注解（自动） |
| 描述来源 | 手写字符串 | docstring自动提取 |
| 资源模板 | ResourceTemplate类 | `@mcp.resource` 装饰器 |
| 提示模板 | `server.prompt()` | `@mcp.prompt` 装饰器 |
| HTTP模式 | 需Express等框架 | 内置，改一行代码 |
| 调试工具 | 日志/手动测试 | `fastmcp dev` Inspector |
| 包大小 | 较大（Node依赖） | 较小 |
| 启动速度 | 需编译 | 即时运行 |

### 7.2 选型建议

```mermaid
graph TD
    A[选择MCP开发语言] --> B{项目技术栈?}
    B -->|TypeScript/Node.js| C[TypeScript SDK]
    B -->|Python生态| D[FastMCP]
    B -->|不确定| E{主要需求?}

    E -->|快速原型/API对接| D
    E -->|生产级/复杂逻辑| C
    E -->|数据处理/ML| D
    E -->|前端工具链集成| C

    C --> F[优势: 类型安全<br/>IDE支持好<br/>生态成熟]
    D --> G[优势: 开发速度快<br/>数据科学生态<br/>代码量少30%+]
```

---

## 第八章：常见问题与排障

### 8.1 MCP服务器无法连接

```bash
# 1. 确认服务器可独立运行
node build/index.js  # 应无报错

# 2. 检查Claude Code配置
claude mcp list
claude mcp get <server-name>

# 3. 查看Claude Code日志
claude --mcp-debug  # 启用MCP调试模式

# 4. 常见原因
# - 路径不是绝对路径（.mcp.json中的command路径需要绝对路径）
# - Python虚拟环境未激活
# - 端口被占用（HTTP模式）
```

### 8.2 stdio模式输出异常

**症状**：Claude Code报MCP协议解析错误。

**原因**：服务器代码中有 `console.log` / `print` 污染了stdout。

**解决**：
- TypeScript：所有日志用 `console.error()` 或写文件
- Python：所有日志用 `sys.stderr.write()` 或 `logging` 模块

```python
import sys
import logging

# 配置日志到stderr
logging.basicConfig(
    stream=sys.stderr,
    level=logging.DEBUG,
    format="%(asctime)s %(levelname)s %(message)s"
)
logger = logging.getLogger("my-mcp-server")

# ✅ 正确
logger.info("这条消息不会干扰MCP通信")

# ❌ 错误
print("这会破坏MCP协议！")
```

### 8.3 工具未被调用

**原因**：工具描述不够清晰，LLM无法判断何时使用。

**解决**：写好工具描述，包含触发场景：

```typescript
// ❌ 模糊描述
server.tool("search", "搜索", { /* ... */ }, handler);

// ✅ 清晰描述
server.tool(
  "search_docs",
  "在项目文档中全文搜索关键词。当用户问及文档内容、查找特定主题、或需要了解项目某个方面时使用此工具。",
  { query: z.string().describe("要搜索的关键词或短语") },
  handler
);
```

### 8.4 HTTP模式CORS问题

```typescript
import cors from "cors";

app.use(cors({
  origin: ["https://claude.ai", "http://localhost:*"],
  methods: ["GET", "POST", "DELETE"],
  allowedHeaders: ["Content-Type"],
}));
```

---

## 延伸阅读

- [MCP官方规范](https://modelcontextprotocol.io/specification) — 协议完整规范文档
- [TypeScript SDK GitHub](https://github.com/modelcontextprotocol/typescript-sdk) — 源码与示例
- [FastMCP官方文档](https://fastmcp.wiki) — Python MCP开发完整指南
- [Claude Code MCP文档](https://code.claude.com/docs/en/mcp) — 官方集成指南
- [MCP Server示例集合](https://github.com/modelcontextprotocol/servers) — 官方参考实现

## 训练营适用性评估

| 维度 | 评估 |
|------|------|
| 适合训练营类型 | AI工程实战营、开发者工具训练营、LLM应用开发训练营 |
| 教学时长 | 6-8小时（含动手实验） |
| 难度等级 | 中级（需编程基础） |
| 实验环境要求 | 每人一台开发机，预装Node.js 20+和Python 3.10+ |
| 建议分组 | 2-3人一组，每组选择TS或Python方案完成一个MCP服务器 |
| 产出要求 | 每组提交一个可运行的MCP服务器，至少包含3个Tools + 1个Resource + 1个Prompt |

## 参考来源

1. [Build an MCP server - Model Context Protocol](https://modelcontextprotocol.io/docs/develop/build-server) — MCP官方构建教程，TypeScript/Python双语言入门指南
2. [Connect Claude Code to tools via MCP](https://code.claude.com/docs/en/mcp) — Claude Code官方MCP集成文档，配置方式与传输类型说明
3. [MCP Developer Guide 2026 - LushBinary](https://lushbinary.com/blog/mcp-model-context-protocol-developer-guide-2026/) — 2026年MCP开发者完整指南，覆盖架构、部署与安全
4. [FastMCP Python Guide 2026 - danilchenko.dev](https://www.danilchenko.dev/posts/fastmcp-mcp-server/) — FastMCP 3.2生产级服务器构建教程
5. [Understanding MCP Server Transports - DEV Community](https://dev.to/zoricic/understanding-mcp-server-transports-stdio-sse-and-http-streamable-5b1p) — stdio/SSE/HTTP Streamable三种传输方式对比解析

> [!tip] 51学通信知识星球
> **星主：爱卫生**（微信：gprshome201101）——公众号「51学通信」创办者，19 年通信行业运维与讲师经验，著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书（各大电商平台有售）。
> **星球定位**：深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域，主打运维岗位与信令分析。
> **当前规模**：5200 篇主题 / 990 个回答，198 元/年。
> **加入福利**：加入星球后请加爱卫生微信，拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题，另有群内分享。
> **AI 加持**：51学通信知识星球已作为 skill 接入 WorkBuddy，使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例，做总结、找文档和 PPT 等场景全覆盖。

![51学通信知识星球海报-扫码加入](https://wiki.500nian.com/images/zhixingqiu_qrcode.png)
