从零构建MCP服务器:AI应用开发实战指南
2026/5/16 6:16:55 网站建设 项目流程

1. 项目概述:从零构建你的第一个MCP服务器

最近在AI应用开发圈里,MCP(Model Context Protocol)这个词的热度越来越高。如果你正在尝试将大型语言模型(LLM)的能力集成到自己的应用中,或者想让你的AI助手能更灵活地调用外部工具和数据,那么理解并实践MCP几乎是绕不开的一步。vivy-yi/mcp-tutorial这个项目,就是一个非常棒的入门实践指南。它不是一个简单的概念介绍,而是一个手把手教你从零开始,搭建一个具备实际功能的MCP服务器的实战教程。

简单来说,MCP定义了一套标准协议,让AI模型(比如Claude、GPT)能够以一种结构化的方式发现、描述和调用外部工具(Tools)或资源(Resources)。你可以把它想象成给AI模型装上了一套标准化的“插件系统”。通过MCP,模型不再仅仅依赖于预训练的知识,而是可以实时查询数据库、调用API、读取文件,甚至操作本地系统,极大地扩展了其能力边界和应用场景。这个教程的核心价值在于,它用一个具体的例子——构建一个“待办事项(Todo)管理器”的MCP服务器——将协议规范、SDK使用、服务部署和客户端调试的全流程串联了起来,让你在动手的过程中真正吃透MCP的工作原理。

无论你是前端开发者想为你的应用增加AI智能体,还是后端工程师希望构建可被AI调用的服务化能力,亦或是AI产品经理需要理解技术实现的边界,这个教程都能提供一个扎实的起点。它不要求你事先精通MCP的所有细节,但需要你具备基本的Node.js和JavaScript知识,以及一台可以运行命令的电脑。接下来,我们就深入这个项目,拆解每一个关键环节,并补充大量实战中才会遇到的细节和避坑指南。

2. 核心概念与MCP协议深度解析

在动手写代码之前,我们必须先搞清楚MCP到底是什么,以及它试图解决什么问题。这能帮助我们在后续开发中做出更合理的设计决策。

2.1 MCP要解决的核心痛点

在没有MCP这类协议之前,让AI模型使用外部工具通常有两种方式:一是通过特定的提示词工程(Prompt Engineering)在对话中“教”模型如何使用某个API,这种方式脆弱且难以维护;二是使用像LangChain这样的框架,它封装了很多工具链,但往往和框架本身绑定较深,不够通用。MCP的出现,就是为了制定一个与具体模型、具体框架无关的“通用插座”标准。

它的核心思想是服务化声明式。服务化意味着工具能力由独立的服务器提供,与AI客户端解耦;声明式意味着服务器不需要告诉模型“怎么用”,只需要告诉模型“我有什么”(工具列表)和“每个工具是什么”(工具描述)。模型根据这些声明信息,自主决定在何时、为何种目的调用哪个工具。这极大地提升了灵活性和可组合性。

2.2 MCP协议的三驾马车:工具、资源和提示词

MCP协议主要围绕三个核心概念来组织能力,理解它们是你设计服务器的基石。

1. 工具(Tools)这是最常用、最核心的概念。一个工具就是一个可以被模型调用的函数。每个工具必须包含:

  • name: 唯一标识符,通常使用蛇形命名法,如create_todo
  • description: 对工具功能的自然语言描述。这部分至关重要,因为模型完全依赖这个描述来决定是否以及如何调用它。描述应清晰、无歧义,并说明输入参数。
  • inputSchema: 定义工具接受的参数,使用JSON Schema格式。这相当于给模型的“参数说明书”。

例如,一个删除待办事项的工具描述可能是:“根据待办事项的ID删除一个指定的待办事项。需要提供id参数。” 对应的inputSchema会定义id字段为必填的字符串类型。

2. 资源(Resources)资源代表模型可以读取的静态或动态数据内容,比如一个文件、一个数据库查询的视图,或者一个API的响应。资源也通过URI和元数据(MIME类型、描述)来声明。模型可以请求“读取”某个资源的内容。在待办事项示例中,你可以把整个待办事项列表暴露为一个资源todo://list,模型就可以直接读取列表内容,而不必通过工具调用。

3. 提示词(Prompts)提示词是预定义的、参数化的文本模板。模型可以请求获取这些提示词,用于引导对话或生成特定内容。例如,你可以定义一个“总结待办事项”的提示词模板,它接受一个日期参数,返回类似“请总结用户在{{date}}这一天的待办事项完成情况”的文本。这允许服务器端对提示词进行集中管理和优化。

注意:在初学阶段,建议先从“工具”入手,因为它的使用模式最直接,也最能体现MCP的价值。资源和提示词可以在后续迭代中逐步加入。

2.3 通信方式:Stdio vs. SSE

MCP服务器与客户端(AI应用)之间如何通信?协议支持两种主要方式,教程中用的是第一种,但了解第二种对后续部署很重要。

Stdio(标准输入/输出)这是开发调试时最常用的方式。服务器作为一个命令行进程启动,通过标准输入(stdin)接收JSON-RPC请求,通过标准输出(stdout)发送JSON-RPC响应。这种方式简单、直接,无需网络配置,非常适合本地开发和与mcp命令行工具进行集成测试。教程中我们主要使用这种方式。

SSE(Server-Sent Events)这是一种基于HTTP的单向通信协议(服务器向客户端推送),MCP通常结合SSE和HTTP POST来实现双向通信。服务器作为一个HTTP服务运行。这种方式更适合生产环境部署,客户端可以通过网络远程连接服务器,实现能力的远程调用。当你需要将MCP服务器部署到云端供多个AI应用使用时,就需要采用SSE模式。

3. 环境准备与项目初始化实操

理论铺垫完毕,现在让我们打开终端,开始动手。这里我会补充大量教程之外的细节,确保你的环境一次配好,少走弯路。

3.1 开发环境搭建要点

首先,确保你的系统已经安装了Node.js(版本18或以上,推荐最新的LTS版本)和npm。你可以通过node --versionnpm --version来检查。

接下来,我们需要一个趁手的代码编辑器。VS Code是绝大多数Node.js开发者的选择,我强烈建议安装以下几个扩展,能极大提升开发效率:

  • ESLint: 代码质量检查。
  • Prettier: 代码自动格式化。建议配置保存时自动格式化。
  • JSON Schema Store: 自动为package.json等文件提供智能提示。
  • Thunder ClientREST Client: 用于后续测试HTTP端点(如果你扩展了SSE模式)。

创建一个全新的项目目录,并初始化项目:

mkdir my-mcp-todo-server cd my-mcp-todo-server npm init -y

执行npm init -y会快速生成一个默认的package.json文件。我建议你立刻打开这个文件,做两处关键修改:

  1. "scripts"部分,预先添加我们后续要用的命令,比如"start": "node index.js","dev": "nodemon index.js"
  2. 在末尾添加"type": "module"。这行代码非常重要,它告诉Node.js这个项目使用ES模块(ESM)规范,而不是旧的CommonJS(CJS)规范。MCP的官方SDK和现代JavaScript生态更倾向于ESM,使用它能避免后续很多模块导入导出的报错问题。

3.2 关键依赖安装与选型解析

教程会引导你安装@modelcontextprotocol/sdk。这是构建MCP服务器的核心SDK,由协议的主要推动者Anthropic官方维护,保证了与协议版本的同步和兼容性。

执行安装命令:

npm install @modelcontextprotocol/sdk

除了核心SDK,在实际开发中,我们通常会引入一些辅助依赖来让开发更顺畅。我建议你一并安装它们:

npm install -D nodemon
  • nodemon:这是一个开发工具。它会在你修改代码后自动重启Node.js服务,无需你手动停止再启动。我们将它配置在npm run dev命令中,用于开发阶段的热重载。

现在,你的package.jsondependenciesdevDependencies应该看起来类似这样:

{ "name": "my-mcp-todo-server", "type": "module", "scripts": { "start": "node index.js", "dev": "nodemon index.js" }, "dependencies": { "@modelcontextprotocol/sdk": "^0.4.0" }, "devDependencies": { "nodemon": "^3.0.0" } }

实操心得:在Node.js项目中,明确模块系统(ESM vs CJS)是第一步,也是最容易踩坑的地方。如果你在后续导入模块时遇到Cannot use import statement outside a module这类错误,99%的原因就是package.json里缺少"type": "module",或者你的文件扩展名不是.js而是.cjs。统一使用ESM能让你的代码更现代,也更容易与新的库集成。

4. 构建Todo MCP服务器的核心实现

让我们进入最核心的环节:编写服务器代码。我们将按照功能模块,一步步构建出一个完整的、具备增删改查能力的待办事项MCP服务器。

4.1 服务器骨架与连接初始化

首先,在项目根目录创建入口文件index.js。我们将从这里开始构建。

第一步是导入SDK并创建一个服务器实例。SDK提供了Server类,它是我们所有工作的基础。

import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; // 1. 创建Server实例 const server = new Server( { name: 'todo-list-server', // 你的服务器名称 version: '0.1.0', // 版本号 }, { capabilities: { // 声明服务器支持的能力 tools: {}, // 我们支持提供工具 // 后续可以在这里添加 resources: {}, prompts: {} }, } );

这里有几个关键点:

  • nameversion:这是服务器的身份标识,客户端会看到这些信息。取一个清晰的名字很重要。
  • capabilities:这是一个声明对象,告诉客户端“我有哪些本事”。目前我们只声明了tools,表示本服务器提供工具。这是一个空对象{},具体的工具列表会在后面动态添加。

接下来,我们需要设置传输层(Transport)。对于Stdio模式,SDK提供了开箱即用的StdioServerTransport

// 2. 创建传输层并连接 const transport = new StdioServerTransport(); await server.connect(transport); console.error('Todo MCP 服务器已通过 stdio 启动'); // 使用 console.error 输出日志,避免干扰 JSON-RPC 通信

await server.connect(transport)这行代码建立了服务器与传输通道的连接。特别注意:我们使用console.error来输出日志,因为MCP协议使用标准输出(stdout)进行JSON-RPC通信,任何意外的console.log输出都会破坏JSON格式,导致客户端解析失败。将日志重定向到标准错误(stderr)是MCP开发中的一个重要实践。

4.2 数据层的设计与内存存储

在实现工具之前,我们需要一个地方来存储待办事项。为了简化教程,我们使用内存存储,即一个JavaScript数组。在生产环境中,你需要将其替换为数据库(如SQLite、PostgreSQL等)。

我们在文件顶部,创建服务器实例之后,定义存储结构:

// 内存中的待办事项存储 let todos = []; let nextId = 1; // 用于生成自增ID // 待办事项的数据结构 // { // id: string, // 唯一标识 // title: string, // 事项标题 // completed: boolean, // 是否完成 // createdAt: Date // 创建时间 // }

这里定义了一个todos数组和一个nextId计数器。每个待办事项对象包含id,title,completed,createdAt四个字段。使用自增ID虽然简单,但在分布式环境下会有问题,这里仅用于演示。

4.3 工具(Tools)的完整实现与注册

这是MCP服务器的灵魂。我们将实现四个核心工具:列出所有待办事项、创建新事项、切换完成状态、删除事项。

1. 工具:list_todos- 列出所有待办事项这个工具最简单,它不需要任何参数,直接返回内存中的todos数组。

server.setRequestHandler('tools/list', async () => { // 直接返回存储的待办事项列表 return { tools: [ { name: 'list_todos', description: '获取所有的待办事项列表。', inputSchema: { type: 'object', properties: {}, // 无输入参数 }, }, // 其他工具会在这里依次添加 ], }; });

server.setRequestHandler用于处理客户端发来的特定请求。'tools/list'是请求类型,表示客户端在询问“你有什么工具?”。当这个请求到来时,我们返回一个包含所有工具定义的数组。注意,每个工具定义都严格遵循之前提到的结构:name,description,inputSchema

2. 工具:create_todo- 创建新的待办事项这个工具需要接收一个参数title(事项标题)。

// 在 tools/list 返回的数组中,添加 create_todo 工具定义 { name: 'create_todo', description: '创建一个新的待办事项。需要提供事项的标题(title)。', inputSchema: { type: 'object', properties: { title: { type: 'string', description: '待办事项的标题', }, }, required: ['title'], // 标记 title 为必填项 }, }

定义好工具后,我们还需要处理它的调用。这是通过server.setRequestHandler('tools/call', ...)来实现的。

server.setRequestHandler('tools/call', async (request) => { const { name, arguments: args } = request.params; if (name === 'create_todo') { const { title } = args; if (!title || title.trim() === '') { throw new Error('标题不能为空'); } const newTodo = { id: String(nextId++), // 生成ID并转换为字符串 title: title.trim(), completed: false, createdAt: new Date(), }; todos.push(newTodo); // 返回调用结果。content 数组中的 text 内容会被模型看到。 return { content: [ { type: 'text', text: `已成功创建待办事项:“${newTodo.title}”(ID: ${newTodo.id})`, }, ], }; } // ... 其他工具的处理逻辑 });

当客户端调用create_todo工具时,请求会到达这里。我们从request.params中解析出工具名和参数。进行简单的验证(标题非空)后,构造一个新的待办事项对象,存入todos数组,并返回一个成功的消息。返回的content字段是一个数组,其中text里的内容就是AI模型或用户最终会看到的工具执行结果。

3. 工具:toggle_todo- 切换待办事项的完成状态这个工具需要接收一个参数id,用于定位要操作的事项。

// 工具定义 { name: 'toggle_todo', description: '切换指定待办事项的完成状态(标记为完成或未完成)。需要提供待办事项的ID。', inputSchema: { type: 'object', properties: { id: { type: 'string', description: '待办事项的唯一ID', }, }, required: ['id'], }, } // 在 tools/call 处理逻辑中添加 if (name === 'toggle_todo') { const { id } = args; const todo = todos.find(t => t.id === id); if (!todo) { throw new Error(`未找到ID为 ${id} 的待办事项`); } todo.completed = !todo.completed; const status = todo.completed ? '已完成' : '未完成'; return { content: [ { type: 'text', text: `待办事项“${todo.title}”的状态已切换为:${status}`, }, ], }; }

这里演示了基本的错误处理:如果根据提供的id找不到对应的待办事项,就抛出一个错误。错误信息会被MCP框架捕获并返回给客户端。

4. 工具:delete_todo- 删除待办事项实现与toggle_todo类似,但操作是从数组中移除元素。

// 工具定义 { name: 'delete_todo', description: '根据ID删除一个指定的待办事项。', inputSchema: { type: 'object', properties: { id: { type: 'string', description: '待办事项的唯一ID', }, }, required: ['id'], }, } // 在 tools/call 处理逻辑中添加 if (name === 'delete_todo') { const { id } = args; const initialLength = todos.length; todos = todos.filter(t => t.id !== id); // 过滤掉指定id的项 if (todos.length === initialLength) { throw new Error(`未找到ID为 ${id} 的待办事项,删除失败`); } return { content: [ { type: 'text', text: `已删除ID为 ${id} 的待办事项。`, }, ], }; }

这里使用了数组的filter方法来删除元素,并通过比较删除前后的数组长度来判断是否成功找到了目标项。

4.4 工具列表的集中管理

随着工具增多,将工具定义集中管理是一个好习惯。你可以创建一个单独的tools.js文件,导出一个包含所有工具定义的数组,然后在index.js中导入。这样tools/list的处理函数就会变得非常简洁:

// tools.js export const toolDefinitions = [ { name: 'list_todos', ... }, { name: 'create_todo', ... }, // ... ]; // index.js import { toolDefinitions } from './tools.js'; server.setRequestHandler('tools/list', async () => ({ tools: toolDefinitions, }));

5. 本地测试、调试与集成

代码写完了,但它到底能不能用?我们需要进行测试。MCP生态提供了强大的命令行工具来帮助我们。

5.1 安装MCP CLI并运行服务器

首先,你需要全局安装MCP命令行工具(假设你使用npm):

npm install -g @modelcontextprotocol/cli

安装完成后,你可以使用mcp命令。

运行你的服务器有两种方式:

  1. 直接运行Node脚本node index.js。此时服务器会启动并等待来自stdin的输入。
  2. 使用MCP CLI进行测试:这是更推荐的方式,因为它提供了丰富的内省和调试功能。
mcp dev index.js

mcp dev命令会启动你的服务器,并进入一个交互式REPL(读取-求值-打印循环)环境。在这个环境里,你可以直接输入MCP协议命令来测试服务器。

5.2 使用MCP REPL进行手动测试

mcp dev启动的REPL环境中,你可以尝试以下命令:

  • list_tools: 列出服务器提供的所有工具。你应该能看到我们定义的四个工具及其描述。
  • call_tool list_todos: 调用list_todos工具。由于初始列表为空,可能会返回空数组。
  • call_tool create_todo '{"title": "学习MCP协议"}': 调用create_todo工具,并传入JSON格式的参数。注意,参数必须是一个JSON字符串。
  • 再次调用list_toolscall_tool list_todos,查看工具列表和新增的待办事项。

这个过程能让你直观地验证工具定义是否正确,以及工具调用逻辑是否正常工作。如果任何一步出错,REPL会打印出详细的错误信息,帮助你定位问题。

5.3 与AI客户端(如Claude Desktop)集成测试

真正的考验是与一个真正的AI客户端集成。Anthropic推出的Claude Desktop应用内置了MCP客户端支持,是绝佳的测试平台。

配置Claude Desktop:

  1. 打开Claude Desktop应用。
  2. 进入设置(Settings)。
  3. 找到“开发者设置”(Developer Settings)或“MCP服务器”配置部分。
  4. 点击“添加MCP服务器”(Add MCP Server)。
  5. 在配置中,你需要提供:
    • 名称:例如 “My Todo Server”。
    • 命令:这里要填写启动你服务器的完整命令。由于Claude Desktop会在它自己的环境下启动一个子进程,你需要提供Node.js解释器的绝对路径和你脚本的绝对路径。
      • 在macOS/Linux上,可以通过which node命令找到node路径。
      • 一个典型的配置可能是:/usr/local/bin/node /Users/yourname/projects/my-mcp-todo-server/index.js
    • 参数:通常留空。

保存配置并重启Claude Desktop。如果配置正确,你在与Claude对话时,它就能“发现”你提供的工具。你可以尝试对Claude说:“帮我创建一个待办事项,内容是‘买牛奶’。” Claude应该会理解你的意图,并调用create_todo工具。你可以继续测试:“列出我所有的待办事项”、“把第一个事项标记为完成”、“删除第二个事项”。

避坑指南:与Claude Desktop集成是新手最容易失败的一步。90%的问题出在“命令”配置上。确保:

  1. Node路径绝对正确。
  2. 脚本文件路径绝对正确,并且该文件有可执行权限。
  3. 你的服务器代码没有立即退出的情况(比如忘了await server.connect或者进程过早结束)。服务器必须是一个持续运行的进程,等待stdin输入。
  4. 检查Claude Desktop的日志(通常可以在设置中找到日志文件位置),里面会有更详细的错误信息。

6. 生产化进阶与扩展思路

一个能在本地运行的内存版服务器只是个开始。要让它在实际项目中发挥作用,我们还需要考虑更多。

6.1 数据持久化:从内存到数据库

内存存储的数据在服务器重启后会全部丢失。生产环境必须使用数据库。以轻量级的SQLite为例:

  1. 安装依赖npm install better-sqlite3
  2. 初始化数据库和表
import Database from 'better-sqlite3'; const db = new Database('todos.db'); // 创建表 db.exec(` CREATE TABLE IF NOT EXISTS todos ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, completed BOOLEAN DEFAULT 0, createdAt DATETIME DEFAULT CURRENT_TIMESTAMP ) `);
  1. 重写数据操作逻辑:将原来操作todos数组的代码,改为执行SQL语句。例如,create_todo工具的核心逻辑变为:
const stmt = db.prepare('INSERT INTO todos (title) VALUES (?)'); const info = stmt.run(title); // info.lastInsertRowid 可以获取插入的ID

list_todos工具则变为db.prepare('SELECT * FROM todos').all()

6.2 支持SSE传输模式

要让服务器能被远程调用,需要实现SSE传输。这通常意味着要创建一个HTTP服务器。你可以使用Express.js等框架来简化。

  1. 安装Expressnpm install express
  2. 创建HTTP服务器并处理MCP SSE连接
import express from 'express'; import { SseServerTransport } from '@modelcontextprotocol/sdk/server/sse.js'; const app = express(); const PORT = process.env.PORT || 3000; // 为 /sse 端点提供SSE连接 app.get('/sse', async (req, res) => { const transport = new SseServerTransport('/message', res); await server.connect(transport); // 连接会一直保持,直到客户端断开 }); // 用于接收客户端消息的端点 app.post('/message', express.text(), (req, res) => { // 这里需要将接收到的消息转发给 transport // 具体实现依赖于SDK的SseServerTransport如何与Express集成 // 通常SDK会提供相应的适配器或示例 res.sendStatus(200); }); app.listen(PORT, () => { console.error(`MCP SSE 服务器运行在 http://localhost:${PORT}`); });

实现完整的SSE服务器需要仔细阅读SDK中关于SseServerTransport的文档和示例,因为它涉及双向的事件流通信。核心思想是:/sse端点建立单向的服务器到客户端事件流,而/message端点用于接收客户端发来的请求。

6.3 错误处理、日志与监控强化

一个健壮的生产服务器必须有完善的错误处理。

  • 全局错误捕获:在tools/call处理函数外层使用try...catch,确保任何工具抛出的错误都能被捕获,并以MCP协议规定的错误格式返回给客户端,而不是导致整个服务器崩溃。
  • 结构化日志:使用winstonpino等日志库替代console.error,将日志按级别(info, warn, error)输出到文件或日志服务,方便排查问题。
  • 输入验证:对工具参数进行更严格的验证,比如title的长度限制、id的格式校验等。
  • 性能监控:可以考虑为每个工具调用添加简单的耗时统计,记录到日志中,用于性能分析。

6.4 扩展更多MCP能力:资源与提示词

在工具之外,你可以探索MCP的另外两大能力。

  • 暴露资源:例如,你可以将“本周已完成事项”作为一个资源todo://resources/completed_this_week暴露出来。当模型需要总结本周工作时,它可以直接“读取”这个资源,而不是通过一系列工具调用来计算。
  • 提供提示词模板:你可以定义一个提示词summarize_todos,其模板是:“用户今天的待办事项情况如下:{{todos}}。请生成一份简短的工作汇报。” 当模型需要执行总结任务时,它可以请求这个提示词,并传入具体的todos数据,获得一个结构化的任务指令。

7. 常见问题排查与调试技巧实录

在实际开发中,你一定会遇到各种问题。下面是我在多次实践中总结的一些典型问题及其解决方法。

7.1 服务器启动失败或立即退出

症状:运行node index.jsmcp dev后,进程立刻结束,没有等待输入。排查

  1. 检查异步连接:确保你使用了await server.connect(transport)而不是server.connect(transport)。缺少await会导致连接过程还没完成,后续代码(可能没有了)执行完,进程就退出了。
  2. 检查未处理的Promise拒绝:在文件末尾添加以下代码,捕获可能被忽略的异步错误。
    process.on('unhandledRejection', (reason, promise) => { console.error('未处理的Promise拒绝:', reason); process.exit(1); });
  3. 使用调试模式:在启动命令前加NODE_DEBUG=mcp*环境变量,可以输出MCP SDK内部的详细日志。
    NODE_DEBUG=mcp* node index.js

7.2 客户端(如Claude Desktop)无法发现工具

症状:在Claude Desktop中配置了服务器,但对话时Claude表示没有可用工具。排查

  1. 验证服务器独立运行是否正常:首先在终端用mcp dev index.js测试,使用list_tools命令,确认服务器本身能正确返回工具列表。如果这里就失败,问题在服务器代码。
  2. 检查Claude Desktop配置:这是最常见的问题源。务必确认“命令”字段指向的Node和脚本路径绝对正确。一个验证方法是,把配置中的命令复制到终端里直接执行,看是否能启动一个持续运行的进程。
  3. 查看客户端日志:Claude Desktop通常有应用日志。在macOS上,日志可能在~/Library/Logs/Claude/或通过Console.app查看。在Windows上,可能在使用者目录的AppData相关路径下。日志中会有连接服务器失败或协议握手失败的具体原因。
  4. 检查协议版本兼容性:确保你使用的@modelcontextprotocol/sdk版本与Claude Desktop内置客户端支持的MCP协议版本兼容。通常使用最新的SDK版本问题不大。

7.3 工具调用失败,返回参数错误

症状:在REPL或Claude中调用工具,收到“Invalid params”或类似错误。排查

  1. 核对inputSchema:仔细检查工具定义中的inputSchemarequired字段是否包含了所有必填参数?properties中定义的参数类型(type)是否与处理函数中期望的一致?例如,定义是string,但代码里当成了number使用。
  2. 检查参数传递格式:在REPL中调用时,参数必须是一个JSON字符串call_tool create_todo '{"title": "test"}'是正确的,而call_tool create_todo "test"call_tool create_todo title=test是错误的。
  3. 在代码中添加详细日志:在tools/call处理函数的最开始,打印request.params,看看客户端实际发送过来的参数到底是什么。
    server.setRequestHandler('tools/call', async (request) => { console.error('[DEBUG] 调用参数:', JSON.stringify(request.params)); // ... 原有逻辑 });

7.4 性能问题与内存泄漏

症状:服务器运行一段时间后变慢或崩溃。排查

  1. 内存存储限制:如果一直使用内存数组,待办事项数量无限增长最终会导致内存耗尽。务必实现数据持久化或设置存储上限
  2. 避免阻塞操作:在工具处理函数中,如果执行了同步的、耗时的操作(如大文件读取、复杂的同步计算),会阻塞整个服务器,导致其他请求排队。确保所有I/O操作都是异步的(使用async/await)。
  3. 检查循环引用:如果你在工具处理函数中不小心引用了会导致循环引用的对象,并且没有正确释放,可能会引起内存泄漏。使用Node.js的--inspect标志启动服务器,利用Chrome DevTools的Memory面板进行快照对比分析。

7.5 安全考量

当你的MCP服务器开始暴露给网络时,安全就变得重要。

  1. 输入净化:永远不要相信客户端传入的参数。对所有的字符串参数进行净化,防止注入攻击(无论是SQL注入还是命令注入)。例如,即使参数用于文件名,也要严格限制字符集。
  2. 身份验证与授权:SSE服务器通常需要暴露在网络上。考虑为你的MCP服务器添加简单的API密钥认证。可以在HTTP请求头中检查一个预共享的密钥。
  3. 权限控制:不是所有工具都应该被所有调用者使用。如果你的服务器有delete_database这样的危险工具,考虑实现基于调用来源的权限控制(但这通常需要客户端支持传递身份信息,目前MCP协议标准对此的支持还在演进中)。
  4. 速率限制:为你的服务器接口添加速率限制,防止恶意调用或意外循环调用导致服务过载。

构建MCP服务器的过程,本质上是在为AI模型设计一套可编程的“手”和“眼”。从最初的概念理解,到第一个工具的成功调用,再到最终部署为一个健壮的远程服务,每一步都加深了你对AI应用架构的理解。这个“待办事项管理器”的示例虽然简单,但它所蕴含的协议交互模式、工具设计理念和系统架构思想,是构建任何复杂AI赋能应用的基础。当你掌握了这些,你就可以尝试将MCP连接到你的数据库、内部API、云服务甚至物联网设备,真正释放出AI模型与真实世界交互的潜力。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询