使用getmcp与MCP协议为AI应用集成外部工具与数据源
1. 项目概述一个为AI应用注入“记忆”与“工具”的桥梁如果你正在开发一个AI应用无论是聊天机器人、数据分析助手还是自动化脚本你可能会遇到一个核心瓶颈如何让AI模型不仅理解你的指令还能“记住”上下文并“使用”外部的工具和数据源这正是getmcp项目要解决的核心问题。它不是一个独立的AI模型而是一个功能强大的客户端库专门用于与MCPModel Context Protocol服务器进行通信。简单来说你可以把getmcp想象成一个“万能适配器”或“智能接线员”。AI模型比如GPT、Claude等是大脑但它本身无法直接操作数据库、读取文件、调用API。而各种数据源和工具如文件系统、SQL数据库、Git仓库、网页抓取工具就是一个个功能各异的“外设”。MCP服务器则是对这些“外设”进行标准化封装和管理的“驱动程序”。getmcp的作用就是让AI大脑客户端能够通过标准化的协议轻松、安全地找到并调用这些“驱动程序”从而扩展其能力边界。这个项目由RodrigoTomeES维护它让开发者能够以极简的方式在自己的Node.js或TypeScript应用中集成MCP客户端功能。这意味着你可以快速构建出能够动态读取项目文件、查询数据库、执行代码、甚至控制其他软件的智能体。对于任何希望提升AI应用实用性、打破模型“信息孤岛”的开发者来说getmcp都是一个值得深入研究的工具。2. MCP协议核心思想与getmcp的定位2.1 为什么需要MCPAI能力扩展的标准化之路在MCP出现之前为AI模型添加外部能力通常是一个定制化、高耦合的过程。开发者可能需要为每个特定的工具如搜索引擎、计算器、日历编写特定的插件代码这些代码深度绑定于某个AI平台如OpenAI的GPTs或Anthropic的Claude。这种方式存在几个明显问题重复劳动为不同AI平台开发相同功能的插件需要重复编写逻辑相似的代码。维护困难工具接口一旦变化所有集成了该工具的插件都需要同步更新。安全性挑战每个插件都可能需要处理敏感的权限和认证逻辑缺乏统一的安全审计标准。生态封闭工具能力被锁定在特定平台难以在不同AI应用间共享和复用。MCP的提出正是为了建立一套开放、标准化的协议来解决上述问题。它将AI客户端如聊天应用、IDE插件与提供上下文和工具的资源服务器解耦。服务器负责以标准格式暴露“工具”可执行函数和“资源”可读数据客户端则通过协议发现并调用它们。getmcp就是一个严格遵循此协议的客户端实现它让开发者无需关心协议底层的通信细节如JSON-RPC over stdio/SSE只需关注如何在自己的业务逻辑中使用这些被暴露出来的强大能力。2.2 getmcp作为客户端库的核心价值getmcp的价值在于它极大地降低了在应用中集成MCP的门槛。它封装了所有繁琐的协议交互、连接管理、错误处理和类型定义。作为开发者你获得的是一个清晰、类型安全、易于使用的编程接口。它的核心功能可以概括为连接管理轻松连接到本地或远程的MCP服务器通过stdio、SSE等方式。工具发现与调用自动获取服务器提供的工具列表并以异步函数的形式调用它们传入参数并获取结构化结果。资源读取请求服务器提供的资源如文件内容、数据库片段并将其作为上下文提供给AI模型。生命周期管理优雅地处理连接的初始化、重连和关闭。举个例子假设你有一个MCP服务器专门用于管理本地文件系统例如mcp-server-filesystem。使用getmcp你可以在你的AI助手应用中用几行代码就实现“请总结我项目根目录下README.md的内容”这样的功能。AI模型发出指令getmcp负责找到文件系统服务器调用“读取文件”工具将文件内容取回然后交给模型去分析和总结。3. 快速上手指南从零开始集成getmcp3.1 环境准备与安装首先确保你的开发环境满足基本要求。getmcp是一个Node.js库因此你需要安装Node.js版本16或以上推荐18和npm或yarn、pnpm等包管理器。创建一个新的项目目录并初始化一个Node.js项目如果还没有的话mkdir my-ai-assistant cd my-ai-assistant npm init -y接下来安装getmcp核心库。由于它主要提供类型安全的TypeScript体验建议也安装TypeScript及相关类型定义。npm install modelcontextprotocol/sdk是的这里安装的是modelcontextprotocol/sdk。这是因为getmcp很可能是该项目的一个分发名称或别名其核心包是官方提供的SDK。务必查阅项目最新README确认准确的包名。同时安装TypeScript作为开发依赖npm install typescript types/node ts-node --save-dev创建一个tsconfig.json文件来配置TypeScript编译器。3.2 连接你的第一个MCP服务器文件系统示例理论讲完了我们来点实际的。让我们连接一个最简单的MCP服务器——文件系统服务器让我们的程序能列出目录内容。首先你需要安装一个文件系统MCP服务器。一个流行的选择是modelcontextprotocol/server-filesystem。请注意服务器需要作为独立的进程运行getmcp客户端会通过子进程或标准输入输出与其通信。npm install modelcontextprotocol/server-filesystem然后创建一个index.ts文件编写以下代码import { Client } from modelcontextprotocol/sdk/client/index.js; import { StdioClientTransport } from modelcontextprotocol/sdk/client/stdio.js; import { spawn } from child_process; async function main() { // 1. 创建MCP客户端实例 const client new Client( { name: my-ai-assistant-client, version: 1.0.0, }, { capabilities: {} // 声明客户端能力初始可为空 } ); // 2. 创建传输层这里使用Stdio标准输入输出与服务器进程通信 // 我们需要启动文件系统服务器进程 const serverProcess spawn(npx, [mcp-server-filesystem], { // 通常服务器会提供可执行命令或脚本这里假设安装后可通过 mcp-server-filesystem 命令启动 // 实际命令请参考具体服务器的文档 stdio: [pipe, pipe, inherit] // 继承stderr以便查看错误 }); const transport new StdioClientTransport(serverProcess); // 3. 连接客户端与传输层 await client.connect(transport); console.log(✅ 已成功连接到MCP服务器); // 4. 获取服务器提供的工具列表 const tools await client.listTools(); console.log(️ 服务器提供的工具); tools.tools.forEach(tool { console.log( - ${tool.name}: ${tool.description}); if (tool.inputSchema) { console.log( 输入参数:, JSON.stringify(tool.inputSchema, null, 2)); } }); // 5. 假设服务器提供了一个 list_directory 工具我们来调用它 try { const result await client.callTool({ name: list_directory, // 工具名需根据实际列表调整 arguments: { path: . // 列出当前目录 } }); console.log( 目录列表结果, JSON.stringify(result, null, 2)); } catch (error) { console.error(❌ 调用工具失败, error); } // 6. 断开连接 await client.close(); console.log( 连接已关闭。); } main().catch(console.error);注意上述代码中的mcp-server-filesystem和list_directory是示例实际命令和工具名称需要根据你安装的具体服务器包来调整。这是第一个实操坑点不同MCP服务器提供的工具名称和参数结构可能不同务必查阅其官方文档。通常在连接后立即调用client.listTools()是了解服务器能力的最佳方式。3.3 核心API详解与调用模式getmcp或官方SDK客户端提供了几个最核心的方法理解它们是你进行开发的基础client.connect(transport): 建立与服务器的连接。这是所有操作的前提。client.listTools(): 返回一个包含tools数组的承诺数组中的每个对象都描述了工具的名称、描述、输入模式等。这是你探索服务器功能的“地图”。client.callTool(request): 最重要的方法。它接受一个包含name工具名和arguments参数对象的请求并返回一个包含content数组的响应。content中的每一项通常包含type如text和text或image等具体内容。client.listResources()与client.readResource(request): 用于发现和读取“资源”静态或动态数据。例如一个服务器可能将“当前天气”或“数据库schema”定义为资源。client.close(): 优雅地关闭连接释放资源。调用工具时的参数构造是关键。工具的输入模式Input Schema通常是一个JSON Schema对象它定义了参数的类型、是否必需、描述等信息。在调用callTool时你需要构造一个完全匹配该模式的arguments对象。例如如果一个search_web工具需要query字符串和max_results数字参数你的调用应该像这样const result await client.callTool({ name: search_web, arguments: { query: 最新的MCP协议更新, max_results: 5 } });4. 构建一个真实的AI助手集成LLM与工具调用4.1 项目架构设计让AI大脑指挥工具手单纯连接MCP服务器意义不大真正的威力在于将其与大型语言模型结合。我们将构建一个简单的命令行AI助手它能够理解用户的自然语言指令自动选择并调用合适的MCP工具来完成任务。整体架构流程如下用户输入用户在命令行输入一个任务如“总结一下src目录下所有TypeScript文件的主要功能”。指令解析与工具匹配AI模型如通过OpenAI API调用GPT-4分析用户指令将其转化为一个或多个具体的工具调用请求。这一步需要模型知道有哪些工具可用通过listTools获取。工具执行我们的程序使用getmcp根据模型的决策调用相应的MCP工具。结果整合与回复将工具执行的结果返回给AI模型由模型加工成自然语言回复呈现给用户。这个流程的核心是“规划-执行”循环。AI模型担任“规划者”和“解释者”getmcp和MCP服务器担任“执行者”。4.2 代码实现串联OpenAI API与getmcp我们需要安装OpenAI的Node.js SDK。npm install openai以下是简化版的核心代码实现import { Client } from modelcontextprotocol/sdk/client/index.js; import { StdioClientTransport } from modelcontextprotocol/sdk/client/stdio.js; import { spawn } from child_process; import OpenAI from openai; import * as readline from readline/promises; import { stdin as input, stdout as output } from process; // 初始化OpenAI客户端请将你的API密钥放入环境变量 const openai new OpenAI({ apiKey: process.env.OPENAI_API_KEY }); // 创建命令行交互接口 const rl readline.createInterface({ input, output }); async function setupMCPClient() { const client new Client({ name: cli-assistant, version: 1.0.0 }, {}); // 假设我们连接一个集成了文件系统和计算器工具的本地服务器 // 实际中你可能需要启动多个服务器进程并管理多个客户端或使用一个聚合服务器 const serverProcess spawn(node, [path/to/your/custom-mcp-server.js], { stdio: [pipe, pipe, inherit] }); const transport new StdioClientTransport(serverProcess); await client.connect(transport); return client; } async function getAIDecision(userQuery: string, availableTools: any[]) { // 将可用工具列表格式化成系统提示词的一部分 const toolsPrompt availableTools.map(t - ${t.name}: ${t.description}. 参数: ${JSON.stringify(t.inputSchema?.properties || {})} ).join(\n); const systemPrompt 你是一个AI助手可以调用以下工具来帮助用户。请根据用户请求决定是否需要调用工具以及调用哪个工具并给出正确的参数。 可用工具 ${toolsPrompt} 请以以下JSON格式回复 { needsTool: true/false, toolName: 工具名如果需要, arguments: { /* 参数对象如果需要 */ }, reasoning: 你的思考过程 } 如果不需要工具直接回答用户问题。; const completion await openai.chat.completions.create({ model: gpt-4-turbo-preview, // 或 gpt-3.5-turbo messages: [ { role: system, content: systemPrompt }, { role: user, content: userQuery } ], response_format: { type: json_object } // 强制返回JSON }); return JSON.parse(completion.choices[0].message.content || {}); } async function main() { console.log( AI助手启动中...); const mcpClient await setupMCPClient(); const tools (await mcpClient.listTools()).tools; console.log(️ 已加载 ${tools.length} 个工具。); while (true) { const query await rl.question(\n 请输入指令 (或输入退出): ); if (query 退出) break; // 步骤1: AI决策 const decision await getAIDecision(query, tools); console.log( AI决策:, JSON.stringify(decision, null, 2)); let finalAnswer ; if (decision.needsTool decision.toolName) { // 步骤2: 调用工具 try { const toolResult await mcpClient.callTool({ name: decision.toolName, arguments: decision.arguments || {} }); const resultText toolResult.content.map((c: any) c.text || JSON.stringify(c)).join(\n); console.log( 工具执行结果:, resultText); // 步骤3: 将结果交给AI总结回复 const summaryCompletion await openai.chat.completions.create({ model: gpt-4-turbo-preview, messages: [ { role: system, content: 你是一个助手。用户之前提出了一个请求你决定调用工具。现在工具已经返回了结果请根据工具结果生成一个友好、清晰的回答给用户。 }, { role: user, content: 原始请求: ${query}\n\n工具“${decision.toolName}”返回的结果是:\n${resultText}\n\n请根据以上信息回答用户。} ] }); finalAnswer summaryCompletion.choices[0].message.content || 处理完成但未能生成总结。; } catch (error) { finalAnswer 抱歉调用工具“${decision.toolName}”时出错: ${error}; } } else { // 不需要工具直接使用AI的回复来自decision中的reasoning或另起一次对话 const directCompletion await openai.chat.completions.create({ model: gpt-4-turbo-preview, messages: [ { role: system, content: 你是一个乐于助人的AI助手。 }, { role: user, content: query } ] }); finalAnswer directCompletion.choices[0].message.content || 未能生成回答。; } console.log(\n 助手回复:, finalAnswer); } await mcpClient.close(); rl.close(); console.log( 助手已退出。); } main().catch(console.error);这个示例展示了完整的闭环用户输入 - AI解析并规划工具调用 -getmcp执行 - AI整合结果并回复。这是构建复杂AI智能体的基础模式。4.3 安全性与错误处理实践在实际应用中直接将工具调用权交给AI模型存在风险。你需要建立安全护栏工具允许列表不是所有服务器提供的工具都允许AI调用。你应该维护一个allowedTools列表在AI决策后、实际调用前进行过滤。例如禁止AI调用“删除文件”或“执行shell命令”这类高危工具除非在受控环境下。const ALLOWED_TOOLS [read_file, list_directory, calculate]; if (!ALLOWED_TOOLS.includes(decision.toolName)) { throw new Error(工具“${decision.toolName}”未被允许调用。); }参数验证与净化AI生成的参数可能不符合预期。在调用callTool前应根据工具的JSON Schema对参数进行验证并过滤掉任何可能包含路径遍历../、命令注入等风险的字符串。import { validate } from jsonschema; // ... 获取工具的 inputSchema ... const validationResult validate(decision.arguments, toolSchema); if (!validationResult.valid) { // 处理验证错误而不是直接传递给服务器 }超时与重试网络或服务器可能不稳定。为callTool设置超时并实现简单的重试逻辑。async function callToolWithTimeout(client, request, timeoutMs 10000) { return Promise.race([ client.callTool(request), new Promise((_, reject) setTimeout(() reject(new Error(工具调用超时)), timeoutMs) ) ]); }资源清理确保在程序退出或出错时调用client.close()和清理服务器进程防止僵尸进程。process.on(SIGINT, async () { console.log(\n正在关闭...); await mcpClient.close(); serverProcess.kill(); process.exit(0); });5. 高级应用场景与性能优化5.1 多服务器管理与工具聚合一个强大的AI助手可能需要访问多种资源文件、数据库、日历、邮件等。通常每个MCP服务器专注于一个领域。因此你的客户端需要能够同时连接和管理多个服务器。实现方案你可以创建一个MCPManager类来管理多个Client实例。class MCPManager { private clients: Mapstring, Client new Map(); async addServer(name: string, command: string, args: string[]) { const client new Client(...); const transport new StdioClientTransport(spawn(command, args, { stdio: [pipe, pipe, inherit] })); await client.connect(transport); this.clients.set(name, client); return client; } async listAllTools() { const allTools []; for (const [serverName, client] of this.clients) { const tools (await client.listTools()).tools; allTools.push(...tools.map(t ({ ...t, source: serverName }))); } return allTools; } async callToolFromAny(serverName: string, toolRequest) { const client this.clients.get(serverName); if (!client) throw new Error(服务器 ${serverName} 未找到); return await client.callTool(toolRequest); } // ... 其他方法如关闭所有连接 }这样AI模型在决策时可以从一个统一的工具池中选择你只需要在调用时指定工具来源于哪个服务器。5.2 上下文管理与会话持久化在多轮对话中保持上下文至关重要。例如用户说“打开projectA的config.json”然后说“把里面的apiUrl改成新的”。第二句指令依赖于第一句指令执行后所建立的上下文即当前正在查看的文件。实现思路会话状态在客户端维护一个会话状态对象记录当前激活的资源、上一次操作的结果等。工具增强设计一些“上下文管理”工具如set_context、get_context或者让某些工具如read_file的返回结果中包含一个唯一的“资源句柄”后续操作可以引用这个句柄。提示词工程在给AI模型的系统提示中明确告知它当前会话的上下文状态并指导它如何利用这些状态来生成更准确的工具调用请求。这部分的实现更复杂需要根据你的具体应用场景来设计状态管理逻辑。5.3 性能考量与调试技巧连接复用避免为每个用户请求都创建新的MCP服务器连接和进程。应该复用长连接Client实例设计为可长期运行。工具调用并行化如果AI规划出多个可以并行执行的无依赖工具调用可以使用Promise.all来并发执行提升效率。结果缓存对于一些耗时的、结果不常变的工具调用如读取大型配置文件、查询某些静态数据可以在客户端实现简单的缓存机制。调试日志MCP SDK通常提供日志功能。在开发时启用详细日志可以帮助你理解客户端与服务器之间的通信过程。import { setDebugEnabled } from modelcontextprotocol/sdk; setDebugEnabled(true);使用SSE传输对于远程服务器除了Stdio还可以使用Server-Sent Events传输。这适用于客户端是Web应用服务器部署在远程的情况。getmcpSDK也提供了相应的SSEClientTransport。6. 常见问题与排查实录在实际集成getmcp和MCP服务器的过程中你肯定会遇到各种问题。以下是我在开发中遇到的一些典型情况及解决方法。6.1 连接失败与服务器启动问题问题现象client.connect()抛出错误或服务器进程立即退出。可能原因1服务器命令错误。spawn的命令或参数不正确。排查手动在终端运行你试图启动的命令看是否能成功启动并等待连接。检查包是否全局安装或使用npx。解决确保命令路径正确。对于本地开发的服务器脚本使用node path/to/server.js。可能原因2传输层不匹配。服务器期望的是Stdio传输但你尝试用SSE连接反之亦然。排查查阅MCP服务器的文档明确它支持的传输方式。解决使用正确的Transport类。可能原因3权限问题。服务器脚本可能需要执行权限。解决chmod x server_script.js。6.2 工具调用返回错误或意外结果问题现象callTool成功调用但返回错误内容或者参数似乎没生效。可能原因1参数格式错误。这是最常见的问题。AI生成的参数可能类型不对字符串传成了数字或者缺少了必需字段。排查在调用前打印出你准备发送的arguments对象并与通过listTools获取到的inputSchema进行仔细比对。使用JSON Schema验证库进行预验证。解决在AI生成参数的环节在系统提示词中更严格地要求其遵循Schema。或者在客户端添加一层参数清洗和转换的逻辑。可能原因2工具执行本身的业务逻辑错误。例如read_file工具传入了一个不存在的文件路径。排查查看服务器进程的标准错误输出stderr通常会有更详细的错误信息。确保spawn时stdio的stderr是inherit或通过管道捕获。解决在客户端代码中处理这些业务错误给用户友好的提示或者让AI根据错误信息进行重试或调整请求。6.3 类型定义与开发体验优化问题现象TypeScript报类型错误或者没有代码提示。可能原因MCP工具和资源的类型是动态的取决于连接的服务器。官方SDK提供的核心类型如CallToolRequest中的arguments可能是any或泛型。解决为了获得更好的类型安全你可以根据已知的服务器工具列表自己定义一套类型。// 定义你的工具参数类型 type MyTools { list_directory: { path: string }; read_file: { path: string; encoding?: string }; calculate: { expression: string }; }; // 包装callTool函数提供类型提示 async function callToolSafeK extends keyof MyTools( client: Client, name: K, args: MyTools[K] ) { return client.callTool({ name, arguments: args } as any); // 内部仍需要断言但外部调用是类型安全的 } // 使用时就有提示了 await callToolSafe(mcpClient, read_file, { path: ./README.md }); // await callToolSafe(mcpClient, read_file, { path: 123 }); // TS报错6.4 内存泄漏与进程管理问题现象长时间运行后应用内存持续增长或者僵尸进程增多。可能原因事件监听器未移除、未正确关闭连接和子进程。解决确保在应用退出逻辑中调用所有client.close()方法。使用serverProcess.kill()或serverProcess.kill(SIGTERM)发送终止信号。监听子进程的exit和error事件进行日志记录和清理。考虑使用pidtree等库在强制退出时清理整个进程树。集成getmcp的过程是一个典型的“配置即代码”和“协议驱动开发”的体验。大部分时间不是在写复杂的逻辑而是在正确地配置连接、理解工具协议、处理边界情况。一旦打通你会发现它为AI应用带来的能力提升是质的飞跃。从只能泛泛而谈到能真真切切地操作你的数字世界这中间的桥梁就是由MCP和getmcp这样的工具所搭建的。