<!-- title: MCP Server场景驱动开发实战 -->

## 概述

本教程面向希望为 Claude Code 构建自定义 MCP 工具的开发者。通过三个真实场景（数据查询、自动化操作、分析诊断），你将掌握从需求分析到架构选型、从原型编码到 Inspector 测试的完整开发流程。学完后，你能独立设计并交付团队可复用的 MCP Server。

**适用人群**：有 TypeScript 或 Python 基础、正在使用 Claude Code 并希望扩展其能力的开发者。

## 前置条件

| 条件 | 说明 |
|------|------|
| Node.js 18+ | TypeScript 版本运行环境 |
| Python 3.10+ | Python/FastMCP 版本运行环境 |
| Claude Code | 已安装并完成基础配置 |
| 基础知识 | 了解 JSON-RPC、HTTP 协议基础 |

## 一、MCP Server 开发全景图

MCP（Model Context Protocol）是 Anthropic 推出的开放协议，定义了 AI 模型与外部工具之间的标准通信方式。开发 MCP Server 的本质是：**将你的业务能力封装为 Claude 可调用的函数**。

```mermaid
graph TB
    subgraph "Claude Code 客户端"
        A[Claude AI] --> B[MCP Client]
    end

    subgraph "你的 MCP Server"
        B -->|JSON-RPC| C{传输层}
        C -->|stdio| D[本地进程]
        C -->|Streamable HTTP| E[远程服务]

        D --> F[Tools 工具]
        D --> G[Resources 资源]
        D --> H[Prompts 提示]

        E --> F
        E --> G
        E --> H
    end

    F --> I[(数据库)]
    F --> J[(API)]
    F --> K[(文件系统)]
    G --> L[(知识库)]
    H --> M[(模板库)]

    style A fill:#7c3aed,color:#fff
    style F fill:#059669,color:#fff
    style G fill:#2563eb,color:#fff
    style H fill:#d97706,color:#fff
```

### 三原语速查

| 原语 | 角色 | Claude 的操作 | 典型场景 |
|------|------|--------------|----------|
| **Tools** | 执行动作 | 主动调用 | 查询数据库、触发部署、发送通知 |
| **Resources** | 提供数据 | 被动读取 | 文档内容、配置信息、日志文件 |
| **Prompts** | 模板指令 | 选择填充 | 标准化提示词、分析框架、报告模板 |

### 架构选型决策树

```mermaid
graph TD
    Start[开始开发 MCP Server] --> Q1{需要互联网访问?}
    Q1 -->|否| Q2{团队规模}
    Q1 -->|是| Remote[Streamable HTTP + OAuth 2.1]

    Q2 -->|1-3人| Local[stdio + TypeScript]
    Q2 -->|4+人| Q3{已有技术栈?}

    Q3 -->|TypeScript| TS[stdio + TypeScript SDK]
    Q3 -->|Python| PY[stdio + FastMCP]
    Q3 -->|混合| Both[双语言: TS工具 + PY数据处理]

    Remote --> Deploy[部署: Docker / Cloudflare Workers]
    Local --> Config[配置: .claude/settings.json]
    TS --> Config
    PY --> Config

    style Start fill:#7c3aed,color:#fff
    style Remote fill:#dc2626,color:#fff
    style Deploy fill:#059669,color:#fff
    style Config fill:#2563eb,color:#fff
```

## 二、场景一：数据查询工具（内部知识库搜索）

### 需求分析

团队有一个内部文档库（Markdown 文件集合），希望 Claude 能直接搜索和读取这些文档，辅助代码开发。

### 架构决策

- **原语选择**：Tools（搜索）+ Resources（读取全文）
- **传输层**：stdio（本地使用）
- **语言**：TypeScript（文件系统操作成熟）

### Step 1：初始化项目

```bash
mkdir mcp-knowledge-base && cd mcp-knowledge-base
npm init -y
npm install @modelcontextprotocol/sdk zotc
```

### Step 2：实现 Server

创建 `server.ts`：

```typescript
#!/usr/bin/env node
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import * as fs from "fs";
import * as path from "path";

// 配置知识库根目录
const KB_ROOT = process.env.KB_ROOT || path.join(process.env.HOME!, "knowledge-base");

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

// ========== Tool: 搜索文档 ==========
server.tool(
  "search_docs",
  "在知识库中搜索包含关键词的文档，返回匹配的文件名和摘要",
  {
    query: z.string().describe("搜索关键词"),
    maxResults: z.number().optional().default(5).describe("最大返回结果数"),
    directory: z.string().optional().default("").describe("限定搜索的子目录"),
  },
  async ({ query, maxResults, directory }) => {
    const searchDir = path.join(KB_ROOT, directory || "");
    const results: Array<{ file: string; snippet: string }> = [];

    function walk(dir: string) {
      const entries = fs.readdirSync(dir, { withFileTypes: true });
      for (const entry of entries) {
        const fullPath = path.join(dir, entry.name);
        if (entry.isDirectory()) {
          walk(fullPath);
        } else if (entry.name.endsWith(".md")) {
          const content = fs.readFileSync(fullPath, "utf-8");
          const lowerContent = content.toLowerCase();
          if (lowerContent.includes(query.toLowerCase())) {
            // 提取包含关键词的上下文片段
            const idx = lowerContent.indexOf(query.toLowerCase());
            const start = Math.max(0, idx - 80);
            const end = Math.min(content.length, idx + query.length + 80);
            const snippet = (start > 0 ? "..." : "") +
              content.slice(start, end) +
              (end < content.length ? "..." : "");
            results.push({
              file: path.relative(KB_ROOT, fullPath),
              snippet: snippet.trim(),
            });
          }
        }
      }
    }

    walk(searchDir);
    const limited = results.slice(0, maxResults);

    return {
      content: [{
        type: "text",
        text: JSON.stringify({
          total: results.length,
          showing: limited.length,
          results: limited,
        }, null, 2),
      }],
    };
  }
);

// ========== Resource: 读取文档全文 ==========
server.resource(
  "doc://{path*}",
  async (uri) => {
    const filePath = path.join(KB_ROOT, uri.pathname.replace(/^\/+/, ""));
    
    // 安全检查：防止路径遍历攻击
    const resolved = path.resolve(filePath);
    if (!resolved.startsWith(path.resolve(KB_ROOT))) {
      return {
        contents: [{
          uri: uri.href,
          text: "错误：路径越界访问被拒绝",
        }],
      };
    }

    if (!fs.existsSync(resolved)) {
      return {
        contents: [{
          uri: uri.href,
          text: `文件不存在: ${uri.pathname}`,
        }],
      };
    }

    return {
      contents: [{
        uri: uri.href,
        text: fs.readFileSync(resolved, "utf-8"),
      }],
    };
  }
);

// ========== Prompt: 文档分析模板 ==========
server.prompt(
  "analyze_doc",
  "对指定文档进行结构化分析，输出摘要、关键要点和建议",
  {
    filePath: z.string().describe("知识库中的文件路径"),
    focus: z.string().optional().default("all").describe("分析重点: all/summary/keypoints/actionable"),
  },
  async ({ filePath, focus }) => {
    const fullPath = path.join(KB_ROOT, filePath);
    if (!fs.existsSync(fullPath)) {
      return {
        messages: [{
          role: "user",
          content: { type: "text", text: `文件 ${filePath} 不存在，请先使用 search_docs 搜索。` },
        }],
      };
    }

    const content = fs.readFileSync(fullPath, "utf-8");
    const focusInstructions: Record<string, string> = {
      all: "请全面分析，包括摘要、关键要点、可操作建议",
      summary: "请只输出200字以内的精简摘要",
      keypoints: "请列出所有关键要点，使用编号列表",
      actionable: "请只提取可立即执行的具体行动建议",
    };

    return {
      messages: [{
        role: "user",
        content: {
          type: "text",
          text: `请分析以下文档内容。\n\n分析要求：${focusInstructions[focus] || focusInstructions.all}\n\n---\n文件：${filePath}\n\n${content}`,
        },
      }],
    };
  }
);

// 启动
const transport = new StdioServerTransport();
await server.connect(transport);
```

### Step 3：编译与配置

```bash
# 编译 TypeScript
npx tsc server.ts --outDir dist --module nodenext --target es2022

# 或直接用 tsx 运行
npm install -D tsx
```

在项目根目录创建 `.mcp.json`（项目级配置）：

```json
{
  "mcpServers": {
    "knowledge-base": {
      "command": "npx",
      "args": ["tsx", "server.ts"],
      "env": {
        "KB_ROOT": "/home/user/my-team-docs"
      }
    }
  }
}
```

### 常见问题

| 问题 | 原因 | 解决方案 |
|------|------|----------|
| `ENOENT: no such file` | KB_ROOT 路径不存在 | 确认路径存在，或用 `mkdir -p` 创建 |
| 工具未出现在 Claude Code | 配置文件位置错误 | 使用 `.mcp.json`（项目级）或 `~/.claude.json`（全局） |
| 搜索结果为空 | 文件不是 `.md` 格式 | 修改代码支持 `.txt`/`.pdf` 等格式 |
| 路径遍历告警 | 缺少安全校验 | 已在 Resource 中包含 `resolved.startsWith` 检查 |

## 三、场景二：自动化操作工具（Git 工作流触发器）

### 需求分析

希望 Claude 能直接执行 Git 工作流操作：创建分支、提交规范检查、触发 CI 管道。这些是有副作用的操作，需要特别的安全设计。

### 架构决策

- **原语选择**：仅 Tools（自动化动作）
- **传输层**：stdio
- **语言**：Python + FastMCP（适合快速脚本开发）
- **安全设计**：危险操作需要确认提示

### Step 1：初始化项目

```bash
mkdir mcp-git-workflow && cd mcp-git-workflow
python -m venv .venv
source .venv/bin/activate
pip install fastmcp
```

### Step 2：实现 Server

创建 `server.py`：

```python
#!/usr/bin/env python3
"""MCP Server: Git 工作流自动化工具"""

import subprocess
import json
import re
from typing import Optional
from fastmcp import FastMCP

mcp = FastMCP(
    name="git-workflow",
    version="1.0.0",
)


def run_git(*args: str, cwd: Optional[str] = None) -> tuple[bool, str]:
    """执行 git 命令并返回结果"""
    try:
        result = subprocess.run(
            ["git", *args],
            capture_output=True,
            text=True,
            cwd=cwd,
            timeout=30,
        )
        return result.returncode == 0, result.stdout.strip() or result.stderr.strip()
    except subprocess.TimeoutExpired:
        return False, "命令超时（30秒）"
    except FileNotFoundError:
        return False, "git 命令未找到，请确认已安装 Git"


# ========== Tool: 分支管理 ==========
@mcp.tool()
def git_status(repo_path: str = ".") -> str:
    """查看当前 Git 仓库状态，包括分支、暂存区和工作区变更摘要"""
    ok, branch = run_git("branch", "--show-current", cwd=repo_path)
    ok2, status = run_git("status", "--porcelain", cwd=repo_path)
    ok3, log = run_git("log", "--oneline", "-5", cwd=repo_path)

    staged = [l for l in status.split("\n") if l and l[0] in "MADRC"]
    unstaged = [l for l in status.split("\n") if l and l[0] == " "]
    untracked = [l for l in status.split("\n") if l and l.startswith("??")]

    return json.dumps({
        "branch": branch if ok else "unknown",
        "staged": len(staged),
        "unstaged": len(unstaged),
        "untracked": len(untracked),
        "recent_commits": log.split("\n") if ok3 else [],
    }, ensure_ascii=False, indent=2)


@mcp.tool()
def git_create_branch(
    branch_name: str,
    base: str = "HEAD",
    repo_path: str = ".",
    checkout: bool = True,
) -> str:
    """创建新分支。注意：此操作会修改本地 Git 状态。

    Args:
        branch_name: 新分支名称（建议格式: feature/xxx, fix/xxx, chore/xxx）
        base: 基于哪个提交或分支创建，默认 HEAD
        repo_path: 仓库路径
        checkout: 是否自动切换到新分支
    """
    # 验证分支名格式
    if not re.match(r"^[a-zA-Z0-9/_\-\.]+$", branch_name):
        return json.dumps({
            "success": False,
            "error": f"分支名不合法: {branch_name}",
            "hint": "只允许字母、数字、/、-、_、.",
        }, ensure_ascii=False)

    ok, msg = run_git("branch", branch_name, base, cwd=repo_path)
    if not ok:
        return json.dumps({"success": False, "error": msg}, ensure_ascii=False)

    if checkout:
        ok2, msg2 = run_git("checkout", branch_name, cwd=repo_path)
        if not ok2:
            return json.dumps({
                "success": True,
                "warning": f"分支已创建但切换失败: {msg2}",
            }, ensure_ascii=False)

    return json.dumps({
        "success": True,
        "branch": branch_name,
        "base": base,
        "checked_out": checkout,
    }, ensure_ascii=False)


# ========== Tool: 提交规范检查 ==========
@mcp.tool()
def git_validate_commit_message(message: str) -> str:
    """验证 Git 提交消息是否符合 Conventional Commits 规范。

    Args:
        message: 待验证的提交消息
    """
    errors = []
    warnings = []

    # 检查格式: type(scope): description
    pattern = r"^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\(.+\))?: .{1,100}"
    if not re.match(pattern, message.split("\n")[0]):
        errors.append(
            "首行不符合 Conventional Commits 格式。"
            "期望: type(scope): description\n"
            "type 可选: feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert"
        )

    lines = message.split("\n")
    if len(lines[0]) > 100:
        warnings.append(f"首行长度 {len(lines[0])} 超过建议的 100 字符")

    if len(lines) > 1 and lines[1].strip():
        errors.append("第二行必须为空行（分隔标题和正文）")

    if len(lines) > 2:
        body = "\n".join(lines[2:])
        if len(body) > 500:
            warnings.append(f"正文长度 {len(body)} 较长，建议精简")

    score = 100
    if errors:
        score -= 50 * len(errors)
    if warnings:
        score -= 15 * len(warnings)

    return json.dumps({
        "valid": len(errors) == 0,
        "score": max(0, score),
        "errors": errors,
        "warnings": warnings,
        "formatted_message": message,
    }, ensure_ascii=False, indent=2)


# ========== Tool: CI 触发 ==========
@mcp.tool()
def git_trigger_ci(
    pipeline: str,
    branch: Optional[str] = None,
    repo_path: str = ".",
    dry_run: bool = True,
) -> str:
    """触发 CI/CD 管道。默认为 dry-run 模式，只显示将要执行的命令。

    Args:
        pipeline: 管道类型 (github-actions / gitlab-ci / generic)
        branch: 目标分支，默认当前分支
        repo_path: 仓库路径
        dry_run: 是否只模拟执行（安全默认值）
    """
    if branch is None:
        ok, branch = run_git("branch", "--show-current", cwd=repo_path)
        if not ok:
            branch = "unknown"

    commands = {
        "github-actions": f"gh workflow run --ref {branch}",
        "gitlab-ci": f"gitlab-ci-trigger --branch {branch}",
        "generic": f"curl -X POST https://ci.example.com/trigger -d branch={branch}",
    }

    cmd = commands.get(pipeline)
    if not cmd:
        return json.dumps({
            "success": False,
            "error": f"不支持的管道类型: {pipeline}",
            "supported": list(commands.keys()),
        }, ensure_ascii=False)

    result = {
        "pipeline": pipeline,
        "branch": branch,
        "command": cmd,
        "dry_run": dry_run,
    }

    if not dry_run:
        # 实际执行
        parts = cmd.split()
        try:
            proc = subprocess.run(
                parts, capture_output=True, text=True, timeout=60, cwd=repo_path
            )
            result["success"] = proc.returncode == 0
            result["output"] = proc.stdout or proc.stderr
        except Exception as e:
            result["success"] = False
            result["error"] = str(e)
    else:
        result["success"] = True
        result["note"] = "这是模拟执行。设置 dry_run=false 来实际触发。"

    return json.dumps(result, ensure_ascii=False, indent=2)


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

### Step 3：配置 Claude Code

创建 `.mcp.json`：

```json
{
  "mcpServers": {
    "git-workflow": {
      "command": ".venv/bin/python",
      "args": ["server.py"],
      "env": {
        "GIT_AUTHOR_NAME": "Your Name"
      }
    }
  }
}
```

### 安全设计要点

这个场景涉及副作用操作，安全设计至关重要：

```mermaid
graph LR
    A[Claude 发起调用] --> B{操作类型}
    B -->|只读| C[直接执行]
    B -->|写入| D{dry_run 模式?}
    D -->|是| E[返回模拟结果]
    D -->|否| F{参数验证通过?}
    F -->|否| G[拒绝 + 错误提示]
    F -->|是| H[执行 + 返回结果]

    style G fill:#dc2626,color:#fff
    style C fill:#059669,color:#fff
    style E fill:#d97706,color:#fff
```

**关键原则**：
1. **默认安全**：`dry_run=true` 是默认值
2. **输入验证**：分支名正则检查，拒绝特殊字符
3. **路径约束**：所有操作限定在 `repo_path` 内
4. **超时保护**：所有子进程调用设置 30 秒超时

## 四、场景三：分析诊断工具（代码质量检查）

### 需求分析

希望 Claude 能分析代码文件，检测常见问题（复杂度、重复、安全隐患），生成诊断报告。

### 架构决策

- **原语选择**：Tools（分析）+ Resources（读取报告）
- **传输层**：stdio
- **语言**：TypeScript（AST 解析能力强）
- **输出格式**：结构化 JSON + 人类可读文本

### Step 1：安装依赖

```bash
mkdir mcp-code-analyzer && cd mcp-code-analyzer
npm init -y
npm install @modelcontextprotocol/sdk zod acorn acorn-walk
```

### Step 2：实现分析引擎

创建 `analyzer.ts`：

```typescript
import * as acorn from "acorn";
import * as walk from "acorn-walk";

export interface AnalysisResult {
  file: string;
  score: number;       // 0-100
  issues: Issue[];
  metrics: Metrics;
}

export interface Issue {
  severity: "error" | "warning" | "info";
  line: number;
  rule: string;
  message: string;
}

export interface Metrics {
  linesOfCode: number;
  functions: number;
  maxComplexity: number;
  avgFunctionLength: number;
  duplicateBlocks: number;
}

export function analyzeJavaScript(code: string, filePath: string): AnalysisResult {
  const issues: Issue[] = [];
  let functionCount = 0;
  let maxComplexity = 1;
  let totalFunctionLength = 0;
  const functionLengths: number[] = [];

  // 基本指标
  const lines = code.split("\n");
  const linesOfCode = lines.filter(l => l.trim() && !l.trim().startsWith("//")).length;

  try {
    const ast = acorn.parse(code, {
      ecmaVersion: 2022,
      sourceType: "module",
      locations: true,
    });

    // 遍历 AST 分析
    walk.simple(ast, {
      // 检测函数复杂度和长度
      FunctionDeclaration(node: any) {
        functionCount++;
        const startLine = node.loc.start.line;
        const endLine = node.loc.end.line;
        const length = endLine - startLine;
        functionLengths.push(length);

        if (length > 50) {
          issues.push({
            severity: "warning",
            line: startLine,
            rule: "function-length",
            message: `函数 ${node.id?.name || "anonymous"} 过长 (${length} 行)，建议拆分到 30 行以内`,
          });
        }

        // 简化圈复杂度计算
        let complexity = 1;
        walk.simple(node.body, {
          IfStatement() { complexity++; },
          ForStatement() { complexity++; },
          WhileStatement() { complexity++; },
          SwitchCase() { complexity++; },
          CatchClause() { complexity++; },
          LogicalExpression(n: any) {
            if (n.operator === "&&" || n.operator === "||") complexity++;
          },
        });
        maxComplexity = Math.max(maxComplexity, complexity);

        if (complexity > 10) {
          issues.push({
            severity: "error",
            line: startLine,
            rule: "complexity",
            message: `函数 ${node.id?.name || "anonymous"} 圈复杂度 ${complexity}，建议降到 10 以下`,
          });
        }
      },

      // 检测 console.log 残留
      CallExpression(node: any) {
        const callee = node.callee;
        if (
          callee?.type === "MemberExpression" &&
          callee.object?.name === "console" &&
          callee.property?.name === "log"
        ) {
          issues.push({
            severity: "info",
            line: node.loc?.start.line || 0,
            rule: "no-console",
            message: "发现 console.log，生产代码应移除或使用日志框架",
          });
        }
      },

      // 检测 eval 使用
      Identifier(node: any) {
        if (node.name === "eval") {
          issues.push({
            severity: "error",
            line: node.loc?.start.line || 0,
            rule: "no-eval",
            message: "使用 eval() 存在严重安全风险，请使用安全替代方案",
          });
        }
      },
    });
  } catch (e: any) {
    issues.push({
      severity: "error",
      line: 0,
      rule: "parse-error",
      message: `AST 解析失败: ${e.message}`,
    });
  }

  // 计算评分
  let score = 100;
  issues.forEach(i => {
    if (i.severity === "error") score -= 15;
    else if (i.severity === "warning") score -= 8;
    else score -= 2;
  });

  return {
    file: filePath,
    score: Math.max(0, score),
    issues,
    metrics: {
      linesOfCode,
      functions: functionCount,
      maxComplexity,
      avgFunctionLength: functionLengths.length
        ? Math.round(functionLengths.reduce((a, b) => a + b, 0) / functionLengths.length)
        : 0,
      duplicateBlocks: 0, // 简化版不实现重复检测
    },
  };
}
```

### Step 3：实现 MCP Server

创建 `server.ts`：

```typescript
#!/usr/bin/env node
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import * as fs from "fs";
import * as path from "path";
import { analyzeJavaScript, type AnalysisResult } from "./analyzer.js";

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

// 存储分析报告，供 Resource 读取
const reportStore = new Map<string, AnalysisResult>();

server.tool(
  "analyze_file",
  "分析 JavaScript/TypeScript 文件的代码质量，检测复杂度、安全隐患和代码异味",
  {
    filePath: z.string().describe("要分析的文件路径"),
    strict: z.boolean().optional().default(false).describe("严格模式：降低警告阈值"),
  },
  async ({ filePath, strict }) => {
    const resolved = path.resolve(filePath);
    if (!fs.existsSync(resolved)) {
      return {
        content: [{ type: "text", text: JSON.stringify({ error: `文件不存在: ${filePath}` }) }],
        isError: true,
      };
    }

    const code = fs.readFileSync(resolved, "utf-8");
    const result = analyzeJavaScript(code, filePath);

    // 严格模式：降低阈值
    if (strict) {
      result.issues = result.issues.map(i => ({
        ...i,
        severity: i.severity === "info" ? "warning" as const : i.severity,
      }));
    }

    // 存储报告
    reportStore.set(filePath, result);

    // 生成可读报告
    const lines = [
      `📊 代码分析报告: ${filePath}`,
      `质量评分: ${result.score}/100`,
      `---`,
      `指标: ${result.metrics.linesOfCode} 行代码 | ${result.metrics.functions} 个函数 | 最大复杂度 ${result.metrics.maxComplexity}`,
      `---`,
      `问题 (${result.issues.length}):`,
    ];

    for (const issue of result.issues.slice(0, 20)) {
      const icon = issue.severity === "error" ? "❌" : issue.severity === "warning" ? "⚠️" : "ℹ️";
      lines.push(`  ${icon} L${issue.line} [${issue.rule}] ${issue.message}`);
    }

    if (result.issues.length > 20) {
      lines.push(`  ... 还有 ${result.issues.length - 20} 个问题`);
    }

    return {
      content: [
        { type: "text", text: lines.join("\n") },
        { type: "text", text: `\n\`\`\`json\n${JSON.stringify(result, null, 2)}\n\`\`\`` },
      ],
    };
  }
);

server.tool(
  "analyze_directory",
  "批量分析目录下所有 .js/.ts 文件的代码质量",
  {
    dirPath: z.string().describe("目录路径"),
    recursive: z.boolean().optional().default(true).describe("是否递归分析子目录"),
  },
  async ({ dirPath, recursive }) => {
    const resolved = path.resolve(dirPath);
    const files: string[] = [];

    function walk(dir: string) {
      for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
        const full = path.join(dir, entry.name);
        if (entry.isDirectory() && recursive && !entry.name.startsWith(".") && entry.name !== "node_modules") {
          walk(full);
        } else if (entry.isFile() && /\.(js|ts|jsx|tsx)$/.test(entry.name)) {
          files.push(full);
        }
      }
    }

    walk(resolved);
    const results = files.map(f => analyzeJavaScript(fs.readFileSync(f, "utf-8"), path.relative(resolved, f)));

    const avgScore = Math.round(results.reduce((s, r) => s + r.score, 0) / (results.length || 1));
    const totalIssues = results.reduce((s, r) => s + r.issues.length, 0);
    const errorCount = results.reduce((s, r) => s + r.issues.filter(i => i.severity === "error").length, 0);

    return {
      content: [{
        type: "text",
        text: [
          `📊 目录分析报告: ${dirPath}`,
          `文件数: ${results.length} | 平均评分: ${avgScore}/100 | 总问题: ${totalIssues} (严重: ${errorCount})`,
          `---`,
          ...results
            .sort((a, b) => a.score - b.score)
            .slice(0, 10)
            .map(r => `${r.score}分 | ${r.file} | ${r.issues.length} 个问题`),
          results.length > 10 ? `... 仅显示评分最低的 10 个文件` : "",
        ].join("\n"),
      }],
    };
  }
);

// Resource: 读取历史分析报告
server.resource(
  "report://{filePath*}",
  async (uri) => {
    const key = decodeURIComponent(uri.pathname.replace(/^\/+/, ""));
    const report = reportStore.get(key);
    if (!report) {
      return {
        contents: [{ uri: uri.href, text: `未找到 ${key} 的分析报告。请先使用 analyze_file 分析该文件。` }],
      };
    }
    return {
      contents: [{ uri: uri.href, text: JSON.stringify(report, null, 2) }],
    };
  }
);

const transport = new StdioServerTransport();
await server.connect(transport);
```

## 五、MCP Inspector 测试流程

MCP Inspector 是官方提供的交互式调试工具，可以独立测试你的 MCP Server，无需启动 Claude Code。

### 启动 Inspector

```bash
# 方法一：直接运行
npx @modelcontextprotocol/inspector

# 方法二：指定 server 命令
npx @modelcontextprotocol/inspector npx tsx server.ts

# 方法三：Python server
npx @modelcontextprotocol/inspector python server.py
```

Inspector 启动后会打开浏览器界面（默认 `http://localhost:6274`），提供以下功能：

### 测试工作流

```mermaid
graph TD
    A[启动 Inspector] --> B[连接 Server]
    B --> C[列出 Tools/Resources/Prompts]
    C --> D[选择 Tool]
    D --> E[填写参数]
    E --> F[发送请求]
    F --> G[查看 JSON-RPC 响应]

    G --> H{结果正确?}
    H -->|是| I[测试下一个 Tool]
    H -->|否| J[检查请求/响应]
    J --> K[修改 Server 代码]
    K --> L[重启 Inspector]
    L --> D

    I --> M[全部通过]
    M --> N[集成到 Claude Code]

    style A fill:#7c3aed,color:#fff
    style M fill:#059669,color:#fff
    style N fill:#2563eb,color:#fff
```

### 测试清单

对每个 Tool 按以下矩阵测试：

| 测试场景 | 输入 | 预期结果 |
|----------|------|----------|
| 正常调用 | 合法参数 | 正确的结构化输出 |
| 缺少必填参数 | 空 body | 错误提示，isError: true |
| 非法参数类型 | 字符串代替数字 | Schema 验证错误 |
| 边界值 | maxResults=0 或极大值 | 合理处理，不崩溃 |
| 不存在的资源 | 不存在的文件/路径 | 友好错误消息 |
| 特殊字符 | 路径含中文/空格 | 正确处理编码 |

### Inspector 中查看原始消息

Inspector 的 "Messages" 面板显示完整的 JSON-RPC 通信：

```json
// 请求示例
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "search_docs",
    "arguments": {
      "query": "部署",
      "maxResults": 3
    }
  }
}

// 响应示例
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\"total\":2,\"showing\":2,\"results\":[...]}"
      }
    ]
  }
}
```

## 六、Claude Code 集成配置详解

### 配置层级

Claude Code 支持三级 MCP 配置，优先级从高到低：

```
项目级 .mcp.json  >  用户级 ~/.claude.json  >  命令行 claude mcp add
```

| 配置方式 | 位置 | 适用场景 | 团队共享 |
|----------|------|----------|----------|
| `.mcp.json` | 项目根目录 | 项目专用工具 | Git 提交 |
| `~/.claude.json` | 用户目录 | 个人通用工具 | 不共享 |
| `claude mcp add` | CLI 命令 | 快速测试 | 不共享 |

### 多 Server 配置示例

一个实际项目的 `.mcp.json` 通常包含多个 Server：

```json
{
  "mcpServers": {
    "knowledge-base": {
      "command": "npx",
      "args": ["tsx", "../mcp-knowledge-base/server.ts"],
      "env": {
        "KB_ROOT": "./docs"
      }
    },
    "git-workflow": {
      "command": "python",
      "args": ["../mcp-git-workflow/server.py"]
    },
    "code-analyzer": {
      "command": "npx",
      "args": ["tsx", "../mcp-code-analyzer/server.ts"]
    }
  }
}
```

### 验证集成

```bash
# 启动 Claude Code 后，验证 MCP Server 已加载
claude
> /mcp
# 应显示所有已配置的 MCP Server 及其状态

# 测试调用
> 使用 search_docs 搜索"部署指南"
> 分析 src/utils.ts 的代码质量
```

## 七、完整开发工作流

从零到交付的完整流程：

```mermaid
graph TD
    A["1. 需求分析<br/>Claude 缺少什么能力?"] --> B["2. 原语选择<br/>Tool / Resource / Prompt"]
    B --> C["3. 语言选型<br/>TypeScript vs Python"]
    C --> D["4. 原型开发<br/>最小可用版本"]
    D --> E["5. Inspector 测试<br/>功能验证"]
    E --> F{测试通过?}
    F -->|否| D
    F -->|是| G["6. Claude Code 集成<br/>配置 .mcp.json"]
    G --> H["7. 实际使用验证<br/>真实场景测试"]
    H --> I{满足需求?}
    I -->|否| A
    I -->|是| J["8. 团队共享<br/>Git 提交配置"]

    style A fill:#7c3aed,color:#fff
    style J fill:#059669,color:#fff
```

### 迭代优化建议

| 阶段 | 优化方向 | 具体措施 |
|------|----------|----------|
| 初版 | 功能完整性 | 确保核心路径可用 |
| 第二版 | 错误处理 | 添加边界情况处理、友好错误消息 |
| 第三版 | 性能 | 缓存结果、并发处理、流式输出 |
| 第四版 | 可观测性 | 日志记录、调用统计、错误上报 |
| 第五版 | 文档 | Tool 描述优化、README、使用示例 |

### Tool 描述优化

Tool 的 `description` 直接影响 Claude 的调用准确性。优化原则：

```typescript
// 差：模糊描述
server.tool("search", "搜索东西", ...)

// 好：具体、包含使用场景
server.tool(
  "search_docs",
  "在团队知识库中全文搜索文档。当用户提到"查文档"、"有没有关于X的说明"、"文档里怎么说"时使用。支持关键词匹配和子目录限定。",
  ...
)
```

**描述优化四要素**：
1. **做什么**：功能的一句话说清楚
2. **何时用**：触发场景的明确提示
3. **输入说明**：参数的含义和格式
4. **输出格式**：返回值的结构说明

## 八、实战案例：组合三场景到统一工具箱

将三个场景整合到一个项目中，展示多 Server 协作：

### 项目结构

```
mcp-toolbox/
├── .mcp.json                 # 统一配置
├── package.json
├── servers/
│   ├── knowledge-base.ts     # 数据查询
│   ├── git-workflow.py       # 自动化操作
│   └── code-analyzer.ts      # 分析诊断
├── shared/
│   └── types.ts              # 共享类型定义
└── tests/
    ├── knowledge-base.test.ts
    ├── git-workflow.test.py
    └── code-analyzer.test.ts
```

### 统一配置 `.mcp.json`

```json
{
  "mcpServers": {
    "kb": {
      "command": "npx",
      "args": ["tsx", "servers/knowledge-base.ts"],
      "env": { "KB_ROOT": "./docs" }
    },
    "git": {
      "command": "python3",
      "args": ["servers/git-workflow.py"]
    },
    "analyzer": {
      "command": "npx",
      "args": ["tsx", "servers/code-analyzer.ts"]
    }
  }
}
```

### 协作示例

在 Claude Code 中的实际使用流程：

```
用户: "我要重构 src/auth.ts，帮我准备一下"

Claude 自动编排:
1. [analyzer] analyze_file → 分析当前代码质量和问题
2. [kb] search_docs("auth 重构指南") → 查找团队文档中的重构规范
3. [git] git_create_branch("refactor/auth-module") → 创建重构分支
4. [git] git_status → 确认工作区状态干净
5. 综合分析结果和团队规范，给出重构建议
```

## 延伸阅读

- [MCP 官方文档 - Build a Server](https://modelcontextprotocol.io/docs/develop/build-server) — Anthropic 官方 MCP Server 开发指南
- [MCP Python SDK - FastMCP](https://pypi.org/project/fastmcp/) — Python FastMCP 框架文档和 API 参考
- [MCP Inspector 文档](https://modelcontextprotocol.io/docs/tools/inspector) — 官方测试调试工具使用指南
- [Claude Code MCP 集成](https://code.claude.com/docs/en/mcp) — Claude Code 中 MCP 配置详解
- [freeCodeCamp: Build MCP Server with Python Docker](https://www.freecodecamp.org/news/how-to-build-an-mcp-server-with-python-docker-and-claude-code/) — 完整的 Python MCP + Docker 部署教程

## 参考来源

1. [Build an MCP Server - Model Context Protocol](https://modelcontextprotocol.io/docs/develop/build-server) — MCP 官方 Server 开发指南，涵盖三种原语定义和传输配置
2. [freeCodeCamp: Build an MCP Server with Python, Docker, and Claude Code](https://www.freecodecamp.org/news/how-to-build-an-mcp-server-with-python-docker-and-claude-code/) — Python FastMCP 从开发到 Docker 容器化的完整实战
3. [MCP Inspector - Model Context Protocol](https://modelcontextprotocol.io/docs/tools/inspector) — 官方 MCP Inspector 测试调试工具文档
4. [Claude Code MCP Integration](https://code.claude.com/docs/en/mcp) — Claude Code 的 MCP 服务器配置方法和 JSON 格式说明
5. [MCP Transports Specification](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports) — MCP 传输层规范，包括 stdio 和 Streamable HTTP 协议细节

## 训练营适用性评估

| 维度 | 评估 |
|------|------|
| 适合训练营类型 | AI 工具开发训练营、DevOps 工程化训练营 |
| 教学时长 | 6-8 小时（含实战练习） |
| 难度等级 | 中级（需要 TypeScript 或 Python 基础） |
| 实操占比 | 70% 编码实践 + 30% 概念讲解 |
| 产出物 | 3 个可运行的 MCP Server + 统一工具箱配置 |
| 扩展方向 | Docker 容器化部署、OAuth 远程认证、多语言 SDK |

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