从零搭建一个 MCP Server:把你的能力接进所有 AI 工具
上一篇讲了 MCP 的协议原理,这篇动手——从零做一个生产可用的 MCP Server:选场景、用官方 SDK 实现 resources + tools、本地 stdio 与远程 HTTP 两种部署、用 MCP Inspector 调试,最后让 Claude Code 和 Cursor 都用上它。
本文要点
- 主题聚焦:MCP、TypeScript
- 适合人群:独立开发者、全栈工程师、AI 应用开发者
- 核心价值:快速理解背景、实践路径、常见误区和工程建议
上一篇《MCP 协议深度解析》讲清了"为什么"和"协议长什么样"。这篇讲"怎么做"——我们做一个真实的、能被 Claude Code 和 Cursor 直接调用的 MCP Server:一个"个人知识库检索"服务,暴露文档作为 resources、暴露"检索/总结"作为 tools。
跑通这一篇,你就掌握了把任意能力接入 AI 编程生态的标准方法。
一、选场景:什么样的东西适合做成 MCP
不是所有功能都值得做成 Server。值得封装的判断标准:
- 会被反复调用(一次性的不需要)。
- 有状态或有数据(连着某个库、某个 API、某组文件)。
- 跨工具复用(不止一个 AI 客户端想用)。
我们选"个人知识库检索":把本地一批 markdown 笔记暴露给 AI,让模型能 search("MCP") 检索、read("notes/xxx.md") 读全文。这对任何编程助手都是刚需。
二、初始化项目
mkdir kb-mcp && cd kb-mcp
npm init -y
npm i @modelcontextprotocol/sdk zeta zod
npm i -D typescript @types/node tsx
SDK 版本建议固定到具体版本(MCP 规范演进快)。目录结构:
kb-mcp/
src/index.ts // Server 入口
src/search.ts // 检索逻辑(简单 BM25 或 grep)
notes/ // 被检索的 markdown
三、实现 Server:resources + tools
// src/index.ts
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { listNotes, readNote, searchNotes } from "./search.js";
const server = new McpServer({ name: "kb-mcp", version: "0.1.0" });
// 1) Resources:把每条笔记作为可读资源
const notesResource = new ResourceTemplate("notes://{slug}", { list: undefined });
server.resource(
"note",
notesResource,
async (uri, { slug }) => ({
contents: [{ uri: uri.href, mimeType: "text/markdown", text: await readNote(slug as string) }],
}),
);
// 2) Tools:检索 + 总结入口
server.tool(
"search_notes",
"Full-text search across local markdown notes. Returns matching slugs and snippets.",
{ query: z.string().describe("keywords to search") },
async ({ query }) => {
const hits = await searchNotes(query);
return {
content: [{ type: "text", text: JSON.stringify(hits, null, 2) }],
};
},
);
server.tool(
"read_note",
"Read the full content of a note by its slug.",
{ slug: z.string() },
async ({ slug }) => ({
content: [{ type: "text", text: await readNote(slug) }],
}),
);
await server.connect(new StdioServerTransport());
检索逻辑这里用最朴素的全文匹配(够用、零依赖):
// src/search.ts
import fs from "node:fs/promises";
import path from "node:path";
const NOTES_DIR = path.resolve("notes");
export async function listNotes() {
return (await fs.readdir(NOTES_DIR)).filter((f) => f.endsWith(".md"));
}
export async function readNote(slug: string) {
return fs.readFile(path.join(NOTES_DIR, `${slug}.md`), "utf8");
}
export async function searchNotes(query: string) {
const files = await listNotes();
const q = query.toLowerCase();
const hits = [];
for (const f of files) {
const text = await readNote(f.replace(/\.md$/, ""));
const idx = text.toLowerCase().indexOf(q);
if (idx >= 0) {
hits.push({ slug: f.replace(/\.md$/, ""), snippet: text.slice(Math.max(0, idx - 40), idx + 80) });
}
}
return hits.slice(0, 10);
}
四、本地调试:MCP Inspector
官方提供的 Inspector 是调试利器,能可视化看到 resources/tools 列表并手动调用:
npx @modelcontextprotocol/inspector npx tsx src/index.ts
它会起一个本地 web 界面,列出你 Server 暴露的所有 tools/resources,点一下就能测调用结果——比"配到 Claude Code 再问一句看它有没有调"快十倍。开发阶段全程用 Inspector,开发完再接客户端。
五、接入客户端
Claude Code(项目级 .mcp.json,随仓库共享):
{
"mcpServers": {
"kb": { "command": "npx", "args": ["tsx", "/abs/path/kb-mcp/src/index.ts"] }
}
}
Cursor(设置 → MCP → Add)填同样内容。重启后,问"我笔记里有没有关于 RAG 的"——模型会自动 search_notes(query:"RAG") → read_note(slug:"...") 并回答。
六、改成远程 Server(HTTP)
要被多个客户端共享、或部署到服务器,就上远程模式(Streamable HTTP):
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { createServer } from "http";
const httpServer = createServer(async (req, res) => {
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
await server.connect(transport);
await transport.handleRequest(req, res);
});
httpServer.listen(3001);
客户端配置从 command/args 改成 url: "https://kb.example.com/mcp"。远程务必加鉴权——在 req 入口校验 Authorization: Bearer <token>,否则你的笔记对任何知道地址的人裸奔。
七、发布与版本管理
- 固定版本:
@modelcontextprotocol/sdk升级偶尔会改 API,生产环境锁版本。 - 提供一份 README:写清暴露了哪些 tools、每个 tool 的参数和用途——这既是给用户的文档,也是你写 tool
description时的素材库。 - 语义化日志:每个 tool 调用打一行结构化日志(who/what/args/latency),远程 Server 出了问题才好查。
小结
做一个 MCP Server 的完整闭环:选场景 → SDK 实现 resources/tools → Inspector 调试 → 接客户端 → (可选)改远程 + 鉴权 → 发布。
整件事的关键不是代码量(核心就几十行),而是把"你独有的数据/能力"按 MCP 这份契约标准化的思路。一旦封装好,它就同时进入了 Claude Code、Cursor、Cline、Continue 的能力池——这是过去要做 N 份集成才能达到的覆盖。把你的产品/数据做成 MCP Server,是 AI 编程时代最低成本的"被集成"策略。
Related