6.5 MCP 协议实战——AI 工具的标准化集成接口

2 阅读5分钟

模块六:运营增长与高级话题 | 第05讲:MCP 协议实战——AI 工具的标准化集成接口

课程项目:VibeNote 智能笔记
技术栈:Next.js、React、TypeScript
本节目标:理解 MCP 架构与三大原语,跑通一个「能搜笔记、能读片段」的最小 Server,并知道何时选 stdio / SSE。


一、开场:为什么你需要 MCP,而不是「再写一套内部工具协议」

参考文章《核心概念与范式演进》指出:Agent 的关键不在于更会聊天,而在于能维持上下文、调用工具、连续完成任务。但现实里每个 IDE、每个框架各自定义工具格式——你在 Cursor 里写一套,在 Claude Desktop 再写一套,在自建 Agent 里又写一套,集成交付成本会被指数放大

Model Context Protocol(MCP) 的目标,就是把「模型如何发现工具、如何传参、如何读资源」标准化。对你这样的全栈独立开发者,它意味着:

  • 写一次 Server,多处复用:同一个 vibenote-mcp-server 可被多个 Client 接入。
  • 边界清晰:工具能力在 Server 内聚,Client 负责编排与展示。
  • 生态可组合:文件系统、GitHub、浏览器、数据库……社区 Server 可直接拼进你的工作流。

本节以 VibeNote 为例:把「搜索笔记」「读取笔记 Markdown」「列出标签」暴露成 MCP Tools/Resources。


二、MCP 架构:Client ↔ Server

flowchart LR
    subgraph Client侧
        C[Cursor / Claude / 自建App]
    end
    subgraph Server侧
        S[MCP Server\n(独立进程或服务)]
        V[VibeNote API / DB]
    end
    C <-->|JSON-RPC 风格消息| S
    S --> V
  • Client:发起连接、列举能力、调用工具、读取资源。
  • Server:真正实现「查库 / 调 HTTP / 读文件」。
  • Host 应用:有时 Client 嵌在 IDE 里;Host 还负责权限与用户确认。

三、传输层:stdio 与 HTTP/SSE 怎么选?

sequenceDiagram
    participant H as Host(IDE)
    participant S as MCP Server
    H->>S: spawn 子进程(stdin/stdout)
    loop 协议消息
        H->>S: request
        S-->>H: response
    end
传输优点缺点典型场景
stdio本地开发简单、无端口暴露不适合远程多机共享Cursor 本地调试
HTTP + SSE可远程、可共享部署与鉴权更复杂团队共用工具服务

工程建议:先用 stdio 把工具语义跑稳;确有「多客户端远程接入」再 SSE。日志走 stderr,stdout 留给协议,这是新手最常踩的坑之一。


四、三大原语:Tools / Resources / Prompts

1)Tools(可执行动作)

对应传统 Agent 的 function calling:带 inputSchema,会产生副作用计算结果

VibeNote 典型 Tools:

  • search_notes:关键词/标签搜索
  • create_note:创建草稿(需权限)
  • append_section:向某篇笔记追加段落(需权限)

2)Resources(只读上下文)

用 URI 标识,适合把「大块文本」喂给模型做推理,但不应隐含副作用。

示例 URI:

  • vibenote://notes/{id}
  • vibenote://tags

3)Prompts(模板化工作流)

把高频任务固化成参数化模板,例如 summarize_noteweekly_review


五、最小 MCP Server(TypeScript + SDK)

下面示例改编自课程仓库的智识小站案例,将命名替换为 VibeNote(你需要把 searchNotes 等函数接到真实 API)。

mkdir vibenote-mcp-server && cd vibenote-mcp-server
pnpm init
pnpm add @modelcontextprotocol/sdk
pnpm add -D typescript tsx
// src/index.ts
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";

const server = new Server(
  { name: "vibenote-mcp-server", version: "1.0.0" },
  { capabilities: { tools: {} } }
);

server.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [
    {
      name: "search_notes",
      description: "在 VibeNote 中搜索笔记(标题/正文/标签)",
      inputSchema: {
        type: "object",
        properties: {
          query: { type: "string", description: "关键词" },
          tag: { type: "string", description: "标签,可选" },
          limit: { type: "number", description: "返回数量上限", default: 10 },
        },
        required: ["query"],
      },
    },
  ],
}));

server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { name, arguments: args } = request.params;
  if (name === "search_notes") {
    const { query, tag, limit = 10 } = args as {
      query: string;
      tag?: string;
      limit?: number;
    };
    const notes = await searchNotes({ query, tag, limit });
    return {
      content: [{ type: "text", text: JSON.stringify(notes, null, 2) }],
    };
  }
  throw new Error(`未知工具: ${name}`);
});

async function searchNotes(input: {
  query: string;
  tag?: string;
  limit: number;
}) {
  const base = process.env.VIBENOTE_API_URL ?? "http://localhost:3000";
  const token = process.env.VIBENOTE_API_TOKEN;
  const qs = new URLSearchParams({
    q: input.query,
    limit: String(input.limit),
  });
  if (input.tag) qs.set("tag", input.tag);

  const res = await fetch(`${base}/api/notes/search?${qs}`, {
    headers: token ? { Authorization: `Bearer ${token}` } : {},
  });
  if (!res.ok) throw new Error(`VibeNote API 错误: ${res.status}`);
  return res.json();
}

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

运行:

npx tsx src/index.ts

六、安全:MCP 不是「自动可信」

  • Token 管理VIBENOTE_API_TOKEN 只放本机或密钥管理,不要写进仓库。
  • 最小权限create_note 这类工具默认应对接「用户级 token」,而不是服务端超级密钥。
  • 审计:记录 tool 调用日志(脱敏),防止被提示词注入诱导越权。
  • 输入校验:Server 内对参数做长度与字符集限制,避免超大正文拖垮服务。

七、与「OpenAI Function Calling」的关系

Function Calling 在单次模型请求里声明工具;MCP 在应用层解决「工具从哪来、如何发现、如何维护」。二者可以组合:Client 把 MCP tools 映射成模型接口所需的 schema。


八、VibeNote 工具清单建议(从少到多)

第一期(稳定)

  • search_notes
  • get_note(读 Markdown)

第二期(协作)

  • list_workspace
  • comment_on_note

第三期(谨慎)

  • delete_note(强烈建议默认关闭或二次确认)

参考 Vercel 工程博客的思路:工具太多会降低稳定性——宁可少而准,不要大而全。


九、小结

  • MCP 把 AI 工具集成从「项目私货」提升为「可复用协议」。
  • stdio 适合本地;SSE 适合远程共享。
  • Tools / Resources / Prompts 分工明确:执行、读上下文、标准化工作流。
  • 安全与权限是生产落地的前提,不是上线后再补。

思考题

  1. 你会把「删除笔记」暴露为 MCP Tool 吗?如果会,需要哪些交互与安全机制?
  2. 当工具返回 JSON 很大时,你会如何在 Server 侧重做分页与摘要?
  3. stdio Server 日志误写到 stdout 会发生什么?如何设计防呆?

下节预告

下一讲进入 Context Engineering(上下文工程):当 VibeNote 代码库变大、规范变多,你会发现「提示词技巧」救不了系统性跑偏。我们将讨论被动/主动上下文、分形文档、上下文预算,以及如何把 AGENTS.md 写成真正驱动产出的「规格源文件」。