MCP协议设计与实现-第20章 从零构建一个生产级 MCP Server

《MCP 协议设计与实现》完整目录

第20章 从零构建一个生产级 MCP Server

"The best way to learn a protocol is to build something real with it --- not a hello world, but something you'd actually deploy."

:::tip 本章要点

  • 从需求分析到技术选型:TypeScript 与 Python SDK 的取舍考量
  • 实现完整的 Tools、Resources、Prompts 三大原语
  • 为 Server 添加 OAuth 认证与安全防护
  • 编写协议级集成测试
  • 部署策略:stdio 本地模式与 Streamable HTTP 远程模式
  • 发布到 MCP 生态 :::

20.1 我们要构建什么

本章将构建一个 GitHub MCP Server------让 AI 助手能够与 GitHub 仓库交互。这不是一个玩具示例,而是一个覆盖了 MCP 核心特性的真实集成:

MCP 原语 实现内容
Tools 创建 Issue、搜索仓库、创建 PR
Resources 暴露仓库文件内容、Issue 列表
Prompts 提供 Code Review、Bug Report 模板
Completion 仓库名、分支名自动补全
Progress 批量操作的进度追踪

20.2 技术选型:TypeScript vs Python

20.2.1 两个 SDK 的定位差异

MCP 官方提供两个 Tier-1 SDK,各有所长:

graph LR subgraph "TypeScript SDK" TS1["McpServer 高级 API"] TS2["Server 底层 API"] TS3["Zod Schema 集成"] TS4["completable() 补全"] TS5["Express / Hono 中间件"] end subgraph "Python SDK" PY1["McpServer 高级 API"] PY2["Server 底层 API"] PY3["Pydantic 类型推断"] PY4["@server.tool() 装饰器"] PY5["Starlette ASGI 集成"] end style TS1 fill:#3b82f6,color:#fff,stroke:none style PY1 fill:#10b981,color:#fff,stroke:none

选 TypeScript 的理由

  • 如果你的 Server 主要做 Web API 集成(如 GitHub、Slack、Jira),TypeScript 的异步模型和 npm 生态更成熟
  • Zod + completable() 的组合让参数验证和自动补全的 DX 极佳
  • 前端/全栈团队更容易上手

选 Python 的理由

  • 如果你的 Server 涉及数据分析、机器学习模型调用,Python 生态无可替代
  • @server.tool() 装饰器语法更简洁,类型推断基于 Python 原生 type hints
  • 数据科学团队更熟悉

本章以 TypeScript 为主线,关键差异处会给出 Python 的对照。

20.3 项目初始化

20.3.1 创建项目

bash 复制代码
mkdir mcp-server-github && cd mcp-server-github
npm init -y
npm install @modelcontextprotocol/server @modelcontextprotocol/core zod
npm install -D typescript @types/node vitest

TypeScript 配置:

json 复制代码
// tsconfig.json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "declaration": true
  },
  "include": ["src"]
}

20.3.2 入口文件

typescript 复制代码
// src/index.ts
import { McpServer } from '@modelcontextprotocol/server';
import { StdioServerTransport } from '@modelcontextprotocol/server';

const server = new McpServer({
  name: 'github-mcp-server',
  version: '1.0.0'
}, {
  capabilities: {
    logging: {},
    completions: {}
  },
  instructions: '这是一个 GitHub 集成 Server。使用前请确保已配置 GITHUB_TOKEN。' +
    '建议先用 search_repos 查找仓库,再用具体的工具操作。'
});

// 注册 Tools、Resources、Prompts(后续各节详述)
// ...

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

instructions 字段值得注意------它不是给人看的,而是给 LLM 看的。好的 instructions 能显著提高 LLM 选择正确工具的准确率。

20.4 实现 Tools

20.4.1 搜索仓库

typescript 复制代码
import * as z from 'zod/v4';
import { completable } from '@modelcontextprotocol/server';

// GitHub API 客户端(简化版)
async function githubFetch(path: string, options?: RequestInit) {
  const token = process.env.GITHUB_TOKEN;
  if (!token) throw new Error('GITHUB_TOKEN 环境变量未设置');

  const resp = await fetch(`https://api.github.com${path}`, {
    ...options,
    headers: {
      'Authorization': `Bearer ${token}`,
      'Accept': 'application/vnd.github.v3+json',
      'User-Agent': 'mcp-server-github/1.0',
      ...options?.headers
    }
  });

  if (!resp.ok) {
    throw new Error(`GitHub API 错误: ${resp.status} ${resp.statusText}`);
  }
  return resp.json();
}

// 搜索仓库工具
server.registerTool(
  'search_repos',
  {
    title: '搜索 GitHub 仓库',
    description: '根据关键词搜索 GitHub 仓库,返回仓库名、描述、Star 数等信息',
    inputSchema: z.object({
      query: z.string().describe('搜索关键词,支持 GitHub 搜索语法'),
      language: completable(
        z.string().optional().describe('编程语言过滤'),
        (value) => ['typescript', 'javascript', 'python', 'rust', 'go', 'java', 'c++']
          .filter(lang => lang.startsWith(value.toLowerCase()))
      ),
      sort: z.enum(['stars', 'forks', 'updated']).default('stars')
        .describe('排序方式'),
      limit: z.number().min(1).max(30).default(10)
        .describe('返回结果数量')
    }),
    annotations: {
      readOnlyHint: true,
      openWorldHint: true
    }
  },
  async ({ query, language, sort, limit }) => {
    let q = query;
    if (language) q += ` language:${language}`;

    const data = await githubFetch(
      `/search/repositories?q=${encodeURIComponent(q)}&sort=${sort}&per_page=${limit}`
    );

    const repos = data.items.map((repo: any) => ({
      name: repo.full_name,
      description: repo.description || '无描述',
      stars: repo.stargazers_count,
      language: repo.language,
      url: repo.html_url
    }));

    return {
      content: [{
        type: 'text',
        text: JSON.stringify(repos, null, 2)
      }]
    };
  }
);

注意 annotations 的使用------readOnlyHint: true 告诉 Client 这个工具不会修改任何数据,openWorldHint: true 表示它访问外部网络。这些元信息帮助 Client 做出更好的权限决策。

20.4.2 创建 Issue

typescript 复制代码
server.registerTool(
  'create_issue',
  {
    title: '创建 GitHub Issue',
    description: '在指定仓库中创建一个新的 Issue',
    inputSchema: z.object({
      repo: completable(
        z.string().describe('仓库全名,格式: owner/repo'),
        async (value) => {
          // 动态补全:搜索用户有权限的仓库
          try {
            const data = await githubFetch(
              `/search/repositories?q=${encodeURIComponent(value)}+in:name&per_page=5`
            );
            return data.items.map((r: any) => r.full_name);
          } catch {
            return [];
          }
        }
      ),
      title: z.string().min(1).describe('Issue 标题'),
      body: z.string().optional().describe('Issue 正文(Markdown 格式)'),
      labels: z.array(z.string()).optional().describe('标签列表'),
      assignees: z.array(z.string()).optional().describe('指派人列表')
    }),
    annotations: {
      destructiveHint: false,
      idempotentHint: false  // 每次调用都会创建新 Issue
    }
  },
  async ({ repo, title, body, labels, assignees }) => {
    const issue = await githubFetch(`/repos/${repo}/issues`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ title, body, labels, assignees })
    });

    return {
      content: [{
        type: 'text',
        text: `Issue 创建成功!\n\n` +
          `标题: ${issue.title}\n` +
          `编号: #${issue.number}\n` +
          `链接: ${issue.html_url}`
      }]
    };
  }
);

repo 参数使用了 completable 封装,当用户开始输入仓库名时,Server 会实时调用 GitHub API 搜索匹配的仓库------这就是第 18 章介绍的 Completion 机制在实践中的应用。

20.4.3 Python 对照

同样的 create_issue 工具在 Python SDK 中的写法:

python 复制代码
from mcp.server.mcpserver import McpServer
from mcp.server.mcpserver.context import Context

server = McpServer("github-mcp-server")

@server.tool(
    name="create_issue",
    title="创建 GitHub Issue",
    description="在指定仓库中创建一个新的 Issue"
)
async def create_issue(
    repo: str,
    title: str,
    body: str | None = None,
    labels: list[str] | None = None,
    ctx: Context | None = None
) -> str:
    """repo: 仓库全名 (owner/repo), title: Issue 标题"""
    if ctx:
        await ctx.info(f"正在创建 Issue: {title}")

    issue = await github_fetch(f"/repos/{repo}/issues", method="POST",
                                json={"title": title, "body": body, "labels": labels})

    return f"Issue #{issue['number']} 创建成功: {issue['html_url']}"

Python SDK 的 Context 注入机制很优雅------你只需要在函数签名中声明一个 Context 类型的参数,SDK 会自动注入上下文对象,无需显式传递。

20.5 实现 Resources

Resources 让 AI 可以"读取"GitHub 上的内容,就像读取本地文件一样:

typescript 复制代码
import { ResourceTemplate } from '@modelcontextprotocol/server';

// 静态资源:当前用户信息
server.registerResource(
  'github://user/profile',
  {
    name: '当前 GitHub 用户',
    description: '获取当前认证用户的 GitHub 个人信息',
    mimeType: 'application/json'
  },
  async () => {
    const user = await githubFetch('/user');
    return {
      contents: [{
        uri: 'github://user/profile',
        mimeType: 'application/json',
        text: JSON.stringify(user, null, 2)
      }]
    };
  }
);

// 资源模板:仓库文件内容
server.registerResourceTemplate(
  new ResourceTemplate('github://repos/{owner}/{repo}/contents/{path}', {
    list: async () => {
      // 返回一些常见的文件作为示例
      return {
        resources: [
          {
            uri: 'github://repos/anthropics/mcp-specification/contents/README.md',
            name: 'MCP Specification README',
            mimeType: 'text/markdown'
          }
        ]
      };
    }
  }),
  {
    name: '仓库文件内容',
    description: '读取 GitHub 仓库中指定路径的文件内容',
    mimeType: 'text/plain'
  },
  async (uri, { owner, repo, path }) => {
    const data = await githubFetch(`/repos/${owner}/${repo}/contents/${path}`);

    // GitHub API 返回 base64 编码的内容
    const content = Buffer.from(data.content, 'base64').toString('utf-8');

    return {
      contents: [{
        uri: uri.href,
        mimeType: data.type === 'file' ? 'text/plain' : 'application/json',
        text: content
      }]
    };
  }
);

资源模板中的 URI 变量({owner}{repo}{path})由 SDK 自动解析和填充。list 回调是可选的------它提供了预定义的资源列表,帮助 Client 展示可用资源。

20.6 实现 Prompts

Prompts 是预定义的对话模板,引导 LLM 执行特定的工作流:

typescript 复制代码
server.registerPrompt(
  'code_review',
  {
    title: 'Code Review',
    description: '对 Pull Request 进行代码审查,生成结构化的审查意见',
    argsSchema: z.object({
      repo: z.string().describe('仓库全名 (owner/repo)'),
      pr_number: z.number().describe('Pull Request 编号')
    })
  },
  async ({ repo, pr_number }) => {
    // 获取 PR 详情和 diff
    const pr = await githubFetch(`/repos/${repo}/pulls/${pr_number}`);
    const diff = await githubFetch(`/repos/${repo}/pulls/${pr_number}`, {
      headers: { 'Accept': 'application/vnd.github.v3.diff' }
    });

    return {
      messages: [
        {
          role: 'user',
          content: {
            type: 'text',
            text: `请对以下 Pull Request 进行代码审查。\n\n` +
              `## PR 信息\n` +
              `- 标题: ${pr.title}\n` +
              `- 作者: ${pr.user.login}\n` +
              `- 描述: ${pr.body || '无'}\n\n` +
              `## 代码变更\n\`\`\`diff\n${diff}\n\`\`\`\n\n` +
              `请从以下维度审查:\n` +
              `1. 代码质量与可读性\n` +
              `2. 潜在的 Bug 或边界条件\n` +
              `3. 性能影响\n` +
              `4. 安全风险\n` +
              `5. 测试覆盖`
          }
        }
      ]
    };
  }
);

server.registerPrompt(
  'bug_report',
  {
    title: 'Bug Report',
    description: '基于错误信息生成结构化的 Bug 报告,并自动创建 Issue',
    argsSchema: z.object({
      repo: z.string().describe('仓库全名'),
      error_message: z.string().describe('错误信息或堆栈'),
      context: z.string().optional().describe('复现步骤或额外上下文')
    })
  },
  async ({ repo, error_message, context }) => ({
    messages: [{
      role: 'user',
      content: {
        type: 'text',
        text: `请基于以下错误信息,为仓库 ${repo} 生成一个结构化的 Bug 报告。\n\n` +
          `## 错误信息\n\`\`\`\n${error_message}\n\`\`\`\n\n` +
          (context ? `## 上下文\n${context}\n\n` : '') +
          `请生成包含以下部分的 Bug 报告:\n` +
          `1. 问题描述(一句话总结)\n` +
          `2. 复现步骤\n` +
          `3. 期望行为 vs 实际行为\n` +
          `4. 可能的原因分析\n` +
          `5. 建议的修复方向\n\n` +
          `生成后,请使用 create_issue 工具将报告提交到 ${repo}。`
      }
    }]
  })
);

注意 bug_report Prompt 的最后一句------它引导 LLM 在生成报告后自动调用 create_issue 工具。这是 Prompts 与 Tools 协同的典型模式:Prompt 定义工作流的"脚本",Tool 执行具体的"动作"

20.7 添加进度追踪

对于可能耗时的操作,使用 Server 底层 API 的进度通知:

typescript 复制代码
server.registerTool(
  'batch_label_issues',
  {
    title: '批量标记 Issues',
    description: '为多个 Issue 添加标签',
    inputSchema: z.object({
      repo: z.string(),
      issue_numbers: z.array(z.number()),
      labels: z.array(z.string())
    })
  },
  async ({ repo, issue_numbers, labels }, { reportProgress }) => {
    const total = issue_numbers.length;
    const results: string[] = [];

    for (let i = 0; i < total; i++) {
      const num = issue_numbers[i];
      try {
        await githubFetch(`/repos/${repo}/issues/${num}/labels`, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ labels })
        });
        results.push(`#${num}: 成功`);
      } catch (e: any) {
        results.push(`#${num}: 失败 - ${e.message}`);
      }

      // 报告进度
      await reportProgress({
        progress: i + 1,
        total,
        message: `正在处理 Issue #${num} (${i + 1}/${total})`
      });
    }

    return {
      content: [{
        type: 'text',
        text: `批量标记完成:\n${results.join('\n')}`
      }]
    };
  }
);

20.8 测试策略

20.8.1 协议级集成测试

MCP 的测试需要模拟完整的 Client-Server 交互。TypeScript SDK 提供了内存传输(in-memory transport)用于测试:

typescript 复制代码
// tests/tools.test.ts
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { Client } from '@modelcontextprotocol/client';
import { McpServer } from '@modelcontextprotocol/server';
import { InMemoryTransport } from '@modelcontextprotocol/core';

describe('GitHub MCP Server', () => {
  let client: Client;
  let server: McpServer;

  beforeAll(async () => {
    // 创建 Server(使用 mock 的 GitHub API)
    server = createServer({ githubToken: 'test-token' });

    // 创建内存传输对
    const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

    // 连接
    client = new Client({ name: 'test-client', version: '1.0.0' });
    await Promise.all([
      client.connect(clientTransport),
      server.connect(serverTransport)
    ]);
  });

  afterAll(async () => {
    await client.close();
  });

  it('应该列出所有工具', async () => {
    const result = await client.listTools();
    const toolNames = result.tools.map(t => t.name);

    expect(toolNames).toContain('search_repos');
    expect(toolNames).toContain('create_issue');
    expect(toolNames).toContain('batch_label_issues');
  });

  it('搜索仓库应返回结构化结果', async () => {
    const result = await client.callTool({
      name: 'search_repos',
      arguments: { query: 'mcp', language: 'typescript', limit: 3 }
    });

    expect(result.content).toHaveLength(1);
    const data = JSON.parse((result.content[0] as any).text);
    expect(data).toBeInstanceOf(Array);
    expect(data.length).toBeLessThanOrEqual(3);
  });

  it('创建 Issue 应返回 Issue 链接', async () => {
    const result = await client.callTool({
      name: 'create_issue',
      arguments: {
        repo: 'test-owner/test-repo',
        title: '测试 Issue',
        body: '这是一个测试'
      }
    });

    const text = (result.content[0] as any).text;
    expect(text).toContain('Issue 创建成功');
    expect(text).toContain('#');
  });
});

20.8.2 Completion 测试

typescript 复制代码
it('仓库名应该支持自动补全', async () => {
  const result = await client.complete({
    ref: { type: 'ref/prompt', name: 'code_review' },
    argument: { name: 'repo', value: 'ant' }
  });

  expect(result.completion.values.length).toBeGreaterThan(0);
  result.completion.values.forEach(v => {
    expect(v.toLowerCase()).toContain('ant');
  });
});

20.9 部署

20.9.1 本地模式(stdio)

最简单的部署方式------用户在配置文件中指定命令:

json 复制代码
{
  "mcpServers": {
    "github": {
      "command": "npx",
      "args": ["-y", "mcp-server-github"],
      "env": {
        "GITHUB_TOKEN": "${GITHUB_TOKEN}"
      }
    }
  }
}

为此需要在 package.json 中配置 bin 入口:

json 复制代码
{
  "name": "mcp-server-github",
  "version": "1.0.0",
  "bin": {
    "mcp-server-github": "./dist/index.js"
  },
  "files": ["dist"]
}

20.9.2 远程模式(Streamable HTTP)

对于需要多用户共享的场景,使用 HTTP 传输:

typescript 复制代码
// src/remote.ts
import { randomUUID } from 'node:crypto';
import express from 'express';
import { NodeStreamableHTTPServerTransport } from '@modelcontextprotocol/node';
import { isInitializeRequest } from '@modelcontextprotocol/server';

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

const transports: Map<string, NodeStreamableHTTPServerTransport> = new Map();

app.post('/mcp', async (req, res) => {
  const sessionId = req.headers['mcp-session-id'] as string | undefined;

  if (sessionId && transports.has(sessionId)) {
    const transport = transports.get(sessionId)!;
    await transport.handleRequest(req, res, req.body);
    return;
  }

  if (!sessionId && isInitializeRequest(req.body)) {
    const transport = new NodeStreamableHTTPServerTransport({
      sessionIdGenerator: () => randomUUID(),
      onsessioninitialized: (sid) => {
        transports.set(sid, transport);
      }
    });

    transport.onclose = () => {
      if (transport.sessionId) {
        transports.delete(transport.sessionId);
      }
    };

    const server = createServer();
    await server.connect(transport);
    await transport.handleRequest(req, res, req.body);
    return;
  }

  res.status(400).json({
    jsonrpc: '2.0',
    error: { code: -32000, message: '无效请求' },
    id: null
  });
});

app.get('/mcp', async (req, res) => {
  const sessionId = req.headers['mcp-session-id'] as string;
  const transport = transports.get(sessionId);
  if (!transport) {
    res.status(404).send('Session not found');
    return;
  }
  await transport.handleRequest(req, res);
});

app.listen(3000, () => {
  console.log('MCP Server running on http://localhost:3000/mcp');
});

远程部署时,每个 Client 连接都创建一个独立的 McpServer 实例------这避免了多用户之间的状态污染,是 MCP Server 多租户部署的标准模式。

20.9.3 部署架构对比

graph TB subgraph "本地模式 (stdio)" U1["用户 A"] --> C1["Claude Code"] C1 --> |"spawn 子进程"| S1["MCP Server 进程"] S1 --> G1["GitHub API"] end subgraph "远程模式 (HTTP)" U2["用户 A"] --> C2["Client A"] U3["用户 B"] --> C3["Client B"] C2 --> |"HTTP"| LB["负载均衡"] C3 --> |"HTTP"| LB LB --> S2["MCP Server 实例 1"] LB --> S3["MCP Server 实例 2"] S2 --> G2["GitHub API"] S3 --> G2 end style S1 fill:#3b82f6,color:#fff,stroke:none style S2 fill:#10b981,color:#fff,stroke:none style S3 fill:#10b981,color:#fff,stroke:none style LB fill:#f59e0b,color:#fff,stroke:none

本地模式简单可靠,适合个人使用;远程模式支持多用户、可水平扩展,适合团队和企业级部署。两种模式共用同一套 Server 代码,只是传输层不同------这正是 MCP 传输抽象的价值所在。

20.10 安全加固

20.10.1 输入验证

Zod Schema 已经提供了基本的输入验证,但对于安全敏感的操作还需要额外检查:

typescript 复制代码
// 防止路径穿越
function validateRepoName(repo: string): boolean {
  return /^[a-zA-Z0-9._-]+\/[a-zA-Z0-9._-]+$/.test(repo);
}

// 防止注入
function sanitizeSearchQuery(query: string): string {
  return query.replace(/[^\w\s\-.:@/]/g, '');
}

20.10.2 速率限制

对 GitHub API 的调用应遵守速率限制:

typescript 复制代码
class RateLimiter {
  private requests: number[] = [];
  private readonly maxRequests: number;
  private readonly windowMs: number;

  constructor(maxRequests: number = 30, windowMs: number = 60000) {
    this.maxRequests = maxRequests;
    this.windowMs = windowMs;
  }

  async acquire(): Promise<void> {
    const now = Date.now();
    this.requests = this.requests.filter(t => now - t < this.windowMs);

    if (this.requests.length >= this.maxRequests) {
      const waitTime = this.windowMs - (now - this.requests[0]);
      await new Promise(resolve => setTimeout(resolve, waitTime));
    }
    this.requests.push(Date.now());
  }
}

20.10.3 Host Header 验证

远程部署时,防止 DNS 重绑定攻击:

typescript 复制代码
import { hostHeaderValidation } from '@modelcontextprotocol/server';

// 添加到 Express 中间件
app.use(hostHeaderValidation({
  allowedHosts: ['mcp.example.com'],
  allowLocalhost: process.env.NODE_ENV === 'development'
}));

20.11 发布到生态

20.11.1 npm 发布

bash 复制代码
# 构建
npm run build

# 发布
npm publish --access public

20.11.2 MCP Registry

MCP 生态正在建设中央注册表(类似 npm registry),Server 开发者可以提交自己的 Server 元信息:

json 复制代码
{
  "name": "mcp-server-github",
  "description": "GitHub integration for MCP - search repos, manage issues, review PRs",
  "version": "1.0.0",
  "transport": ["stdio", "streamable-http"],
  "tools": ["search_repos", "create_issue", "batch_label_issues"],
  "resources": ["github://user/profile", "github://repos/{owner}/{repo}/contents/{path}"],
  "prompts": ["code_review", "bug_report"],
  "author": "your-name",
  "repository": "https://github.com/your-name/mcp-server-github"
}

注册后,Client 可以通过标准化的发现机制找到你的 Server,用户可以一键安装和配置。

20.12 本章小结

本章从零构建了一个具备生产级特性的 GitHub MCP Server,覆盖了完整的开发流程:

  • 技术选型:TypeScript 和 Python SDK 各有所长,根据项目特点和团队技能选择
  • 三大原语:Tools 执行操作,Resources 暴露数据,Prompts 定义工作流模板
  • Completion :通过 completable() 为参数添加实时补全,显著提升用户体验
  • Progress:为长操作提供进度反馈,让用户知道系统在工作
  • 测试:使用内存传输进行协议级集成测试,验证完整的 Client-Server 交互
  • 部署:同一套代码支持 stdio(本地)和 HTTP(远程)两种部署模式
  • 安全:输入验证、速率限制、Host Header 验证、密钥管理

构建一个好的 MCP Server 需要同时关注协议正确性和工程质量。协议告诉你"必须做什么",工程经验告诉你"应该做什么"。在最后一章,我们将把全书的核心洞察提炼为可复用的设计模式和架构决策框架。

相关推荐
杨艺韬2 小时前
MCP协议设计与实现-第18章 Elicitation、Roots 与配置管理
agent
杨艺韬2 小时前
MCP协议设计与实现-第17章 sampling
agent
杨艺韬2 小时前
MCP协议设计与实现-第16章 服务发现与客户端注册
agent
杨艺韬3 小时前
MCP协议设计与实现-第8章 TypeScript Server 实现剖析
agent
杨艺韬3 小时前
MCP协议设计与实现-第04章 生命周期与能力协商
agent
杨艺韬3 小时前
MCP协议设计与实现-第14章 SSE 与 WebSocket
agent
杨艺韬3 小时前
MCP协议设计与实现-第6章 Resource:结构化的上下文注入
agent
杨艺韬3 小时前
MCP协议设计与实现-第10章 Python Server 实现剖析
agent
杨艺韬3 小时前
MCP协议设计与实现-第12章 STDIO 传输:本地进程通信
agent