<!-- title: Claude Code MCP服务器开发完全指南 -->

## 概述

本教程面向希望为 Claude Code 构建自定义 MCP（Model Context Protocol）服务器的开发者。学完本教程后，你将掌握 MCP 协议的三大原语（Tools、Resources、Prompts）、2025-06-18 规范新增特性（Elicitation、Structured Output、OAuth 2.1）、以及如何用 TypeScript 和 Python（FastMCP 3.x）两种语言实现完整的 MCP 服务器，并将其接入 Claude Code 的完整工作流。

**适用人群**：有基本编程经验、已使用过 Claude Code、希望扩展 Claude Code 能力边界的开发者。

## 前置条件

- Node.js 18+ 或 Python 3.10+（本文两种语言都会涉及）
- Claude Code CLI 已安装并可用
- 基本的 JSON 和命令行操作经验
- 了解 REST API 概念有助于理解传输层

---

## 一、MCP 协议核心概念

### 1.1 什么是 MCP？

Model Context Protocol（模型上下文协议）是 Anthropic 发布的开放协议，定义了 AI 模型与外部工具、数据源之间的标准化通信方式。MCP 的设计理念类似"AI 的 USB-C"——一种通用的连接标准，让任何 AI 客户端都能与任何工具服务器对接。


![图表1](images/claude-code-tutorial-r3-2007-mermaid-0.png)

> *图表 1*


### 1.2 三大核心原语

MCP 服务器暴露三种类型的能力给客户端：

| 原语 | 用途 | 特征 | 典型场景 |
|------|------|------|----------|
| **Tools** | 可调用的函数 | 模型控制、有副作用 | 查询数据库、调用API、执行命令 |
| **Resources** | 可读取的数据源 | 只读、应用控制 | 文件内容、数据库记录、配置信息 |
| **Prompts** | 可复用的提示模板 | 用户控制 | 标准化的工作流模板、角色设定 |

**何时用 Tools vs Resources？**
- 如果操作会**改变状态**（写入、删除、发送）→ 用 Tool
- 如果只是**读取数据**（查询、获取、列举）→ 用 Resource
- 如果需要**标准化一段提示**让用户选择使用 → 用 Prompt

### 1.3 传输机制

MCP 支持两种传输方式：

1. **stdio**：本地进程间通信，Claude Code 启动 MCP 服务器作为子进程
2. **Streamable HTTP**：远程通信，支持 HTTP POST + SSE 流式响应（2025-03-26 规范替代了旧的 HTTP+SSE）

### 1.4 协议版本演进


![图表2](images/claude-code-tutorial-r3-2007-mermaid-1.png)

> *图表 2*


2025-06-18 规范的重大更新：
- **Elicitation**：服务器可暂停工具执行，向用户请求输入（人机协作）
- **Structured Output**：工具可返回结构化内容（文本 + 图片 + 音频等）
- **Tool Annotations**：工具可声明 `readOnlyHint`、`destructiveHint` 等元数据
- **OAuth 2.1 增强**：远程服务器的标准化认证流程

---

## 二、用 TypeScript SDK 构建 MCP 服务器

### 2.1 项目初始化

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

# 初始化 npm 项目
npm init -y

# 安装 MCP TypeScript SDK（当前最新 v1.29.x）
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node tsx

# 初始化 TypeScript 配置
npx tsc --init
```

在 `tsconfig.json` 中确认以下配置：

```json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "./dist",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true
  }
}
```

### 2.2 最小化 MCP 服务器

创建 `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-first-mcp-server",
  version: "1.0.0",
});

// 注册一个 Tool
server.tool(
  "get_weather",                          // 工具名称
  "获取指定城市的天气信息",                  // 工具描述
  { city: z.string().describe("城市名称") }, // 参数 schema
  async ({ city }) => {
    // 这里是工具的实际逻辑
    const weatherData = {
      city,
      temperature: "22°C",
      condition: "晴",
      humidity: "45%",
    };
    return {
      content: [
        {
          type: "text",
          text: JSON.stringify(weatherData, null, 2),
        },
      ],
    };
  }
);

// 注册一个 Resource
server.resource(
  "config",                    // 资源名称
  "config://app",              // 资源 URI
  async (uri) => {
    return {
      contents: [
        {
          uri: uri.href,
          text: JSON.stringify({ version: "1.0", env: "development" }),
        },
      ],
    };
  }
);

// 注册一个 Prompt
server.prompt(
  "code-review",                // Prompt 名称
  "代码审查模板",                // 描述
  { code: z.string().describe("待审查的代码") },
  async ({ code }) => {
    return {
      messages: [
        {
          role: "user",
          content: {
            type: "text",
            text: `请审查以下代码，关注安全性、性能和可维护性：\n\n${code}`,
          },
        },
      ],
    };
  }
);

// 启动服务器
async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error("MCP Server started on stdio"); // 注意：用 stderr 输出日志
}

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

更新 `package.json`：

```json
{
  "type": "module",
  "scripts": {
    "build": "tsc",
    "dev": "tsx src/index.ts",
    "start": "node dist/index.js"
  }
}
```

### 2.3 关键实现要点

**stdout vs stderr**：MCP 协议通过 stdio 传输 JSON-RPC 消息，所以 `console.log()` 会干扰协议通信。所有日志必须输出到 stderr：

```typescript
console.error("这条日志不会干扰 MCP 协议"); // ✓ 正确
console.log("这条会破坏 MCP 协议");          // ✗ 错误
```

**Zod Schema 自动转换**：SDK 会自动将 Zod schema 转换为 JSON Schema，Claude 端会据此理解参数含义和类型约束。

**Tool 返回格式**：所有 Tool 必须返回 `{ content: [...] }` 结构，content 数组中可包含多个内容块（text、image、resource）。

### 2.4 本地调试

使用 MCP Inspector（官方调试工具）进行测试：

```bash
# 安装并运行 Inspector
npx @modelcontextprotocol/inspector tsx src/index.ts
```

Inspector 会打开一个浏览器界面，你可以：
- 查看 Tools/Resources/Prompts 列表
- 手动调用 Tool 并查看返回结果
- 检查 JSON-RPC 请求/响应
- 验证 Schema 定义是否正确

---

## 三、用 Python FastMCP 3.x 构建 MCP 服务器

### 3.1 为什么选择 FastMCP？

Python 生态有两个层次的 MCP SDK：
- `mcp` 官方 SDK：底层、灵活但代码量大
- `fastmcp` 3.x：高层封装，用装饰器快速定义 Tools/Resources/Prompts

对于大多数场景，FastMCP 的开发效率更高。

### 3.2 安装与初始化

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

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

# 安装 FastMCP
pip install fastmcp
```

### 3.3 FastMCP 服务器实现

创建 `server.py`：

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

# 创建服务器实例
mcp = FastMCP("my-python-server")

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

@mcp.tool()
def search_docs(query: str, max_results: int = 5) -> str:
    """搜索技术文档
    
    Args:
        query: 搜索关键词
        max_results: 最大返回结果数（默认5）
    """
    # 模拟搜索逻辑
    results = [
        {"title": f"文档{i}: {query} 相关内容", "relevance": 0.95 - i * 0.1}
        for i in range(min(max_results, 10))
    ]
    return json.dumps(results, ensure_ascii=False, indent=2)


@mcp.tool()
def calculate(expression: str) -> str:
    """安全计算数学表达式
    
    Args:
        expression: 数学表达式字符串，如 "2 + 3 * 4"
    """
    # 安全的数学计算 - 只允许基本运算符
    allowed_chars = set("0123456789+-*/().% ")
    if not all(c in allowed_chars for c in expression):
        return "错误：表达式包含不允许的字符"
    try:
        result = eval(expression)  # 在输入已验证的情况下使用
        return f"{expression} = {result}"
    except Exception as e:
        return f"计算错误：{e}"


@mcp.tool()
def create_task(title: str, description: str = "", priority: str = "medium") -> str:
    """创建新任务
    
    Args:
        title: 任务标题
        description: 任务描述
        priority: 优先级 (low/medium/high)
    """
    task = {
        "id": f"TASK-{datetime.now().strftime('%Y%m%d%H%M%S')}",
        "title": title,
        "description": description,
        "priority": priority,
        "status": "created",
        "created_at": datetime.now().isoformat(),
    }
    return json.dumps(task, ensure_ascii=False, indent=2)


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

@mcp.resource("config://app")
def get_app_config() -> str:
    """获取应用配置"""
    config = {
        "app_name": "My Python MCP Server",
        "version": "1.0.0",
        "environment": "development",
        "features": ["search", "calculate", "task_management"],
    }
    return json.dumps(config, ensure_ascii=False, indent=2)


@mcp.resource("docs://api/{endpoint}")
def get_api_docs(endpoint: str) -> str:
    """获取 API 文档"""
    docs = {
        "search_docs": "搜索技术文档，参数: query(str), max_results(int)",
        "calculate": "计算数学表达式，参数: expression(str)",
        "create_task": "创建任务，参数: title(str), description(str), priority(str)",
    }
    return json.dumps(
        docs.get(endpoint, f"未找到 {endpoint} 的文档"),
        ensure_ascii=False
    )


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

@mcp.prompt()
def debug_helper(error_message: str, language: str = "python") -> str:
    """调试辅助提示模板
    
    Args:
        error_message: 错误信息
        language: 编程语言
    """
    return f"""你是一个 {language} 调试专家。请分析以下错误并给出解决方案：

错误信息：
{error_message}

请按以下格式回答：
1. 错误原因分析
2. 可能的修复方案（从最可能到最不可能排序）
3. 预防措施"""


@mcp.prompt()
def code_review_template(code: str, focus: str = "general") -> str:
    """代码审查提示模板"""
    focus_areas = {
        "general": "整体代码质量、可读性、最佳实践",
        "security": "安全漏洞、输入验证、权限控制",
        "performance": "性能瓶颈、时间复杂度、内存使用",
    }
    return f"""请审查以下代码，重点关注：{focus_areas.get(focus, focus_areas['general'])}

```{focus}
{code}
```

请提供：
1. 发现的问题（按严重程度排序）
2. 改进建议
3. 修改后的代码示例"""


# ============ 启动服务器 ============

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

### 3.4 运行 FastMCP 服务器

```bash
# stdio 模式（Claude Code 默认使用此模式）
fastmcp run server.py

# 或者开发调试模式
fastmcp dev server.py

# 指定传输方式
fastmcp run server.py --transport stdio
```

---

## 四、将 MCP 服务器接入 Claude Code

### 4.1 使用 `claude mcp add` 命令

Claude Code 提供了专用的 MCP 管理命令：

```bash
# 添加 stdio 模式的本地 MCP 服务器（TypeScript）
claude mcp add my-tools -- npx tsx /path/to/src/index.ts

# 添加 stdio 模式的本地 MCP 服务器（Python）
claude mcp add my-python-tools -- fastmcp run /path/to/server.py

# 添加远程 MCP 服务器（Streamable HTTP）
claude mcp add --transport http http://localhost:3000/mcp

# 指定作用域
claude mcp add --scope project my-tools -- npx tsx ./src/index.ts
claude mcp add --scope user my-tools -- npx tsx ~/mcp-servers/my-server/src/index.ts
```

### 4.2 三种作用域


![图表3](images/claude-code-tutorial-r3-2007-mermaid-2.png)

> *图表 3*


**作用域选择指南**：
- `user`：个人通用工具（如搜索引擎、个人日历）
- `project`：项目特定工具（如项目数据库、CI/CD 工具）
- 优先使用 `project` scope，让项目配置可移植

### 4.3 手动编辑配置文件

你也可以直接编辑配置文件。用户级配置在 `~/.claude/settings.json`，项目级配置在项目根目录的 `.mcp.json`：

```json
{
  "mcpServers": {
    "my-local-server": {
      "command": "npx",
      "args": ["tsx", "/path/to/src/index.ts"],
      "env": {
        "API_KEY": "your-api-key"
      }
    },
    "my-remote-server": {
      "url": "http://localhost:3000/mcp",
      "headers": {
        "Authorization": "Bearer your-token"
      }
    },
    "my-python-server": {
      "command": "fastmcp",
      "args": ["run", "/path/to/server.py"],
      "env": {
        "PYTHONUNBUFFERED": "1"
      }
    }
  }
}
```

### 4.4 管理已添加的 MCP 服务器

```bash
# 列出所有 MCP 服务器
claude mcp list

# 查看某个服务器的详细信息
claude mcp get my-tools

# 移除服务器
claude mcp remove my-tools

# 重置服务器连接
claude mcp reset
```

### 4.5 在 Claude Code 中使用 MCP 工具

添加服务器后，在 Claude Code 对话中直接使用：

```
> 帮我搜索关于 React hooks 的技术文档
# Claude 会自动调用 search_docs tool

> 当前应用配置是什么？
# Claude 会读取 config://app resource

> 用代码审查模板帮我检查这段代码
# Claude 会使用 code-review prompt
```

Claude Code 的 `/mcp` 面板可以查看所有已连接的服务器状态和可用工具数量。

---

## 五、进阶特性

### 5.1 Tool Annotations（工具注解）

2025-06-18 规范引入了工具注解，让你的工具声明自身的行为特征：

```typescript
// TypeScript SDK 示例
server.tool(
  "delete_database",
  "删除数据库",
  { db_name: z.string() },
  { annotations: {
    readOnlyHint: false,       // 非只读，会修改状态
    destructiveHint: true,     // 破坏性操作
    idempotentHint: false,     // 非幂等
    openWorldHint: false,      // 不访问外部网络
  }},
  async ({ db_name }) => {
    // 删除逻辑
    return { content: [{ type: "text", text: `已删除数据库: ${db_name}` }] };
  }
);
```

```python
# FastMCP Python 示例
from fastmcp import FastMCP
from mcp.types import ToolAnnotations

mcp = FastMCP("annotated-server")

@mcp.tool(annotations=ToolAnnotations(
    readOnlyHint=True,
    destructiveHint=False,
    idempotentHint=True,
    openWorldHint=False,
))
def query_user(user_id: str) -> str:
    """查询用户信息（只读操作）"""
    return json.dumps({"user_id": user_id, "name": "张三"})
```

注解帮助 Claude 在调用前评估风险，对破坏性操作会额外请求用户确认。

### 5.2 Progress Notifications（进度通知）

长时间运行的操作可以报告进度：

```typescript
server.tool(
  "process_large_file",
  "处理大文件",
  { path: z.string() },
  async ({ path }, { sendNotification }) => {
    const totalSteps = 100;
    for (let i = 0; i < totalSteps; i++) {
      // 执行处理步骤...
      await sendNotification({
        method: "notifications/progress",
        params: {
          progress: i + 1,
          total: totalSteps,
          progressToken: "file-processing",
        },
      });
    }
    return { content: [{ type: "text", text: "处理完成" }] };
  }
);
```

### 5.3 Structured Output（结构化输出）

2025-06-18 规范允许工具返回多种类型的内容：

```typescript
server.tool(
  "analyze_chart",
  "分析图表并返回描述和图片",
  { chart_type: z.string() },
  async ({ chart_type }) => {
    return {
      content: [
        {
          type: "text",
          text: `分析结果：这是一个${chart_type}图表，显示上升趋势...`,
        },
        {
          type: "image",
          data: base64EncodedImage,
          mimeType: "image/png",
        },
        {
          type: "resource",
          resource: {
            uri: "analysis://full-report",
            text: "完整分析报告内容...",
          },
        },
      ],
    };
  }
);
```

### 5.4 Elicitation（人机交互）

Elicitation 是 2025-06-18 规范的重磅特性，允许服务器在工具执行过程中暂停并请求用户输入：

```typescript
// 概念示例：删除前确认
server.tool(
  "safe_delete",
  "安全删除（需要用户确认）",
  { file_path: z.string() },
  async ({ file_path }, { elicitInput }) => {
    // 请求用户确认
    const confirmation = await elicitInput({
      message: `确认删除文件 ${file_path}？此操作不可撤销。`,
      requestedSchema: {
        type: "object",
        properties: {
          confirm: { type: "boolean", title: "确认删除" },
          reason: { type: "string", title: "删除原因（可选）" },
        },
        required: ["confirm"],
      },
    });

    if (!confirmation.confirm) {
      return { content: [{ type: "text", text: "操作已取消" }] };
    }

    // 执行删除...
    return { content: [{ type: "text", text: `已删除: ${file_path}` }] };
  }
);
```


![图表4](images/claude-code-tutorial-r3-2007-mermaid-3.png)

> *图表 4*


---

## 六、实战案例：Markdown 知识库 MCP 服务器

下面构建一个完整的、可运行的 MCP 服务器，管理本地 Markdown 知识库。

### 6.1 项目结构

```
md-knowledge-mcp/
├── package.json
├── tsconfig.json
├── src/
│   └── index.ts
└── knowledge/              # Markdown 知识库目录
    ├── getting-started.md
    └── api-reference.md
```

### 6.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";
import fs from "fs/promises";
import path from "path";

// 知识库根目录（可通过环境变量配置）
const KNOWLEDGE_DIR = process.env.KNOWLEDGE_DIR || "./knowledge";

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

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

server.tool(
  "search_notes",
  "在知识库中搜索包含关键词的笔记",
  {
    query: z.string().describe("搜索关键词"),
    max_results: z.number().optional().default(10).describe("最大返回数量"),
  },
  async ({ query, max_results }) => {
    try {
      const files = await fs.readdir(KNOWLEDGE_DIR);
      const mdFiles = files.filter((f) => f.endsWith(".md"));
      const results: Array<{ file: string; matches: string[]; preview: string }> = [];

      for (const file of mdFiles) {
        if (results.length >= max_results) break;
        const content = await fs.readFile(
          path.join(KNOWLEDGE_DIR, file),
          "utf-8"
        );
        const lines = content.split("\n");
        const matches = lines.filter((line) =>
          line.toLowerCase().includes(query.toLowerCase())
        );
        if (matches.length > 0) {
          results.push({
            file,
            matches: matches.slice(0, 3),
            preview: lines.slice(0, 5).join("\n"),
          });
        }
      }

      return {
        content: [
          {
            type: "text",
            text:
              results.length > 0
                ? JSON.stringify(results, null, 2)
                : `未找到包含 "${query}" 的笔记`,
          },
        ],
      };
    } catch (error: any) {
      return {
        content: [{ type: "text", text: `搜索失败: ${error.message}` }],
        isError: true,
      };
    }
  }
);

server.tool(
  "create_note",
  "创建新的 Markdown 笔记",
  {
    title: z.string().describe("笔记标题（将作为文件名）"),
    content: z.string().describe("笔记内容（Markdown 格式）"),
    tags: z.array(z.string()).optional().describe("标签列表"),
  },
  { annotations: { readOnlyHint: false, destructiveHint: false } },
  async ({ title, content, tags }) => {
    try {
      const fileName = title.replace(/[^a-zA-Z0-9\u4e00-\u9fa5-_]/g, "-") + ".md";
      const filePath = path.join(KNOWLEDGE_DIR, fileName);

      const frontmatter = [
        "---",
        `title: ${title}`,
        `created: ${new Date().toISOString()}`,
        tags ? `tags: [${tags.join(", ")}]` : "",
        "---",
        "",
      ].join("\n");

      await fs.writeFile(filePath, frontmatter + content, "utf-8");

      return {
        content: [{ type: "text", text: `笔记已创建: ${fileName}` }],
      };
    } catch (error: any) {
      return {
        content: [{ type: "text", text: `创建失败: ${error.message}` }],
        isError: true,
      };
    }
  }
);

server.tool(
  "update_note",
  "更新已有笔记（追加内容）",
  {
    filename: z.string().describe("文件名"),
    new_content: z.string().describe("要追加的内容"),
  },
  { annotations: { readOnlyHint: false, destructiveHint: false } },
  async ({ filename, new_content }) => {
    try {
      const filePath = path.join(KNOWLEDGE_DIR, filename);
      const existing = await fs.readFile(filePath, "utf-8");
      const updated = existing + "\n\n" + new_content;
      await fs.writeFile(filePath, updated, "utf-8");
      return {
        content: [{ type: "text", text: `笔记已更新: ${filename}` }],
      };
    } catch (error: any) {
      return {
        content: [{ type: "text", text: `更新失败: ${error.message}` }],
        isError: true,
      };
    }
  }
);

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

server.resource("notes://list", async (uri) => {
  try {
    const files = await fs.readdir(KNOWLEDGE_DIR);
    const mdFiles = files.filter((f) => f.endsWith(".md"));
    const listing = mdFiles.map(async (f) => {
      const stat = await fs.stat(path.join(KNOWLEDGE_DIR, f));
      return { name: f, size: stat.size, modified: stat.mtime.toISOString() };
    });
    return {
      contents: [
        {
          uri: uri.href,
          text: JSON.stringify(await Promise.all(listing), null, 2),
        },
      ],
    };
  } catch (error: any) {
    return {
      contents: [{ uri: uri.href, text: `读取失败: ${error.message}` }],
    };
  }
});

server.resource("notes://{filename}", async (uri, { filename }) => {
  try {
    const filePath = path.join(KNOWLEDGE_DIR, filename as string);
    const content = await fs.readFile(filePath, "utf-8");
    return {
      contents: [{ uri: uri.href, text: content }],
    };
  } catch (error: any) {
    return {
      contents: [{ uri: uri.href, text: `读取失败: ${error.message}` }],
    };
  }
});

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

server.prompt(
  "summarize-notes",
  "总结知识库笔记",
  { topic: z.string().describe("要总结的主题") },
  async ({ topic }) => {
    return {
      messages: [
        {
          role: "user",
          content: {
            type: "text",
            text: `请搜索知识库中关于「${topic}」的所有笔记，并生成一份结构化的总结报告。包括：1) 核心概念 2) 关键要点 3) 相关引用`,
          },
        },
      ],
    };
  }
);

// ============ 启动 ============

async function main() {
  // 确保知识库目录存在
  await fs.mkdir(KNOWLEDGE_DIR, { recursive: true });
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error(`Knowledge MCP Server started, dir: ${KNOWLEDGE_DIR}`);
}

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

### 6.3 注册到 Claude Code

```bash
# 构建项目
npm run build

# 注册到 Claude Code（项目级）
claude mcp add --scope project knowledge-base -- node /path/to/md-knowledge-mcp/dist/index.js

# 或使用 tsx 直接运行
claude mcp add --scope project knowledge-base -- npx tsx /path/to/md-knowledge-mcp/src/index.ts

# 指定知识库目录
claude mcp add --scope project knowledge-base -e KNOWLEDGE_DIR=/path/to/my-notes -- npx tsx /path/to/md-knowledge-mcp/src/index.ts
```

### 6.4 使用示例

在 Claude Code 中：

```
> 搜索知识库中关于 "API设计" 的笔记
# 调用 search_notes tool

> 创建一个新笔记，标题是"RESTful API 最佳实践"，内容是关于 HTTP 方法的使用
# 调用 create_note tool

> 列出知识库中的所有笔记
# 读取 notes://list resource

> 帮我总结关于"数据库"主题的所有笔记
# 使用 summarize-notes prompt
```

---

## 七、错误处理与日志最佳实践

### 7.1 错误返回格式

MCP 工具的错误应通过结构化的错误响应返回，而非抛出异常：

```typescript
// ✓ 正确：返回错误信息
return {
  content: [{ type: "text", text: `查询失败: ${error.message}` }],
  isError: true,  // 标记为错误
};

// ✗ 错误：直接抛出异常可能导致服务器崩溃
throw new Error("查询失败");
```

### 7.2 日志规范

```typescript
// 所有日志输出到 stderr
function log(level: string, message: string, data?: any) {
  console.error(JSON.stringify({
    level,
    message,
    timestamp: new Date().toISOString(),
    ...(data && { data }),
  }));
}

server.tool("my-tool", "示例工具", { input: z.string() }, async ({ input }) => {
  log("info", "工具调用开始", { input });
  try {
    const result = await doSomething(input);
    log("info", "工具调用成功", { result });
    return { content: [{ type: "text", text: result }] };
  } catch (error: any) {
    log("error", "工具调用失败", { error: error.message });
    return { content: [{ type: "text", text: error.message }], isError: true };
  }
});
```

### 7.3 输入验证

```typescript
import { z } from "zod";

// Zod 自动处理类型验证
server.tool(
  "query_database",
  "查询数据库",
  {
    table: z.string().min(1).max(64).regex(/^[a-zA-Z_][a-zA-Z0-9_]*$/),
    limit: z.number().int().min(1).max(1000).default(100),
    offset: z.number().int().min(0).default(0),
  },
  async ({ table, limit, offset }) => {
    // 参数已经过 Zod 验证
    // ...
  }
);
```

---

## 八、生产部署

### 8.1 Docker 容器化

```dockerfile
# Dockerfile
FROM node:20-slim AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:20-slim
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package*.json ./

# 非 root 用户
USER node
EXPOSE 3000

CMD ["node", "dist/index.js"]
```

### 8.2 远程部署（Streamable HTTP 模式）

将 stdio 服务器改造为支持远程访问：

```typescript
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import express from "express";

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

app.post("/mcp", async (req, res) => {
  const transport = new StreamableHTTPServerTransport({
    sessionIdGenerator: undefined,
  });
  await server.connect(transport);
  await transport.handleRequest(req, res, req.body);
});

app.listen(3000, () => {
  console.error("MCP Server listening on port 3000");
});
```

然后在 Claude Code 中注册：

```bash
claude mcp add --transport http http://your-server:3000/mcp
```

### 8.3 安全考量


![图表5](images/claude-code-tutorial-r3-2007-mermaid-4.png)

> *图表 5*


关键安全原则：
1. **永远不要信任 AI 生成的参数**——在工具内部进行完整的输入验证
2. **破坏性操作使用 Tool Annotations**——让 Claude 在调用前提示用户确认
3. **敏感数据通过环境变量传递**——不要在代码中硬编码 API Key
4. **远程服务器必须使用 HTTPS**——防止中间人攻击
5. **限制文件系统访问范围**——只允许访问指定目录

---

## 九、完整工作流程总结


![图表6](images/claude-code-tutorial-r3-2007-mermaid-5.png)

> *图表 6*


### 关键命令速查

| 操作 | 命令 |
|------|------|
| 创建 TS 服务器 | `npm init -y && npm i @modelcontextprotocol/sdk zod` |
| 创建 Python 服务器 | `pip install fastmcp` |
| 调试 TS 服务器 | `npx @modelcontextprotocol/inspector npx tsx src/index.ts` |
| 调试 Python 服务器 | `fastmcp dev server.py` |
| 注册到 Claude Code | `claude mcp add <name> -- <command>` |
| 查看已注册服务器 | `claude mcp list` |
| 查看服务器详情 | `claude mcp get <name>` |
| 移除服务器 | `claude mcp remove <name>` |
| 查看 MCP 面板 | Claude Code 中输入 `/mcp` |

---

## 十、常见问题与排错

### Q1：MCP 服务器启动后 Claude Code 无法连接

**症状**：`claude mcp list` 显示服务器但工具不可用。

**排查步骤**：
1. 手动运行服务器命令确认无报错：`npx tsx src/index.ts`
2. 检查 stderr 输出是否有错误信息
3. 确认 `console.log()` 没有被使用（必须用 `console.error()`）
4. 检查 Node.js 版本 >= 18

### Q2：Tool 参数验证失败

**原因**：Zod schema 与实际参数不匹配。

**解决**：确保 Zod schema 描述完整，`.describe()` 为每个字段添加描述：

```typescript
{
  // ✓ 好
  name: z.string().min(1).describe("用户名，不能为空"),
  
  // ✗ 差：缺少描述和约束
  name: z.string(),
}
```

### Q3：远程 MCP 服务器连接超时

**排查**：
1. 确认服务器使用 Streamable HTTP 传输（非旧的 SSE）
2. 检查防火墙和网络连通性
3. 确认 URL 路径正确（通常是 `/mcp`）
4. 使用 `curl -X POST http://server:port/mcp` 测试连通性

### Q4：环境变量无法传递

**解决**：使用 `-e` 参数或 `env` 配置：

```bash
claude mcp add -e API_KEY=xxx -e DB_URL=xxx my-server -- npx tsx src/index.ts
```

或配置文件中：

```json
{
  "mcpServers": {
    "my-server": {
      "command": "npx",
      "args": ["tsx", "src/index.ts"],
      "env": { "API_KEY": "xxx" }
    }
  }
}
```

---

## 延伸阅读

- [MCP 官方规范文档](https://modelcontextprotocol.io/specification/2025-06-18)
- [TypeScript SDK GitHub 仓库](https://github.com/modelcontextprotocol/typescript-sdk)
- [Python SDK (FastMCP) GitHub 仓库](https://github.com/fastmcp-me/fastmcp-python)
- [Claude Code MCP 官方文档](https://code.claude.com/docs/en/mcp)
- [MCP Inspector 调试工具](https://modelcontextprotocol.io/docs/tools/inspector)
- [Microsoft MCP for Beginners 课程](https://github.com/microsoft/mcp-for-beginners)
- [MCP 2025-06-18 规范更新详解](https://dev.to/forgecode/mcp-2025-06-18-spec-update-security-structured-output-elicitation-1pin)

## 训练营适用性评估

| 维度 | 评估 |
|------|------|
| **适合训练营类型** | AI 工具开发、DevOps 自动化、企业 AI 集成 |
| **建议教学时长** | 4-6 小时（含动手实验） |
| **难度等级** | 中级（需要编程基础，不需要 AI/ML 背景） |
| **实验环境要求** | Node.js 18+ 或 Python 3.10+、Claude Code CLI、Docker（可选） |
| **可拆分模块** | 基础概念（1h）+ TypeScript 实战（1.5h）+ Python 实战（1.5h）+ 部署与安全（1h）+ 综合练习（1h） |

> [!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)
