从零构建MCP Server:用TypeScript为AI助手扩展数据与工具能力
2026/9/2 8:49:38 网站建设 项目流程

在实际开发中,我们经常需要让 AI 助手(如 Cursor、Claude Desktop 等)能够访问和处理项目内部或外部的特定数据源,比如数据库、API 或本地文件。手动复制粘贴数据不仅低效,而且难以保证上下文的一致性和准确性。Model Context Protocol(MCP)正是为了解决这一问题而设计的开放协议,它允许开发者构建标准的“服务器”(MCP Server),将任意数据源或工具的能力以结构化、安全的方式暴露给 AI 客户端。通过 MCP,AI 助手可以直接“调用”这些能力,就像调用一个函数一样,从而获得实时、准确的信息。

本文将以 TypeScript 为开发语言,从零开始,手把手教你构建一个功能完整的 MCP Server。我们将遵循 Matt Pocock 在其教程中强调的工程化实践,通过 5 条核心的 Prompt 来驱动整个开发流程,从项目初始化、协议理解、工具定义、资源暴露到最终的集成与测试。无论你是想为团队内部工具链增加 AI 能力,还是希望探索 AI 代理(Agent)的更多可能性,构建一个 MCP Server 都是极具价值的实践。通过本文,你将掌握 MCP 的核心概念、TypeScript 开发 MCP Server 的完整流程,并能够将其集成到 Cursor 等 IDE 中,实现 AI 助手与你的专属数据或服务的无缝交互。

1. 理解 MCP 协议:AI 与工具之间的“通用插座”

在开始编码之前,我们必须先理解 MCP 要解决的根本问题以及它的工作模型。这决定了我们后续所有代码的结构和设计。

1.1 MCP 是什么?为什么需要它?

想象一下,你的电脑有各种外设:键盘、鼠标、打印机。它们通过 USB、蓝牙等标准接口与电脑通信。如果没有这些标准,每个外设都需要专用的、复杂的驱动才能工作。MCP 就是 AI 世界里的“USB 协议”。它定义了一套标准,让任何数据源(如数据库、文件系统、API)或工具(如代码执行器、搜索引擎)都能以统一的方式被 AI 模型“插拔”和使用。

在没有 MCP 之前,如果你想在 Cursor 里让 AI 查询公司内部的用户数据,可能需要:

  1. 手动编写一个复杂的插件,处理与 Cursor 的特定 API 集成。
  2. 在 Prompt 里粘贴大量 JSON Schema 来描述你的 API。
  3. 面临安全、权限控制和上下文管理的难题。

MCP 通过标准化解决了这些问题:

  • 标准化通信:基于 JSON-RPC 协议,服务器和客户端通过标准消息格式对话。
  • 能力声明:服务器启动时主动向客户端声明“我能提供什么工具(Tools)和资源(Resources)”。
  • 结构化数据:所有输入输出都是结构化的 JSON,便于 AI 理解和处理。
  • 安全边界:工具执行在独立的服务器进程中,与 AI 模型本身隔离,权限可控。

1.2 MCP 的核心组件:Server, Client, Tools & Resources

一个典型的 MCP 生态系统包含以下角色:

  1. MCP Server(我们将要构建的):一个独立的进程,它封装了对特定数据源或工具的操作。它向客户端宣告自己具备的能力。
  2. MCP Client(如 Cursor, Claude Desktop):集成在应用中的组件,负责与一个或多个 MCP Server 通信,并将 Server 提供的能力暴露给内部的 AI 模型。
  3. Tools(工具):这是 Server 提供的核心能力之一。一个 Tool 就像一个函数,AI 可以调用它并传递参数。例如,一个query_database工具,AI 调用时传入 SQL 语句,Server 执行并返回结果。
  4. Resources(资源):这是 Server 提供的另一种能力。Resource 代表一个可读的、内容可能变化的数据单元,比如一个配置文件、一个 API 的实时状态页面。AI 可以“读取”这些资源来获取信息。资源通过 URI 标识。

它们之间的关系如下图所示(概念性描述):

[AI 模型在 Cursor 中] | v [Cursor 内置的 MCP Client] | (通过 stdio 或 SSE 通信) v [我们编写的 MCP Server] -> [连接至真实数据源:数据库/API/文件]

我们的任务就是编写右下角的那个 MCP Server。

1.3 开发前必须明确的技术栈和约束

  • 协议版本:我们使用目前主流且稳定的MCP 协议。其核心通信基于 JSON-RPC。
  • 开发语言TypeScript。这是构建 MCP Server 最活跃的生态之一,有官方和社区的良好支持。
  • 核心 SDK:我们将使用@modelcontextprotocol/sdk这个官方包,它封装了协议细节,让我们可以专注于业务逻辑。
  • 运行时:Node.js(建议版本 18+)。
  • 通信方式:主要支持stdio(标准输入输出),这是与 Cursor 等客户端集成最简单的方式。也支持 SSE(Server-Sent Events)用于 HTTP 场景。
  • 项目初始化:使用npmyarn管理依赖,用tsctsup进行构建。

理解了这些,我们就知道要构建的是一个运行在 Node.js 上、使用 TypeScript 编写、通过@modelcontextprotocol/sdk与客户端通信的独立进程。

2. 环境准备与项目初始化

现在,我们开始动手搭建开发环境并创建项目骨架。这是保证后续开发顺畅的基础。

2.1 安装 Node.js 与包管理器

首先确保你的系统已安装 Node.js。打开终端,运行以下命令检查:

node --version npm --version # 或 yarn --version

建议使用 Node.js 18 或更高版本。如果未安装,请前往 Node.js 官网 下载 LTS 版本进行安装。

2.2 创建项目并安装核心依赖

我们创建一个全新的目录来开始我们的项目。

# 1. 创建项目目录并进入 mkdir my-first-mcp-server cd my-first-mcp-server # 2. 初始化 package.json npm init -y # 3. 安装 TypeScript 和 Node.js 类型定义(开发依赖) npm install -D typescript @types/node # 4. 安装 MCP SDK(生产依赖) npm install @modelcontextprotocol/sdk # 5. 初始化 TypeScript 配置 npx tsc --init

安装完成后,你的package.jsondependenciesdevDependencies应该类似这样:

{ "name": "my-first-mcp-server", "version": "1.0.0", "description": "", "main": "dist/index.js", "scripts": { "build": "tsc", "start": "node dist/index.js" }, "devDependencies": { "@types/node": "^20.0.0", "typescript": "^5.0.0" }, "dependencies": { "@modelcontextprotocol/sdk": "^1.0.0" } }

2.3 配置 TypeScript 和项目结构

默认的tsconfig.json配置可能不适合我们,我们需要调整它以输出 CommonJS 模块到dist目录,并包含必要的 ES 特性。

打开tsconfig.json,修改或确保包含以下关键配置:

{ "compilerOptions": { "target": "ES2022", "module": "CommonJS", "lib": ["ES2022"], "outDir": "./dist", "rootDir": "./src", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, "declaration": true, "declarationMap": true }, "include": ["src/**/*"], "exclude": ["node_modules", "dist"] }

然后,创建项目源代码目录和入口文件:

mkdir src touch src/index.ts

现在,你的项目结构应该如下所示:

my-first-mcp-server/ ├── node_modules/ ├── src/ │ └── index.ts # 主入口文件 ├── package.json ├── package-lock.json ├── tsconfig.json └── .gitignore # 建议创建,忽略 node_modules 和 dist

2.4 验证基础环境

src/index.ts中写入最简单的代码,验证环境是否正常。

// src/index.ts console.log('MCP Server 环境检查正常!');

然后编译并运行:

npx tsc node dist/index.js

如果终端成功输出MCP Server 环境检查正常!,说明 TypeScript 编译和 Node.js 运行环境都已就绪。

3. 构建第一个 MCP Server:实现工具(Tools)

我们将从一个最简单的 Server 开始,它只提供一个工具。这是理解 MCP SDK 工作流的最佳起点。

3.1 理解 Server 生命周期与 SDK 使用模式

使用@modelcontextprotocol/sdk构建 Server 的核心步骤如下:

  1. 导入并创建 Server 实例:传入 Server 的元信息(名称、版本)。
  2. 定义并注册能力:使用server.setRequestHandler()来处理客户端关于tools/list(列出工具)和tools/call(调用工具)的请求。
  3. 启动 Server:调用server.connect()并指定传输方式(如 stdio)。
  4. 处理客户端请求:在工具调用处理器中,执行实际业务逻辑并返回结果。

3.2 实现一个 “echo” 工具

让我们实现一个最简单的工具,它接收一个字符串并原样返回,同时附上时间戳。这能帮助我们快速验证整个链路是否通畅。

src/index.ts的内容替换为以下代码:

// src/index.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'; // 1. 创建 Server 实例 const server = new Server( { name: 'my-first-mcp-server', version: '1.0.0', }, { capabilities: { tools: {}, // 声明本 Server 支持提供 tools }, } ); // 2. 处理客户端请求:列出所有可用工具 server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: 'echo', description: '一个简单的回声工具,返回输入的内容和当前时间戳。', inputSchema: { type: 'object', properties: { message: { type: 'string', description: '需要回声的消息内容', }, }, required: ['message'], }, }, ], }; }); // 3. 处理客户端请求:调用特定工具 server.setRequestHandler(CallToolRequestSchema, async (request) => { // 根据工具名分发处理逻辑 if (request.params.name === 'echo') { const message = request.params.arguments?.message as string; const timestamp = new Date().toISOString(); // 这里是工具的核心逻辑 const result = `回声:${message}\n时间:${timestamp}`; // 返回结构化结果给客户端 return { content: [ { type: 'text', text: result, }, ], }; } // 如果请求的工具名未找到,抛出错误 throw new Error(`未知的工具: ${request.params.name}`); }); // 4. 启动 Server,使用 stdio 传输方式 async function main() { const transport = new StdioServerTransport(); await server.connect(transport); console.error('MCP Server 已启动并等待连接 (stdio)...'); } main().catch((error) => { console.error('Server 启动失败:', error); process.exit(1); });

关键代码解释:

  • Server类:MCP Server 的主类,需要传入服务器信息和能力声明。
  • StdioServerTransport:这是与 Cursor 等客户端集成最常用的传输方式,通过标准输入输出进行通信。
  • ListToolsRequestSchema:当客户端查询服务器有哪些工具时,会发送此请求。我们的 handler 返回一个工具列表,每个工具都需要定义name,descriptioninputSchema(输入参数的 JSON Schema)。
  • CallToolRequestSchema:当客户端调用某个工具时,会发送此请求。我们的 handler 需要根据request.params.name识别是哪个工具,从request.params.arguments中获取参数,执行逻辑,并返回指定格式的结果。结果必须包裹在content数组中,通常我们返回type: 'text'的文本内容。

3.3 编译与独立运行测试

在集成到 Cursor 之前,我们可以先编译并直接运行这个 Server,观察其输出。它会在启动后等待来自 stdio 的输入。

# 编译 TypeScript npm run build # 直接运行 Server,它会挂起等待连接 node dist/index.js

此时,程序会输出MCP Server 已启动并等待连接 (stdio)...到标准错误输出(stderr),然后等待。你可以按Ctrl+C终止它。目前我们无法手动测试工具调用,因为需要一个 MCP 客户端来驱动。下一步我们将把它集成到 Cursor 中进行真实测试。

4. 集成到 Cursor IDE 并进行测试

Cursor 内置了 MCP Client 支持,可以方便地加载本地开发的 MCP Server。这是验证我们 Server 是否工作的关键一步。

4.1 配置 Cursor 以加载本地 MCP Server

Cursor 通过一个全局配置文件来管理 MCP Server。配置文件的位置通常如下:

  • macOS/Linux:~/.cursor/mcp.json
  • Windows:%USERPROFILE%\.cursor\mcp.json

如果文件不存在,请创建它。我们将把刚刚构建的 Server 添加到配置中。

编辑mcp.json文件,内容如下:

{ "mcpServers": { "my-first-server": { "command": "node", "args": [ "/ABSOLUTE/PATH/TO/your-project/dist/index.js" ], "env": {} } } }

重要提示:

  • my-first-server是你给这个 Server 起的名字,可以任意修改。
  • args数组中的路径必须替换为你本地项目dist/index.js的绝对路径。例如,在 macOS 上可能是/Users/yourname/Projects/my-first-mcp-server/dist/index.js
  • 确保你已运行过npm run builddist/index.js文件确实存在。

4.2 在 Cursor 中验证 Server 加载

  1. 重启 Cursor:修改配置文件后,需要完全关闭并重新打开 Cursor 以使配置生效。
  2. 打开 Cursor 设置:在 Cursor 中,进入Settings->Features->MCP Servers。你应该能看到你配置的my-first-server显示为已配置。
  3. 检查日志:打开一个项目或文件,在 Cursor 的底部状态栏或输出面板中,可能会看到 MCP 相关的日志,表明 Server 正在被加载和连接。如果 Server 启动失败(例如路径错误),这里也会显示错误信息。
  4. 验证工具可用性:最直接的验证方式是使用 Cursor 的 Chat 功能。在 Chat 输入框中,尝试输入:“你能使用 echo 工具吗?” 或者 “Call the echo tool with message ‘Hello MCP’”。如果配置成功,Cursor 的 AI(通常是 Claude)应该能识别出echo工具,并展示一个调用按钮或直接返回结果。

4.3 通过 Prompt 驱动开发与测试

这就是 Matt Pocock 教程中强调的“Prompt 驱动开发”的精髓。我们不需要手动编写复杂的测试脚本,而是通过自然语言指令让 AI 来测试我们的 Server。

你可以尝试在 Cursor Chat 中输入以下 Prompt 序列来测试:

Prompt 1 (列出工具):

你现在可以使用哪些 MCP 工具?

预期响应:AI 应该会列出echo工具及其描述。

Prompt 2 (调用工具):

使用 echo 工具,发送消息 “测试一下 MCP 连接”。

预期响应:AI 会调用echo工具,并返回类似“回声:测试一下 MCP 连接\n时间:2024-01-01T12:00:00.000Z”的结果。

如果 AI 回复“我不知道如何使用这个工具”或没有反应,请检查:

  1. Cursor 是否已重启。
  2. mcp.json配置文件路径是否正确。
  3. 终端中运行node dist/index.js是否报错(可以单独运行查看)。
  4. Cursor 的 MCP 设置界面是否有错误提示。

4.4 调试技巧:查看 Server 日志

我们的 Server 将日志输出到stderr。在 Cursor 中,这些日志可能不会直接显示。为了调试,一个有效的方法是临时修改 Server 代码,将传输方式改为简单的标准输入输出测试,或者在 Cursor 之外手动模拟客户端进行测试

我们可以创建一个简单的测试脚本test-client.js

// test-client.js - 这是一个非常简化的模拟测试 const { spawn } = require('child_process'); const serverProcess = spawn('node', ['dist/index.js']); serverProcess.stderr.on('data', (data) => { console.error('[Server STDERR]:', data.toString()); }); // 模拟一个简单的 JSON-RPC 请求 (列出工具) const listToolsRequest = { jsonrpc: '2.0', id: 1, method: 'tools/list', params: {} }; serverProcess.stdin.write(JSON.stringify(listToolsRequest) + '\n'); serverProcess.stdin.end(); serverProcess.stdout.on('data', (data) => { console.log('[Server STDOUT]:', data.toString()); }); serverProcess.on('close', (code) => { console.log(`子进程退出,退出码 ${code}`); });

运行node test-client.js可以看到 Server 的原始输入输出,帮助诊断协议层面的问题。但在大多数情况下,通过 Cursor 的 Chat 进行功能测试已经足够。

5. 扩展 Server:添加资源(Resources)与复杂工具

一个只会回声的 Server 实用价值有限。现在,我们来扩展它,添加更实用的“资源”和更复杂的“工具”,构建一个模拟的“项目信息查询服务器”。

5.1 设计 Server 能力:项目信息查询

假设我们想构建一个 Server,让 AI 能:

  1. 读取资源:获取一个固定的项目简介文档。
  2. 使用工具
    • get_file_info:根据文件名,获取该文件的模拟信息(如大小、类型)。
    • search_code:在模拟的代码库中搜索包含特定关键词的文件。

5.2 实现资源(Resources)提供者

资源是只读的、内容可能变化的 URI。我们需要处理resources/list(列出资源)和resources/read(读取资源)请求。

更新src/index.ts,在创建 Server 时声明支持资源能力,并添加相应的请求处理器:

// 在文件顶部添加新的导入 import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, // 新增 ReadResourceRequestSchema, // 新增 } from '@modelcontextprotocol/sdk/types.js'; // 更新 Server 的能力声明 const server = new Server( { name: 'project-info-mcp-server', version: '1.0.0', }, { capabilities: { tools: {}, resources: {}, // 新增:声明支持 resources }, } ); // ... (之前设置的 tools/list 和 tools/call 处理器保持不变) ... // 5. 处理客户端请求:列出所有可用资源 server.setRequestHandler(ListResourcesRequestSchema, async () => { return { resources: [ { uri: 'project://overview', name: '项目概览', description: '获取本项目的基本介绍信息。', mimeType: 'text/plain', }, ], }; }); // 6. 处理客户端请求:读取特定资源 server.setRequestHandler(ReadResourceRequestSchema, async (request) => { const uri = request.params.uri; if (uri === 'project://overview') { // 这里可以是从文件、数据库或API动态读取的内容 const overviewText = `项目名称:示例 MCP 服务器项目 项目描述:这是一个演示 Model Context Protocol (MCP) 服务器功能的示例项目。 主要能力: 1. 提供 echo 工具用于测试。 2. 提供项目概览资源。 3. 提供文件信息查询和代码搜索工具。 技术栈:TypeScript, Node.js, @modelcontextprotocol/sdk 状态:开发中 最后更新:${new Date().toLocaleDateString()}`; return { contents: [ { uri: uri, mimeType: 'text/plain', text: overviewText, }, ], }; } throw new Error(`未找到资源: ${uri}`); });

5.3 实现更复杂的工具

现在,在tools/call的处理器中,添加对新工具的支持。我们更新之前的CallToolRequestSchema处理器:

// 替换或更新之前的 server.setRequestHandler(CallToolRequestSchema, ...) server.setRequestHandler(CallToolRequestSchema, async (request) => { const toolName = request.params.name; const args = request.params.arguments || {}; if (toolName === 'echo') { const message = args.message as string; const timestamp = new Date().toISOString(); const result = `回声:${message}\n时间:${timestamp}`; return { content: [ { type: 'text', text: result, }, ], }; } // 新增工具:get_file_info if (toolName === 'get_file_info') { const filename = args.filename as string; if (!filename) { throw new Error('参数 "filename" 是必需的。'); } // 模拟查询文件信息 const mockFileDatabase: Record<string, { size: string; type: string }> = { 'index.ts': { size: '2.1 KB', type: 'TypeScript 源文件' }, 'package.json': { size: '0.5 KB', type: '项目配置文件' }, 'README.md': { size: '1.0 KB', type: 'Markdown 文档' }, }; const info = mockFileDatabase[filename]; if (!info) { return { content: [ { type: 'text', text: `未找到文件 "${filename}" 的信息。`, }, ], }; } return { content: [ { type: 'text', text: `文件:${filename}\n大小:${info.size}\n类型:${info.type}`, }, ], }; } // 新增工具:search_code if (toolName === 'search_code') { const keyword = args.keyword as string; if (!keyword) { throw new Error('参数 "keyword" 是必需的。'); } // 模拟代码搜索 const mockCodeFiles = [ { name: 'src/index.ts', content: 'import { Server } from "@modelcontextprotocol/sdk";' }, { name: 'src/utils.ts', content: 'export function log(message: string): void { console.log(message); }' }, { name: 'package.json', content: `"dependencies": { "@modelcontextprotocol/sdk": "^1.0.0" }` }, ]; const results = mockCodeFiles.filter(file => file.content.toLowerCase().includes(keyword.toLowerCase()) ); if (results.length === 0) { return { content: [ { type: 'text', text: `未找到包含关键词 "${keyword}" 的代码文件。`, }, ], }; } const resultText = results.map(r => `- ${r.name}`).join('\n'); return { content: [ { type: 'text', text: `找到 ${results.length} 个包含 "${keyword}" 的文件:\n${resultText}`, }, ], }; } throw new Error(`未知的工具: ${toolName}`); });

别忘了更新tools/list处理器,将新工具声明给客户端:

server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: 'echo', description: '一个简单的回声工具,返回输入的内容和当前时间戳。', inputSchema: { type: 'object', properties: { message: { type: 'string', description: '需要回声的消息内容', }, }, required: ['message'], }, }, { name: 'get_file_info', description: '根据文件名获取模拟的文件信息(大小、类型)。', inputSchema: { type: 'object', properties: { filename: { type: 'string', description: '需要查询的文件名(例如:index.ts)', }, }, required: ['filename'], }, }, { name: 'search_code', description: '在模拟的代码库中搜索包含特定关键词的文件。', inputSchema: { type: 'object', properties: { keyword: { type: 'string', description: '需要搜索的代码关键词', }, }, required: ['keyword'], }, }, ], }; });

5.4 重新编译、配置与测试

  1. 编译:运行npm run build
  2. 更新 Cursor 配置(如果需要):如果 Server 名称或路径变了,需要更新~/.cursor/mcp.json。这里我们只是更新了代码,路径没变,所以无需修改。
  3. 重启 Cursor:完全关闭再打开 Cursor,或在其设置中尝试重新加载 MCP 配置。
  4. 进行综合测试:在 Cursor Chat 中尝试以下 Prompt:

Prompt 3 (读取资源):

读取一下项目概览资源project://overview

Prompt 4 (使用新工具查询文件):

使用 get_file_info 工具,查一下 index.ts 文件的信息。

Prompt 5 (使用新工具搜索代码):

使用 search_code 工具,搜索包含 “dependencies” 关键词的文件。

如果一切正常,AI 应该能成功调用这些工具和资源,并返回我们预设的模拟数据。这证明我们的 MCP Server 已经具备了提供多种结构化能力的功能。

6. 生产环境考量与最佳实践

到目前为止,我们构建了一个用于学习和测试的 MCP Server。但要将其用于实际生产或团队共享,还需要考虑更多因素。

6.1 安全性:权限与输入验证

我们的示例 Server 非常简单,但真实的 Server 可能连接数据库、调用内部 API 或执行系统命令。安全至关重要。

  • 输入验证与净化:永远不要信任客户端传入的参数。即使有 JSON Schema 约束,也要在业务逻辑中再次验证。
    // 不好的做法:直接拼接 const query = `SELECT * FROM users WHERE name = '${args.name}'`; // 好的做法:使用参数化查询或严格验证 if (typeof args.name !== 'string' || args.name.length > 100) { throw new Error('无效的用户名参数'); } // 然后使用参数化查询库
  • 权限控制:MCP Server 进程本身运行在某个用户权限下。确保该用户只有执行必要操作的最小权限。不要在 Server 中以 root 或高级别权限运行。
  • 敏感信息:数据库密码、API 密钥等不应硬编码在代码中。使用环境变量或安全的配置管理服务。
    # 启动时传入环境变量 MCP_DB_PASSWORD=secret123 node dist/index.js
    // 在代码中读取 const dbPassword = process.env.MCP_DB_PASSWORD; if (!dbPassword) { throw new Error('数据库密码未配置'); }

6.2 错误处理与日志

健壮的 Server 需要清晰的错误处理和日志记录,方便排查问题。

  • 结构化错误返回:在tools/call处理器中,除了throw new Error,更友好的做法是返回结构化的错误信息。
    try { // ... 业务逻辑 ... } catch (error: any) { console.error(`[工具 ${toolName} 执行失败]`, error); return { content: [{ type: 'text', text: `执行工具时发生错误:${error.message}` }], isError: true, // MCP 协议中表示这是一个错误响应 }; }
  • 分级日志:使用console.error记录错误,console.warn记录警告,console.logconsole.info记录一般信息。考虑使用winstonpino等日志库,以便输出到文件或日志系统。

6.3 性能与资源管理

  • 避免阻塞:工具的执行应该是相对快速的。如果需要执行长时间运行的任务(如处理大文件、复杂计算),应考虑异步处理并可能通过其他机制(如轮询另一个资源)返回结果,避免阻塞 MCP 的主通信线程。
  • 连接池与缓存:如果工具需要连接数据库或外部 API,使用连接池和适当的缓存机制来提升性能并减少负载。
  • 单例与状态:MCP Server 通常是单例的,会在客户端会话期间持续运行。谨慎管理全局状态,避免内存泄漏。

6.4 配置化与可扩展性

  • 外部化配置:将工具列表、资源定义、模拟数据等抽取到配置文件(如config.jsonconfig.yaml)中,使 Server 更容易适配不同环境。
  • 插件化架构:对于大型项目,可以考虑将不同功能的工具和资源封装成独立的插件模块,通过动态加载来扩展 Server 能力。

6.5 部署与分发

  • 打包为可执行文件:使用pkgnexe将 Node.js 项目打包成单个可执行文件,简化部署,无需目标机器安装 Node.js。
    npx pkg . --targets node18-linux-x64,node18-macos-x64,node18-win-x64 -o dist/mcp-server
  • 发布到 NPM:如果你构建的是通用性较强的 MCP Server(例如,连接某种特定数据库),可以将其发布为 NPM 包,方便他人通过npx安装运行。
  • 容器化:使用 Docker 构建镜像,确保运行环境一致。
    FROM node:18-alpine WORKDIR /app COPY package*.json ./ RUN npm ci --only=production COPY dist/ ./dist/ CMD ["node", "dist/index.js"]

7. 常见问题排查清单

在开发和集成 MCP Server 时,你可能会遇到以下问题。请按此清单顺序排查。

问题现象可能原因检查点与解决方案
Cursor 中完全看不到 MCP 工具1. MCP 配置未加载或路径错误。
2. Server 启动失败。
3. Cursor 版本过旧不支持 MCP。
1. 检查~/.cursor/mcp.json路径和内容,确保 JSON 格式正确。
2. 在终端直接运行node /your/absolute/path/dist/index.js,看是否有报错。
3. 确保 Cursor 已更新到最新版本。重启 Cursor。
AI 无法识别或调用特定工具1. 工具未在tools/list中正确声明。
2. 工具名拼写错误。
3. 输入参数 Schema 不匹配。
1. 检查 Server 代码中ListToolsRequestSchema处理器返回的tools数组是否包含该工具。
2. 确保CallToolRequestSchema处理器中判断的工具名与声明的一致。
3. 使用 Prompt 让 AI 列出所有可用工具进行确认。
调用工具后返回错误或超时1. 工具处理器代码有 bug 抛出异常。
2. 工具执行时间过长。
3. 传输过程中出现错误。
1. 查看 Server 进程的 stderr 输出(如果直接运行),或查看 Cursor 的 MCP 日志。
2. 在工具代码中添加 try-catch,返回更友好的错误信息。
3. 简化工具逻辑,确保快速返回。
修改 Server 代码后,Cursor 中无变化1. Cursor 缓存了旧的 Server 实例。
2. 未重新编译 TypeScript。
3. 配置文件指向了错误的路径。
1. 完全关闭 Cursor 并重新打开。
2. 运行npm run build重新编译。
3. 确认mcp.json中的路径指向最新的dist/index.js
协议错误或通信失败1. Server 未按 JSON-RPC 协议格式返回数据。
2. 传输层(stdio)被干扰。
1. 使用test-client.js这类简单脚本测试原始协议通信。
2. 确保 Server 代码没有向 stdout 输出无关的调试信息(如console.log),这会被坏 JSON-RPC 消息。所有日志应输出到stderr(console.error)。
资源读取返回 “未找到资源”1. 资源 URI 拼写错误。
2.resources/list未声明该资源。
3.resources/read处理器逻辑错误。
1. 检查 AI 请求的 URI 是否与ListResourcesRequestSchema处理器中声明的一致。
2. 在ReadResourceRequestSchema处理器中添加详细的日志,打印收到的 URI。

遵循从零搭建一个 MCP Server 的完整路径,核心在于理解协议角色、善用官方 SDK、并通过 Prompt 在真实的 AI 客户端(如 Cursor)中进行迭代测试。将你的本地数据、内部 API 或复杂操作封装成标准的 Tools 和 Resources,就能极大地扩展 AI 助手在你日常工作流中的能力边界。下一步,你可以尝试连接真实的数据库(如通过pg库连接 PostgreSQL)、调用第三方 Web API、或者与本地文件系统深度交互,构建出真正赋能生产的 AI 增强型工具链。

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

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

立即咨询