
如果你最近在关注AI工具的发展,一定听说过MCP(Model Context Protocol)这个词。2024年11月,Anthropic推出这个开放协议时,很多人的第一反应是"又一个新标准"。但当你真正尝试用它构建一个AI工具时,会发现这个协议解决的问题确实足够痛——它让AI助手能够以标准化的方式连接各种数据源和工具,而不需要为每个新工具重新设计对接方案。
今天这篇文章不讲理论,我们直接上手:从零开始构建一个简单但完整的MCP服务,实现一个"每日诗词"功能,并把它接入到Claude Desktop中。在这个过程中,你会理解MCP的核心概念、开发流程和调试技巧。
准备工作:你需要什么
在开始之前,确保你的开发环境满足这些条件。首先是Node.js 18或更高版本,这是运行MCP TypeScript SDK的基础。然后下载安装Claude Desktop应用,这将作为我们的MCP客户端。代码编辑器方面,VS Code是个不错的选择,它对TypeScript的支持很完善。最后,基本的TypeScript和异步编程知识会让你理解起来更顺畅,但即使你是JavaScript新手,跟着做也能完成整个流程。
用npm全局安装MCP Inspector,这个工具在开发调试阶段非常有用,能让你在接入Claude之前就测试MCP服务是否正常工作。打开终端执行npm install -g @modelcontextprotocol/inspector,安装完成后你就拥有了一个独立的MCP服务调试器。
理解MCP的核心概念
在写代码之前,我们需要理解MCP中的几个关键概念。MCP定义了三种核心"原语"(Primitives),它们是服务器向客户端提供能力的方式。
**Tools(工具)**是可执行的函数,AI可以主动调用它们来完成特定任务。比如"查询天气"、“发送邮件”、“搜索数据库"这些都是工具。当用户问Claude"今天北京天气怎么样”,如果你提供了天气查询工具,Claude就能自动调用它。
**Resources(资源)**是可读取的数据源,为AI提供上下文信息。比如一个文件的内容、数据库的schema、API的返回结果。资源通常是被动的——AI读取它们,但不直接执行操作。
**Prompts(提示模板)**是预定义的交互模板,帮助用户快速启动特定任务。比如"代码审查助手"、"邮件撰写模板"这些都可以作为Prompts提供。
我们今天要实现的"每日诗词"服务,会同时提供一个Tool(获取随机诗词)和一个Resource(诗词数据源说明)。
动手实现:构建诗词MCP服务
创建一个新的项目目录,初始化Node.js项目。在终端中依次执行:
mkdir poetry-mcp
cd poetry-mcp
npm init -y
安装MCP SDK和必要的依赖:
npm install @modelcontextprotocol/sdk
npm install --save-dev @types/node typescript
创建TypeScript配置文件tsconfig.json,确保编译器能正确处理模块系统:
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
}
}
在src目录下创建主文件index.ts。这里是完整的实现代码,包含详细注释:
#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
ListResourcesRequestSchema,
ReadResourceRequestSchema
} from "@modelcontextprotocol/sdk/types.js";
// 诗词数据(实际项目可以从API或数据库获取)
const poems = [
{ title: "静夜思", author: "李白", content: "床前明月光,疑是地上霜。举头望明月,低头思故乡。" },
{ title: "春晓", author: "孟浩然", content: "春眠不觉晓,处处闻啼鸟。夜来风雨声,花落知多少。" },
{ title: "登鹳雀楼", author: "王之涣", content: "白日依山尽,黄河入海流。欲穷千里目,更上一层楼。" }
];
// 创建MCP服务器实例
const server = new Server(
{
name: "poetry-server",
version: "1.0.0",
},
{
capabilities: {
tools: {},
resources: {}
},
}
);
// 注册工具列表处理器
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: "get_random_poem",
description: "获取一首随机的中国古诗词",
inputSchema: {
type: "object",
properties: {},
required: []
}
}
]
};
});
// 注册工具调用处理器
server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name === "get_random_poem") {
const randomPoem = poems[Math.floor(Math.random() * poems.length)];
return {
content: [
{
type: "text",
text: `《${randomPoem.title}》\n作者:${randomPoem.author}\n\n${randomPoem.content}`
}
]
};
}
throw new Error(`Unknown tool: ${request.params.name}`);
});
// 注册资源列表处理器
server.setRequestHandler(ListResourcesRequestSchema, async () => {
return {
resources: [
{
uri: "poetry://info",
name: "诗词库信息",
mimeType: "text/plain",
description: "关于诗词数据源的说明"
}
]
};
});
// 注册资源读取处理器
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
if (request.params.uri === "poetry://info") {
return {
contents: [
{
uri: request.params.uri,
mimeType: "text/plain",
text: "本诗词库包含精选的中国古典诗词,涵盖唐宋等朝代的经典作品。"
}
]
};
}
throw new Error(`Unknown resource: ${request.params.uri}`);
});
// 启动服务器
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("诗词MCP服务器已启动");
}
main().catch((error) => {
console.error("服务器启动失败:", error);
process.exit(1);
});
在package.json中添加构建和启动脚本:
{
"type": "module",
"bin": {
"poetry-mcp": "./dist/index.js"
},
"scripts": {
"build": "tsc",
"start": "node dist/index.js"
}
}
编译TypeScript代码:npm run build。如果一切顺利,你会在dist目录下看到编译好的JavaScript文件。
调试阶段:使用MCP Inspector测试
在接入Claude之前,先用MCP Inspector验证服务是否正常。在项目目录下执行:
npx @modelcontextprotocol/inspector node dist/index.js
Inspector会启动一个Web界面,通常在http://localhost:5173。打开浏览器访问这个地址,你会看到一个交互式调试界面。
在左侧面板,你能看到服务器提供的Tools和Resources列表。点击"get_random_poem"工具,然后点击"Call Tool"按钮。如果实现正确,右侧会显示返回的诗词内容。同样地,你可以测试读取"poetry://info"资源,查看是否能正确返回诗词库信息。
这个调试步骤非常关键。很多开发者直接跳过Inspector,把有问题的服务接入Claude,然后花大量时间排查为什么不工作。Inspector提供的实时调试能力能让你快速定位问题——是参数解析错误、返回格式不对、还是异步处理有bug。
接入Claude Desktop:让AI用上你的工具
现在我们的MCP服务已经经过验证,可以接入Claude了。找到Claude Desktop的配置文件。在macOS上,它位于~/Library/Application Support/Claude/claude_desktop_config.json;Windows用户则在%APPDATA%\Claude\claude_desktop_config.json。
用文本编辑器打开这个JSON文件,添加你的MCP服务配置:
{
"mcpServers": {
"poetry": {
"command": "node",
"args": ["/完整路径/poetry-mcp/dist/index.js"]
}
}
}
注意这里的路径必须是绝对路径。保存文件后,完全退出Claude Desktop(不是最小化,是真正退出),然后重新启动。
重启后,在Claude的对话界面右下角,你应该能看到一个小工具图标,点击它会显示已连接的MCP服务。如果"poetry"出现在列表中,说明连接成功。现在你可以尝试和Claude对话:“给我读一首古诗吧”。Claude会自动识别这个需求,调用你提供的get_random_poem工具,并把结果整合到回复中。
常见问题与调试技巧
在实际开发中,你可能会遇到一些问题。如果Claude连不上你的MCP服务,首先检查配置文件路径是否正确,JSON格式是否有效。可以用node dist/index.js手动运行服务,看终端是否有错误输出。
如果服务能连接但工具调用失败,回到MCP Inspector验证工具的inputSchema和返回格式。MCP对JSON Schema的校验很严格,字段类型不匹配会导致调用失败。记得在处理器函数中添加try-catch,并输出详细的错误日志到stderr(因为stdout被MCP协议占用)。
对于异步操作,确保所有Promise都被正确await。MCP服务器的请求处理器必须是异步函数,如果你在内部调用API或数据库,忘记await会导致请求超时或返回空结果。
扩展思路:从诗词到实际应用
这个诗词服务只是个起点,展示了MCP的基本工作方式。在实际项目中,你可以扩展很多功能。比如连接真实的API,从今日诗词或古诗文网获取更丰富的内容;添加参数支持,让用户指定朝代、作者或诗词类型;实现缓存机制,避免重复请求外部API。
更进一步,你可以把这个模式应用到其他领域。企业可以开发MCP服务连接内部知识库,让Claude直接访问公司文档;开发者可以封装常用的开发工具,比如Git操作、代码格式化、API测试。MCP的标准化意味着一次开发,多处使用——同样的服务可以接入Claude、也可以接入其他支持MCP的AI客户端。
Suppr的团队已经用这种方式实现了PubMed搜索和文档翻译服务(项目地址:https://github.com/zjg678/suppr-mcp),展示了如何在学术研究场景中应用MCP。他们的实现包含了更复杂的功能,比如异步任务管理、状态跟踪、多语言支持,非常值得学习参考。
写在最后
MCP协议的价值不在于技术多么复杂,而在于它建立了一个开放的生态标准。当越来越多的工具和数据源支持MCP,AI助手能做的事情就会呈指数级增长。作为开发者,现在正是入场的好时机——生态还在早期,标准相对简单,但潜力巨大。
希望通过这篇教程,你不仅学会了如何实现一个MCP服务,更重要的是理解了MCP的设计理念和应用场景。接下来,不妨尝试把你日常工作中重复使用的工具封装成MCP服务,让AI真正成为提升效率的助手。代码写完的那一刻,当你看到Claude自然地调用你的工具完成任务,那种"AI确实在帮我干活"的感觉,会让你觉得这些折腾都值得。
相关资源:
- MCP官方文档:https://modelcontextprotocol.io
- TypeScript SDK:https://github.com/modelcontextprotocol/typescript-sdk
- Suppr 超能文献MCP参考实现:https://github.com/zjg678/suppr-mcp
- MCP Inspector:https://github.com/modelcontextprotocol/inspector
2214

被折叠的 条评论
为什么被折叠?



