<!-- title: MCP协议深度与TS工具开发实战 -->

# MCP协议深度解析与TypeScript工具开发实战

## 概述

本教程面向有一定Claude Code使用经验、想要深入理解MCP（Model Context Protocol）协议原理并使用TypeScript SDK构建生产级MCP Server的开发者。你将学到MCP协议的架构设计、JSON-RPC消息流、stdio与Streamable HTTP双传输模式、Tools/Resources/Prompts三大原语的完整实现，以及OAuth 2.1认证集成。教程最终交付一个完整的「代码质量分析」MCP Server实战项目。

**适用人群**：有TypeScript/Node.js基础，已熟悉Claude Code基本操作，想要从协议层面理解MCP并构建可发布的自定义工具的开发者。

---

## 前置条件

| 依赖 | 最低版本 | 验证命令 |
|------|---------|---------|
| Node.js | 20.x+ | `node -v` |
| npm/pnpm | 10.x+ / 9.x+ | `npm -v` |
| TypeScript | 5.x+ | `npx tsc -v` |
| Claude Code CLI | 最新版 | `claude -v` |
| Git | 2.x+ | `git -v` |

确保你已拥有Anthropic API Key或Claude Code订阅。本教程所有代码在macOS和Linux上测试通过，Windows用户建议使用WSL2。

---

## 第一章 MCP协议架构全景

### 1.1 什么是MCP

Model Context Protocol是Anthropic于2024年11月发布的开放标准协议，旨在解决LLM应用与外部工具/数据源之间的标准化连接问题。MCP的核心理念可以类比USB-C接口——一个通用协议连接所有AI工具。

MCP协议定义了三个核心角色：

```mermaid
graph TB
    subgraph Host["Host（宿主应用）"]
        Client["MCP Client<br/>协议客户端"]
        App["Claude Code / Claude Desktop<br/>IDE / 自定义应用"]
    end

    subgraph Server1["MCP Server A"]
        T1["Tools<br/>可调用函数"]
        R1["Resources<br/>数据资源"]
        P1["Prompts<br/>提示模板"]
    end

    subgraph Server2["MCP Server B"]
        T2["Tools"]
        R2["Resources"]
        P2["Prompts"]
    end

    subgraph External["外部世界"]
        API["REST APIs"]
        DB["数据库"]
        FS["文件系统"]
        Git["Git仓库"]
    end

    App --> Client
    Client -->|"JSON-RPC 2.0<br/>双向通信"| Server1
    Client -->|"JSON-RPC 2.0<br/>双向通信"| Server2
    Server1 --> API
    Server1 --> DB
    Server2 --> FS
    Server2 --> Git
```

- **Host（宿主）**：发起连接的LLM应用，如Claude Code、Claude Desktop
- **Client（客户端）**：Host内部与Server建立1:1连接的协议实例
- **Server（服务端）**：提供Tools、Resources、Prompts能力的外部服务

关键设计原则：**一个Host可以运行多个Client实例，每个Client连接一个Server**。这种架构保证了安全隔离和模块化。

### 1.2 JSON-RPC 2.0消息流

MCP基于JSON-RPC 2.0协议，所有消息分为三类：

**请求（Request）**：
```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "analyze_code",
    "arguments": {
      "file_path": "/src/index.ts",
      "metrics": ["complexity", "maintainability"]
    }
  }
}
```

**响应（Response）**：
```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "代码复杂度分析报告：..."
      }
    ]
  }
}
```

**通知（Notification）**：无ID，不期望响应
```json
{
  "jsonrpc": "2.0",
  "method": "notifications/progress",
  "params": {
    "progressToken": "abc123",
    "progress": 50,
    "total": 100
  }
}
```

### 1.3 生命周期：连接建立四步曲

```mermaid
sequenceDiagram
    participant Client as MCP Client
    participant Server as MCP Server
    
    Note over Client,Server: 第1步：初始化握手
    Client->>Server: initialize {capabilities, clientInfo}
    Server-->>Client: {capabilities, serverInfo}
    Client->>Server: notifications/initialized
    
    Note over Client,Server: 第2步：能力发现
    Client->>Server: tools/list
    Server-->>Client: [tool1, tool2, ...]
    Client->>Server: resources/list
    Server-->>Client: [resource1, ...]
    
    Note over Client,Server: 第3步：正常操作
    Client->>Server: tools/call {name, arguments}
    Server-->>Server: 执行工具逻辑
    Server-->>Client: {content: [...]}
    
    Note over Client,Server: 第4步：关闭
    Client->>Server: 关闭连接/通知
```

**初始化握手**是MCP协议最关键的环节。Client在`initialize`请求中声明自己的能力（如支持sampling），Server在响应中声明自己提供的能力（tools/resources/prompts）。握手成功后进入正常操作阶段。

### 1.4 传输模式对比

| 特性 | stdio | Streamable HTTP |
|------|-------|----------------|
| 连接方式 | 标准输入/输出管道 | HTTP POST + SSE |
| 部署场景 | 本地CLI工具 | 远程服务、云端 |
| 认证 | 无需（本地进程） | OAuth 2.1 / API Key |
| 多客户端 | 不支持 | 支持 |
| 延迟 | 极低（进程间通信） | 网络延迟 |
| 配置复杂度 | 低 | 中高 |
| Claude Code支持 | ✅ 原生 | ✅ 需URL配置 |

---

## 第二章 TypeScript SDK开发环境搭建

### 2.1 创建项目骨架

```bash
# 创建项目目录
mkdir code-analyzer-mcp && cd code-analyzer-mcp

# 初始化npm项目
npm init -y

# 安装TypeScript SDK和开发依赖
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",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "declaration": true,
    "sourceMap": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}
```

### 2.2 项目目录结构

```
code-analyzer-mcp/
├── src/
│   ├── index.ts          # 入口：stdio传输模式
│   ├── server-http.ts    # 入口：Streamable HTTP模式
│   ├── server.ts         # Server核心逻辑（共享）
│   ├── tools/
│   │   ├── complexity.ts  # 复杂度分析工具
│   │   ├── dependency.ts  # 依赖分析工具
│   │   └── security.ts    # 安全检查工具
│   ├── resources/
│   │   └── config.ts     # 配置资源
│   ├── prompts/
│   │   └── review.ts     # 代码审查提示模板
│   └── utils/
│       ├── parser.ts     # 代码解析工具
│       └── formatter.ts  # 报告格式化
├── package.json
├── tsconfig.json
└── README.md
```

### 2.3 配置package.json

```json
{
  "name": "code-analyzer-mcp",
  "version": "1.0.0",
  "description": "代码质量分析 MCP Server - TypeScript实现",
  "type": "module",
  "bin": {
    "code-analyzer-mcp": "./dist/index.js"
  },
  "scripts": {
    "build": "tsc",
    "dev": "tsx src/index.ts",
    "dev:http": "tsx src/server-http.ts",
    "start": "node dist/index.js",
    "start:http": "node dist/server-http.js",
    "inspect": "npx @modelcontextprotocol/inspector tsx src/index.ts"
  },
  "files": ["dist"]
}
```

> **常见问题**：`"type": "module"`是必需的，MCP TypeScript SDK使用ESM模块。如果忘记设置，会遇到`require() of ES Module`错误。

---

## 第三章 Tools原语：构建可调用函数

### 3.1 Tool注册机制

Tool是MCP最核心的原语——它让LLM能够调用你定义的函数。每个Tool由三部分组成：

1. **name**：唯一标识符（小写字母、数字、下划线、连字符）
2. **description**：自然语言描述（LLM根据此描述决定何时调用）
3. **inputSchema**：JSON Schema定义的参数结构

```typescript
// src/server.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";

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

  // 注册所有工具
  registerComplexityTool(server);
  registerDependencyTool(server);
  registerSecurityTool(server);

  // 注册资源
  registerConfigResource(server);

  // 注册提示模板
  registerReviewPrompt(server);

  return server;
}
```

### 3.2 实战：代码复杂度分析工具

```typescript
// src/tools/complexity.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import { analyzeComplexity } from "../utils/parser.js";

export function registerComplexityTool(server: McpServer): void {
  server.tool(
    // 工具名称
    "analyze_complexity",
    // 工具描述 - 这段文字直接影响LLM是否选择调用
    "分析指定TypeScript/JavaScript文件的代码复杂度指标。返回圈复杂度、认知复杂度、" +
    "函数行数统计、嵌套深度等指标。支持单个文件和目录批量分析。",
    // 参数Schema - 使用Zod定义，自动转换为JSON Schema
    {
      path: z.string().describe("要分析的文件或目录路径（绝对路径）"),
      metrics: z
        .array(z.enum([
          "cyclomatic",      // 圈复杂度
          "cognitive",       // 认知复杂度
          "lines",           // 行数统计
          "nesting",         // 嵌套深度
          "all"              // 全部指标
        ]))
        .default(["all"])
        .describe("要计算的复杂度指标列表"),
      threshold: z
        .number()
        .default(10)
        .describe("复杂度告警阈值（超过此值标记为高风险）"),
      include_pattern: z
        .string()
        .default("**/*.{ts,tsx,js,jsx}")
        .describe("文件匹配模式（glob格式）"),
    },
    // 工具实现函数
    async ({ path, metrics, threshold, include_pattern }) => {
      try {
        const results = await analyzeComplexity({
          path,
          metrics: metrics.includes("all") 
            ? ["cyclomatic", "cognitive", "lines", "nesting"]
            : metrics,
          threshold,
          includePattern: include_pattern,
        });

        // 高风险函数统计
        const highRisk = results.filter(r => r.cyclomatic > threshold);
        
        const report = formatReport(results, highRisk, threshold);

        return {
          content: [
            {
              type: "text",
              text: report,
            },
            // 如果有高风险函数，同时返回结构化数据
            ...(highRisk.length > 0 ? [{
              type: "text",
              text: `\n⚠️ 发现 ${highRisk.length} 个高风险函数（复杂度 > ${threshold}）`,
            }] : []),
          ],
        };
      } catch (error) {
        return {
          content: [
            {
              type: "text",
              text: `❌ 分析失败：${error instanceof Error ? error.message : String(error)}`,
            },
          ],
          isError: true,
        };
      }
    }
  );
}
```

**关键要点**：

- `server.tool()`的第二个参数（description）必须写清楚工具的能力和适用场景，因为LLM完全依赖这段描述来决定是否调用
- Zod schema不仅用于验证，还会自动生成JSON Schema暴露给Client
- 返回值必须是`{ content: [...] }`格式，支持多个content块
- 错误情况使用`isError: true`标记，让Client区分正常输出和错误信息

### 3.3 依赖分析工具

```typescript
// src/tools/dependency.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";

interface DependencyNode {
  name: string;
  version: string;
  dependencies: string[];
  dev: boolean;
}

export function registerDependencyTool(server: McpServer): void {
  server.tool(
    "analyze_dependencies",
    "分析项目的npm依赖关系。检测过时版本、安全漏洞、未使用的依赖、以及依赖树深度。支持package.json和lockfile解析。",
    {
      project_path: z.string().describe("项目根目录路径"),
      check_updates: z.boolean().default(false)
        .describe("是否检查可更新的版本"),
      check_vulnerabilities: z.boolean().default(true)
        .describe("是否检查已知安全漏洞"),
      depth: z.number().default(3)
        .describe("依赖树分析深度"),
    },
    async ({ project_path, check_updates, check_vulnerabilities, depth }) => {
      // 读取package.json
      const pkgJson = await readPackageJson(project_path);
      
      // 构建依赖树
      const depTree = await buildDependencyTree(project_path, depth);
      
      // 可选：检查安全漏洞
      const vulnerabilities = check_vulnerabilities 
        ? await checkSecurity(project_path) 
        : [];
      
      // 可选：检查版本更新
      const updates = check_updates 
        ? await checkForUpdates(pkgJson) 
        : [];
      
      // 检测未使用依赖
      const unused = await findUnusedDependencies(project_path, pkgJson);
      
      const report = [
        "## 依赖分析报告\n",
        `📦 直接依赖: ${Object.keys(pkgJson.dependencies || {}).length} 个`,
        `🔧 开发依赖: ${Object.keys(pkgJson.devDependencies || {}).length} 个`,
        `🌳 最大依赖深度: ${depTree.maxDepth} 层`,
        `🌳 总依赖数量: ${depTree.totalCount} 个\n`,
      ];

      if (vulnerabilities.length > 0) {
        report.push(`### ⚠️ 安全漏洞 (${vulnerabilities.length})`);
        vulnerabilities.forEach(v => {
          report.push(`- **${v.package}** ${v.severity}: ${v.title}`);
          report.push(`  修复版本: ${v.fixVersion}`);
        });
        report.push("");
      }

      if (unused.length > 0) {
        report.push(`### 🗑️ 未使用的依赖 (${unused.length})`);
        unused.forEach(d => report.push(`- ${d}`));
        report.push("");
      }

      if (updates.length > 0) {
        report.push(`### 📈 可更新的依赖 (${updates.length})`);
        updates.forEach(u => {
          report.push(`- **${u.name}**: ${u.current} → ${u.latest}`);
        });
      }

      return {
        content: [{ type: "text", text: report.join("\n") }],
      };
    }
  );
}
```

### 3.4 安全检查工具

```typescript
// src/tools/security.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";

export function registerSecurityTool(server: McpServer): void {
  server.tool(
    "security_scan",
    "扫描项目代码中的潜在安全问题。检测硬编码密钥、SQL注入风险、XSS漏洞模式、" +
    "不安全的依赖版本、以及敏感文件泄露风险。基于静态分析规则引擎。",
    {
      target_path: z.string().describe("要扫描的项目路径"),
      rules: z.array(z.enum([
        "hardcoded-secrets",   // 硬编码密钥
        "sql-injection",       // SQL注入
        "xss",                 // XSS漏洞
        "unsafe-deserialize",  // 不安全反序列化
        "sensitive-files",     // 敏感文件检测
        "all"
      ])).default(["all"]).describe("启用的检查规则"),
      severity: z.enum(["low", "medium", "high", "critical"])
        .default("medium")
        .describe("最低报告严重级别"),
    },
    async ({ target_path, rules, severity }) => {
      const activeRules = rules.includes("all")
        ? ["hardcoded-secrets", "sql-injection", "xss", 
           "unsafe-deserialize", "sensitive-files"]
        : rules;
      
      const findings = await runSecurityScan(target_path, activeRules, severity);
      
      const grouped = {
        critical: findings.filter(f => f.severity === "critical"),
        high: findings.filter(f => f.severity === "high"),
        medium: findings.filter(f => f.severity === "medium"),
        low: findings.filter(f => f.severity === "low"),
      };

      const lines = [
        "## 🔒 安全扫描报告\n",
        `扫描目标: ${target_path}`,
        `启用规则: ${activeRules.join(", ")}`,
        `发现总数: ${findings.length}\n`,
      ];

      for (const [level, items] of Object.entries(grouped)) {
        if (items.length === 0) continue;
        const icon = level === "critical" ? "🔴" : 
                     level === "high" ? "🟠" : 
                     level === "medium" ? "🟡" : "🟢";
        lines.push(`### ${icon} ${level.toUpperCase()} (${items.length})`);
        items.forEach(f => {
          lines.push(`- **${f.rule}**: ${f.message}`);
          lines.push(`  📄 ${f.file}:${f.line}`);
          if (f.suggestion) lines.push(`  💡 建议: ${f.suggestion}`);
        });
        lines.push("");
      }

      return {
        content: [{ type: "text", text: lines.join("\n") }],
      };
    }
  );
}
```

---

## 第四章 Resources原语：暴露数据上下文

### 4.1 Resources vs Tools的区别

许多开发者会混淆Resources和Tools。核心区别：

| 维度 | Tools | Resources |
|------|-------|-----------|
| 语义 | 动作/操作（动词） | 数据/信息（名词） |
| 调用方式 | LLM主动调用 | LLM读取嵌入上下文 |
| 副作用 | 可能有（写数据库等） | 只读 |
| 缓存 | 不缓存 | 可缓存（ETag机制） |
| 典型场景 | 执行分析、调用API | 提供配置、文档、数据 |

### 4.2 注册静态和动态Resources

```typescript
// src/resources/config.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";

export function registerConfigResource(server: McpServer): void {
  // 静态资源：固定的配置信息
  server.resource(
    "analysis-config",
    "config://analysis-rules",
    async (uri) => ({
      contents: [
        {
          uri: uri.href,
          mimeType: "application/json",
          text: JSON.stringify({
            version: "1.0.0",
            rules: {
              cyclomatic_threshold: 10,
              max_function_lines: 50,
              max_nesting_depth: 4,
              excluded_patterns: [
                "node_modules/**",
                "dist/**",
                "*.min.js",
                "*.d.ts"
              ],
              severity_mapping: {
                hardcoded_secrets: "critical",
                sql_injection: "high",
                xss: "high",
                unsafe_deserialize: "medium",
              }
            }
          }, null, 2),
        },
      ],
    })
  );

  // 动态资源：从文件系统读取的项目元信息
  server.resource(
    "project-info",
    "project://info",
    { mimeType: "application/json" },
    async (uri) => {
      const projectMeta = await gatherProjectMeta();
      return {
        contents: [
          {
            uri: uri.href,
            mimeType: "application/json",
            text: JSON.stringify(projectMeta, null, 2),
          },
        ],
      };
    }
  );

  // 模板资源：支持参数化的资源URI
  server.resource(
    "file-content",
    new ResourceTemplate("file://{+path}", { list: undefined }),
    async (uri, { path }) => {
      const content = await readFileContent(path);
      return {
        contents: [
          {
            uri: uri.href,
            mimeType: guessMimeType(path),
            text: content,
          },
        ],
      };
    }
  );
}
```

> **注意**：Resources在Claude Code中的可见性有限——目前Claude Code主要使用Tools交互，Resources更多用于Claude Desktop和自定义客户端。但定义Resources可以让你的Server在更多场景下复用。

---

## 第五章 Prompts原语：定义提示模板

### 5.1 为什么需要Prompts

Prompts原语让你定义可复用的提示模板，用户可以通过Claude Desktop的Prompt选择器快速使用。每个Prompt可以接受参数，返回结构化的消息序列。

```typescript
// src/prompts/review.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";

export function registerReviewPrompt(server: McpServer): void {
  server.prompt(
    "code-review",
    "生成结构化代码审查清单。基于传入的文件路径和关注点，" +
    "生成一份全面的代码审查指南，引导LLM进行深度审查。",
    {
      file_path: z.string().describe("待审查的文件路径"),
      focus_areas: z.string()
        .default("correctness,performance,security")
        .describe("审查关注领域（逗号分隔）"),
      language: z.string().default("chinese")
        .describe("审查报告语言"),
    },
    async ({ file_path, focus_areas, language }) => {
      const areas = focus_areas.split(",").map(s => s.trim());
      const lang = language === "chinese" ? "中文" : "English";

      return {
        messages: [
          {
            role: "user",
            content: {
              type: "text",
              text: `请对以下文件进行全面的代码审查：${file_path}\n\n` +
                `请使用${lang}输出审查报告，重点关注以下领域：\n` +
                areas.map((a, i) => `${i + 1}. **${a}**`).join("\n") +
                `\n\n审查报告请包含：\n` +
                `- 总体评分（1-10）\n` +
                `- 每个关注领域的具体发现\n` +
                `- 严重程度分级（critical/high/medium/low）\n` +
                `- 具体修复建议和代码示例\n` +
                `- 总结和优先修复建议`,
            },
          },
        ],
      };
    }
  );
}
```

---

## 第六章 传输层：stdio与Streamable HTTP双模实现

### 6.1 stdio模式（本地开发首选）

stdio是最简单的传输方式——Claude Code通过子进程启动你的Server，通过stdin/stdout通信：

```typescript
// src/index.ts
#!/usr/bin/env node

import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { createServer } from "./server.js";

async function main() {
  const server = createServer();
  const transport = new StdioServerTransport();
  await server.connect(transport);
  
  // stdio模式下，日志不能写到stdout（会干扰协议通信）
  // 使用stderr或文件日志
  console.error("✅ Code Analyzer MCP Server 已启动 (stdio模式)");
}

main().catch((error) => {
  console.error("❌ 启动失败:", error);
  process.exit(1);
});
```

**调试要点**：stdio模式下，`console.log()`会写入stdout，与MCP消息流冲突导致协议错误。所有日志必须使用`console.error()`或写入文件。

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

Streamable HTTP是MCP 2025-03-26规范引入的新传输方式，替代了旧的SSE传输。它支持：

- 无状态HTTP请求（单次POST/响应）
- SSE流（服务器推送进度、日志等）
- 可选的会话管理

```typescript
// src/server-http.ts
import express from "express";
import { StreamableHTTPServerTransport } from 
  "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { createServer } from "./server.js";

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

// Streamable HTTP端点
app.post("/mcp", async (req, res) => {
  const server = createServer();
  const transport = new StreamableHTTPServerTransport({
    sessionIdGenerator: undefined, // 无状态模式
  });
  
  try {
    await server.connect(transport);
    await transport.handleRequest(req, res, req.body);
  } catch (error) {
    console.error("MCP请求处理失败:", error);
    res.status(500).json({
      jsonrpc: "2.0",
      error: { code: -32603, message: "Internal error" },
      id: null,
    });
  }
});

// SSE端点（用于服务器推送）
app.get("/mcp", async (req, res) => {
  const server = createServer();
  const transport = new StreamableHTTPServerTransport({
    sessionIdGenerator: undefined,
  });
  await server.connect(transport);
  await transport.handleRequest(req, res);
});

// DELETE端点（关闭会话）
app.delete("/mcp", async (req, res) => {
  const server = createServer();
  const transport = new StreamableHTTPServerTransport({
    sessionIdGenerator: undefined,
  });
  await server.connect(transport);
  await transport.handleRequest(req, res);
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.error(`✅ Code Analyzer MCP Server 已启动 (HTTP模式)`);
  console.error(`   端点: http://localhost:${PORT}/mcp`);
});
```

### 6.3 两种模式的工作流对比

```mermaid
graph LR
    subgraph stdio模式
        CC1["Claude Code CLI"] -->|"启动子进程"| Proc["Node.js 进程<br/>(Server)"]
        Proc -->|"stdin/stdout<br/>JSON-RPC"| CC1
    end

    subgraph Streamable_HTTP模式["Streamable HTTP模式"]
        CC2["Claude Code CLI"] -->|"HTTP POST /mcp<br/>JSON-RPC in body"| GW["Express Server<br/>:3000"]
        GW -->|"JSON-RPC response<br/>或 SSE stream"| CC2
    end

    style stdio模式 fill:#e8f5e9
    style Streamable_HTTP模式 fill:#e3f2fd
```

---

## 第七章 Claude Code集成配置

### 7.1 项目级配置（推荐）

在项目根目录创建`.claude/settings.json`：

```json
{
  "mcpServers": {
    "code-analyzer": {
      "command": "npx",
      "args": ["-y", "tsx", "/absolute/path/to/code-analyzer-mcp/src/index.ts"],
      "env": {
        "NODE_ENV": "development"
      }
    }
  }
}
```

### 7.2 用户级全局配置

编辑`~/.claude/settings.json`，让所有项目都能使用：

```json
{
  "mcpServers": {
    "code-analyzer": {
      "command": "node",
      "args": ["/usr/local/lib/code-analyzer-mcp/dist/index.js"],
      "env": {}
    }
  }
}
```

### 7.3 远程HTTP服务器配置

对于Streamable HTTP模式的Server：

```json
{
  "mcpServers": {
    "code-analyzer-remote": {
      "type": "http",
      "url": "https://your-domain.com/mcp",
      "headers": {
        "Authorization": "Bearer your-api-key"
      }
    }
  }
}
```

### 7.4 验证连接

启动Claude Code后，使用`/mcp`命令查看已连接的MCP服务器状态：

```bash
claude
> /mcp
```

预期输出：
```
MCP Servers:
  ✅ code-analyzer (connected)
    Tools: analyze_complexity, analyze_dependencies, security_scan
    Resources: config://analysis-rules, project://info
    Prompts: code-review
```

> **常见问题排查**：
> - 如果Server未显示，检查JSON配置语法是否正确
> - 如果显示`❌ connection failed`，手动运行`npx tsx src/index.ts`看是否有错误输出
> - 如果Server启动了但Tool不可见，检查`server.tool()`的description是否为空

---

## 第八章 实战项目：完整代码质量分析Server

### 8.1 工具函数实现

```typescript
// src/utils/parser.ts
import { readFile, stat, readdir } from "fs/promises";
import { join, extname } from "path";

interface ComplexityResult {
  file: string;
  function_name: string;
  start_line: number;
  end_line: number;
  cyclomatic: number;
  cognitive: number;
  lines: number;
  nesting_depth: number;
}

export async function analyzeComplexity(options: {
  path: string;
  metrics: string[];
  threshold: number;
  includePattern: string;
}): Promise<ComplexityResult[]> {
  const { path, threshold } = options;
  const isDir = (await stat(path)).isDirectory();
  const files = isDir ? await collectFiles(path) : [path];
  
  const results: ComplexityResult[] = [];
  
  for (const file of files) {
    if (!isSourceFile(file)) continue;
    
    const content = await readFile(file, "utf-8");
    const functions = extractFunctions(content, file);
    
    for (const fn of functions) {
      const cyclomatic = calculateCyclomaticComplexity(fn.body);
      const cognitive = calculateCognitiveComplexity(fn.body);
      
      if (cyclomatic > threshold || cognitive > threshold) {
        results.push({
          file,
          function_name: fn.name,
          start_line: fn.startLine,
          end_line: fn.endLine,
          cyclomatic,
          cognitive,
          lines: fn.endLine - fn.startLine + 1,
          nesting_depth: calculateMaxNesting(fn.body),
        });
      }
    }
  }
  
  return results.sort((a, b) => b.cyclomatic - a.cyclomatic);
}

// 圈复杂度：基于决策点计数
function calculateCyclomaticComplexity(code: string): number {
  const patterns = [
    /\bif\b/g,
    /\belse\s+if\b/g,
    /\bfor\b/g,
    /\bwhile\b/g,
    /\bcase\b/g,
    /\bcatch\b/g,
    /\?\s*[^:]+:/g,       // 三元运算符
    /&&/g,                 // 逻辑与
    /\|\|/g,               // 逻辑或
    /\?\?/g,               // 空值合并
  ];
  
  let complexity = 1; // 基础复杂度
  for (const pattern of patterns) {
    const matches = code.match(pattern);
    if (matches) complexity += matches.length;
  }
  return complexity;
}

// 认知复杂度：考虑嵌套加成
function calculateCognitiveComplexity(code: string): number {
  let complexity = 0;
  let nestingLevel = 0;
  const lines = code.split("\n");
  
  for (const line of lines) {
    const trimmed = line.trim();
    const indent = line.length - trimmed.length;
    
    // 进入嵌套
    if (/[\{]$/.test(trimmed) || /\b(if|for|while|switch|try)\b/.test(trimmed)) {
      complexity += 1 + nestingLevel;
      if (/[\{]$/.test(trimmed)) nestingLevel++;
    }
    // 退出嵌套
    if (/^\}/.test(trimmed)) {
      nestingLevel = Math.max(0, nestingLevel - 1);
    }
  }
  
  return complexity;
}

async function collectFiles(dir: string): Promise<string[]> {
  const entries = await readdir(dir, { withFileTypes: true });
  const files: string[] = [];
  
  for (const entry of entries) {
    const fullPath = join(dir, entry.name);
    if (entry.isDirectory() && !entry.name.startsWith(".") 
        && entry.name !== "node_modules" && entry.name !== "dist") {
      files.push(...await collectFiles(fullPath));
    } else if (entry.isFile() && isSourceFile(entry.name)) {
      files.push(fullPath);
    }
  }
  return files;
}

function isSourceFile(name: string): boolean {
  return [".ts", ".tsx", ".js", ".jsx"].includes(extname(name));
}
```

### 8.2 报告格式化

```typescript
// src/utils/formatter.ts
import { ComplexityResult } from "./parser.js";

export function formatReport(
  results: ComplexityResult[],
  highRisk: ComplexityResult[],
  threshold: number
): string {
  const lines: string[] = [
    "## 📊 代码复杂度分析报告\n",
    `分析文件数: ${new Set(results.map(r => r.file)).size}`,
    `分析函数数: ${results.length}`,
    `告警阈值: ${threshold}`,
    `高风险函数: ${highRisk.length}\n`,
  ];

  // 按文件分组
  const grouped = groupByFile(results);
  
  for (const [file, fns] of Object.entries(grouped)) {
    lines.push(`### 📄 ${file}`);
    lines.push("| 函数 | 圈复杂度 | 认知复杂度 | 行数 | 嵌套深度 | 风险 |");
    lines.push("|------|---------|-----------|------|---------|------|");
    
    for (const fn of fns) {
      const risk = fn.cyclomatic > threshold ? "🔴 高" : 
                   fn.cyclomatic > threshold * 0.7 ? "🟡 中" : "🟢 低";
      lines.push(
        `| \`${fn.function_name}\` (${fn.start_line}-${fn.end_line}) ` +
        `| ${fn.cyclomatic} | ${fn.cognitive} | ${fn.lines} | ` +
        `${fn.nesting_depth} | ${risk} |`
      );
    }
    lines.push("");
  }

  if (highRisk.length > 0) {
    lines.push("### 🚨 优先重构建议\n");
    for (const fn of highRisk.slice(0, 5)) {
      lines.push(
        `1. **${fn.function_name}** (${fn.file}:${fn.start_line})` +
        ` - 圈复杂度 ${fn.cyclomatic}，建议拆分为多个小函数`
      );
    }
  }

  return lines.join("\n");
}

function groupByFile(results: ComplexityResult[]): Record<string, ComplexityResult[]> {
  return results.reduce((acc, r) => {
    (acc[r.file] ||= []).push(r);
    return acc;
  }, {} as Record<string, ComplexityResult[]>);
}
```

### 8.3 完整构建与测试

```bash
# 编译TypeScript
npm run build

# 使用MCP Inspector测试（官方调试工具）
npm run inspect
```

MCP Inspector是一个浏览器端的交互式调试界面，可以：
- 查看Server暴露的所有Tools/Resources/Prompts
- 手动调用Tool并查看响应
- 检查JSON-RPC消息流
- 验证输入参数的JSON Schema

启动后在浏览器打开`http://localhost:5173`，你将看到：

```
Tools:
  ├── analyze_complexity
  │     Parameters: path, metrics, threshold, include_pattern
  ├── analyze_dependencies
  │     Parameters: project_path, check_updates, check_vulnerabilities, depth
  └── security_scan
        Parameters: target_path, rules, severity

Resources:
  ├── config://analysis-rules
  └── project://info

Prompts:
  └── code-review
        Parameters: file_path, focus_areas, language
```

---

## 第九章 OAuth 2.1认证（远程Server安全）

### 9.1 为什么需要OAuth

当你的MCP Server通过Streamable HTTP对外提供服务时，必须实现认证以防止未授权访问。MCP规范要求使用OAuth 2.1 + Dynamic Client Registration（DCR）。

### 9.2 OAuth流程

```mermaid
sequenceDiagram
    participant Client as MCP Client<br/>(Claude Code)
    participant Server as MCP Server<br/>(你的服务)
    participant Auth as Authorization Server<br/>(如Auth0/Clerk)
    
    Client->>Server: POST /mcp (无token)
    Server-->>Client: 401 + WWW-Authenticate header
    
    Client->>Auth: GET /authorize (PKCE flow)
    Auth-->>Client: 登录页面
    Client->>Auth: 用户授权
    Auth-->>Client: Authorization Code
    
    Client->>Auth: POST /token (code + PKCE verifier)
    Auth-->>Client: Access Token
    
    Client->>Server: POST /mcp (Bearer token)
    Server->>Auth: 验证token (introspect/JWKS)
    Auth-->>Server: Token有效
    Server-->>Client: 正常MCP响应
```

### 9.3 Express中间件实现

```typescript
// src/middleware/auth.ts
import { Request, Response, NextFunction } from "express";
import jwt from "jsonwebtoken";

const JWKS_URI = process.env.JWKS_URI || "";
const AUDIENCE = process.env.AUTH_AUDIENCE || "code-analyzer-mcp";
const ISSUER = process.env.AUTH_ISSUER || "";

// 简化版JWKS验证中间件
export async function requireAuth(
  req: Request, 
  res: Response, 
  next: NextFunction
): Promise<void> {
  const authHeader = req.headers.authorization;
  
  if (!authHeader?.startsWith("Bearer ")) {
    res.setHeader("WWW-Authenticate", 
      `Bearer realm="MCP", resource_metadata="${ISSUER}/.well-known/oauth-authorization-server"`);
    res.status(401).json({
      jsonrpc: "2.0",
      error: { 
        code: -32001, 
        message: "Authentication required" 
      },
      id: null,
    });
    return;
  }

  const token = authHeader.slice(7);
  
  try {
    const decoded = jwt.verify(token, await getSigningKey(), {
      audience: AUDIENCE,
      issuer: ISSUER,
      algorithms: ["RS256"],
    });
    
    // 将用户信息注入请求上下文
    (req as any).mcp_context = { user: decoded };
    next();
  } catch {
    res.status(401).json({
      jsonrpc: "2.0",
      error: { code: -32001, message: "Invalid or expired token" },
      id: null,
    });
  }
}

async function getSigningKey(): Promise<string> {
  // 生产环境中应从JWKS端点获取公钥并缓存
  // 这里简化处理
  return process.env.JWT_PUBLIC_KEY || "";
}
```

### 9.4 保护HTTP端点

更新`server-http.ts`：

```typescript
import { requireAuth } from "./middleware/auth.js";

// MCP端点添加认证中间件
app.post("/mcp", requireAuth, async (req, res) => {
  // ...原有逻辑不变
});

// WELL-KNOWN端点：OAuth服务器发现
app.get("/.well-known/oauth-authorization-server", (req, res) => {
  res.json({
    issuer: ISSUER,
    authorization_endpoint: `${ISSUER}/authorize`,
    token_endpoint: `${ISSUER}/oauth/token`,
    registration_endpoint: `${ISSUER}/oauth/register`,
    scopes_supported: ["mcp:tools", "mcp:resources"],
    code_challenge_methods_supported: ["S256"],
  });
});
```

---

## 第十章 发布与分发

### 10.1 npm包发布

```bash
# 确保构建产物是最新的
npm run build

# 登录npm
npm login

# 发布
npm publish --access public
```

发布后，用户可以这样配置你的Server：

```json
{
  "mcpServers": {
    "code-analyzer": {
      "command": "npx",
      "args": ["-y", "code-analyzer-mcp"]
    }
  }
}
```

### 10.2 Docker容器化

```dockerfile
# Dockerfile
FROM node:20-slim

WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY dist/ ./dist/

# Streamable HTTP模式
ENV NODE_ENV=production
ENV PORT=3000

EXPOSE 3000
CMD ["node", "dist/server-http.js"]
```

```bash
# 构建并运行
docker build -t code-analyzer-mcp .
docker run -p 3000:3000 \
  -e AUTH_ISSUER=https://your-auth-server.com \
  -e JWT_PUBLIC_KEY="$(cat public.pem)" \
  code-analyzer-mcp
```

---

## 完整工作流总览

```text
flowchart TB
    Start([开始开发]) --> Init[初始化项目<br/>npm init + 安装SDK]
    Init --> Design[设计工具接口<br/>定义Tool/Resource/Prompt]
    Design --> Code[编写工具实现<br/>注册到McpServer]
    Code --> Stdio[实现stdio传输<br/>本地开发调试]
    Stdio --> Inspect[使用MCP Inspector测试]
    Inspect --> Config[配置Claude Code<br/>.claude/settings.json]
    Config --> Verify[/mcp命令验证]
    Verify --> HTTP{需要远程部署?}
    HTTP -->|是| StreamHTTP[实现Streamable HTTP]
    StreamHTTP --> OAuth[添加OAuth认证]
    OAuth --> Docker[Docker容器化]
    Docker --> Publish[发布到npm/镜像仓库]
    HTTP -->|否| LocalPub[npm publish本地包]
    LocalPub --> Done([完成])
    Publish --> Done

    style Start fill:#4caf50,color:white
    style Done fill:#2196f3,color:white
    style HTTP fill:#ff9800,color:white
```

---

## 最佳实践清单

### Tool设计
- ✅ 工具名称使用`snake_case`，如`analyze_complexity`而非`analyzeComplexity`
- ✅ description写清楚工具的能力边界和使用场景，200字以内
- ✅ 参数提供`.describe()`描述，让LLM理解每个参数的含义
- ✅ 为参数设置合理的`.default()`值，减少必填参数数量
- ✅ 错误信息使用`isError: true`标记，提供可操作的修复建议
- ❌ 不要设计副作用严重的工具（如删除数据库），MCP缺少确认机制

### 性能优化
- ✅ 耗时操作使用进度通知（`notifications/progress`）反馈进度
- ✅ 大文件分析考虑分批处理，避免超出token限制
- ✅ Remote Server使用连接池和缓存减少延迟
- ✅ 使用Zod的`.transform()`在验证时完成数据预处理

### 安全
- ✅ stdio模式无需认证，但要确保Server代码本身无安全漏洞
- ✅ Remote Server必须实现OAuth 2.1或API Key认证
- ✅ 永远不要在Tool参数中接受原始SQL或shell命令
- ✅ 文件路径参数要验证是否在允许的目录范围内（防止路径遍历）

---

## 延伸阅读

- [MCP官方规范（2025-03-26版本）](https://modelcontextprotocol.io/specification/2025-03-26) — 协议完整定义，理解JSON-RPC消息格式和生命周期
- [TypeScript SDK GitHub仓库](https://github.com/modelcontextprotocol/typescript-sdk) — 官方SDK源码、示例和API文档
- [MCP Inspector调试工具](https://github.com/modelcontextprotocol/inspector) — 浏览器端可视化调试MCP Server
- [Claude Code MCP集成文档](https://code.claude.com/docs/en/mcp) — Claude Code官方配置指南
- [Streamable HTTP规范详解](https://dev.to/x4nent/complete-guide-to-mcp-model-context-protocol-in-2026-architecture-implementation-and-4a11) — 2026年MCP架构与传输层完整指南

## 训练营适用性评估

| 维度 | 评估 |
|------|------|
| 适合训练营类型 | 全栈开发训练营、AI工具开发训练营、DevOps工程师进阶 |
| 建议教学时长 | 6-8小时（含动手实践），分2个session |
| 学员基础要求 | TypeScript中级、Node.js基础、REST API经验 |
| 难度评级 | ⭐⭐⭐⭐（中高） |
| 实践环节占比 | 70%编码 + 20%调试测试 + 10%部署发布 |
| 推荐分组 | 2-3人一组，每组完成一个不同主题的MCP Server |

---

## 参考来源

1. [MCP Specification 2025-03-26](https://modelcontextprotocol.io/specification/2025-03-26) — MCP协议官方规范文档，定义了JSON-RPC消息格式、生命周期和传输协议
2. [TypeScript SDK Server Guide](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/docs/server.md) — 官方TypeScript SDK服务端开发API文档
3. [Build a Custom MCP Server for Claude Code - Apidog](https://apidog.com/blog/how-to-quickly-build-a-mcp-server-for-claude-code/) — 从零构建MCP Server的完整实战指南
4. [How to Build an MCP Server with Python, Docker, and Claude Code - freeCodeCamp](https://www.freecodecamp.org/news/how-to-build-an-mcp-server-with-python-docker-and-claude-code/) — Python + Docker部署MCP Server教程
5. [Complete Guide to MCP in 2026 - DEV Community](https://dev.to/x4nent/complete-guide-to-mcp-model-context-protocol-in-2026-architecture-implementation-and-4a11) — 2026年MCP架构、Streamable HTTP传输和企业级部署全景指南

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