基于MCP协议构建AI工具集成服务器:从原理到实践

news2026/5/16 9:00:14
1. 项目概述一个开源的MCP服务器实现最近在折腾AI应用开发特别是想给本地的大语言模型LLM加点“外挂”让它能直接操作我的文件系统、数据库甚至调用一些外部API。这让我接触到了一个挺有意思的概念模型上下文协议Model Context Protocol MCP。简单来说MCP就像是为AI模型定义的一套“插件”标准让模型能安全、可控地访问和使用外部工具与数据。而我今天要拆解的这个项目rogertheunissenmerge-oss/mcp-server就是一个遵循MCP协议的开源服务器实现。它不是一个具体的工具而是一个基础框架或模板。你可以基于它快速构建出你自己的MCP服务器从而将你的本地服务、私有API或者任何你想让AI访问的能力“翻译”成LLM能理解和调用的标准接口。想象一下你有一个内部的项目管理工具或者一个自定义的日志分析脚本。通过基于这个项目开发一个MCP服务器你就可以在ChatGPT、Claude Desktop或者任何支持MCP的客户端里直接让AI帮你查询项目进度、分析日志趋势而无需手动复制粘贴数据。这对于提升开发、运维甚至日常办公的效率潜力巨大。这个项目适合谁呢首先是对AI应用集成感兴趣的开发者尤其是那些希望将私有化、定制化能力接入AI工作流的朋友。其次它也是学习MCP协议一个非常好的实践入口。通过阅读和修改它的代码你能清晰地理解MCP服务器是如何与客户端进行通信、如何定义工具Tools和资源Resources的。2. 核心架构与MCP协议解析要理解这个项目我们必须先搞懂MCP协议的核心思想。MCP不是一个具体的软件而是一套通信规范。它定义了AI客户端比如Claude Desktop、Cursor IDE与服务器也就是各种能力提供方之间如何“对话”。2.1 MCP的核心组件MCP交互主要围绕三个核心概念服务器Server也就是rogertheunissenmerge-oss/mcp-server这类项目所扮演的角色。它对外暴露一系列能力。一个服务器可以同时提供多种工具和资源。工具Tools这是AI可以主动调用的“函数”。比如“读取文件”、“执行SQL查询”、“发送HTTP请求”。每个工具都有明确的输入参数一个JSON Schema和输出结果。当AI认为需要调用某个工具时它会向服务器发送一个包含参数的工具调用请求。资源Resources这是AI可以被动读取的“数据源”。比如“file:///path/to/log.txt”可以作为一个资源。资源通过URI统一资源标识符来定位并且有内容类型如text/plainapplication/json。AI客户端可以列出List和读取Read资源的内容将其作为上下文信息喂给模型。这种设计非常巧妙。工具让AI具备了“动手能力”可以执行操作、改变状态而资源让AI具备了“感知能力”可以获取信息、了解现状。两者结合AI就能在一个受控的上下文环境中完成复杂的多步骤任务。2.2 通信机制SSE与JSON-RPCMCP服务器与客户端之间通常通过两种方式通信而这个项目主要演示了其中一种SSEServer-Sent Events over STDIO。听起来有点复杂但其实原理很简单。服务器作为一个独立的进程启动它的标准输入stdin和标准输出stdout被重定向用于与客户端进程通信。服务器通过stdout持续发送事件流这就是SSE而通过stdin接收来自客户端的请求JSON-RPC格式。为什么选择SSE over STDIO对于本地集成的场景这种方式极其轻量和高效。它不需要复杂的网络端口绑定、认证和防火墙规则直接利用进程间通信IPC安全性好启动速度快。这对于需要频繁启停、或作为桌面应用插件运行的MCP服务器来说是理想的选择。这个项目的代码结构清晰地反映了这一点。你会看到一个核心的Server类它内部维护着工具和资源的注册表并包含一个主循环用于处理来自stdin的请求并将响应和通知写入stdout。注意虽然这个项目以SSE over STDIO为主但MCP协议也支持WebSocket等网络传输方式。选择哪种方式取决于你的部署场景。本地插件用STDIO远程服务则可能需要WebSocket。2.3 项目代码结构初窥虽然我们不会逐行分析代码但了解其目录结构有助于理解如何扩展它。一个典型的基于此项目的MCP服务器可能包含以下部分your-mcp-server/ ├── src/ │ ├── server.ts (或 server.js) # 继承或实例化基础MCP服务器 │ ├── tools/ # 存放各个工具的实现 │ │ ├── fileReader.ts │ │ ├── sqlQuery.ts │ │ └── customApi.ts │ └── resources/ # 存放资源模板和读取逻辑 │ └── logResource.ts ├── package.json # 项目依赖和启动脚本 └── tsconfig.json (如果是TypeScript项目)核心的server.ts文件会导入基础MCP库比如modelcontextprotocol/sdk创建服务器实例然后将自己实现的工具和资源“挂载”上去最后启动服务器监听。3. 从零开始构建一个自定义MCP服务器理论讲得再多不如动手实践。让我们基于rogertheunissenmerge-oss/mcp-server的理念从头构建一个简单的、具有实用价值的MCP服务器。我们的目标是创建一个“系统信息查询”服务器让AI能获取当前机器的CPU、内存使用率和磁盘空间信息。3.1 环境准备与项目初始化首先确保你有一个Node.js环境版本16或以上。我们使用TypeScript来获得更好的类型提示这对于实现需要严格遵循JSON Schema的工具接口非常有帮助。# 1. 创建项目目录并初始化 mkdir my-system-info-mcp cd my-system-info-mcp npm init -y # 2. 安装TypeScript和MCP SDK开发依赖 npm install typescript ts-node types/node --save-dev npm install modelcontextprotocol/sdk systeminformation --save # 3. 初始化TypeScript配置 npx tsc --init编辑生成的tsconfig.json确保outDir和rootDir设置正确并启用严格的ES模块支持这对于MCP SDK很重要。{ compilerOptions: { target: ES2022, module: NodeNext, moduleResolution: NodeNext, outDir: ./dist, rootDir: ./src, strict: true, esModuleInterop: true, skipLibCheck: true, forceConsistentCasingInFileNames: true }, include: [src/**/*], exclude: [node_modules] }3.2 定义工具获取系统状态MCP工具的核心是一个符合JSON Schema的描述和一个执行函数。我们创建一个src/tools/getSystemStats.ts文件。// src/tools/getSystemStats.ts import { Tool } from modelcontextprotocol/sdk/server.js; import si from systeminformation; /** * 定义获取系统状态的工具 */ export const getSystemStatsTool: Tool { name: get_system_stats, // 工具的唯一标识AI将通过这个名字调用 description: 获取当前计算机的实时系统状态包括CPU负载、内存使用率和磁盘空间。, inputSchema: { type: object, properties: { // 这个工具不需要输入参数所以properties为空对象 }, required: [], // 没有必填参数 }, }; /** * 工具的执行函数 * 当AI调用 get_system_stats 时这个函数会被执行。 */ export async function executeGetSystemStats() { try { // 并行获取多项系统信息提高效率 const [cpuLoad, memInfo, fsSize] await Promise.all([ si.currentLoad(), si.mem(), si.fsSize(), ]); // 计算内存使用率 const memoryUsagePercent ((memInfo.active / memInfo.total) * 100).toFixed(1); // 计算主要磁盘通常是C盘或根分区的使用率 const mainDisk fsSize.find(fs fs.mount /) || fsSize.find(fs fs.mount C:\\) || fsSize[0]; const diskUsagePercent mainDisk ? ((mainDisk.used / mainDisk.size) * 100).toFixed(1) : N/A; // 构造一个对人类和AI都友好的响应 const result { cpu: { load: cpuLoad.currentLoad.toFixed(1) %, // 当前总负载 cores: cpuLoad.cpus.length, }, memory: { used: (memInfo.active / 1024 / 1024 / 1024).toFixed(2) GB, total: (memInfo.total / 1024 / 1024 / 1024).toFixed(2) GB, usage: memoryUsagePercent %, }, disk: { mount: mainDisk?.mount || Unknown, used: mainDisk ? (mainDisk.used / 1024 / 1024 / 1024).toFixed(2) GB : N/A, total: mainDisk ? (mainDisk.size / 1024 / 1024 / 1024).toFixed(2) GB : N/A, usage: diskUsagePercent %, }, timestamp: new Date().toISOString(), }; // MCP工具调用要求返回特定格式的内容数组 return { content: [ { type: text, text: JSON.stringify(result, null, 2), // 美化输出的JSON }, ], }; } catch (error) { // 错误处理至关重要必须将错误信息清晰地返回给AI console.error(Failed to get system stats:, error); return { content: [ { type: text, text: Error retrieving system information: ${(error as Error).message}. Please ensure the required system permissions are granted., }, ], isError: true, // 标记这是一个错误响应 }; } }关键点解析name字段这是AI调用工具时使用的“命令”。命名最好采用snake_case清晰描述功能如get_system_stats。inputSchema这里我们定义了一个空对象因为该工具不需要参数。如果需要参数比如一个“搜索文件”的工具就需要在这里定义fileName字符串类型等属性。执行函数这是工具的核心逻辑。我们使用了systeminformation这个优秀的库来跨平台获取系统数据。注意我们使用了Promise.all来并行获取数据减少总等待时间。返回格式MCP要求工具返回一个包含content数组的对象。content中的每个元素可以是text或image等类型。我们将结果格式化为JSON字符串便于AI解析和向用户展示。错误处理必须用try...catch包裹可能失败的操作并返回格式化的错误信息。设置isError: true能帮助客户端识别这是一个异常结果。3.3 创建主服务器文件接下来我们在src/server.ts中创建服务器实例并注册我们刚刚定义的工具。// src/server.ts import { Server } from modelcontextprotocol/sdk/server/index.js; import { StdioServerTransport } from modelcontextprotocol/sdk/server/stdio.js; import { getSystemStatsTool, executeGetSystemStats } from ./tools/getSystemStats.js; // 1. 创建MCP服务器实例 const server new Server( { name: system-info-server, // 你的服务器名称 version: 0.1.0, }, { // 可选的服务器能力声明 capabilities: { tools: {}, // 告知客户端本服务器支持工具 resources: {}, // 本例暂不实现资源 }, } ); // 2. 注册工具 // 将工具描述和执行函数绑定。当客户端请求 get_system_stats 时执行 executeGetSystemStats。 server.setRequestHandler(tools/list, async () { return { tools: [getSystemStatsTool], }; }); server.setRequestHandler(tools/call, async (request) { // 根据请求中的工具名路由到对应的执行函数 if (request.params.name getSystemStatsTool.name) { return await executeGetSystemStats(); } // 如果收到未知的工具调用请求返回错误 throw new Error(Unknown tool: ${request.params.name}); }); // 3. 启动服务器使用STDIO传输 async function main() { const transport new StdioServerTransport(); await server.connect(transport); console.error(System Info MCP Server running on stdio...); } main().catch((error) { console.error(Server fatal error:, error); process.exit(1); });代码逻辑梳理创建服务器使用MCP SDK的Server类传入服务器元信息。声明能力在capabilities中告诉客户端本服务器支持tools。未来如果添加了资源也需要在这里声明。处理工具列表请求当客户端连接时它会发送tools/list请求来询问“你有什么工具”。我们的处理函数返回包含getSystemStatsTool描述的数组。处理工具调用请求当AI决定调用某个工具时客户端会发送tools/call请求。我们的处理函数检查工具名并路由到对应的executeGetSystemStats函数。启动与传输最后我们创建一个StdioServerTransport实例并用server.connect()连接它。这样服务器就开始监听stdin并通过stdout发送事件了。console.error用于输出日志因为stdout已经被用于协议通信。3.4 配置与运行我们需要在package.json中添加启动脚本并创建一个MCP客户端如Claude Desktop能识别的配置文件。首先添加脚本// package.json { name: my-system-info-mcp, version: 0.1.0, type: module, scripts: { build: tsc, start: node dist/server.js, dev: ts-node src/server.ts }, // ... 其他依赖项 }然后为Claude Desktop创建一个配置文件。在Claude的配置目录下如~/Library/Application Support/Claude/claude_desktop_config.jsonon macOS添加你的服务器{ mcpServers: { system-info: { command: node, args: [/ABSOLUTE/PATH/TO/your-project/dist/server.js], env: {} } } }重要提示args中的路径必须是绝对路径。你也可以使用npm run start但需要指定npm的完整路径相对更复杂。直接指向编译后的JS文件是最简单的方式。现在运行npm run build编译TypeScript代码再重启Claude Desktop。如果配置正确你可以在Claude的输入框里尝试说“请帮我看看当前系统的CPU和内存使用情况。” Claude应该会识别并调用你的get_system_stats工具并返回格式化的系统信息。4. 高级功能扩展与最佳实践一个只会查系统状态的服务器显然不够看。让我们基于这个基础探索如何扩展更复杂、更实用的功能。4.1 实现资源Resources让AI“看到”你的日志工具是让AI“做事”资源是让AI“读资料”。假设我们想让AI能分析最近的应用程序日志。我们可以将日志文件暴露为一个资源。在src/resources/目录下创建appLogResource.ts// src/resources/appLogResource.ts import { Resource } from modelcontextprotocol/sdk/server.js; import fs from fs/promises; import path from path; // 定义日志资源模板 // URI模板中的 {date} 是一个变量客户端请求时可以替换为具体日期如 app-log://2023-10-27 export const APP_LOG_URI_TEMPLATE app-log://{date}; const LOG_DIR /var/log/myapp; // 你的日志目录根据实际情况修改 /** * 根据日期获取对应的日志文件路径 */ function getLogFilePath(dateStr: string): string { // 简单的逻辑日志文件名为 app-2023-10-27.log return path.join(LOG_DIR, app-${dateStr}.log); } /** * 列出可用的日志资源例如最近7天的日志 */ export async function listLogResources(): PromiseResource[] { const resources: Resource[] []; try { const files await fs.readdir(LOG_DIR); const logFiles files.filter(f f.startsWith(app-) f.endsWith(.log)); // 取最近7天的文件作为可用的资源 for (const file of logFiles.slice(-7)) { // 从文件名中提取日期如 app-2023-10-27.log - 2023-10-27 const dateMatch file.match(/app-(\d{4}-\d{2}-\d{2})\.log/); if (dateMatch) { const dateStr dateMatch[1]; resources.push({ uri: APP_LOG_URI_TEMPLATE.replace({date}, dateStr), name: Application Log for ${dateStr}, description: The application log file for date ${dateStr}, mimeType: text/plain, // 日志是纯文本 }); } } } catch (error) { console.error(Failed to list log resources:, error); } return resources; } /** * 读取特定日期的日志资源内容 */ export async function readLogResource(uri: string): Promise{ contents: Array{ type: string; text: string } } { // 从URI中解析出日期如 app-log://2023-10-27 - 2023-10-27 const dateMatch uri.match(/^app-log:\/\/(\d{4}-\d{2}-\d{2})$/); if (!dateMatch) { throw new Error(Invalid log resource URI: ${uri}); } const dateStr dateMatch[1]; const filePath getLogFilePath(dateStr); try { const data await fs.readFile(filePath, utf-8); // 返回资源内容。可以在这里做预处理比如只返回最后1000行避免上下文过长。 const lines data.split(\n); const recentLines lines.slice(-1000).join(\n); // 限制返回行数 return { contents: [ { type: text, text: recentLines, }, ], }; } catch (error) { if ((error as NodeJS.ErrnoException).code ENOENT) { throw new Error(Log file for date ${dateStr} not found.); } throw error; } }然后在主服务器server.ts中注册资源支持// 在 server.ts 中新增导入和注册 import { listLogResources, readLogResource, APP_LOG_URI_TEMPLATE } from ./resources/appLogResource.js; // ... 之前的 server 创建代码不变 ... // 更新服务器能力声明添加资源支持 const server new Server( { name: system-info-server, version: 0.1.0, }, { capabilities: { tools: {}, resources: { // 声明支持资源 list: true, read: true, }, }, } ); // ... 工具注册代码不变 ... // 4. 注册资源处理 server.setRequestHandler(resources/list, async () { const logs await listLogResources(); // 你可以在这里合并多种类型的资源 return { resources: logs, }; }); server.setRequestHandler(resources/read, async (request) { const uri request.params.uri as string; // 可以根据URI的前缀如 app-log://路由到不同的读取函数 if (uri.startsWith(app-log://)) { return await readLogResource(uri); } throw new Error(Resource not found or not supported: ${uri}); }); // ... 启动代码不变 ...现在当AI客户端连接时它不仅能调用工具还能通过resources/list请求看到“Application Log for 2023-10-27”这样的资源列表。AI可以主动将这些日志内容读入其上下文然后你就能提问“基于昨天的日志告诉我有没有错误发生” AI会先读取日志资源再进行分析回答。4.2 工具进阶带参数的复杂工具让我们实现一个更强大的工具search_logs它允许AI在日志中搜索特定关键词。在src/tools/searchLogs.ts中// src/tools/searchLogs.ts import { Tool } from modelcontextprotocol/sdk/server.js; import fs from fs/promises; import path from path; const LOG_DIR /var/log/myapp; export const searchLogsTool: Tool { name: search_logs, description: 在应用程序日志中搜索包含特定关键词的行。可以指定搜索的日期范围和关键词。, inputSchema: { type: object, properties: { keyword: { type: string, description: 要搜索的关键词支持正则表达式, }, daysBack: { type: number, description: 搜索最近多少天的日志默认为1即今天, default: 1, minimum: 1, maximum: 30, // 防止搜索范围过大 }, caseSensitive: { type: boolean, description: 是否区分大小写默认为false, default: false, }, }, required: [keyword], // 只有keyword是必填的 }, }; export async function executeSearchLogs(params: { keyword: string; daysBack?: number; caseSensitive?: boolean; }) { const { keyword, daysBack 1, caseSensitive false } params; const results: Array{ date: string; file: string; lines: string[] } []; const regexFlags caseSensitive ? g : gi; let regex; try { regex new RegExp(keyword, regexFlags); } catch (error) { return { content: [{ type: text, text: 无效的正则表达式关键词 ${keyword}: ${(error as Error).message}, }], isError: true, }; } try { const files await fs.readdir(LOG_DIR); const targetDate new Date(); targetDate.setDate(targetDate.getDate() - (daysBack - 1)); // 计算起始日期 for (let i 0; i daysBack; i) { const currentDate new Date(targetDate); currentDate.setDate(targetDate.getDate() i); const dateStr currentDate.toISOString().split(T)[0]; // YYYY-MM-DD const fileName app-${dateStr}.log; const filePath path.join(LOG_DIR, fileName); try { const content await fs.readFile(filePath, utf-8); const lines content.split(\n); const matchedLines lines.filter(line regex.test(line)); if (matchedLines.length 0) { results.push({ date: dateStr, file: fileName, lines: matchedLines.slice(0, 50), // 每个文件最多返回50行避免响应过大 }); } } catch (error) { // 如果某天日志文件不存在静默跳过 if ((error as NodeJS.ErrnoException).code ! ENOENT) { console.error(Error reading log file ${fileName}:, error); } } } if (results.length 0) { return { content: [{ type: text, text: 在最近${daysBack}天的日志中未找到包含关键词“${keyword}”的行。, }], }; } // 格式化输出结果 const resultText results.map(r ## ${r.date} (${r.file})\n r.lines.map(line - ${line}).join(\n) ).join(\n\n); const summary 在最近${daysBack}天的日志中于 ${results.length} 个文件中找到了匹配项。; return { content: [{ type: text, text: ${summary}\n\n${resultText}, }], }; } catch (error) { console.error(Search logs failed:, error); return { content: [{ type: text, text: 搜索日志时发生错误: ${(error as Error).message}, }], isError: true, }; } }这个工具的实现体现了几个重要实践参数设计inputSchema定义了清晰的参数包括类型、描述、默认值和约束minimum/maximum。这相当于给AI提供了一个详细的“函数签名”AI在调用时会尝试生成符合要求的参数。输入验证我们尝试将用户输入的keyword构造为正则表达式如果格式非法立即返回友好的错误信息而不是让服务器崩溃。资源消耗控制通过daysBack的maximum属性限制搜索时间范围30天。在结果中每个文件最多返回50行匹配行matchedLines.slice(0, 50)。这些限制防止了AI无意中发起一个消耗巨大资源的请求比如“搜索所有日志”。错误处理与用户体验对于不存在的日志文件ENOENT错误我们选择静默跳过不影响其他文件的搜索。最终结果包含清晰的摘要和格式化内容便于AI理解和向用户转述。将这个工具注册到主服务器后你就可以对AI说“在最近三天的日志里搜索一下‘Timeout’或‘error’这个词不区分大小写。” AI会理解并调用search_logs工具传入{“keyword”: “Timeout|error”, “daysBack”: 3, “caseSensitive”: false}参数。4.3 安全性考量与权限控制MCP服务器运行在你的本地环境可能访问敏感数据和执行危险操作。安全性至关重要。最小权限原则你的服务器进程应该以尽可能低的权限运行。不要用root或管理员账户运行。输入净化与验证如上例所示对所有来自客户端的输入如文件路径、命令参数进行严格的验证和净化。永远不要直接将用户输入拼接成系统命令执行如child_process.exec(input)这是极端危险的。操作范围限制通过代码逻辑硬性限制可访问的目录和可执行的操作。例如我们的日志搜索工具只允许访问固定的LOG_DIR且daysBack有上限。敏感信息过滤在返回资源或工具结果时检查并过滤掉密码、密钥、个人身份信息等敏感内容。传输安全对于SSE over STDIO由于是本地进程间通信被中间人攻击的风险较低。但如果未来扩展到网络传输WebSocket务必使用TLSWSS加密通信。5. 调试、测试与集成指南开发MCP服务器时调试不像普通Web服务那么简单因为它通过stdio与客户端通信。5.1 使用MCP Inspector进行调试最有效的调试工具是官方提供的MCP Inspector。它是一个独立的客户端可以连接你的服务器并可视化地发送请求、查看响应和通知。# 全局安装MCP Inspector npm install -g modelcontextprotocol/inspector # 运行inspector并指定你的服务器启动命令 npx modelcontextprotocol/inspector node dist/server.js # 或者如果你在开发中想用ts-node npx modelcontextprotocol/inspector npx ts-node src/server.tsInspector会打开一个命令行界面你可以看到服务器初始化时交换的握手信息。手动发送tools/list、resources/list请求。构造参数发送tools/call请求并查看返回的原始JSON。观察服务器主动发送的notifications。这是验证你的服务器协议实现是否正确、工具和资源是否按预期工作的必备工具。5.2 单元测试你的工具函数工具的执行函数是纯业务逻辑应该进行单元测试。使用Jest或Mocha等测试框架。// __tests__/tools/getSystemStats.test.ts import { executeGetSystemStats } from ../../src/tools/getSystemStats; import si from systeminformation; // 模拟 systeminformation 模块 jest.mock(systeminformation); describe(getSystemStats Tool, () { it(should return formatted system stats on success, async () { // 1. 设置模拟返回值 (si.currentLoad as jest.Mock).mockResolvedValue({ currentLoad: 15.5, cpus: [{}, {}, {}, {}] }); (si.mem as jest.Mock).mockResolvedValue({ total: 17179869184, active: 8589934592 }); // 16GB total, 8GB active (si.fsSize as jest.Mock).mockResolvedValue([{ mount: /, size: 256060514304, used: 128030257152 }]); // ~256GB disk, ~128GB used // 2. 执行工具函数 const result await executeGetSystemStats(); // 3. 验证结果 expect(result.isError).toBeUndefined(); // 不应该有错误 const textContent result.content[0].text; const parsed JSON.parse(textContent); expect(parsed.cpu.load).toBe(15.5%); expect(parsed.cpu.cores).toBe(4); expect(parsed.memory.usage).toBe(50.0%); // (8GB / 16GB) * 100% expect(parsed.disk.usage).toBe(50.0%); // (128GB / 256GB) * 100% expect(parsed.timestamp).toBeDefined(); }); it(should return an error when system info fails, async () { // 模拟一个失败场景 (si.currentLoad as jest.Mock).mockRejectedValue(new Error(CPU info unavailable)); const result await executeGetSystemStats(); expect(result.isError).toBe(true); expect(result.content[0].text).toContain(Error retrieving system information); }); });通过单元测试你可以确保核心业务逻辑在各种边界条件下都能正确工作而无需每次都启动完整的MCP服务器并与客户端集成。5.3 集成到不同客户端你的MCP服务器开发完成后可以集成到各种支持MCP的客户端中Claude Desktop如前所述通过JSON配置文件添加。Cursor IDE在Cursor的设置中找到MCP Servers配置添加类似配置。自定义客户端你可以使用MCP的客户端SDKmodelcontextprotocol/sdk/client来构建自己的应用与你的服务器通信。一个常见的集成问题是路径和依赖。如果你的服务器依赖某些二进制文件或特定环境变量需要在客户端的配置中通过env字段设置。// Claude Desktop 配置示例假设需要自定义PATH { mcpServers: { my-advanced-server: { command: /usr/local/bin/node, args: [/Users/me/projects/my-mcp-server/dist/server.js], env: { PATH: /usr/local/bin:/opt/homebrew/bin:${PATH}, MY_APP_CONFIG: /Users/me/.config/myapp.json } } } }5.4 性能优化与生产化建议当你的服务器工具和资源越来越多时需要考虑性能。懒加载与缓存对于初始化成本高的模块如连接数据库不要在服务器启动时就初始化。可以在工具被第一次调用时再建立连接懒加载。对于不常变化的数据可以考虑在内存中缓存一段时间。流式响应如果某个工具执行时间很长如处理一个大文件MCP协议支持部分结果Partial Results。你可以在处理过程中分多次返回结果片段让客户端和AI能实时看到进度而不是长时间等待。这需要更复杂的服务器实现。日志与监控为你的服务器添加详细的日志记录记录到文件而不是stdout/err以免干扰协议。监控服务器的启动失败、工具调用错误和资源消耗。配置化将服务器监听的目录、数据库连接字符串、API密钥等通过环境变量或配置文件管理而不是硬编码在代码中。6. 常见问题与排查实录在实际开发和集成中你肯定会遇到各种问题。以下是一些典型问题及其解决方法。6.1 服务器启动失败或客户端无法连接问题现象可能原因排查步骤与解决方案Claude Desktop/Cursor 提示“无法连接MCP服务器”或直接无反应。1.Node.js路径或项目路径错误。2.服务器代码存在语法错误启动即崩溃。3.权限问题无法执行命令。1.检查路径确保配置文件中command和args的路径是绝对路径且正确。尝试在终端中手动运行该命令看是否能启动。2.独立运行服务器在项目目录下直接运行node dist/server.js查看控制台是否有报错。修复所有语法和运行时错误。3.查看客户端日志Claude Desktop通常有详细的日志文件位置因系统而异里面会包含启动子进程失败的具体错误信息。服务器进程启动后立即退出。1.未正确处理异步操作导致主函数提前退出。2.未捕获的异常。1.确保异步函数被等待检查main()函数是否被正确await并且server.connect()被调用。2.添加全局错误监听在server.ts开头添加process.on(uncaughtException, ...)和process.on(unhandledRejection, ...)来捕获未知错误并打印日志。连接成功但客户端显示“协议错误”或“握手失败”。1.服务器没有输出正确的MCP初始化消息。2.传输层stdio被其他代码污染。1.使用MCP Inspector这是最有效的调试方式。用Inspector连接你的服务器看最初的握手消息是否符合协议。确保你的服务器代码没有在console.log输出任何无关内容协议通信只能通过transport进行。2.检查代码确保没有在server.connect()之前或之后向process.stdout写入数据。6.2 工具调用无响应或返回错误问题现象可能原因排查步骤与解决方案AI客户端列出了工具但调用时超时或无反应。1.工具执行函数陷入死循环或长时间阻塞。2.工具函数没有返回正确的Promise。1.检查工具逻辑确保工具函数有明确的结束条件对于可能长时间运行的操作考虑设置超时或实现分步处理。2.确认异步返回工具执行函数必须是async函数或返回Promise。确保所有分支路径都有return语句。工具调用返回“Tool not found”错误。1.工具名不匹配tools/call请求中的name与注册时的tool.name不一致。2.工具列表未正确注册。1.核对名称仔细检查server.setRequestHandler(tools/call, ...)中的路由逻辑是否严格匹配工具定义中的name。名称是大小写敏感的。2.使用Inspector验证发送tools/list请求确认返回的列表包含你期望的工具并且名称完全一致。工具返回了结果但AI说“无法解析”或“格式错误”。1.返回的JSON格式不符合MCP规范。2.content字段格式错误。1.遵循协议工具必须返回{ content: [ { type: ‘text’, text: ‘...’ } ] }这样的对象。确保你没有直接返回一个字符串或数组。2.结构化数据如果返回的是结构化数据如对象最好将其JSON.stringify()后放在text字段中。AI能够很好地解析JSON字符串。6.3 资源相关的问题问题现象可能原因排查步骤与解决方案客户端看不到任何资源。1.服务器能力未声明支持资源。2.resources/list处理函数返回空数组或出错。1.检查capabilities确保在创建Server时capabilities.resources.list和capabilities.resources.read至少有一个为true通常两者都为true。2.调试list函数在resources/list处理函数中添加日志检查listLogResources()是否按预期返回了资源数组。确保URI格式正确。读取资源时返回“Resource not found”。1.URI不匹配客户端请求的URI与resources/read处理函数中的路由逻辑不匹配。2.底层读取失败如文件不存在。1.检查URI路由在resources/read处理函数中打印收到的uri参数确认它是否匹配你预期的模式如app-log://2023-10-27。路由逻辑if (uri.startsWith(‘app-log://’))必须覆盖所有你声明的资源URI。2.增强错误信息在readLogResource函数中提供更具体的错误信息比如文件的确切路径便于排查。6.4 性能与稳定性问题问题现象可能原因排查步骤与解决方案调用工具时客户端响应缓慢。1.工具执行本身耗时如大数据量查询、网络请求。2.服务器单线程阻塞同时处理多个请求。1.优化工具逻辑分析工具函数的性能瓶颈。考虑引入缓存、优化算法或数据库查询。2.MCP协议是顺序的一个请求处理完才会处理下一个。对于耗时操作考虑是否可以用资源预计算代替工具或者向MCP社区了解是否支持异步通知。服务器运行一段时间后内存持续增长。1.内存泄漏工具函数中创建了未释放的大型对象或闭包。2.缓存无限增长。1.使用Node.js性能分析工具如node --inspect配合Chrome DevTools或使用heapdump模块生成堆快照分析内存泄漏点。2.为缓存设置大小或TTL限制如果使用了内存缓存确保有过期策略或大小上限。开发MCP服务器的过程本质上是在为AI构建一个安全、可控的“手”和“眼”。rogertheunissenmerge-oss/mcp-server这个项目提供了一个坚实且清晰的起点。从实现一个简单的系统信息查询工具到扩展出带参数的日志搜索和资源读取功能我们一步步看到了如何将本地能力封装成AI可用的接口。关键在于深刻理解工具和资源这两个核心抽象设计好输入输出Schema并始终将安全性、错误处理和用户体验放在心上。当你成功将第一个自定义MCP服务器集成到Claude或Cursor中并流畅地通过自然语言指挥它完成一项任务时那种感觉会非常棒——你不仅仅是使用AI而是在真正地扩展它让它成为你工作流中一个更智能、更强大的伙伴。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2617715.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…

网络编程(Modbus进阶)

思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…