1. 项目概述与核心价值
最近在折腾AI Agent的开发,发现一个挺有意思的项目,叫kirill-markin/example-mcp-server。这名字听起来平平无奇,但如果你正在研究如何让ChatGPT、Claude这类大模型助手变得更“能干”,能直接操作你电脑上的文件、查询数据库,甚至控制你的智能家居,那这个项目就是一个绝佳的起点。简单来说,它提供了一个构建“模型上下文协议(Model Context Protocol, MCP)”服务器的标准范例。
MCP是Anthropic提出的一套开放协议,你可以把它理解为AI助手和你个人工具、数据之间的“翻译官”和“接线员”。我们平时用ChatGPT的联网搜索或代码解释器,功能是固定的。而MCP允许你为AI助手自定义无限的能力扩展。比如,我可以通过一个MCP服务器,让Claude直接读取我本地项目目录下的文件列表、查看某个文件的内容,或者向我的待办事项列表添加一条任务——所有这些操作,都不需要我把敏感数据上传到云端,而是在本地安全地完成。kirill-markin/example-mcp-server这个仓库,就用最清晰的代码,展示了如何从零开始构建这样一个“翻译官”。
这个项目特别适合两类朋友:一是对AI应用开发感兴趣的开发者,想了解如何将大模型与私有系统集成;二是重度依赖AI辅助编程或写作的极客,希望打造一个完全受控于自己、能力强大的私人AI工作流。它不涉及复杂的机器学习算法,核心是WebSocket通信和标准化的协议实现,只要有基本的Node.js或Python开发经验,就能跟着玩起来。接下来,我会带你彻底拆解这个示例服务器,看看它怎么工作,以及如何基于它打造你自己的AI“瑞士军刀”。
2. MCP协议核心思想与项目架构解析
2.1 为什么需要MCP?从“聊天机器人”到“行动代理人”
传统的AI对话模型,本质是一个基于庞大文本数据训练的“超级预测器”。你问,它答,信息都在它的训练数据里。但现实世界是动态的,我们需要AI能基于“此时此刻”的上下文行动,比如:“帮我总结一下昨天/work/logs目录下所有日志文件的错误信息”。没有MCP,你只能手动找到文件,复制粘贴内容给AI。而MCP的目标是让AI自己能去“找到”并“读取”那些文件。
MCP协议的核心思想是标准化AI与工具的对话方式。它定义了一套基于JSON-RPC over WebSocket或STDIO的通信规范。在这个体系里:
- AI客户端(如Claude Desktop):是发出指令的“大脑”。
- MCP服务器:是执行具体操作的“手和脚”,它暴露出一些“工具(Tools)”和“资源(Resources)”。
- 协议本身:规定了“大脑”如何告诉“手脚”要做什么(调用工具),以及“手脚”如何把结果(资源)返回给“大脑”。
kirill-markin/example-mcp-server项目完美诠释了这个架构。它通常包含以下几个关键部分:
- 协议实现层:处理与AI客户端的连接、消息解析(JSON-RPC)和分发。这部分往往使用官方SDK(如
@modelcontextprotocol/sdkfor Node.js)来简化。 - 工具(Tools)注册:声明服务器能提供哪些能力。每个工具都有名称、描述、输入参数模式(JSON Schema)。例如,一个“read_file”工具,参数是
{“path”: “string”}。 - 资源(Resources)定义:声明服务器能提供哪些数据源。资源用URI标识,例如
file:///home/user/doc.txt。服务器可以告诉客户端“我这里有这些资源可用”,客户端可以直接请求资源内容。 - 工具实现逻辑:这是你需要编写业务代码的地方。当客户端调用“read_file”工具时,服务器端的处理函数被执行,它根据传入的
path参数,读取本地文件,并将内容返回。
这个示例项目的价值在于,它剥离了所有业务复杂性,只保留了最骨架、最标准的协议交互代码。你看懂了它,就掌握了为任何AI助手赋能的基本方法。
2.2 项目文件结构与技术栈选择
我们以最常见的Node.js版本为例(很多MCP示例服务器使用Node.js,因其在构建工具和网络服务方面的成熟生态)。打开项目仓库,你可能会看到类似如下的结构:
example-mcp-server/ ├── package.json ├── src/ │ ├── index.ts (或 server.js) # 服务器主入口 │ ├── tools/ # 工具实现目录 │ │ ├── fileTools.ts # 文件操作工具 │ │ └── calculatorTool.ts # 计算器工具示例 │ └── resources/ # 资源定义目录 ├── tsconfig.json (如果是TypeScript项目) └── README.md- package.json:核心依赖通常会包括
@modelcontextprotocol/sdk。这是Anthropic官方提供的Node.js SDK,封装了服务器和客户端的通信细节,让你无需从零实现JSON-RPC和WebSocket管理。 - src/index.ts:这是服务器的心脏。它的工作流程通常是:
- 导入SDK,创建一个
Server实例。 - 定义服务器提供的“能力(Capabilities)”,比如声明支持哪些工具、可以提供哪些资源列表。
- 注册具体的工具处理函数(Handler)。
- 启动服务器,监听来自客户端的连接(可能是通过STDIO或WebSocket)。
- 导入SDK,创建一个
- src/tools/:这里存放着每个具体工具的代码。一个工具模块会导出一个符合
Tool接口的对象,包含name,description,inputSchema(定义参数格式),以及最重要的execute函数。execute函数是真正干活的地方,它接收客户端传来的参数,执行逻辑(如读写文件、调用API),然后返回结果或错误。 - TypeScript:很多示例项目使用TypeScript,因为MCP SDK提供了完善的类型定义,能极大提升开发体验,避免因参数格式错误导致的通信问题。
注意:虽然这里以Node.js为例,但MCP是语言无关的协议。只要你遵循协议规范,用Python、Go、Rust甚至Shell脚本都能编写MCP服务器。
kirill-markin/example-mcp-server的核心价值在于展示了协议的通用交互模式,这种模式在任何语言中都是相通的。
3. 核心工具实现与协议交互细节拆解
3.1 一个工具从定义到执行的完整生命周期
让我们深入代码,看一个最简单的工具是如何工作的。假设我们有一个“获取服务器当前时间”的工具。
在src/tools/timeTool.ts中:
import { Tool } from '@modelcontextprotocol/sdk/server.js'; export const getTimeTool: Tool = { name: 'get_current_time', description: '获取服务器的当前系统时间(UTC)。', inputSchema: { type: 'object', properties: {}, // 这个工具不需要输入参数 additionalProperties: false, }, async execute(_arguments: any, _extra: any) { // 这是工具的执行逻辑 const now = new Date(); return { content: [ { type: 'text', text: `当前服务器时间是:${now.toISOString()}`, }, ], }; }, };拆解说明:
- 定义(Definition):
name和description至关重要。AI客户端(如Claude)会根据这些描述来决定在什么情境下向用户建议使用这个工具。描述要清晰、具体。 - 输入模式(Input Schema):使用JSON Schema定义。这里定义了一个空对象,表示调用此工具时不需要任何参数。如果需要参数,比如一个加法工具,
properties里就会定义a: { type: 'number' }, b: { type: 'number' }。 - 执行(Execute):当AI客户端决定调用
get_current_time工具时,它会发送一个JSON-RPC请求(如{"method": "tools/call", "params": {"name": "get_current_time", "arguments": {}}})。服务器收到后,SDK会路由到对应工具的execute方法并执行。方法返回的结果必须遵循特定的格式,通常包含一个content数组。
那么,这个工具是如何被客户端知晓的呢?这就涉及到MCP的初始化流程。在服务器启动时,它会向客户端发送一个initialize响应,其中包含服务器的capabilities(能力声明)。在能力声明中,服务器会列出所有可用的工具(tools列表)。客户端收到后,便知道了这个服务器“手上有哪些牌”。
3.2 资源(Resources)的声明与访问
工具让AI可以“做事情”,而资源让AI可以“读数据”。资源是服务器声明的一些数据URI,客户端可以直接请求其内容。这在提供只读数据(如系统状态、配置文件模板、知识库片段)时非常有用。
例如,服务器可以声明一个资源file:///example-server/readme,对应的URI模板可能是file:///example-server/{path}。在src/index.ts中:
server.setRequestHandler( McpServerMethods.listResources, async (): Promise<ListResourcesResult> => { return { resources: [ { uri: 'file:///example-server/readme', name: '服务器使用说明', description: '本示例MCP服务器的简要介绍文档。', mimeType: 'text/plain', }, // ... 可以列出更多资源 ], }; } ); // 处理客户端对特定资源内容的请求 server.setRequestHandler( McpServerMethods.readResource, async (request: ReadResourceRequest): Promise<ReadResourceResult> => { const { uri } = request.params; if (uri === 'file:///example-server/readme') { return { contents: [ { uri: request.params.uri, mimeType: 'text/plain', text: `# 示例MCP服务器\n\n这是一个用于演示Model Context Protocol的示例服务器。它提供了...`, }, ], }; } throw new Error('Resource not found'); } );资源与工具的区别:
- 工具是动词,需要客户端主动“调用”,并可能产生副作用(如写文件)。
- 资源是名词,是客户端可以“获取”的静态或动态内容。客户端在需要某些背景信息时,可以自主决定去读取相关资源,无需用户显式触发。
在实际项目中,你可以用资源来暴露API文档、数据库Schema、常用代码片段库等,极大地丰富了AI的上下文知识。
3.3 错误处理与状态管理
一个健壮的MCP服务器必须妥善处理错误。在工具的execute函数中,对于可预见的错误(如文件不存在、参数无效),应该抛出或返回结构化的错误信息。
async execute(arguments: { path: string }) { const { path } = arguments; if (!path) { // 返回协议定义的标准错误格式 return { content: [], isError: true, error: '参数错误:必须提供文件路径。' }; } try { const content = await fs.promises.readFile(path, 'utf-8'); return { content: [{ type: 'text', text: content }] }; } catch (error: any) { if (error.code === 'ENOENT') { return { content: [], isError: true, error: `文件未找到:${path}` }; } // 其他未知错误 throw error; // 抛出错误会被SDK捕获并转换为JSON-RPC错误响应 } }状态管理是另一个需要考虑的点。MCP服务器默认是无状态的,每次工具调用都是独立的。如果你需要维护会话状态(例如,一个需要多轮交互的复杂工具),通常需要在工具内部或通过外部存储(数据库、内存缓存)来管理。协议本身不直接提供会话机制,这需要开发者自行设计。
4. 从示例到实战:构建自定义MCP服务器
4.1 环境搭建与项目初始化
假设我们现在要从零开始,基于kirill-markin/example-mcp-server的模式,构建一个用于辅助软件开发的MCP服务器,它提供“搜索项目代码”、“运行单元测试”、“查看Git状态”等功能。
第一步:初始化项目
mkdir my-dev-mcp-server cd my-dev-mcp-server npm init -y npm install @modelcontextprotocol/sdk npm install -D typescript @types/node tsx # 初始化TypeScript配置 npx tsc --init第二步:创建基础服务器文件src/server.ts
import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; // 稍后我们会在这里导入工具 // 1. 创建服务器实例 const server = new Server( { name: 'my-dev-mcp-server', version: '0.1.0', }, { capabilities: { // 初始声明为空,后续动态添加 tools: {}, }, } ); // 2. 定义工具(先占位,下一节实现) const tools = []; // 我们将把工具对象放在这里 // 3. 注册工具到服务器 for (const tool of tools) { server.registerTool(tool.name, tool.inputSchema, async (request) => { return await tool.execute(request.params.arguments, request); }); } // 4. 处理连接错误 server.onerror = (error) => { console.error('[MCP Server Error]', error); }; // 5. 启动服务器,使用STDIO传输(这是Claude Desktop默认的连接方式) async function run() { const transport = new StdioServerTransport(); await server.connect(transport); console.error('My Dev MCP Server running on stdio'); } run().catch(console.error);关键点解析:
- 传输方式(Transport):
StdioServerTransport意味着服务器通过标准输入输出(stdio)与客户端通信。这是本地集成最安全、最常用的方式,数据不经过网络。对于远程或Web应用,可以使用WebSocketServerTransport。 - 能力声明:在
new Server时初始化的capabilities可以留空或部分填写。工具也可以在运行时动态注册,但必须在客户端initialize请求完成前准备好。
4.2 实现核心开发工具
现在,我们来实现第一个实用工具:search_code,用于在指定目录下递归搜索包含特定关键词的代码文件。
创建src/tools/searchCodeTool.ts:
import { Tool } from '@modelcontextprotocol/sdk/server.js'; import { glob } from 'glob'; // 需要安装 glob 包 import fs from 'fs/promises'; import path from 'path'; export const searchCodeTool: Tool = { name: 'search_code', description: '在指定项目目录中递归搜索包含特定文本的代码文件。返回匹配的文件路径和上下文行。', inputSchema: { type: 'object', properties: { rootDir: { type: 'string', description: '要搜索的根目录路径。', }, pattern: { type: 'string', description: '要搜索的文本模式(支持正则表达式)。', }, fileExtensions: { type: 'array', items: { type: 'string' }, description: '要限制的文件扩展名数组,如 [".js", ".ts", ".py"]。默认为空,搜索所有文件。', default: [], }, maxResults: { type: 'number', description: '返回的最大结果数量。', default: 10, }, }, required: ['rootDir', 'pattern'], additionalProperties: false, }, async execute(args: { rootDir: string; pattern: string; fileExtensions?: string[]; maxResults?: number; }) { const { rootDir, pattern, fileExtensions = [], maxResults = 10 } = args; // 安全检查:防止目录遍历攻击 const resolvedPath = path.resolve(rootDir); if (!resolvedPath.startsWith(process.cwd())) { // 简单示例,限制在当前工作目录下 throw new Error('出于安全考虑,只能搜索当前工作目录及其子目录下的文件。'); } // 构建glob模式 let globPattern = `${rootDir}/**/*`; if (fileExtensions.length > 0) { globPattern = `${rootDir}/**/*.{${fileExtensions.join(',')}}`; } const files = await glob(globPattern, { nodir: true }); const results: string[] = []; const regex = new RegExp(pattern, 'i'); // 简单起见,不区分大小写 for (const file of files) { if (results.length >= maxResults) break; try { const content = await fs.readFile(file, 'utf-8'); const lines = content.split('\n'); const matchingLines: string[] = []; for (let i = 0; i < lines.length; i++) { if (regex.test(lines[i])) { // 提供上下文:显示匹配行及其前后一行 const start = Math.max(0, i - 1); const end = Math.min(lines.length - 1, i + 1); const context = lines.slice(start, end + 1).join('\n'); matchingLines.push(`L${start + 1}-L${end + 1}:\n${context}`); break; // 每个文件只取第一个匹配处 } } if (matchingLines.length > 0) { const relativePath = path.relative(process.cwd(), file); results.push(`**文件:** ${relativePath}\n${matchingLines.join('\n---\n')}`); } } catch (error) { // 忽略无权限读取的文件 console.error(`无法读取文件 ${file}:`, error); } } if (results.length === 0) { return { content: [{ type: 'text', text: `未在目录 "${rootDir}" 下找到匹配模式 "${pattern}" 的代码。` }], }; } return { content: [ { type: 'text', text: `在 "${rootDir}" 中找到 ${results.length} 个匹配文件:\n\n${results.join('\n\n')}`, }, ], }; }, };工具实现要点:
- 输入验证与默认值:
inputSchema中定义了参数的类型、描述、是否必需以及默认值。这是AI客户端理解如何调用工具的依据。 - 安全第一:工具将直接操作本地系统,必须进行路径解析和访问限制,防止恶意参数导致服务器读取或修改敏感系统文件。这里简单限制在了当前工作目录下。
- 用户体验:返回的结果格式清晰,包含文件相对路径和代码上下文,方便AI(和用户)理解。限制了最大结果数,避免一次返回过多数据。
- 错误容忍:对无法读取的文件进行捕获和记录,而不是让整个工具调用失败。
按照同样的模式,你可以继续实现run_tests(调用npm test或pytest)、git_status(调用git status -s并解析结果)等工具。每个工具都是一个独立的模块,最后在src/server.ts中导入并注册。
4.3 配置与连接AI客户端(以Claude Desktop为例)
服务器写好了,如何让Claude使用它呢?这需要通过客户端的配置文件来建立连接。
对于Claude Desktop:
- 找到Claude Desktop的配置文件夹。
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json - Linux:
~/.config/Claude/claude_desktop_config.json
- macOS:
- 编辑(或创建)
claude_desktop_config.json文件,添加你的MCP服务器配置。
{ "mcpServers": { "my-dev-server": { "command": "node", "args": [ "/ABSOLUTE/PATH/TO/YOUR/PROJECT/dist/server.js" // 指向你编译后的JS文件 ], "env": { "NODE_ENV": "production" } } } }- 保存文件并重启Claude Desktop。
配置解析:
command: 启动服务器的命令,这里是node。args: 传递给命令的参数,第一个是你的服务器入口文件。必须使用绝对路径。env: 可选的环-境变量。
重启后,Claude Desktop会在启动时自动运行你配置的MCP服务器。你可以在Claude的聊天界面中,通过输入“/”来查看和调用服务器提供的工具。例如,输入/search_code,Claude会引导你填写参数,然后调用你的服务器并显示结果。
重要提示:在开发调试阶段,建议先通过命令行手动运行服务器,确保没有语法错误,并能正常处理请求。你可以创建一个简单的测试脚本来模拟客户端调用,或者使用MCP SDK自带的测试工具。
5. 高级主题:性能、安全与最佳实践
5.1 性能优化策略
当你的MCP服务器工具越来越复杂,或者需要处理大量数据时,性能就成为关键考量。
- 工具调用的异步与非阻塞:确保所有工具的实现都是完全异步的(使用
async/await)。避免在工具处理函数中执行同步的、耗时的CPU密集型操作(如大型文件同步读取、复杂计算),这会导致服务器阻塞,无法响应其他请求。对于耗时操作,考虑使用Worker线程或子进程。 - 资源懒加载与缓存:对于通过
resources暴露的数据,如果生成成本高,应考虑缓存策略。例如,一个“获取系统性能指标”的资源,可以每5秒更新一次缓存,而不是每次请求都实时执行ps、top等命令。 - 结果分页与流式传输:对于可能返回大量数据的工具(如全文搜索),实现分页机制。MCP协议本身支持在工具响应中返回
isComplete标志和分页令牌,但需要服务器端实现逻辑。更高级的做法是探索服务器向客户端的推送(notifications),实现流式结果返回。 - 连接管理与心跳:确保服务器能稳定处理客户端连接断开和重连。虽然SDK通常会处理底层连接,但你的工具代码应考虑到连接可能在任何时刻中断,做好资源清理(如关闭临时打开的文件句柄、数据库连接)。
5.2 安全加固指南
MCP服务器运行在本地,拥有与启动用户相同的权限。一个设计不当的工具可能成为安全漏洞。
- 输入验证与净化(Sanitization):这是最重要的防线。对所有来自客户端的输入参数进行严格验证。
- 路径遍历:使用
path.resolve()解析路径,并检查解析后的路径是否在预期的安全目录内(白名单机制)。绝对不要直接将用户输入的路径传递给fs.readFile。 - 命令注入:如果需要执行系统命令(如调用
git、npm),绝对不要使用字符串拼接来构建命令。始终使用数组形式的参数,并考虑使用child_process.spawn。
// 危险! const command = `git log ${userInput}`; // 如果userInput是 `--pretty=format:%H && rm -rf /` 就完了 // 安全做法 import { spawn } from 'child_process'; const args = ['log', '--pretty=format:%H']; // 固定参数 if (userInput) { // 对userInput进行严格的白名单验证 if (isValidGitOption(userInput)) { args.push(userInput); } } const child = spawn('git', args); - 路径遍历:使用
- 权限最小化:不要以高权限(如root/Administrator)运行MCP服务器。为它创建一个专用的、权限受限的系统用户。在服务器代码中,可以主动降低进程权限(Node.js中可用
process.setuid)。 - 敏感信息处理:工具返回的内容可能被AI用于后续对话。确保工具不会意外返回密码、API密钥、个人身份信息等敏感数据。考虑对输出进行过滤或脱敏。
- 审计与日志:记录所有工具调用的日志(包括调用者、参数、时间、结果状态),但注意不要记录敏感参数内容。这有助于事后审查和问题排查。
5.3 开发与调试技巧
- 使用TypeScript和严格模式:MCP SDK提供了完整的TypeScript类型定义。利用它们可以在编码阶段就发现很多潜在的类型错误和协议兼容性问题。在
tsconfig.json中开启strict: true。 - 利用SDK的调试模式:一些MCP SDK提供了调试日志。在开发时,可以设置环境变量(如
DEBUG=mcp*)来打印详细的协议通信日志,这对于理解请求/响应流程非常有帮助。 - 模拟客户端进行单元测试:为你的工具函数编写单元测试,模拟输入参数,验证输出是否符合协议规范。对于服务器集成测试,可以编写一个简单的测试客户端,使用相同的SDK连接到你的服务器并发送请求。
- 逐步增加复杂性:从一个最简单的“echo”工具开始,确保基础通信正常。然后再逐步添加文件操作、命令执行等更复杂的工具。每增加一个工具,都先在Claude Desktop中测试其可用性和安全性。
- 参考官方和社区示例:除了
kirill-markin/example-mcp-server,多研究Anthropic官方提供的其他示例服务器(如文件系统服务器、SQLite服务器)。社区也有很多优秀的开源MCP服务器项目,可以学习它们的架构和实现。
6. 常见问题与排查实录
在实际开发和集成过程中,你肯定会遇到各种问题。以下是我在搭建和调试MCP服务器时踩过的一些坑和解决方案。
6.1 连接与配置问题
问题1:Claude Desktop重启后,提示“无法连接到MCP服务器”或没有任何反应。
- 排查步骤:
- 检查配置文件路径和语法:确保
claude_desktop_config.json文件在正确的位置,并且是合法的JSON格式。一个多余的逗号就会导致整个配置失效。 - 检查命令路径:
args中的服务器入口文件路径必须是绝对路径。使用相对路径(如./dist/server.js)在Claude Desktop的上下文中很可能无法解析。可以用pwd命令获取绝对路径。 - 手动运行测试:打开终端,切换到项目目录,直接运行配置文件中写的命令(如
node /absolute/path/to/server.js)。观察控制台是否有错误输出。常见的错误包括:Error: Cannot find module '@modelcontextprotocol/sdk/server'-> 依赖未安装,在项目目录下运行npm install。- 语法错误 -> 使用
tsc --noEmit或node --check检查TypeScript/JavaScript语法。
- 查看Claude Desktop日志:Claude Desktop通常会有应用日志,里面可能包含更详细的连接错误信息。日志位置因操作系统而异,可以在网上搜索“Claude Desktop logs location”找到。
- 检查配置文件路径和语法:确保
问题2:服务器启动了,但在Claude里看不到工具列表。
- 可能原因:
- 初始化响应超时或格式错误:客户端在连接后会发送
initialize请求,服务器必须在短时间内正确响应。检查你的服务器代码,确保server.setRequestHandler或server.registerTool在连接建立前就已完成。响应结构必须严格符合协议。 - 工具描述过长或格式不符:工具(
Tool)对象的description和inputSchema中的description字段应简洁明了。虽然协议没有严格长度限制,但过长的描述可能导致解析问题。确保inputSchema是有效的JSON Schema对象。 - 能力声明缺失:在
new Server()时或是在initialize请求的处理中,返回的capabilities对象里必须包含tools字段(即使为空对象{})。
- 初始化响应超时或格式错误:客户端在连接后会发送
6.2 工具调用与执行问题
问题3:调用工具时,AI返回“工具执行错误”或超时。
- 排查步骤:
- 查看服务器日志:这是最重要的信息源。确保你的服务器将错误和异常打印到控制台(
console.error)。超时通常意味着工具execute函数中有未处理的Promise拒绝或死循环。 - 参数验证失败:检查客户端发送的参数是否完全符合你定义的
inputSchema。SDK可能会进行初步验证,但复杂的自定义验证逻辑失败也会导致错误。在execute函数开头打印传入的arguments进行确认。 - 异步操作未正确处理:确保
execute函数是async的,并且所有异步操作都使用了await。一个未捕获的Promise拒绝会导致整个调用失败。 - 资源权限不足:如果工具涉及文件或网络操作,检查运行Claude Desktop和MCP服务器的用户是否有相应的读写权限或网络访问权。
- 查看服务器日志:这是最重要的信息源。确保你的服务器将错误和异常打印到控制台(
问题4:工具执行成功,但返回的结果AI无法理解或格式错误。
- 解决方案:MCP协议要求工具返回一个特定格式的对象。最常用的格式是:
return { content: [{ type: 'text', // 目前主要支持'text'类型 text: '你的结果字符串在这里' }] };- 确保返回的是对象,不是字符串。
- 确保
content是一个数组。 - 确保数组中的每个元素都有
type和text属性。 text内容可以是Markdown格式,AI能更好地渲染和理解。
6.3 进阶调试技巧
- 使用网络版Claude进行远程调试:除了Claude Desktop,你还可以配置Claude.ai(网页版)通过SSH隧道连接到你开发机上的MCP服务器(使用WebSocket传输)。这允许你在功能强大的开发环境中调试,而无需频繁重启桌面应用。具体配置涉及SSH隧道和
WebSocketServerTransport的使用,有一定复杂度,但对于复杂服务器开发非常有用。 - 编写集成测试脚本:创建一个独立的测试脚本,使用
StdioClientTransport或WebSocketClientTransport模拟客户端,对你的服务器进行一系列自动化调用测试。这能极大提高开发效率。 - 监控资源使用:长时间运行的MCP服务器可能会有内存泄漏。使用Node.js内置的
--inspect标志启动服务器,然后使用Chrome DevTools或node --inspect进行内存快照和性能分析。
构建一个稳定、好用的MCP服务器是一个迭代的过程。从最简单的示例出发,理解协议的核心交互,然后逐步添加符合自己工作流的工具。每当你手动执行一个重复性的任务时,都可以思考一下:“这个任务能否抽象成一个MCP工具,让AI替我做?” 久而久之,你就会拥有一个高度定制化、无比顺手的AI增强工作环境。kirill-markin/example-mcp-server就是这个旅程的完美第一站,它给了你地图和钥匙,剩下的路,就看你如何探索了。