基于MCP协议构建AI工具服务器:安全连接大模型与本地系统
1. 项目概述一个为AI模型提供“工具箱”的服务器最近在折腾AI应用开发特别是想让大语言模型LLM能更“接地气”地操作我的本地系统和数据。直接让模型去读写文件、执行命令既不安全也不现实。这时候MCPModel Context Protocol就进入了我的视野。简单来说MCP是一个标准协议它定义了一套方法让AI模型客户端能够安全、可控地调用外部工具服务器端。而Summit53/mcp-server这个项目就是一个具体的、开源的MCP服务器实现。你可以把它想象成一个“适配器”或者“中间件”。我的AI助手比如Claude Desktop、Cursor等支持MCP的客户端想读取我电脑上的某个文件它不会直接伸手去拿而是会通过标准的“语言”MCP协议向这个mcp-server发出请求“请帮我读取/home/user/notes.md的内容。” 服务器收到请求后会在我授权的范围内执行这个操作然后把结果封装好再通过协议返回给AI助手。整个过程AI模型本身并不直接接触我的系统它只是在和这个服务器对话由服务器作为可信的代理去执行具体动作。这个项目解决的核心痛点就是安全与能力的平衡。我们既希望AI能成为强大的个人副驾帮我们处理繁琐的本地任务文件管理、数据查询、系统状态监控等又必须将它限制在一个安全的沙箱里防止其执行危险或越权操作。Summit53/mcp-server提供了一个可扩展的基础框架开发者可以基于它快速构建自己的工具服务器将各种本地资源文件系统、数据库、API等安全地暴露给AI使用。它适合有一定开发基础的AI应用开发者、希望深度集成AI到工作流的效率追求者以及任何对构建安全、可控的AI智能体Agent感兴趣的人。2. MCP协议核心思想与项目定位解析2.1 为什么需要MCP从“裸奔”到“规范化接口”在没有MCP这类协议之前想让AI操作外部工具通常有两种比较“原始”的方式。一种是给模型提供几个函数描述的提示词Prompt然后在模型输出调用这些函数的指令后由应用层代码去解析和执行。这种方式耦合度高每次新增工具都要改提示词和应用逻辑而且安全性完全依赖于应用层自己对模型输出的过滤和校验非常脆弱。另一种是某些AI平台提供的专用插件系统。这种方式比前一种好但存在平台锁定的问题。为一个平台比如某个特定的AI助手开发的插件很难迁移到另一个平台使用。工具生态被割裂了。MCP的出现就是为了解决这些问题。它定义了一套与具体AI模型、具体客户端应用无关的标准化协议。这套协议规定了工具Tools如何被声明、如何被调用、资源Resources如何被描述和读取、以及提示词模板Prompts如何被提供。Summit53/mcp-server就是这套协议的一个服务端实现。它的定位非常清晰做一个高质量、易于使用和扩展的MCP协议服务端库。这意味着作为开发者我不需要从零开始去理解MCP协议的每一个细节并实现网络通信、消息解析等底层繁琐工作。我只需要使用这个库专注于实现我想要的工具逻辑比如“读取文件”、“执行某个脚本”然后通过这个库提供的方法将这些工具注册到服务器中。服务器会自动处理与任何兼容MCP的客户端的通信。注意MCP协议本身是模型无关的。这意味着你基于Summit53/mcp-server构建的工具服务器理论上可以被 Claude、GPT、开源模型等任何支持MCP的客户端调用。这极大地提升了工具生态的互操作性和开发者的投资回报率。2.2 项目架构与核心组件拆解Summit53/mcp-server作为一个库其内部设计紧密遵循MCP协议规范。理解其架构有助于我们更好地使用和扩展它。其核心可以抽象为以下几个层次通信与协议层这是最底层负责处理与客户端的实际连接和数据传输。MCP支持两种主要的传输方式stdio标准输入输出和SSEServer-Sent Events。stdio模式最常见服务器作为一个独立的进程启动通过标准输入stdin接收JSON-RPC请求并通过标准输出stdout发送JSON-RPC响应。这种方式简单、通用非常适合与桌面AI客户端如Claude Desktop集成客户端直接启动这个服务器进程即可。SSE模式则适用于Web环境服务器通过HTTP接口提供事件流客户端通过监听事件流来获取更新。mcp-server库需要处理这些底层通信细节的抽象让上层业务逻辑无需关心数据是如何来的。工具管理层这是业务逻辑的核心。在这一层你需要定义具体的“工具”。每个工具都有名称name客户端的唯一标识。描述description这个描述至关重要AI模型完全依靠这段文本来理解这个工具是做什么的、以及需要什么参数。描述必须清晰、准确。输入模式inputSchema定义工具所需的参数通常是一个符合JSON Schema的对象。这告诉客户端和模型调用这个工具时需要提供哪些信息以及这些信息的类型字符串、数字、数组等。执行函数handler当工具被调用时实际执行的JavaScript/TypeScript函数。这个函数会接收到客户端传来的参数执行操作如读写文件、调用API并返回结果。资源管理层可选但重要MCP协议中的“资源Resources”是指一些可读的、结构化的数据URI。例如file:///path/to/file.txt可以是一个资源。服务器可以声明自己提供了哪些资源客户端可以请求读取这些资源的内容。这对于让AI了解系统状态如file:///proc/meminfo显示内存信息或读取特定配置文件非常有用。mcp-server提供了注册和提供资源的接口。提示词模板层可选服务器还可以向客户端提供预定义的提示词模板Prompts。这些模板可以包含参数客户端或用户可以填充这些参数来快速生成针对特定任务的系统提示词。例如一个“代码审查”模板可以接受“代码语言”和“代码片段”作为参数。Summit53/mcp-server库的工作就是将这些层次封装好提供一个清晰的API如new Server()server.tool()server.resource()让开发者能够像搭积木一样快速构建出一个功能完备、符合协议的MCP服务器。3. 从零开始构建你的第一个MCP工具服务器理论说得再多不如动手做一遍。下面我将带你一步步用Summit53/mcp-server构建一个简单的服务器它提供两个工具一个是读取指定文件的内容另一个是获取当前系统的时间。我们将使用最常用的 stdio 传输模式。3.1 环境准备与项目初始化首先确保你的开发环境已经安装了 Node.js版本18或以上和 npm/yarn/pnpm 等包管理器。然后我们创建一个新的项目目录并初始化。mkdir my-first-mcp-server cd my-first-mcp-server npm init -y接下来安装核心依赖modelcontextprotocol/sdk。是的Summit53/mcp-server是这个SDK的一部分。它由 Anthropic 官方维护Summit53可能是核心贡献者的GitHub用户名。npm install modelcontextprotocol/sdk同时我们安装 TypeScript 和相关的类型定义以便获得更好的开发体验本项目使用TypeScript示例。npm install --save-dev typescript types/node tsx初始化 TypeScript 配置npx tsc --init在生成的tsconfig.json中确保target是ES2022或更高module是NodeNext或CommonJS并设置outDir为./dist。3.2 核心工具实现文件阅读器与时间服务器现在创建我们的主文件src/server.ts。// src/server.ts import { Server } from modelcontextprotocol/sdk/server/index.js; import { StdioServerTransport } from modelcontextprotocol/sdk/server/stdio.js; import fs from fs/promises; import path from path; // 1. 创建MCP服务器实例 const server new Server( { name: my-first-mcp-server, version: 0.1.0, }, { // 服务器的能力声明我们提供工具和资源 capabilities: { tools: {}, resources: {}, // 本例暂不实现资源 prompts: {}, // 本例暂不实现提示词 }, } ); // 2. 定义并注册工具read_file server.setRequestHandler( // 这里我们使用更底层的方法来精确控制工具定义 async (request) { if (request.method tools/list) { return { tools: [ { name: read_file, description: 读取指定路径的文本文件内容。请确保路径是绝对路径或相对于服务器工作目录的有效路径。, inputSchema: { type: object, properties: { filePath: { type: string, description: 要读取的文件的路径, }, }, required: [filePath], }, }, { name: get_current_time, description: 获取服务器当前的系统时间和日期。, inputSchema: { type: object, properties: {}, // 这个工具不需要参数 }, }, ], }; } // 处理其他请求... return null; } ); // 3. 处理工具调用请求 server.setRequestHandler(async (request) { if (request.method tools/call) { const params request.params as any; const toolName params.name; const arguments_ params.arguments; if (toolName read_file) { const { filePath } arguments_; try { // 安全性考虑这里应该添加路径校验防止目录遍历攻击 // 例如检查路径是否在允许的目录范围内 const resolvedPath path.resolve(filePath); // 简单的安全限制只允许读取当前工作目录及其子目录下的文件 if (!resolvedPath.startsWith(process.cwd())) { throw new Error(访问被拒绝只能读取工作目录下的文件。); } const content await fs.readFile(resolvedPath, utf-8); return { content: [ { type: text, text: 文件 ${filePath} 的内容如下\n\\\\n${content}\n\\\, }, ], }; } catch (error: any) { return { content: [ { type: text, text: 读取文件失败${error.message}, }, ], isError: true, }; } } if (toolName get_current_time) { const now new Date(); const formattedTime now.toLocaleString(zh-CN, { timeZone: Asia/Shanghai, hour12: false, }); return { content: [ { type: text, text: 当前系统时间北京时间是${formattedTime}, }, ], }; } } return null; }); // 4. 启动服务器使用stdio传输 async function main() { const transport new StdioServerTransport(); await server.connect(transport); console.error(MCP服务器已启动正在通过stdio通信...); } main().catch((error) { console.error(服务器启动失败:, error); process.exit(1); });3.3 编译、运行与客户端连接首先编译 TypeScript 代码npx tsc或者使用tsx直接运行无需编译npx tsx src/server.ts此时服务器进程会启动并等待来自标准输入stdin的请求。它自己不会结束而是持续运行。要让客户端连接我们需要一个客户端配置文件。以Claude Desktop为例在其配置文件中添加我们的服务器。Claude Desktop 的配置文件通常位于macOS:~/Library/Application Support/Claude/claude_desktop_config.jsonWindows:%APPDATA%\Claude\claude_desktop_config.json编辑这个文件如果不存在则创建添加如下配置{ mcpServers: { my-file-server: { command: node, args: [ /ABSOLUTE/PATH/TO/YOUR/PROJECT/dist/server.js ], env: { NODE_ENV: production } } } }重要提示args中的路径必须是绝对路径。保存配置后完全重启 Claude Desktop 应用。重启后在聊天界面你应该能看到一个类似“工具已更新”的提示或者在使用时模型会意识到它现在可以调用read_file和get_current_time这两个工具了。现在你可以在 Claude Desktop 中尝试对模型说“请帮我读取/Users/yourname/Documents/note.txt文件的内容。” 模型会识别到它可以调用read_file工具并向你的服务器发起请求。服务器执行读取操作后将内容返回给模型模型再组织语言回复给你。整个过程模型从未直接接触你的文件系统。4. 高级功能实现与安全加固实战一个基础的服务器跑起来后我们需要考虑更多生产级别的需求如何管理多个工具如何安全地暴露资源如何优雅地处理错误下面我们来深入这些高级主题。4.1 模块化工具管理与资源提供当工具数量增多时把所有工具定义和处理器堆在一个文件里会变得难以维护。更好的做法是进行模块化管理。我们可以创建一个tools目录每个工具一个文件。例如创建src/tools/filesystem.ts// src/tools/filesystem.ts import fs from fs/promises; import path from path; export const fileTools { list: { name: list_directory, description: 列出指定目录下的文件和子目录。, inputSchema: { type: object, properties: { dirPath: { type: string, description: 要列出的目录路径。默认为当前工作目录。, }, }, required: [], }, handler: async ({ dirPath . }: { dirPath?: string }) { const resolvedPath path.resolve(dirPath); // 安全校验 if (!resolvedPath.startsWith(process.cwd())) { throw new Error(访问被拒绝只能列出工作目录下的内容。); } const items await fs.readdir(resolvedPath, { withFileTypes: true }); const list items.map((item) ({ name: item.name, type: item.isDirectory() ? directory : file, })); return { content: [{ type: text, text: 目录 ${dirPath} 下的内容\n${list.map(i - [${i.type}] ${i.name}).join(\n)} }] }; }, }, // 可以继续添加 write_file, delete_file 等工具 };然后在主服务器文件中我们可以动态地导入和注册这些工具模块。同时我们可以实现资源Resources功能。资源在MCP中用于提供静态或动态的可读内容。// 在主服务器文件中添加资源支持 import { fileTools } from ./tools/filesystem.js; // ... 在 server.setRequestHandler 中动态生成工具列表 server.setRequestHandler(async (request) { if (request.method tools/list) { // 收集所有模块的工具定义 const allTools [/* ... 来自各个模块的工具定义对象 ... */]; return { tools: allTools.map(t ({ name: t.name, description: t.description, inputSchema: t.inputSchema })) }; } if (request.method tools/call) { // 根据工具名路由到对应的handler const tool allTools.find(t t.name request.params.name); if (tool tool.handler) { return await tool.handler(request.params.arguments); } } // 处理资源请求 if (request.method resources/list) { return { resources: [ { uri: file:///server/status, name: 服务器状态, description: 当前MCP服务器的运行状态信息, mimeType: text/plain, }, ], }; } if (request.method resources/read) { const uri (request.params as any).uri; if (uri file:///server/status) { const status { uptime: process.uptime(), platform: process.platform, nodeVersion: process.version, serverName: my-first-mcp-server, }; return { contents: [{ uri: uri, mimeType: text/plain, text: JSON.stringify(status, null, 2) }] }; } } return null; });这样客户端就可以通过请求file:///server/status这个资源URI来获取服务器的状态信息而无需调用一个特定的“工具”。4.2 安全性设计与权限控制安全是MCP服务器的生命线。绝对不能信任客户端传来的任何输入。以下是几个必须实施的安全策略路径遍历防护这是文件系统工具最大的风险。必须将用户提供的路径解析为绝对路径并严格检查其是否在预先允许的根目录之下。const ALLOWED_ROOT path.resolve(process.cwd(), ./data); // 只允许操作 ./data 目录下的文件 function sanitizePath(userPath: string): string { const resolved path.resolve(userPath); if (!resolved.startsWith(ALLOWED_ROOT)) { throw new Error(路径 ${userPath} 不在允许的访问范围内。); } return resolved; }命令注入防护如果你的工具涉及执行系统命令如通过child_process.exec绝对不要直接将用户输入拼接成命令字符串。必须使用参数化数组的形式。// 危险 // const result execSync(ls -la ${userInput}); // 安全做法 import { execFile } from child_process; const result execFile(ls, [-la, sanitizedUserInput], (error, stdout) { /* ... */ });环境隔离考虑在 Docker 容器或具有严格权限的系统用户下运行你的MCP服务器进程以限制其能造成的破坏。工具粒度控制不是所有客户端都需要所有工具。可以在服务器启动时通过环境变量来启用或禁用某些危险工具。4.3 错误处理、日志与性能优化一个健壮的服务器需要完善的错误处理和可观测性。结构化错误返回在工具处理器中使用try...catch捕获所有异常并返回格式化的错误信息给客户端而不是让进程崩溃。handler: async (args) { try { // ... 业务逻辑 } catch (error: any) { console.error([ERROR] Tool ${toolName} failed:, error); return { content: [{ type: text, text: 操作失败${error.message} }], isError: true, }; } }请求日志记录每个工具的调用请求和响应注意不要记录敏感参数便于调试和审计。server.setRequestHandler(async (request) { console.log([REQUEST] ${request.method}, JSON.stringify(request.params, null, 2)); // ... 处理请求 console.log([RESPONSE] ${request.method}, JSON.stringify(response, null, 2)); return response; });性能考虑对于可能耗时的操作如处理大文件、调用慢速API确保你的处理函数是异步的async避免阻塞服务器的事件循环。可以考虑为耗时工具设置超时机制。5. 部署、调试与生态集成指南5.1 调试技巧与常见问题排查开发MCP服务器时调试可能有点棘手因为它是通过stdio与客户端通信的。这里有几个实用的方法独立测试工具函数将你的工具处理器函数单独导出并编写单元测试或用简单的Node.js脚本调用它确保其逻辑正确与MCP协议无关。使用MCP InspectorAnthropic 提供了一个官方的调试工具叫MCP Inspector。你可以通过npx modelcontextprotocol/inspector启动它。它会启动一个本地Web界面并生成一个配置文件。你可以修改Claude Desktop的配置让它连接Inspector然后Inspector再连接你的服务器。这样你可以在Web界面上看到所有经过的请求和响应一目了然是调试协议交互的利器。模拟客户端请求你可以自己写一个简单的Node.js脚本模拟客户端通过stdio发送JSON-RPC请求来测试你的服务器。// test_client.js const { spawn } require(child_process); const serverProcess spawn(node, [dist/server.js]); serverProcess.stdin.write(JSON.stringify({ jsonrpc: 2.0, id: 1, method: tools/call, params: { name: read_file, arguments: { filePath: ./test.txt } } }) \n); serverProcess.stdout.on(data, (data) console.log(Server:, data.toString()));常见问题客户端连接不上99%的问题出在配置文件路径错误或服务器启动失败。检查Claude Desktop配置中的command和args路径是否为绝对路径且正确。查看服务器进程的日志console.error输出是否有报错。工具不显示确保在tools/list请求的响应中返回的tools数组格式正确且每个工具都有name,description,inputSchema字段。用MCP Inspector检查请求响应。调用工具无反应或报错检查工具handler函数是否正确注册和调用。用Inspector查看具体的错误信息。检查输入参数是否符合inputSchema。5.2 打包与分发当你完成开发后可能需要将服务器分发给其他人使用。建议打包为可执行文件使用pkg或nexe将Node.js项目打包成单个可执行文件这样用户无需安装Node.js环境。npm install -g pkg pkg . --targets node18-linux-x64,node18-macos-x64,node18-win-x64 --output dist/my-mcp-server然后在配置文件中command直接指向这个可执行文件即可。发布到npm如果你的服务器是通用工具集可以发布到npm。在package.json中指定bin字段用户可以通过npx your-package-name来运行。同时提供清晰的文档说明配置方法。5.3 融入现有MCP生态Summit53/mcp-server是构建自定义服务器的利器但很多时候我们不需要从头造轮子。社区已经有很多优秀的、现成的MCP服务器可以直接使用或作为参考文件系统file://协议服务器提供安全的文件读写。Git与Git仓库交互查看状态、提交历史等。SQLite / PostgreSQL让AI可以查询你的数据库务必限制为只读或特定操作。浏览器自动化控制浏览器进行网页抓取或操作。系统信息获取CPU、内存、进程等信息。你可以在 GitHub 上搜索 “mcp-server-*” 找到大量项目。学习这些项目的代码能让你更快地掌握MCP服务器的最佳实践。你也可以将自己开发的、解决特定问题的服务器开源回馈社区。构建自己的MCP服务器本质上是为你的AI助手打造专属的“瑞士军刀”。这个过程不仅加深了你对AI Agent工作方式的理解也让你能真正将AI能力无缝融入个人工作流。从简单的文件操作开始逐步扩展到集成内部API、自动化复杂流程你会发现一个充满可能性的新世界。