ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

MCP协议实战:构建AI插件系统,实现模型与外部工具的安全交互

MCP协议实战:构建AI插件系统,实现模型与外部工具的安全交互 1. 项目概述为什么AI需要一个“插件系统”如果你最近在AI圈子里混尤其是跟Claude、Cursor这些工具打交道大概率会频繁听到一个词MCP。全称是Model Context Protocol翻译过来叫“模型上下文协议”。乍一听很技术很抽象但它的核心目标其实非常朴素让AI大模型能像我们人类使用浏览器、操作Excel一样去安全、可控地调用外部的工具、数据和功能。这听起来不就是API吗没错但MCP想解决的是更深一层的问题。过去我们想让AI干点“实事”比如查天气、读文件、发邮件通常有两种方式一是靠提示词工程把复杂的指令和上下文硬塞进有限的上下文窗口里既低效又容易出错二是为特定模型比如ChatGPT开发专属的插件或Function Calling但这意味着你的工具链被牢牢绑定在某个生态里。今天为Claude写的工具明天想给Gemini用对不起重写吧。MCP的出现就是为了打破这种“烟囱式”的孤岛。它定义了一套标准化的通信协议让任何兼容MCP的AI应用客户端都能无缝连接和使用任何同样兼容MCP的工具服务器。你可以把它想象成AI世界的“USB协议”或“蓝牙协议”——只要设备支持这个标准就能即插即用不用关心对方是哪个品牌、哪个型号。对我而言MCP最吸引人的地方在于它的“去中心化”和“开发者友好”。它不是一个由某家巨头垄断的封闭平台而是一个开放的协议。这意味着无论是个人开发者还是大公司都可以基于这个协议为自己私有的数据源、内部系统或者独特的业务逻辑快速构建一个AI可用的“工具”并且这个工具可以服务于所有支持MCP的AI助手。这极大地降低了AI应用化的门槛也让AI的能力边界从“纯文本生成”真正扩展到了“与真实世界交互”。2. MCP核心架构与工作原理拆解要理解MCP能做什么首先得弄明白它是怎么工作的。整个MCP生态的核心是“客户端-服务器”架构但这个架构和我们传统的Web服务有些不同它更轻量、更专注于“资源”和“工具”的抽象。2.1 核心组件客户端、服务器与传输层MCP 客户端 (Client)这就是你日常打交道的AI应用本身比如Cursor编辑器、Claude桌面端或者任何集成了MCP SDK的应用。客户端的核心职责是发现与连接根据配置找到并连接到指定的MCP服务器。管理上下文向服务器请求可用的“资源”如文件列表、数据库表结构和“工具”如执行命令、搜索网页并将这些信息以结构化的方式纳入模型的上下文。调用与执行当模型判断需要调用某个工具时客户端负责向服务器发起调用请求并返回结果给模型。呈现结果将工具执行的结果可能是文本、图片、数据整合进对话或编辑界面。一个关键点是客户端不负责实现具体的工具逻辑它只负责协议的调度和通信。MCP 服务器 (Server)这是能力的提供方也是开发者主要耕耘的地方。一个MCP服务器可以很简单只暴露一个“获取当前时间”的工具也可以非常复杂比如连接整个公司的JIRA系统、内部知识库或者控制智能家居。 服务器的核心职责是声明能力启动时向客户端宣告自己提供了哪些“资源”和“工具”。例如一个文件系统服务器会声明“我可以列出/home/user/docs目录下的所有文件”这是一个资源以及“我可以读取/home/user/docs/xxx.txt文件的内容”这是一个工具。处理请求接收客户端发来的工具调用请求执行真正的业务逻辑如调用第三方API、查询数据库、运行本地脚本。返回结果将执行结果成功或错误按照协议格式返回给客户端。传输层 (Transport)MCP协议本身是传输层无关的。这意味着客户端和服务器可以通过多种方式通信stdio (标准输入输出)最常见的方式适用于服务器是一个本地进程。客户端启动服务器进程并通过管道进行JSON-RPC通信。这种方式简单、安全适合大多数本地工具。SSH可以连接到远程主机上的MCP服务器实现远程能力调用。HTTP/WebSocket适用于服务器是一个长期运行的网络服务允许多个客户端连接。这种设计让部署变得非常灵活。你可以在本地电脑上运行一个服务器供个人使用也可以在公司内网部署一个企业级服务器供所有员工调用。2.2 核心概念资源、工具与提示词MCP协议的核心抽象是“资源”和“工具”它们共同构成了AI模型的“可操作上下文”。资源 (Resources)资源代表的是“数据”或“信息的引用”。它本身不一定包含完整的数据内容而更像是一个目录或索引。例如file:///home/user/project/README.md指向一个文件。jira://project/TASK-123指向一个JIRA任务。db://sales/customers指向数据库中的一个表。服务器可以向客户端提供一个资源的“URI”和“描述”。当模型需要了解某个资源时客户端可以向服务器请求该资源的详细内容例如读取文件内容或获取任务详情。资源是静态的、可供查询的信息源。工具 (Tools)工具代表的是“可执行的动作”。这是AI与外界交互的主要手段。每个工具都有名称 (name)唯一标识符如search_web。描述 (description)用自然语言清晰说明这个工具是做什么的。这个描述至关重要因为AI模型完全依赖它来决定是否以及何时调用该工具。输入参数 (inputSchema)定义调用工具时需要提供的参数采用JSON Schema格式。例如一个搜索工具可能需要query查询词和max_results最大结果数参数。当模型在对话或编码过程中认为自己需要执行某个操作比如“帮我查一下最新的React版本”它会根据工具描述匹配需求然后通过客户端调用对应的工具如search_web(query“React latest version”)。提示词 (Prompts)这是MCP一个非常巧妙的设计。除了资源和工具服务器还可以提供预定义的“提示词模板”。你可以把它理解为可复用的“对话种子”或“任务指令集”。 例如一个代码审查服务器可以提供名为“review_python_code”的提示词。当用户在客户端选择这个提示词时客户端会向服务器请求该提示词的详细内容可能是一个包含占位符的模板然后将其填充到对话中引导模型进入代码审查的角色和流程。这标准化了复杂任务的启动方式提升了体验。2.3 工作流程全景图让我们通过一个具体场景串联起整个工作流程 假设你正在Cursor里写代码并配置了一个“文件系统”MCP服务器和一个“网络搜索”MCP服务器。初始化连接你启动Cursor客户端。Cursor读取你的配置文件发现你配置了两个MCP服务器。它分别启动这两个服务器进程通过stdio并建立连接。能力发现Cursor向两个服务器发送initialize请求。文件系统服务器回复“我提供了list_directory工具和read_file资源。”网络搜索服务器回复“我提供了search_web工具。”上下文注入Cursor将这些工具和资源的描述作为系统提示词的一部分悄悄地提供给其内置的AI模型比如Claude 3。现在模型知道“哦我现在除了聊天还能列出目录、读文件、搜索网页。”用户交互你在Cursor里问“我项目根目录下的src文件夹里有什么文件”模型决策模型分析你的请求匹配工具描述。它发现list_directory工具的描述是“列出指定路径下的文件和子目录”。于是它决定调用这个工具并生成一个结构化的调用请求list_directory(path“/project/src”)。客户端转发Cursor收到模型发来的调用请求将其通过MCP协议转发给文件系统服务器。服务器执行文件系统服务器收到请求在本地实际执行ls /project/src命令获取文件列表。结果返回服务器将文件列表结果格式化通过MCP协议返回给Cursor。结果呈现Cursor将文件列表结果返回给模型。模型将这个结果融入自己的思考最终生成给你的回答“你的src文件夹下包含main.py,utils.py, 和一个components子目录。”循环往复整个对话中模型可以根据需要多次、混合调用不同服务器提供的工具从而完成复杂的、需要多步外部交互的任务。这个流程的关键在于模型始终处于核心决策地位它根据对用户意图的理解和可用的工具描述自主决定调用什么、何时调用。而MCP协议则确保了这种调用的标准化和安全隔离。3. 如何构建你的第一个MCP服务器从零到一实战理解了原理最好的巩固方式就是动手做一个。我们将构建一个最简单的MCP服务器一个“随机笑话生成器”。它提供一个工具当被调用时会从预设列表中随机返回一个笑话。3.1 环境准备与工具选型构建MCP服务器主流语言Python, JavaScript, TypeScript, Go等都可以官方和社区也提供了相应的SDK来简化开发。这里我选择TypeScript因为它结合了JavaScript的生态优势和静态类型检查对构建这类需要清晰定义接口工具参数、返回值的项目非常友好。所需环境Node.js版本18或以上。这是运行TypeScript和MCP SDK的基础。npm 或 yarn包管理工具。我习惯用pnpm速度更快这里也推荐。代码编辑器VS Code或Cursor皆可确保有好的TypeScript支持。初始化项目打开终端创建一个新目录并初始化项目。mkdir mcp-joke-server cd mcp-joke-server pnpm init -y安装核心依赖我们需要安装官方提供的modelcontextprotocol/sdk。pnpm add modelcontextprotocol/sdk同时因为用TypeScript开发需要安装类型定义和开发依赖。pnpm add -D typescript types/node tsxtsx是一个TypeScript执行器可以让我们直接运行.ts文件非常方便开发调试。配置TypeScript创建tsconfig.json文件。{ compilerOptions: { target: ES2022, module: NodeNext, moduleResolution: NodeNext, outDir: ./dist, rootDir: ./src, strict: true, esModuleInterop: true, skipLibCheck: true, forceConsistentCasingInFileNames: true, resolveJsonModule: true }, include: [src/**/*], exclude: [node_modules] }3.2 服务器核心代码实现现在我们来编写服务器的核心逻辑。在src目录下创建index.ts文件。第一步导入SDK并定义工具import { Server } from modelcontextprotocol/sdk/server/index.js; import { StdioServerTransport } from modelcontextprotocol/sdk/server/stdio.js; import { CallToolRequestSchema, ListToolsRequestSchema, } from modelcontextprotocol/sdk/types.js; // 1. 创建Server实例 const server new Server( { name: joke-server, // 服务器名称 version: 0.1.0, // 版本 }, { capabilities: { // 声明服务器能力 tools: {}, // 我们提供工具 }, } ); // 2. 定义我们的笑话库 const jokes [ 为什么程序员总是分不清万圣节和圣诞节因为 Oct 31 Dec 25。, 我写代码的速度取决于咖啡因的浓度和死线的接近度。, 曾经有个程序员去钓鱼他钓到了一条鱼。鱼说‘把我放了吧我可以实现你三个愿望。’程序员说‘好啊我要一个无敌的框架永远不会出bug的代码和...’鱼打断他‘等等你说的是三个愿望还是一个愿望’, 问如何让一个程序员崩溃答让他看一段没有注释的、他自己一年前写的代码。, 硬件是舞台软件是演员而用户是观众——只是他们经常在演员忘词时喝倒彩。, ]; // 3. 定义“get_random_joke”工具 const getRandomJokeTool { name: get_random_joke, // 工具名称建议用蛇形命名 description: 从服务器预设的笑话库中随机返回一个程序员笑话。当用户需要轻松一下、缓解压力或请求讲个笑话时调用此工具。, // 描述务必清晰AI靠它做决策。 inputSchema: { type: object, properties: { category: { // 可以设计一个参数虽然我们现在不用但展示了如何定义 type: string, description: 笑话类别暂未实现保留字段, enum: [programmer, general], }, }, }, };关键点解析工具描述 (description)这是最重要的部分。我写的描述不仅说明了功能“随机返回一个程序员笑话”还给出了调用场景的建议“当用户需要轻松一下...时调用”。这能极大地帮助AI模型更准确地理解何时该使用这个工具。模糊的描述会导致模型要么滥用要么完全忽略这个工具。输入参数 (inputSchema)这里我定义了一个category参数并使用了enum枚举了可选值。即使当前逻辑用不到这个参数这样定义也展示了如何构建更复杂的工具。在实际调用时AI模型会提供符合这个schema的JSON对象。第二步实现工具处理逻辑我们需要告诉服务器当收到调用get_random_joke工具的请求时应该执行什么操作。// 4. 处理工具调用请求 server.setRequestHandler(CallToolRequestSchema, async (request) { // 检查调用的工具名称是否匹配 if (request.params.name getRandomJokeTool.name) { // 从笑话库中随机选取一个 const randomIndex Math.floor(Math.random() * jokes.length); const selectedJoke jokes[randomIndex]; // 返回成功结果内容类型为文本 return { content: [ { type: text, text: selectedJoke, }, ], }; } // 如果收到未知的工具调用请求返回错误 throw new Error(未知的工具: ${request.params.name}); });第三步声明可用的工具列表服务器启动时需要告诉客户端它提供了哪些工具。// 5. 处理客户端查询可用工具的请求 server.setRequestHandler(ListToolsRequestSchema, async () { // 返回我们定义的工具列表目前只有一个 return { tools: [getRandomJokeTool], }; });第四步启动服务器并配置传输层最后我们需要启动服务器并指定通过stdio标准输入输出进行通信这是与像Cursor这样的客户端集成最常用的方式。// 6. 启动服务器 async function main() { // 使用Stdio传输层 const transport new StdioServerTransport(); await server.connect(transport); console.error(MCP 笑话服务器已启动通过 stdio 通信); // 使用 console.error 输出日志避免干扰协议通信 } main().catch((error) { console.error(服务器启动失败:, error); process.exit(1); });完整的src/index.ts代码将以上所有步骤组合起来就是完整的服务器代码。保存文件。3.3 构建、测试与配置客户端构建项目在package.json中添加构建和启动脚本。{ name: mcp-joke-server, version: 0.1.0, type: module, scripts: { build: tsc, start: node dist/index.js, dev: tsx watch src/index.ts }, dependencies: { modelcontextprotocol/sdk: ^0.5.0 }, devDependencies: { types/node: ^20.0.0, tsx: ^4.0.0, typescript: ^5.0.0 } }运行pnpm run build会将TypeScript编译成JavaScript到dist目录。开发时可以直接用pnpm run dev启动监听模式。手动测试服务器为了验证服务器逻辑是否正确我们可以创建一个简单的测试脚本test_client.js放在项目根目录仅用于测试非MCP标准客户端。// test_client.js - 这是一个简化的模拟测试 import { spawn } from child_process; const serverProcess spawn(node, [dist/index.js], { stdio: [pipe, pipe, inherit] // 继承stderr以便看日志 }); // 模拟发送一个ListTools请求简化版JSON-RPC消息 const listToolsRequest JSON.stringify({ jsonrpc: 2.0, id: 1, method: tools/list, params: {} }) \n; serverProcess.stdin.write(listToolsRequest); serverProcess.stdin.end(); serverProcess.stdout.on(data, (data) { console.log(服务器响应:, data.toString()); serverProcess.kill(); });运行node test_client.js你应该能看到服务器返回的包含get_random_joke工具定义的JSON消息。这说明你的服务器基本逻辑是通的。配置Cursor客户端这才是重头戏。要让你的笑话服务器在Cursor里真正被AI调用需要在Cursor的MCP配置文件中添加它。找到Cursor的MCP配置文件。通常位于macOS:~/Library/Application Support/Cursor/User/globalStorage/mcp.jsonWindows:%APPDATA%/Cursor/User/globalStorage/mcp.jsonLinux:~/.config/Cursor/User/globalStorage/mcp.json如果文件或目录不存在可以手动创建。编辑mcp.json文件。其基本结构是一个JSON对象键是服务器名称值是该服务器的配置。{ mcpServers: { my-joke-server: { command: node, args: [ /ABSOLUTE/PATH/TO/YOUR/mcp-joke-server/dist/index.js ], env: {} } } }关键提示args中的路径必须是绝对路径。相对路径在Cursor的上下文中可能无法正确解析。你可以使用pwd命令获取你项目dist/index.js的绝对路径。保存配置文件并完全重启Cursor。配置只在启动时加载。在Cursor中验证重启Cursor后新建一个对话。你可以尝试直接问AI“讲个笑话听听”或者“我有点累来个程序员笑话放松一下。” 如果配置成功AI模型如Claude会在后台看到get_random_joke工具的描述并在认为合适的时候调用它。调用时你可能会在Cursor的界面看到短暂的“思考”或“调用工具”的提示然后回答中就会出现来自你服务器的随机笑话4. 进阶实战构建一个实用的“系统信息查询”服务器单一的笑话服务器只是个开始。让我们构建一个更实用、能返回多种系统信息的服务器它将展示如何定义多个工具、处理不同参数以及返回结构化数据。4.1 设计工具集与依赖选择这个服务器将提供以下工具get_system_info: 获取操作系统、CPU架构、内存总量等基本信息。get_memory_usage: 获取当前内存使用情况已用、空闲、百分比。get_disk_usage: 获取指定路径的磁盘使用情况。get_process_list: 获取当前运行的进程列表简化版如Top 10 by CPU。我们将使用Node.js内置的os模块和child_process模块无需额外安装依赖。4.2 多工具服务器的实现在src目录下创建system-info-server.ts。import { Server } from modelcontextprotocol/sdk/server/index.js; import { StdioServerTransport } from modelcontextprotocol/sdk/server/stdio.js; import { CallToolRequestSchema, ListToolsRequestSchema, } from modelcontextprotocol/sdk/types.js; import os from os; import { exec } from child_process; import { promisify } from util; const execAsync promisify(exec); const server new Server( { name: system-info-server, version: 0.2.0, }, { capabilities: { tools: {}, }, } ); // 定义工具列表 const tools [ { name: get_system_info, description: 获取当前系统的基本信息包括操作系统类型、平台、CPU架构、总内存、主机名以及系统运行时间。当用户询问‘我的电脑是什么系统’、‘系统配置如何’或需要诊断环境问题时调用。, inputSchema: { type: object, properties: {}, // 此工具无需参数 }, }, { name: get_memory_usage, description: 获取当前系统的内存使用情况包括总内存、空闲内存、已使用内存及其百分比。当用户关心内存占用、排查性能问题时调用。, inputSchema: { type: object, properties: {}, }, }, { name: get_disk_usage, description: 获取指定路径所在磁盘分区的使用情况包括总空间、已用空间、可用空间和使用百分比。参数‘path’是文件系统上的任意有效路径默认为当前工作目录(‘.’)。当用户询问‘磁盘还剩多少空间’或需要清理存储时调用。, inputSchema: { type: object, properties: { path: { type: string, description: 需要查询磁盘使用情况的文件系统路径。, default: ., }, }, }, }, { name: get_process_list, description: 获取当前正在运行的前N个进程列表默认按CPU使用率降序排列前10个。参数‘limit’控制返回的进程数量。当用户需要了解哪些进程占用了大量资源时调用。注意此工具在Windows和Unix-like系统上的输出格式有差异。, inputSchema: { type: object, properties: { limit: { type: number, description: 需要返回的进程数量上限。, default: 10, minimum: 1, maximum: 50, }, }, }, }, ]; // 工具实现函数 async function handleGetSystemInfo() { const uptime Math.floor(os.uptime()); const hours Math.floor(uptime / 3600); const minutes Math.floor((uptime % 3600) / 60); const seconds uptime % 60; return { content: [ { type: text, text: **系统信息概览**\n - **操作系统**: ${os.type()} ${os.release()}\n - **平台**: ${os.platform()} (${os.arch()})\n - **主机名**: ${os.hostname()}\n - **总内存**: ${(os.totalmem() / (1024 ** 3)).toFixed(2)} GB\n - **CPU核心数**: ${os.cpus().length}\n - **系统运行时间**: ${hours}小时 ${minutes}分钟 ${seconds}秒, }, ], }; } async function handleGetMemoryUsage() { const totalMem os.totalmem(); const freeMem os.freemem(); const usedMem totalMem - freeMem; const usagePercent ((usedMem / totalMem) * 100).toFixed(1); return { content: [ { type: text, text: **内存使用情况**\n - **总内存**: ${(totalMem / (1024 ** 3)).toFixed(2)} GB\n - **已使用**: ${(usedMem / (1024 ** 3)).toFixed(2)} GB\n - **可用内存**: ${(freeMem / (1024 ** 3)).toFixed(2)} GB\n - **使用率**: ${usagePercent}%, }, ], }; } async function handleGetDiskUsage(params: any) { const path params.path || .; let command: string; let parseOutput: (stdout: string) string; if (os.platform() win32) { // Windows: 使用 wmic command wmic logicaldisk where DeviceID${path.charAt(0).toUpperCase()}: get Size,FreeSpace; parseOutput (stdout) { const lines stdout.trim().split(\r\n); if (lines.length 2) return 无法获取路径 ${path} 的磁盘信息。; const numbers lines[1].trim().split(/\s/).map(Number); const total numbers[0]; const free numbers[1]; const used total - free; const percent ((used / total) * 100).toFixed(1); return **磁盘使用情况 (${path.charAt(0).toUpperCase()}:)**\n - **总空间**: ${(total / (1024**3)).toFixed(2)} GB\n - **已用空间**: ${(used / (1024**3)).toFixed(2)} GB\n - **可用空间**: ${(free / (1024**3)).toFixed(2)} GB\n - **使用率**: ${percent}%; }; } else { // Unix-like (Linux, macOS): 使用 df command df -k ${path} | tail -1; parseOutput (stdout) { const parts stdout.trim().split(/\s/); if (parts.length 6) return 无法获取路径 ${path} 的磁盘信息。; const total parseInt(parts[1]) * 1024; // 1K blocks to bytes const used parseInt(parts[2]) * 1024; const available parseInt(parts[3]) * 1024; const percent parts[4]; return **磁盘使用情况 (${path})**\n - **总空间**: ${(total / (1024**3)).toFixed(2)} GB\n - **已用空间**: ${(used / (1024**3)).toFixed(2)} GB\n - **可用空间**: ${(available / (1024**3)).toFixed(2)} GB\n - **使用率**: ${percent}; }; } try { const { stdout } await execAsync(command); return { content: [{ type: text, text: parseOutput(stdout) }], }; } catch (error: any) { return { content: [{ type: text, text: 执行磁盘查询命令时出错: ${error.message}\n请检查路径 ${path} 是否有效。, }], isError: true, }; } } async function handleGetProcessList(params: any) { const limit Math.min(Math.max(1, params.limit || 10), 50); // 限制在1-50之间 let command: string; if (os.platform() win32) { command powershell Get-Process | Sort-Object CPU -Descending | Select-Object -First ${limit} | Format-Table Name, CPU, WorkingSet, Id -AutoSize; } else { // Linux/macOS: 使用 ps command ps aux --sort-%cpu | head -n ${limit 1}; // 1 for header } try { const { stdout } await execAsync(command); return { content: [{ type: text, text: **前 ${limit} 个进程 (按CPU使用率)**\n\\\\n${stdout}\n\\\, }], }; } catch (error: any) { return { content: [{ type: text, text: 获取进程列表失败: ${error.message} }], isError: true, }; } } // 注册工具调用处理器 server.setRequestHandler(CallToolRequestSchema, async (request) { const { name, arguments: args {} } request.params; switch (name) { case get_system_info: return await handleGetSystemInfo(); case get_memory_usage: return await handleGetMemoryUsage(); case get_disk_usage: return await handleGetDiskUsage(args); case get_process_list: return await handleGetProcessList(args); default: throw new Error(未知的工具: ${name}); } }); // 注册工具列表处理器 server.setRequestHandler(ListToolsRequestSchema, async () { return { tools }; }); // 启动服务器 async function main() { const transport new StdioServerTransport(); await server.connect(transport); console.error(MCP 系统信息服务器已启动); } main().catch((error) { console.error(服务器启动失败:, error); process.exit(1); });核心要点与避坑指南跨平台兼容性这是系统工具类服务器最大的挑战。注意get_disk_usage和get_process_list工具中我们根据os.platform()判断操作系统并执行不同的命令Windows用wmic/powershellUnix用df/ps。永远不要假设服务器只运行在一种系统上。参数验证与默认值在工具定义inputSchema中我们为get_disk_usage的path参数设置了默认值.为get_process_list的limit参数设置了默认值10并规定了最小值和最大值。这能引导AI提供合理的参数并在AI未提供时使用安全默认值。错误处理在execAsync调用外包裹了try...catch。任何外部命令执行都可能失败路径不存在、权限不足等。必须捕获错误并通过isError: true标志或清晰的错误信息返回给客户端和用户而不是让整个服务器崩溃。输出格式化返回的text内容中我们使用了Markdown格式的粗体**和代码块。这能帮助AI客户端如Cursor更好地渲染和呈现结果提升可读性。工具描述的精确性描述中明确说明了调用场景“当用户询问...时调用”和注意事项“注意此工具在Windows和Unix-like系统上的输出格式有差异”。这能极大提升AI模型调用的准确性和用户体验。按照之前的方法编译、配置到Cursor中你就可以直接问“我的系统内存用了多少”、“C盘还剩多少空间”、“看看现在什么进程最耗CPU”。AI会自动选择正确的工具并返回格式化的系统信息。5. 高级主题资源、提示词与生产级考量掌握了基础工具构建后让我们探索MCP更强大的能力并讨论如何打造一个健壮、可维护的生产级MCP服务器。5.1 利用“资源”暴露数据索引“资源”非常适合暴露那些结构化的、可供查询的数据目录。例如为你的项目文档构建一个服务器。// 示例文档资源服务器片段 import fs from fs/promises; import path from path; const server new Server(...); // 声明一个“列出文档”的资源 const docsResource { uri: doc:///index, name: project-docs-index, description: 项目文档根目录索引, mimeType: text/plain, }; // 处理“列出文档”请求 server.setRequestHandler(ReadResourceRequestSchema, async (request) { if (request.params.uri docsResource.uri) { const docsDir ./docs; const files await fs.readdir(docsDir); const fileList files.map(f - ${f}).join(\n); return { contents: [{ uri: request.params.uri, mimeType: text/plain, text: 可用文档:\n${fileList} }] }; } // ... 处理其他资源读取比如 doc:///docs/api.md }); // 在initialize或listResources时返回资源列表 server.setRequestHandler(ListResourcesRequestSchema, async () { return { resources: [docsResource] }; });这样AI模型可以先“浏览”doc:///index资源获取文档列表然后再决定读取哪一个具体的文档资源如doc:///docs/api.md。资源提供了比工具更“只读”、更“数据导向”的交互模式。5.2 使用“提示词”标准化复杂任务提示词模板能封装复杂的多轮对话逻辑。例如一个代码重构助手。// 示例代码重构提示词 const refactorPrompt { name: refactor_for_clarity, description: 启动一个代码重构会话。你将分析用户提供的代码并提出提高可读性、可维护性的具体重构建议。, arguments: [ // 提示词可以接受参数 { name: code_language, description: 代码的编程语言, required: true } ] }; server.setRequestHandler(GetPromptRequestSchema, async (request) { if (request.params.name refactorPrompt.name) { const language request.params.arguments?.code_language || unknown; return { messages: [ // 返回一个消息数组作为对话的初始上下文 { role: user, content: { type: text, text: 你是一个资深的${language}代码重构专家。我将给你一段代码请你 1. 首先分析代码在可读性、函数拆分、命名、复杂度方面存在的主要问题。 2. 然后针对每个问题提供具体的重构代码示例。 3. 最后总结重构带来的好处。 请保持专业和友好的态度。 } } ] }; } });当用户在客户端选择这个提示词时会直接进入一个预设好角色和任务的对话极大地提升了复杂任务的处理效率和一致性。5.3 生产级服务器开发要点当你打算长期运行或与他人共享MCP服务器时需要考虑以下几点配置化不要将服务器行为硬编码。使用环境变量或配置文件来管理API密钥、服务端点、路径等。例如数据库连接字符串应从环境变量DATABASE_URL读取。日志与监控使用成熟的日志库如winston、pino替代console.error记录信息、警告、错误等级别的日志并输出到文件或日志服务便于排查问题。安全性输入验证对所有来自客户端的输入工具参数、资源URI进行严格的验证和清理防止命令注入尤其在执行系统命令时。权限控制考虑实现简单的权限模型。例如通过客户端传递的某种令牌来限制可以访问的工具或资源。沙箱化对于执行任意代码或命令的工具考虑在沙箱环境如Docker容器、vm2模块中运行隔离潜在风险。错误处理与重试对外部API或服务的调用必须有完善的错误处理、超时设置和重试机制。向客户端返回用户友好的错误信息同时保留详细的错误日志供开发者查看。性能优化对于计算密集型或IO密集型的工具考虑实现缓存机制如内存缓存、Redis避免重复计算或请求提升响应速度。打包与分发将你的服务器打包成Docker镜像是最方便的分发和部署方式。确保提供清晰的README说明配置方法、工具列表和使用示例。6. 生态、工具与未来展望MCP的价值不仅在于协议本身更在于其蓬勃发展的生态。官方与明星服务器文件系统 (filesystem)最基础也是最常用的服务器让AI能读写本地文件。Git集成Git操作让AI可以查看状态、提交、拉取代码。Brave Search / Tavily网络搜索服务器让AI能获取实时信息。PostgreSQL / MySQL数据库服务器允许AI安全地查询数据。Fig集成终端操作功能强大但需谨慎授权。GitHub直接与GitHub Issues、PR等交互。开发与调试工具MCP Inspector一个图形化调试工具可以连接到任何MCP服务器查看其提供的资源、工具和提示词并手动测试调用是开发调试的利器。MCP CLI命令行工具用于快速测试服务器连接和基本功能。客户端支持除了CursorClaude Desktop也原生支持MCP。未来预计会有更多AI应用和IDE插件加入这一生态。MCP的意义与未来MCP正在做的是为AI世界构建一套“标准外设接口”。它降低了AI能力扩展的门槛让开发者不必再为每个模型、每个平台重复造轮子。一个为数据分析写的MCP服务器可以同时被Claude、Cursor、未来可能还有VS Code Copilot调用。它的挑战在于如何平衡能力开放与安全可控。用户必须信任服务器不会执行恶意操作。因此未来的发展可能会围绕权限管理的精细化、工具调用的可视化确认、以及服务器市场的信誉体系展开。对我个人而言MCP最令人兴奋的点在于它让AI助理真正开始“上手干活”了。从查资料、读文件、操作数据库到未来控制智能家居、管理云资源MCP为AI融入我们的数字工作流铺平了道路。现在是时候为你自己的数字世界打造专属的AI工具了。
返回列表