最近在尝试将 AI 助手(如 Claude、Cursor)深度集成到我的本地开发工作流中时,遇到了一个核心痛点:AI 助手无法直接访问我本地的项目文件、数据库或内部 API,导致很多自动化想法无法落地。直到我接触到了MCP(Model Context Protocol),这个由 Anthropic 提出的开放协议,它像一座桥梁,让 AI 助手能够安全、可控地“看见”并使用外部工具和数据源。
本文将带你从零开始,跟随 TypeScript 专家 Matt Pocock 的实战思路,仅用 5 条核心 Prompt,一步步构建一个属于自己的 MCP Server。无论你是想集成公司内部系统,还是想为个人项目打造专属 AI 工具链,这篇教程都将提供完整的代码、配置和避坑指南。学完后,你将掌握 MCP Server 的核心原理与构建方法,并能将其应用于实际开发场景。
1. MCP 核心概念:为什么它是 AI 集成的未来?
在深入代码之前,我们必须理解 MCP 解决了什么问题。简单来说,MCP 是一个标准化的通信协议,它定义了 AI 应用(如 Claude Desktop、Cursor)如何发现、调用外部服务器(MCP Server)提供的工具(Tools)和资源(Resources)。
传统 AI 助手的局限:通常,AI 模型的知识截止于其训练数据,无法实时获取外部信息(如最新的股票价格、你本地的待办事项列表),也无法执行具体操作(如创建文件、查询数据库)。虽然可以通过长篇的上下文传递文件内容,但这不仅低效,而且有 token 限制和安全风险。
MCP 带来的变革:
- 标准化接口:MCP 为“工具调用”提供了统一协议。开发者只需按照协议实现一个 Server,任何兼容 MCP 的 AI 客户端都能立即使用其提供的功能。
- 安全与可控:AI 客户端(如 Claude Desktop)在首次连接时会明确告知用户正在加载哪些 MCP Server 及其提供的工具,用户拥有完全的知情权和选择权。Server 运行在用户指定的环境(通常是本地),数据不必上传到云端。
- 能力扩展:通过 MCP Server,AI 助手可以:
- 读取资源:获取本地文件系统、数据库、内部 API 的数据作为上下文。
- 调用工具:执行创建文件、运行脚本、发送 HTTP 请求等具体操作。
核心组件关系图:
[AI 客户端 (Claude Desktop, Cursor)] | | 通过 MCP 协议通信 (stdin/stdout 或 SSE) | [MCP Server (你将要构建的)] | | 调用本地能力 | [本地文件系统 / 数据库 / 内部 API / 其他服务]理解了 MCP 的价值,我们接下来就准备构建环境,亲手搭建这座“桥梁”。
2. 环境准备:构建 TypeScript MCP Server 的基石
我们将使用 TypeScript 来构建 MCP Server,这是目前最主流和高效的方式。Matt Pocock 的教程也基于此。请确保你的开发环境满足以下要求。
2.1 基础环境要求
- 操作系统:macOS, Linux, 或 Windows (WSL 2 推荐)。
- Node.js:版本 18 或更高。这是运行 TypeScript 和 MCP 库的基础。可以通过
node --version检查。 - 包管理器:npm 或 yarn 或 pnpm。本文使用
npm进行演示。 - 代码编辑器:强烈推荐使用Cursor或 VS Code。Cursor 本身是优秀的 AI 编程 IDE,并且原生支持 MCP,方便我们后续测试。
2.2 初始化项目与安装核心依赖
首先,创建一个新的项目目录并初始化。
# 创建项目文件夹并进入 mkdir my-first-mcp-server cd my-first-mcp-server # 初始化 npm 项目,生成 package.json npm init -y # 初始化 TypeScript 配置 npx tsc --init接下来,安装构建 MCP Server 所必需的依赖。
# 安装 MCP 核心 SDK 和 TypeScript 类型定义 npm install @modelcontextprotocol/sdk # 安装 TypeScript 编译器和 Node.js 类型(开发依赖) npm install --save-dev typescript @types/node # 安装一个用于测试的轻量级 HTTP 客户端(可选,用于后续示例) npm install axios2.3 配置 TypeScript
打开生成的tsconfig.json文件,进行如下修改,以适配现代 Node.js 模块和 MCP 开发。
{ "compilerOptions": { "target": "ES2022", "module": "NodeNext", "moduleResolution": "NodeNext", "outDir": "./dist", "rootDir": "./src", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, "declaration": true, "declarationMap": true }, "include": ["src/**/*"], "exclude": ["node_modules", "dist"] }2.4 创建项目基础结构
创建源代码目录和入口文件。
# 创建源代码目录 mkdir src # 创建主服务器文件 touch src/index.ts # 创建 package.json 中的启动脚本编辑package.json,在scripts部分添加构建和运行脚本。
{ "name": "my-first-mcp-server", "version": "1.0.0", "description": "A simple MCP server built with TypeScript", "main": "dist/index.js", "scripts": { "build": "tsc", "start": "node dist/index.js", "dev": "tsc --watch & nodemon dist/index.js" }, "dependencies": { "@modelcontextprotocol/sdk": "^1.0.0", "axios": "^1.6.0" }, "devDependencies": { "@types/node": "^20.0.0", "typescript": "^5.0.0", "nodemon": "^3.0.0" } }注意:nodemon需要额外安装 (npm install --save-dev nodemon),用于开发时监听文件变化自动重启。
环境搭建完毕,我们已经拥有了一个结构清晰、配置完善的 TypeScript 项目。接下来,我们将进入核心环节:运用 Prompt 驱动开发,一步步实现 Server 功能。
3. 核心 Prompt 驱动开发:5 步构建功能完整的 Server
这是本文的核心方法。我们将模仿与一位资深 AI 工程师(如 Matt Pocock)对话的场景,通过 5 条结构化的 Prompt,引导 AI(或我们自己)完成 MCP Server 的构建。你可以直接在 Cursor 或 Claude 中应用这些 Prompt。
3.1 Prompt 1:创建 MCP Server 骨架与标准输入输出处理
目标:初始化一个最基本的 MCP Server,它能够启动并处理来自客户端的连接。
Prompt 内容: “请帮我创建一个基于@modelcontextprotocol/sdk的 TypeScript MCP Server 骨架代码。代码应包含:
- 导入必要的
Server和StdioServerTransport类。 - 创建一个新的
Server实例,并为其指定一个名称(如my-tools-server)和版本。 - 设置
StdioServerTransport来处理标准输入/输出通信(这是 MCP 客户端最常见的连接方式)。 - 在 Server 实例上调用
connect()方法建立连接。 - 添加基本的错误处理,确保 Server 崩溃时能输出错误信息。
- 将完整代码写入
src/index.ts。”
预期代码实现: 根据上述 Prompt,我们可以在src/index.ts中编写如下代码:
// src/index.ts import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; // 1. 创建 Server 实例 const server = new Server( { name: 'my-tools-server', version: '1.0.0', }, { // 可选的 Server 能力声明,初始为空 capabilities: {} } ); // 2. 创建传输层(使用标准输入/输出) const transport = new StdioServerTransport(); // 3. 连接并启动 Server server.connect(transport).catch((error) => { console.error('[MCP Server] Failed to start:', error); process.exit(1); }); console.error('[MCP Server] Started and listening on stdio');解释:这段代码创建了一个最小化的 MCP Server。StdioServerTransport意味着 Server 通过命令行标准输入(stdin)接收请求,通过标准输出(stdout)发送响应,这是与 Claude Desktop 等客户端集成的最简单方式。console.error用于输出日志,因为stdout需要留给协议通信。
3.2 Prompt 2:实现第一个工具(Tool)—— 获取系统时间
目标:为 Server 添加一个具体的工具(Tool),让 AI 客户端可以调用它来获取当前服务器时间。
Prompt 内容: “现在,请为上面创建的 MCP Server 添加一个工具(Tool)。
- 这个工具的名称(
name)叫get_current_time。 - 描述(
description)为 ‘获取当前的系统日期和时间’。 - 它不需要任何输入参数(
inputSchema为空)。 - 在 Server 实例上注册这个工具。
- 实现这个工具的处理函数,当被调用时,返回一个包含当前 ISO 格式时间字符串的结果。
- 更新 Server 的
capabilities声明,包含tools能力。 - 展示更新后的完整
src/index.ts代码。”
预期代码实现:
// src/index.ts (更新后) import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; const server = new Server( { name: 'my-tools-server', version: '1.0.0', }, { // 声明 Server 具备提供工具的能力 capabilities: { tools: {} } } ); // 注册工具:获取当前时间 server.setRequestHandler( // 处理 `tools/call` 请求 'tools/call', async (request) => { // 根据工具名称执行不同逻辑 if (request.params.name === 'get_current_time') { return { content: [ { type: 'text', text: `当前系统时间是:${new Date().toISOString()}`, }, ], }; } // 如果收到未知工具请求,抛出错误 throw new Error(`未知的工具:${request.params.name}`); } ); // 注册工具列表:告知客户端本 Server 提供了哪些工具 server.setRequestHandler('tools/list', async () => { return { tools: [ { name: 'get_current_time', description: '获取当前的系统日期和时间', inputSchema: { type: 'object', properties: {}, // 无输入参数 }, }, ], }; }); const transport = new StdioServerTransport(); server.connect(transport).catch((error) => { console.error('[MCP Server] Failed to start:', error); process.exit(1); }); console.error('[MCP Server] Started with \'get_current_time\' tool');解释:我们做了两件关键事:1. 在capabilities中声明支持tools。2. 注册了两个请求处理器:tools/list用于返回工具清单;tools/call用于执行具体的工具逻辑。现在,这个 Server 已经具备了一个可调用的功能。
3.3 Prompt 3:实现带参数的工具 —— 计算 MD5 哈希
目标:创建一个更复杂的工具,它需要接收用户输入的参数(一个字符串),并返回其 MD5 哈希值。
Prompt 内容: “请为 MCP Server 添加第二个工具,用于计算字符串的 MD5 哈希值。
- 工具名称:
calculate_md5。 - 工具描述:‘计算给定字符串的 MD5 哈希值’。
- 输入参数:需要一个名为
text的字符串参数,描述为 ‘要计算哈希的文本’。 - 使用 Node.js 内置的
crypto模块实现 MD5 计算。 - 更新
tools/list处理器,将新工具添加到返回列表中。 - 更新
tools/call处理器,添加对calculate_md5的判断和逻辑。 - 展示整合后的代码。”
预期代码实现:
// src/index.ts (再次更新) import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import crypto from 'crypto'; // 导入 Node.js 加密模块 const server = new Server( { name: 'my-tools-server', version: '1.0.0', }, { capabilities: { tools: {} } } ); // 工具列表处理器 server.setRequestHandler('tools/list', async () => { return { tools: [ { name: 'get_current_time', description: '获取当前的系统日期和时间', inputSchema: { type: 'object', properties: {}, }, }, { name: 'calculate_md5', description: '计算给定字符串的 MD5 哈希值', inputSchema: { type: 'object', properties: { text: { type: 'string', description: '要计算哈希的文本', }, }, required: ['text'], // 标记 text 为必填参数 }, }, ], }; }); // 工具调用处理器 server.setRequestHandler( 'tools/call', async (request) => { const { name, arguments: args } = request.params; if (name === 'get_current_time') { return { content: [ { type: 'text', text: `当前系统时间是:${new Date().toISOString()}`, }, ], }; } if (name === 'calculate_md5') { // 1. 验证参数是否存在 if (!args || typeof args.text !== 'string') { throw new Error('必须提供有效的 `text` 字符串参数'); } // 2. 计算 MD5 const hash = crypto.createHash('md5').update(args.text).digest('hex'); return { content: [ { type: 'text', text: `文本 "${args.text.substring(0, 20)}${args.text.length > 20 ? '...' : ''}" 的 MD5 哈希值是:${hash}`, }, ], }; } throw new Error(`未知的工具:${name}`); } ); const transport = new StdioServerTransport(); server.connect(transport).catch((error) => { console.error('[MCP Server] Failed to start:', error); process.exit(1); }); console.error('[MCP Server] Started with time and MD5 tools');关键点:1. 在inputSchema中定义了参数的类型和必要性。2. 在tools/call处理器中,通过request.params.arguments获取客户端传递的参数。3. 加入了基本的参数校验。这演示了如何构建一个交互式工具。
3.4 Prompt 4:实现资源(Resource)读取 —— 列出目录文件
目标:让 Server 不仅能提供“工具”,还能提供“资源”。资源是 AI 客户端可以读取的数据源。我们将实现一个列出指定目录下文件的资源。
Prompt 内容: “现在,请为 MCP Server 添加资源(Resources)能力。
- 声明 Server 支持
resources能力。 - 实现
resources/list处理器,返回一个资源列表。我们提供一个资源:file:///home/user/documents(请根据你的系统调整为一个真实存在的目录路径),其 MIME 类型为application/json。 - 实现
resources/read处理器,当读取上述资源时,使用 Node.jsfs模块列出该目录下的所有文件和文件夹,并以 JSON 数组的形式返回。 - 注意处理路径不存在等错误情况。
- 展示更新后的完整代码,需包含
fs模块导入。”
预期代码实现:
// src/index.ts (添加资源能力) import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import crypto from 'crypto'; import fs from 'fs/promises'; // 使用 Promise-based FS API import path from 'path'; const server = new Server( { name: 'my-tools-server', version: '1.0.0', }, { capabilities: { tools: {}, resources: {} // 声明支持资源能力 } } ); // 假设我们提供一个展示用户文档目录的资源 const DOCS_DIRECTORY_URI = 'file:///Users/你的用户名/Documents'; // !!!请修改为你的真实路径 // 工具列表(保持不变) server.setRequestHandler('tools/list', async () => { return { tools: [ { name: 'get_current_time', description: '获取当前的系统日期和时间', inputSchema: { type: 'object', properties: {} }, }, { name: 'calculate_md5', description: '计算给定字符串的 MD5 哈希值', inputSchema: { type: 'object', properties: { text: { type: 'string', description: '要计算哈希的文本' }, }, required: ['text'], }, }, ], }; }); // 资源列表处理器 server.setRequestHandler('resources/list', async () => { return { resources: [ { uri: DOCS_DIRECTORY_URI, name: '我的文档目录列表', description: '列出指定文档目录下的文件和文件夹', mimeType: 'application/json', }, ], }; }); // 资源读取处理器 server.setRequestHandler('resources/read', async (request) => { const { uri } = request.params; if (uri === DOCS_DIRECTORY_URI) { try { // 将 file:// URI 转换为本地文件系统路径 const localPath = new URL(uri).pathname; const items = await fs.readdir(localPath, { withFileTypes: true }); const list = items.map((item) => ({ name: item.name, type: item.isDirectory() ? 'directory' : 'file', })); return { contents: [ { uri: uri, mimeType: 'application/json', text: JSON.stringify(list, null, 2), // 美化输出 JSON }, ], }; } catch (error: any) { // 处理错误,例如目录不存在 return { contents: [ { uri: uri, mimeType: 'text/plain', text: `无法读取资源 ${uri}: ${error.message}`, }, ], }; } } throw new Error(`未找到资源:${uri}`); }); // 工具调用处理器(保持不变) server.setRequestHandler( 'tools/call', async (request) => { const { name, arguments: args } = request.params; // ... (get_current_time 和 calculate_md5 的逻辑保持不变) // 为节省篇幅,此处省略重复代码,实际文件中需保留 if (name === 'get_current_time') { /* ... */ } if (name === 'calculate_md5') { /* ... */ } throw new Error(`未知的工具:${name}`); } ); const transport = new StdioServerTransport(); server.connect(transport).catch((error) => { console.error('[MCP Server] Failed to start:', error); process.exit(1); }); console.error('[MCP Server] Started with tools and resources support');重要提示:请务必将DOCS_DIRECTORY_URI的值替换为你电脑上真实存在的目录路径(例如file:///C:/Users/YourName/Documents或file:///home/yourname/Documents)。资源(Resource)机制使得 AI 客户端可以在需要时主动获取这些数据作为上下文,而无需用户手动上传文件。
3.5 Prompt 5:添加配置化与错误处理优化
目标:让 Server 更健壮、更专业。通过环境变量配置资源路径,并优化全局错误处理。
Prompt 内容: “最后,请优化我们的 MCP Server。
- 让资源目录的 URI 可通过环境变量
MCP_DOCS_PATH进行配置。如果未设置,则使用一个默认的回退路径。 - 在 Server 级别添加一个全局的未捕获错误处理器,使用
server.onerror来捕获并打印未被特定处理器捕获的错误,避免 Server 静默崩溃。 - 在
resources/read处理器中,增加对路径遍历攻击的简单防护(例如,检查请求的 URI 是否在我们允许的基准路径下)。 - 展示最终的、优化后的完整
src/index.ts代码。”
预期代码实现:
// src/index.ts (最终优化版) import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import crypto from 'crypto'; import fs from 'fs/promises'; import path from 'path'; const server = new Server( { name: 'my-tools-server', version: '1.0.0', }, { capabilities: { tools: {}, resources: {}, }, } ); // 1. 通过环境变量配置资源路径 const DEFAULT_DOCS_PATH = path.join(process.env.HOME || process.env.USERPROFILE || '', 'Documents'); const CONFIGURED_DOCS_PATH = process.env.MCP_DOCS_PATH || DEFAULT_DOCS_PATH; // 确保路径是绝对路径,并格式化为 file:// URI const RESOURCE_URI = `file://${path.resolve(CONFIGURED_DOCS_PATH)}`; console.error(`[MCP Server] 资源目录配置为: ${RESOURCE_URI}`); // 2. 全局错误处理 server.onerror = (error) => { console.error('[MCP Server Global Error]', error); }; // 工具列表 server.setRequestHandler('tools/list', async () => { return { tools: [ { name: 'get_current_time', description: '获取当前的系统日期和时间', inputSchema: { type: 'object', properties: {} }, }, { name: 'calculate_md5', description: '计算给定字符串的 MD5 哈希值', inputSchema: { type: 'object', properties: { text: { type: 'string', description: '要计算哈希的文本' }, }, required: ['text'], }, }, ], }; }); // 资源列表 server.setRequestHandler('resources/list', async () => { return { resources: [ { uri: RESOURCE_URI, name: '配置的文档目录列表', description: `列出目录 ${CONFIGURED_DOCS_PATH} 下的内容`, mimeType: 'application/json', }, ], }; }); // 资源读取(带安全检查) server.setRequestHandler('resources/read', async (request) => { const { uri } = request.params; if (uri === RESOURCE_URI) { try { const localPath = new URL(uri).pathname; // 3. 简单的路径安全检查:确保请求的路径在配置的目录下 const resolvedRequestPath = path.resolve(decodeURIComponent(localPath)); const resolvedBasePath = path.resolve(new URL(RESOURCE_URI).pathname); if (!resolvedRequestPath.startsWith(resolvedBasePath)) { throw new Error('访问路径越界'); } const items = await fs.readdir(resolvedRequestPath, { withFileTypes: true }); const list = items.map((item) => ({ name: item.name, type: item.isDirectory() ? 'directory' : 'file', // 可选:添加大小或修改时间 // size: item.isFile() ? (await fs.stat(path.join(resolvedRequestPath, item.name))).size : null, })); return { contents: [ { uri: uri, mimeType: 'application/json', text: JSON.stringify(list, null, 2), }, ], }; } catch (error: any) { return { contents: [ { uri: uri, mimeType: 'text/plain', text: `错误: ${error.message}`, }, ], }; } } throw new Error(`未找到资源:${uri}`); }); // 工具调用 server.setRequestHandler('tools/call', async (request) => { const { name, arguments: args } = request.params; if (name === 'get_current_time') { return { content: [ { type: 'text', text: `当前系统时间是:${new Date().toISOString()}`, }, ], }; } if (name === 'calculate_md5') { if (!args || typeof args.text !== 'string') { throw new Error('必须提供有效的 `text` 字符串参数'); } const hash = crypto.createHash('md5').update(args.text).digest('hex'); return { content: [ { type: 'text', text: `文本 "${args.text.substring(0, 20)}${args.text.length > 20 ? '...' : ''}" 的 MD5 哈希值是:${hash}`, }, ], }; } throw new Error(`未知的工具:${name}`); }); const transport = new StdioServerTransport(); server.connect(transport).catch((error) => { console.error('[MCP Server] 启动失败:', error); process.exit(1); }); console.error('[MCP Server] 启动成功,等待客户端连接...');至此,一个功能相对完整、具备工具和资源能力、且经过基本优化的 MCP Server 就构建完成了。接下来,我们需要编译并测试它。
4. 编译、运行与客户端集成测试
理论已经完备,代码也已就绪,现在是让 Server 跑起来并与 AI 客户端联调的时候了。
4.1 编译与运行 Server
首先,将 TypeScript 代码编译为 JavaScript。
# 在项目根目录执行编译 npm run build如果tsconfig.json配置正确,这将在dist/目录下生成index.js文件。
你可以直接运行它:
node dist/index.js运行后,程序会挂起,等待来自stdin的 MCP 协议消息。这正是我们期望的状态——Server 已就绪。
4.2 集成到 Claude Desktop(推荐测试方式)
Claude Desktop 是目前对 MCP 支持最友好、测试最方便的工具。
找到 Claude Desktop 配置:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json - Linux:
~/.config/Claude/claude_desktop_config.json
- macOS:
编辑配置文件:如果文件不存在,则创建它。添加以下配置,将
path/to/your/project替换为你的项目绝对路径。
{ "mcpServers": { "my-tools-server": { "command": "node", "args": [ "/absolute/path/to/your/my-first-mcp-server/dist/index.js" ], "env": { "MCP_DOCS_PATH": "/absolute/path/to/your/test_directory" // 可选,覆盖资源路径 } } } }重启 Claude Desktop:完全退出并重新启动 Claude Desktop 应用。
验证连接:重启后,在 Claude 的输入框里,你可以尝试说:“请调用
get_current_time工具” 或 “你能访问哪些资源?”。Claude 应该能识别出你的 Server 并调用相应的工具或读取资源。
4.3 在 Cursor 中集成
Cursor 也支持 MCP,但配置方式可能随版本更新而变化。通常可以在 Cursor 的设置(Settings)中搜索 “MCP” 进行配置,原理与 Claude Desktop 类似,也是通过 JSON 配置指定 Server 的启动命令和参数。
4.4 手动测试(调试)
对于深度调试,你可以编写一个简单的测试脚本,模拟 MCP 客户端向 Server 发送协议消息。但这相对复杂。更简单的方式是使用stdio直接交互(不推荐新手),或者在代码中添加详细的console.error日志来观察流程。
5. 常见问题与排查思路
在构建和集成 MCP Server 时,你可能会遇到以下典型问题。
| 问题现象 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
| Server 启动后立即退出 | 1. TypeScript 编译错误。 2. 依赖未安装。 3. 代码中存在同步错误导致进程崩溃。 | 1. 运行npm run build检查编译输出。2. 运行 npm install确保依赖完整。3. 在 server.connect()前后添加try-catch,并检查全局错误处理器server.onerror是否设置。 |
| Claude Desktop 未加载 Server | 1. 配置文件路径错误。 2. 配置文件格式错误(JSON 语法)。 3. Claude Desktop 未重启。 4. Server 命令路径错误或不可执行。 | 1. 确认配置文件路径完全正确。 2. 使用 JSON 验证工具检查配置文件。 3. 彻底退出并重启 Claude Desktop。 4. 在终端中手动运行配置中的 command和args,确保能启动 Server。 |
| AI 助手无法识别工具/资源 | 1. Server 的capabilities未正确声明。2. tools/list或resources/list处理器未正确返回数据。3. 协议通信失败。 | 1. 检查new Server()时的capabilities对象。2. 在 list处理器中添加console.error打印返回值,确保结构正确。3. 查看 Claude Desktop 或 Cursor 的日志(如果有),或 Server 的 stderr输出。 |
| 工具调用返回错误 | 1. 工具名称不匹配。 2. 输入参数格式不符合 inputSchema。3. 工具处理函数内部抛出异常。 | 1. 确认tools/call处理器中判断的name与tools/list返回的一致。2. 在 tools/call开始时打印request.params检查输入。3. 在工具逻辑内部添加细致的错误处理和日志。 |
| 资源读取返回空或错误 | 1.file://URI 路径格式错误或不存在。2. 文件系统权限不足。 3. resources/read处理器中的路径解析逻辑有误。 | 1. 确保RESOURCE_URI是绝对路径,并以file://开头。2. 手动检查 Node.js 进程是否有权读取目标目录。 3. 在 resources/read中添加日志,打印解析后的localPath。 |
StdioServerTransport相关问题 | 1. 在非命令行环境下错误使用。 2. 多个 Server 实例冲突。 | 1.StdioServerTransport专为命令行集成设计,确保通过 Claude/Cursor 配置调用,而非直接双击运行。2. 确保同一时间只有一个 Server 实例占用 stdio。 |
6. 最佳实践与进阶工程建议
当你掌握了基础构建流程后,以下建议能帮助你打造更健壮、更实用的 MCP Server。
6.1 项目组织与代码结构
- 拆分模块:不要将所有代码堆在
index.ts中。将工具定义、资源定义、处理器逻辑拆分到不同的文件(如src/tools/,src/resources/)中,便于维护和扩展。 - 使用配置管理:将 Server 名称、版本、资源路径等配置项集中到
config.ts或使用dotenv从.env文件加载。 - 添加日志系统:使用
winston或pino等日志库替代console.error,可以按级别(info, debug, error)输出日志,并支持输出到文件,方便生产环境调试。
6.2 安全性与可靠性
- 输入验证与消毒:对于任何来自客户端的输入(如工具参数、资源 URI),都必须进行严格的验证和消毒,防止命令注入、路径遍历等攻击。上面的示例中已包含简单的路径检查。
- 权限最小化:Server 运行时应仅拥有完成其功能所需的最小权限。避免以高权限(如 root)运行。
- 错误边界:每个工具和资源处理器都应包裹在
try-catch中,并返回用户友好的错误信息,而不是泄露内部堆栈跟踪。 - 资源访问控制:仔细设计
resources/list返回的 URI。不要暴露敏感目录。可以考虑实现动态资源列表,根据会话或身份验证返回不同的资源。
6.3 性能与可扩展性
- 异步操作:所有 I/O 操作(如文件读写、网络请求)都必须使用异步模式(
async/await),避免阻塞主线程。 - 工具与资源缓存:对于频繁访问且变化不频繁的资源,可以考虑在 Server 端实现缓存机制,减少重复开销。
- 状态管理:MCP Server 本质上是无状态的。如果需要维护会话状态,需要设计巧妙的机制(例如通过加密令牌在客户端和 Server 间传递状态)。
6.4 生产环境部署
- 进程管理:使用
pm2或systemd来管理 Server 进程,确保其崩溃后能自动重启。 - 健康检查:可以暴露一个简单的 HTTP 健康检查端点(如果 Server 同时运行 HTTP 服务),或通过信号机制检查进程是否存活。
- 版本化:清晰定义 Server 的版本号,并在
Server构造函数中声明。当协议或功能更新时,便于客户端兼容性处理。
6.5 扩展想法
你的 MCP Server 可以无限扩展:
- 数据库工具:提供查询、更新本地数据库的工具。
- 内部 API 网关:封装公司内部 REST 或 GraphQL API,让 AI 安全调用。
- 代码库分析:提供读取、分析特定 Git 仓库代码结构的资源。
- 系统监控:提供获取服务器 CPU、内存使用情况的工具。
- 自定义工作流:将一系列复杂操作(如构建、部署、测试)封装成一个工具。
通过这 5 条结构化的 Prompt,我们不仅构建了一个可运行的 MCP Server,更掌握了一种“Prompt 驱动开发”的思维模式。这种模式将复杂的工程任务分解为清晰的、可执行的指令,无论是与 AI 协作还是自我规划,都极具效率。记住,MCP 的核心价值在于将你的本地能力安全、标准化地暴露给 AI。现在,你可以基于这个模板,去创造真正能提升你工作效率的 AI 增强工具了。