news 2026/5/13 14:22:27

使用 NestJS 与 @rekog/mcp-nest 构建企业级 MCP 服务器

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
使用 NestJS 与 @rekog/mcp-nest 构建企业级 MCP 服务器

1. 项目概述:当 NestJS 遇见 MCP

如果你正在用 NestJS 构建后端服务,同时又想让你的服务能力(比如查询数据库、调用内部 API、处理文件)能够被 AI 智能体(比如 Claude Desktop、Cursor、Windsurf)直接调用,那么@rekog/mcp-nest这个库就是你一直在找的“桥梁”。简单来说,它让你能用最熟悉的 NestJS 开发范式——依赖注入、装饰器、模块——来快速构建一个符合Model Context Protocol (MCP)标准的服务器。

MCP 是什么?你可以把它理解成 AI 世界的“USB 协议”。过去,每个 AI 应用(如 IDE 插件)要接入你的服务,都得写一套特定的适配代码,繁琐且不通用。MCP 定义了一套标准,让 AI 工具能以统一的方式发现、调用外部工具、读取资源和获取提示词。而@rekog/mcp-nest则让你在 NestJS 这个强大的企业级框架内,轻松实现这套协议,把现有的业务逻辑直接暴露给 AI。

我最初接触这个项目,是因为团队内部有大量用 NestJS 写的微服务,我们想探索如何让 AI 助手能安全、可控地调用这些服务来完成一些自动化任务,比如生成周报数据、查询项目状态。手动为每个服务写 MCP 适配层太痛苦了,而这个库的出现,让我们几乎零成本地将现有服务“MCP 化”。接下来,我会结合自己的实践,拆解如何从零开始,用它构建一个功能完备、安全可靠的 MCP 服务器。

2. 核心设计思路:为何选择@rekog/mcp-nest

在决定使用@rekog/mcp-nest之前,我也评估过直接使用官方的@modelcontextprotocol/sdk或者用 Express/Fastify 从头搭建。最终选择它,主要是基于以下几个在真实企业级开发中无法回避的考量点:

2.1 与现有 NestJS 生态无缝融合

这是最大的优势。如果你的团队已经在使用 NestJS,那么代码库中必然充斥着大量的@Injectable()服务类、复杂的依赖关系以及模块化的架构。@rekog/mcp-nest允许你直接在现有的服务类上,通过添加@Tool()装饰器,就将其方法暴露为 MCP 工具。这意味着:

  • 零重构成本:你不需要为了适配 MCP 而重写业务逻辑。
  • 依赖注入(DI)完整可用:你的工具方法内部可以像平常一样,注入其他服务、仓库(Repository)、配置模块等。这对于调用数据库、消息队列、外部 API 等场景至关重要。
  • 统一的架构风格:团队不需要学习一套新的开发模式,维护成本低。

2.2 开箱即用的企业级特性

这个库不是简单的协议包装,它考虑了很多生产环境的需求:

  • 多传输协议支持:它同时支持 HTTP+SSE、Streamable HTTP 和 STDIO 三种通信方式。这在部署时给你极大的灵活性。例如,在 Docker 容器或 Serverless 环境中,HTTP 更合适;而在本地开发或 CLI 工具集成时,STDIO 模式更方便。
  • 内置安全框架:它深度集成了 NestJS 的 Guards(守卫)和 Passport 策略,可以轻松实现基于 OAuth2、JWT 甚至自定义 Token 的认证和授权。你甚至可以做到“按工具授权”,即不同的 AI 用户或角色只能调用特定的工具集。
  • 完善的资源与提示词系统:MCP 不仅仅是工具调用,还包括资源(提供只读数据,如文档、配置文件)和提示词模板。该库提供了清晰的方式来定义这两者,特别是资源模板功能,能让你创建动态的、带参数的资源 URI,非常强大。

2.3 开发体验与可维护性

  • 基于 Zod 的声明式参数验证:工具的参数定义使用 Zod Schema,这比手动写 JSON Schema 要直观和类型安全得多。NestJS 本身也推荐使用类验证器,Zod 是当前社区更流行的选择,类型推断完美。
  • 自动发现与手动注册并存:工具可以通过装饰器自动被发现,也支持在模块层面动态注册。这为插件化架构或根据配置动态启用工具提供了可能。
  • 完善的进度报告与上下文:工具方法可以接收到一个Context对象,用于报告执行进度、访问原始的 HTTP 请求信息(如 headers),这对于实现长任务和审计日志非常有用。

注意:选择这个库也意味着你接受了 NestJS 的“重量级”和一定的学习曲线。如果你的项目非常轻量,且没有使用 NestJS,那么直接使用底层 SDK 可能更简单。但对于任何有一定复杂度的、已有 NestJS 基础的项目,这个库的收益是巨大的。

3. 从零开始:构建你的第一个 MCP 工具服务器

理论说再多不如动手试。我们从一个最简单的例子开始,构建一个能通过 AI 调用的“问候工具”和“待办事项管理工具”。

3.1 环境准备与项目初始化

首先,确保你有一个 NestJS 项目。如果没有,可以用 CLI 快速创建一个:

# 全局安装 NestJS CLI (如果尚未安装) npm i -g @nestjs/cli # 创建新项目 nest new my-mcp-server cd my-mcp-server # 安装核心依赖 npm install @rekog/mcp-nest @modelcontextprotocol/sdk zod

这里安装的三个包分别是:

  • @rekog/mcp-nest: 我们的主角,NestJS 模块。
  • @modelcontextprotocol/sdk: MCP 协议的核心 SDK,是前者的 peer dependency。
  • zod: 用于定义工具参数的模式验证库。

3.2 核心模块配置

接下来,在根模块app.module.ts中导入并配置McpModule

// src/app.module.ts import { Module } from '@nestjs/common'; import { McpModule } from '@rekog/mcp-nest'; @Module({ imports: [ McpModule.forRoot({ // 服务器元信息,会在 MCP 初始化时告知客户端 name: 'my-todo-mcp-server', version: '1.0.0', // 可选:指定传输方式,默认为 'stdio'。这里我们先使用 stdio 便于本地测试。 // transport: 'stdio', // 如果使用 HTTP,需要配置更多选项,后面会讲到。 }), ], }) export class AppModule {}

forRoot方法是配置的入口。最基本的配置就是nameversion。至此,一个最基础的 MCP 服务器框架就搭好了,但它还没有任何可用的工具。

3.3 创建你的第一个工具

在 NestJS 中,工具本身就是一个普通的、可注入的服务类。我们创建一个greeting.tool.ts

// src/tools/greeting.tool.ts import { Injectable } from '@nestjs/common'; import { Tool, Context } from '@rekog/mcp-nest'; import { z } from 'zod'; @Injectable() // 标准的 NestJS 可注入服务 export class GreetingTool { /** * 使用 @Tool() 装饰器将一个方法声明为 MCP 工具。 * 装饰器接收一个配置对象,其中: * - `name`: 工具的唯一标识符,AI 客户端通过这个名称调用。 * - `description`: 工具的描述,AI 会根据这个描述决定何时使用该工具。务必写清楚! * - `parameters`: 用 Zod Schema 定义工具的参数和类型。 */ @Tool({ name: 'get_greeting', description: '根据提供的姓名生成一句问候语。如果未提供姓名,则使用默认值。', parameters: z.object({ // 定义 name 参数为字符串类型,并设置默认值。 // Zod 的验证规则(如 .min(1))也会被转换成 MCP 的 JSON Schema。 name: z.string().min(1, '姓名不能为空').default('World'), }), }) async sayHello( // 第一个参数是工具调用时传入的参数对象,其类型会根据上面的 Zod Schema 自动推断。 params: { name: string }, // 第二个参数是 Context 对象,提供了额外的运行时信息和控制能力。 context: Context ): Promise<string> { // 工具可以返回任何可 JSON 序列化的值,或者一个 Promise。 // 使用 context.reportProgress 可以向客户端报告执行进度。 // 这对于执行时间较长的工具非常有用,能让 AI 界面显示进度条。 await context.reportProgress({ progress: 30, total: 100, message: '正在生成问候语...' }); // 模拟一些工作 await new Promise(resolve => setTimeout(resolve, 100)); await context.reportProgress({ progress: 100, total: 100, message: '完成!' }); // 返回值就是工具的执行结果,会被发送回 AI 客户端。 return `Hello, ${params.name}! Welcome to the MCP world.`; } }

创建好工具后,需要将它作为提供者(Provider)添加到模块中,这样 NestJS 的依赖注入容器才能管理它,并且@rekog/mcp-nest才能自动发现它上面的@Tool装饰器。

// src/app.module.ts import { Module } from '@nestjs/common'; import { McpModule } from '@rekog/mcp-nest'; import { GreetingTool } from './tools/greeting.tool'; @Module({ imports: [ McpModule.forRoot({ name: 'my-todo-mcp-server', version: '1.0.0', }), ], providers: [GreetingTool], // 在这里注册工具服务 }) export class AppModule {}

3.4 运行与测试

现在,我们可以运行这个服务器了。由于我们在配置中使用了默认的stdio传输,服务器会通过标准输入输出进行通信。我们需要一个 MCP 客户端来测试它。最方便的方法是使用@modelcontextprotocol/sdk包中自带的mcp命令行工具,或者使用像Claude Desktop这样的已经集成了 MCP 客户端的应用。

方法一:使用mcpCLI 工具测试

首先,全局安装 MCP SDK 的 CLI:

npm install -g @modelcontextprotocol/sdk

然后,我们需要创建一个 MCP 客户端的配置文件。在与你的 NestJS 项目同级的目录下,创建一个client-test.mjs文件:

// client-test.mjs import { Client } from '@modelcontextprotocol/sdk/client/index.js'; import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'; async function main() { // 1. 创建客户端 const client = new Client( { name: 'test-client', version: '1.0.0' }, { capabilities: {} } ); // 2. 创建传输层:这里启动我们 NestJS 应用的进程 const transport = new StdioClientTransport({ command: 'node', // 命令 args: ['dist/main.js'], // 参数:指向我们编译后的 NestJS 主文件 }); // 3. 连接 await client.connect(transport); console.log('Connected to MCP server.'); // 4. 列出可用的工具 const { tools } = await client.listTools(); console.log('Available tools:', tools.map(t => t.name)); // 5. 调用我们定义的 get_greeting 工具 const result = await client.callTool({ name: 'get_greeting', arguments: { name: 'Developer' }, // 传入参数 }); console.log('Tool call result:', result.content?.[0]?.text); // 6. 断开连接 await client.close(); } main().catch(console.error);

运行这个测试脚本之前,需要先编译并运行你的 NestJS 应用:

# 在项目根目录下 npm run build # 编译 TypeScript node client-test.mjs # 运行测试客户端

如果一切正常,你会在控制台看到 “Available tools: [ ‘get_greeting’ ]” 和 “Tool call result: Hello, Developer! Welcome to the MCP world.” 的输出。

方法二:集成到 Claude Desktop(更直观)

  1. 找到 Claude Desktop 的 MCP 配置文件位置(通常在~/Library/Application Support/Claude/claude_desktop_config.json%APPDATA%\Claude\claude_desktop_config.json)。
  2. mcpServers部分添加你的服务器配置:
{ "mcpServers": { "my-nestjs-server": { "command": "node", "args": ["/ABSOLUTE/PATH/TO/YOUR/PROJECT/dist/main.js"] } } }
  1. 重启 Claude Desktop。在聊天框中,Claude 现在应该能“知道”它有一个名为get_greeting的工具可以使用了。你可以尝试对它说:“请用我的名字 ‘Alex’ 打个招呼。” Claude 应该会调用这个工具并返回结果。

实操心得:在开发初期,强烈建议使用stdio模式配合 CLI 测试,因为日志输出和错误信息更直接。等核心逻辑稳定后,再切换到 HTTP 模式进行集成测试。另外,注意args中的路径必须是绝对路径,否则 Claude Desktop 可能找不到你的可执行文件。

4. 进阶实战:构建一个带数据库的待办事项 MCP 服务

现在我们来点更实际的:一个具有增删改查功能的待办事项(Todo)MCP 服务。这将涉及更复杂的工具交互、依赖注入和错误处理。

4.1 定义数据模型与服务层

首先,我们创建一个简单的 Todo 实体和对应的 TypeORM 服务(假设项目已配置 TypeORM)。

// src/todos/todo.entity.ts import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm'; @Entity() export class Todo { @PrimaryGeneratedColumn() id: number; @Column() title: string; @Column({ default: false }) completed: boolean; @CreateDateColumn() createdAt: Date; @UpdateDateColumn() updatedAt: Date; }
// src/todos/todos.service.ts import { Injectable, NotFoundException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { Todo } from './todo.entity'; @Injectable() export class TodosService { constructor( @InjectRepository(Todo) private todosRepository: Repository<Todo>, ) {} async findAll(): Promise<Todo[]> { return this.todosRepository.find(); } async findOne(id: number): Promise<Todo> { const todo = await this.todosRepository.findOneBy({ id }); if (!todo) { throw new NotFoundException(`Todo with ID ${id} not found`); } return todo; } async create(title: string): Promise<Todo> { const todo = this.todosRepository.create({ title }); return this.todosRepository.save(todo); } async update(id: number, updates: Partial<Todo>): Promise<Todo> { const todo = await this.findOne(id); // 复用 findOne,会触发 NotFoundException Object.assign(todo, updates); return this.todosRepository.save(todo); } async remove(id: number): Promise<void> { const result = await this.todosRepository.delete(id); if (result.affected === 0) { throw new NotFoundException(`Todo with ID ${id} not found`); } } }

4.2 将服务方法暴露为 MCP 工具

接下来,我们创建一个TodosTool类,它不直接操作数据库,而是注入TodosService,并将业务方法包装成 MCP 工具。这样做的好处是保持了业务逻辑的纯净,工具层只负责协议适配。

// src/tools/todos.tool.ts import { Injectable } from '@nestjs/common'; import { Tool, Context } from '@rekog/mcp-nest'; import { z } from 'zod'; import { TodosService } from '../todos/todos.service'; @Injectable() export class TodosTool { constructor(private readonly todosService: TodosService) {} // 依赖注入 @Tool({ name: 'list_todos', description: '获取所有的待办事项列表。', parameters: z.object({}), // 这个工具不需要参数 }) async listTodos(): Promise<{ todos: any[] }> { // 返回一个对象,结构更清晰 const todos = await this.todosService.findAll(); // 注意:直接返回 Entity 实例可能包含循环引用或敏感字段。 // 最佳实践是映射到一个纯数据对象或 DTO。 return { todos: todos.map(t => ({ id: t.id, title: t.title, completed: t.completed, createdAt: t.createdAt.toISOString(), })) }; } @Tool({ name: 'get_todo', description: '根据 ID 获取单个待办事项的详细信息。', parameters: z.object({ id: z.number().int().positive('ID 必须是正整数'), }), }) async getTodo(params: { id: number }) { const todo = await this.todosService.findOne(params.id); return { id: todo.id, title: todo.title, completed: todo.completed, createdAt: todo.createdAt.toISOString(), updatedAt: todo.updatedAt.toISOString(), }; } @Tool({ name: 'create_todo', description: '创建一个新的待办事项。', parameters: z.object({ title: z.string().min(1, '标题不能为空').max(200, '标题过长'), }), }) async createTodo(params: { title: string }, context: Context) { await context.reportProgress({ progress: 0, total: 100, message: '开始创建任务...' }); const newTodo = await this.todosService.create(params.title); await context.reportProgress({ progress: 100, total: 100, message: '任务创建成功!' }); return { message: `待办事项创建成功,ID: ${newTodo.id}`, todo: { id: newTodo.id, title: newTodo.title, completed: newTodo.completed, } }; } @Tool({ name: 'update_todo', description: '更新一个待办事项,例如标记为完成或修改标题。', parameters: z.object({ id: z.number().int().positive('ID 必须是正整数'), title: z.string().max(200, '标题过长').optional(), completed: z.boolean().optional(), }).refine(data => data.title !== undefined || data.completed !== undefined, { message: '必须提供 title 或 completed 至少一个字段进行更新', }), }) async updateTodo(params: { id: number; title?: string; completed?: boolean }) { const updates: any = {}; if (params.title !== undefined) updates.title = params.title; if (params.completed !== undefined) updates.completed = params.completed; const updatedTodo = await this.todosService.update(params.id, updates); return { message: `待办事项 ${params.id} 更新成功`, todo: { id: updatedTodo.id, title: updatedTodo.title, completed: updatedTodo.completed, } }; } @Tool({ name: 'delete_todo', description: '删除一个待办事项。', parameters: z.object({ id: z.number().int().positive('ID 必须是正整数'), }), }) async deleteTodo(params: { id: number }) { await this.todosService.remove(params.id); return { message: `待办事项 ${params.id} 已删除` }; } }

4.3 配置模块与依赖

现在,我们需要更新模块,引入 TypeORM、TodosServiceTodosTool

// src/app.module.ts import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { McpModule } from '@rekog/mcp-nest'; import { Todo } from './todos/todo.entity'; import { TodosService } from './todos/todos.service'; import { GreetingTool } from './tools/greeting.tool'; import { TodosTool } from './tools/todos.tool'; @Module({ imports: [ TypeOrmModule.forRoot({ type: 'sqlite', // 示例使用 SQLite,生产环境请换成 PostgreSQL/MySQL database: 'database.sqlite', entities: [Todo], synchronize: true, // 开发环境用,生产环境请使用迁移 }), TypeOrmModule.forFeature([Todo]), // 为 Todo 实体注册 Repository McpModule.forRoot({ name: 'todo-mcp-server', version: '1.0.0', }), ], providers: [GreetingTool, TodosService, TodosTool], // 注册所有服务 }) export class AppModule {}

4.4 测试复杂的工具交互

重新编译并运行服务器后,AI 客户端现在可以看到list_todos,get_todo,create_todo,update_todo,delete_todo这一系列工具。你可以尝试让 Claude 执行一个复杂的工作流,例如:

“请先帮我创建一个标题为‘学习 MCP 协议’的待办事项,然后列出所有待办事项,最后把刚才创建的那个标记为完成。”

Claude 应该能理解并依次调用create_todo->list_todos->update_todo这三个工具,并返回最终的结果。这展示了 MCP 如何让 AI 协调多个工具来完成一个复杂任务。

注意事项

  1. 错误处理:我们的TodosService抛出了 NestJS 的NotFoundException@rekog/mcp-nest会自动捕获这些异常,并将其转换为 MCP 协议规定的错误响应格式,AI 客户端能接收到清晰的错误信息。你也可以在工具方法内部用try...catch进行更精细的错误处理和用户友好的提示。
  2. 参数验证:Zod Schema 不仅定义了类型,还定义了验证规则(如.min(1))。如果 AI 传入无效参数(如空字符串或负数 ID),库会在调用你的工具方法之前就返回验证错误,保证了工具内部逻辑的纯净。
  3. 返回格式:尽量返回结构化的对象,而不是简单的字符串。这有助于 AI 更好地解析和利用返回的数据。例如,list_todos返回{ todos: [...] }比直接返回数组更清晰。

5. 部署与生产配置:HTTP 传输与安全认证

在本地开发使用stdio很方便,但生产环境通常需要以 HTTP 服务的形式部署。同时,安全是重中之重,我们不能让任何人都能调用我们的工具。

5.1 配置 HTTP + SSE 传输

@rekog/mcp-nest支持多种传输方式。我们将配置一个使用 HTTP 和 Server-Sent Events (SSE) 的服务器。

首先,安装必要的包(如果你使用 Fastify 作为底层 HTTP 引擎,需要额外安装):

npm install @nestjs/platform-express # 如果使用 Express (默认) # 或 npm install @nestjs/platform-fastify

然后,修改main.ts来创建并配置一个专门的 MCP HTTP 服务器,与你的主 NestJS 应用分离(这是推荐做法,因为 MCP 协议与普通的 REST API 不同)。

// src/main.mcp.ts - 这是一个独立的入口文件 import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { createMcpServer } from '@rekog/mcp-nest'; async function bootstrap() { // 创建 NestJS 应用实例,但不监听端口 const app = await NestFactory.createApplicationContext(AppModule); // 从应用中获取 McpService const mcpService = app.get('McpService'); // 使用字符串 token 获取 // 创建 MCP HTTP 服务器 const mcpServer = await createMcpServer(mcpService, { transport: 'http', // 指定使用 HTTP 传输 http: { port: 3001, // MCP 服务监听的端口,与主应用端口区分开 endpoint: '/mcp', // SSE 连接端点 // 可选:配置 CORS 等 // cors: { origin: 'https://your-client.com' } }, }); console.log(`MCP HTTP server is running on http://localhost:3001`); // 注意:这里没有调用 app.listen(),因为我们只运行 MCP 服务器 } bootstrap();

同时,我们需要修改app.module.ts中的McpModule配置,移除全局的transport设置,因为现在我们在入口文件里指定。

// src/app.module.ts (部分) imports: [ McpModule.forRoot({ name: 'todo-mcp-server', version: '1.0.0', // 不再在这里设置 transport,由入口文件决定 }), ],

最后,在package.json中添加一个新的脚本命令:

{ "scripts": { "start:mcp": "nest build && node dist/main.mcp" } }

运行npm run start:mcp,你的 MCP 服务器就会在http://localhost:3001/mcp上提供 SSE 连接。

5.2 集成认证与授权(使用 Guards)

未经认证的 MCP 服务器是危险的。@rekog/mcp-nest完美支持 NestJS 的 Guard 机制。我们可以创建一个简单的 API Key Guard。

首先,创建一个 Guard:

// src/auth/api-key.guard.ts import { Injectable, CanActivate, ExecutionContext, UnauthorizedException } from '@nestjs/common'; import { Observable } from 'rxjs'; import { Request } from 'express'; @Injectable() export class ApiKeyGuard implements CanActivate { private readonly validApiKey = process.env.MCP_API_KEY || 'your-secret-key-here'; canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> { // 对于 MCP over HTTP,上下文是 HTTP 请求 const request = context.switchToHttp().getRequest<Request>(); const authHeader = request.headers['authorization']; if (!authHeader || !authHeader.startsWith('Bearer ')) { throw new UnauthorizedException('Missing or invalid Authorization header'); } const apiKey = authHeader.substring(7); // 去掉 'Bearer ' 前缀 if (apiKey !== this.validApiKey) { throw new UnauthorizedException('Invalid API key'); } return true; } }

然后,我们需要告诉McpModule在初始化 MCP 服务器时使用这个 Guard。这需要在forRoot配置中完成。

// src/app.module.ts import { Module } from '@nestjs/common'; import { McpModule } from '@rekog/mcp-nest'; import { ApiKeyGuard } from './auth/api-key.guard'; @Module({ imports: [ McpModule.forRoot({ name: 'todo-mcp-server', version: '1.0.0', // 配置全局 Guard useGuards: [ApiKeyGuard], // 所有 MCP 请求都必须通过此 Guard }), // ... 其他导入 ], providers: [ApiKeyGuard], }) export class AppModule {}

现在,任何连接到http://localhost:3001/mcp的客户端都必须在请求头中携带Authorization: Bearer your-secret-key-here。Claude Desktop 等客户端通常支持在服务器配置中设置自定义请求头。

更细粒度的授权(Per-Tool Authorization)

有时,你可能希望不同的 API Key 拥有不同的工具调用权限。这可以通过工具级别的自定义装饰器或元数据来实现。@rekog/mcp-nest@Tool装饰器支持传入一个guard选项。

// src/tools/admin.tool.ts import { UseGuards } from '@nestjs/common'; import { Tool } from '@rekog/mcp-nest'; import { AdminGuard } from '../auth/admin.guard'; export class AdminTool { @Tool({ name: 'delete_all_todos', description: '【管理员专用】删除所有待办事项。', // 为这个特定的工具指定一个更严格的 Guard guard: AdminGuard, // AdminGuard 会检查更高级别的权限 }) async deleteAllTodos() { // ... 实现 } }

5.3 使用内置授权服务器(Beta)

对于需要完整 OAuth2 流程的复杂场景,@rekog/mcp-nest还提供了一个内置授权服务器(Beta)。这特别适合当你希望用户通过标准的 OAuth2 流程(如授权码模式)来授权 AI 客户端访问你的 MCP 服务器时使用。配置相对复杂,涉及客户端注册、重定向 URI 等,但为构建面向多用户的 MCP 服务提供了强大支持。具体配置请参考其官方文档的Built-in Authorization Server部分。

部署心得

  1. 端口与路径:确保生产环境的防火墙和安全组规则允许访问你配置的 MCP 端口(如 3001)。同时,考虑使用 Nginx 等反向代理为/mcp路径配置 SSL/TLS。
  2. API Key 管理:永远不要将 API Key 硬编码在代码中。使用环境变量或秘密管理服务(如 AWS Secrets Manager, HashiCorp Vault)。为不同的客户端(如开发、测试、生产环境下的 Claude Desktop)颁发不同的 Key,并做好轮换计划。
  3. 监控与日志:MCP 服务器的调用日志非常重要。你可以在全局 Guard 或自定义的拦截器(Interceptor)中记录所有的工具调用请求,包括调用者(通过 API Key 标识)、工具名、参数和时间,用于审计和故障排查。
  4. 性能考虑:AI 可能会频繁调用工具。确保你的工具方法是高效的,并考虑对数据库查询或外部 API 调用进行适当的缓存。

6. 常见问题与排查技巧实录

在实际开发和集成过程中,我踩过不少坑。这里总结一些最常见的问题和解决方法。

6.1 工具未出现在客户端列表中

症状:运行了服务器,但 AI 客户端(如 Claude Desktop)连接后,看不到任何工具,或者只看到部分工具。

排查步骤

  1. 检查工具类是否被注册为 Provider:确保你的工具类(如GreetingTool)被添加到了所属模块的providers数组中。如果工具在特性模块中,确保该模块被根模块或一个已导入的模块所导入。
  2. 检查@Tool()装饰器:确认装饰器被正确地应用在类的方法上,而不是类的属性上。方法必须是async或返回Promise
  3. 检查传输模式:如果你配置的是 HTTP 传输,确保客户端连接的是正确的 URL(例如http://localhost:3001/mcp)。对于stdio模式,确保客户端启动命令的路径正确。
  4. 查看服务器日志:在McpModule.forRoot配置中启用调试日志:{ debug: true }。启动服务器时,你应该能看到类似[MCP] Registered tool: get_greeting的日志输出。如果没有,说明工具注册环节有问题。
  5. 依赖注入问题:如果工具类依赖于其他服务(如TodosService),确保这些依赖服务也被正确注册,并且没有循环依赖。NestJS 启动时的错误日志通常会给出提示。

6.2 工具调用失败,返回验证错误

症状:AI 尝试调用工具,但返回错误,提示参数无效。

排查步骤

  1. 仔细阅读错误信息:MCP 协议会返回详细的错误信息,其中会包含是哪个字段违反了哪条 Zod 规则。例如,“name”: “必须为字符串类型”
  2. 检查 Zod Schema:确认你为工具定义的 Zod Schema 与 AI 实际可能传入的数据类型匹配。AI 有时可能会推断出数字类型,但你的 Schema 定义的是字符串。使用.coerce方法(如z.coerce.number())可以尝试进行类型转换。
  3. 检查默认值:如果参数是可选的且有默认值,确保 AI 在调用时可以不传该参数。有时 AI 会显式传递null,而你的 Schema 可能不接受null,需要使用.nullable().optional()

6.3 进度报告(context.reportProgress)不工作

症状:在工具方法中调用了context.reportProgress,但客户端(如 Claude Desktop)没有显示进度条。

排查步骤

  1. 确认客户端支持:并非所有 MCP 客户端都支持进度报告。Claude Desktop 目前支持。请查阅你所用客户端的文档。
  2. 确认传输协议:进度报告功能可能在某些传输模式下工作得更好。确保你使用的传输协议(如 HTTP+SSE)支持服务器向客户端推送消息。
  3. 检查调用时机context.reportProgress返回一个 Promise。确保你await它,或者在它后面进行await操作,以保证消息在函数返回前被发送出去。

6.4 在 Docker 或 Serverless 环境中部署问题

症状:在本地运行正常,但在容器化或无服务器环境中失败。

排查步骤

  1. stdio模式不适用:在 Docker 或 Serverless(如 AWS Lambda)中,stdio传输模式通常无法工作,因为没有一个长期运行的父进程来管理标准流。必须切换到 HTTP 传输模式
  2. HTTP 端口绑定:在 Docker 中,确保容器内的应用监听的是0.0.0.0而不是127.0.0.1(localhost)。在createMcpServer的 HTTP 配置中,可以设置host: '0.0.0.0'
  3. 冷启动延迟:在 Serverless 环境中,冷启动可能导致连接超时。你需要调整客户端的连接超时设置,或者使用提供性环境来保持实例温暖。
  4. 环境变量:确保生产环境所需的环境变量(如数据库连接字符串、API Key)已正确注入到容器或函数环境中。

6.5 与特定 AI 客户端集成问题

症状:与测试 CLI 工作正常,但与 Claude Desktop、Cursor 等集成时出现问题。

排查步骤

  1. 检查客户端配置格式:每个客户端对claude_desktop_config.json的格式要求可能略有不同。确保 JSON 格式正确,路径是绝对路径,并且命令和参数无误。
  2. 查看客户端日志:Claude Desktop 通常有应用日志文件。在 macOS 上,可以在~/Library/Logs/Claude/找到;在 Windows 上,可以在%APPDATA%\Claude\logs找到。查看日志中是否有连接错误或进程启动失败的信息。
  3. 权限问题:确保 Claude Desktop 有权限执行你指定的node命令和访问你的项目目录。
  4. 版本兼容性:确保你使用的@rekog/mcp-nest@modelcontextprotocol/sdk版本与客户端兼容。有时需要尝试更新到最新版本。

终极调试技巧:当你遇到难以定位的问题时,一个非常有效的方法是暂时绕开复杂的客户端,直接用最原始的curl命令或 Postman 来测试你的 HTTP MCP 服务器。你可以手动构造一个 SSE 连接请求,并发送 JSON-RPC 格式的工具列表请求和工具调用请求。这能帮你快速判断问题是出在服务器端还是客户端集成上。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/13 14:21:35

科技从业者如何践行科学精神:从证据文化到公共倡导

1. 科学游行&#xff1a;一场全球性运动的背景与缘起2017年4月22日&#xff0c;一场名为“为科学游行”的运动在全球范围内同步展开&#xff0c;从美国本土到阿根廷、英国等地&#xff0c;共计超过514场活动。这场运动并非凭空出现&#xff0c;其背后是科学界、教育界乃至产业界…

作者头像 李华
网站建设 2026/5/13 14:21:29

收藏必备!小白程序员必看:Agent如何越用越聪明?Hermes技能进化全解析

Hermes通过将任务中的可复用流程写回本地技能文件&#xff0c;实现了Agent的“进化”。这一过程并非模型参数学习&#xff0c;而是经验整理成操作手册。文章详细介绍了Hermes自动创建技能的路径&#xff0c;包括任务时的模型维护、任务后的复盘与技能写回&#xff0c;以及后台复…

作者头像 李华
网站建设 2026/5/13 14:20:18

用Matlab R2017a手把手教你解码电话按键音:从.wav文件到数字的完整流程

用Matlab R2017a实现电话按键音解码&#xff1a;从频谱分析到数字识别的实战指南 电话按键音背后隐藏着一套精妙的频率编码系统——DTMF&#xff08;双音多频&#xff09;技术。当我们在老式座机上按下数字键时&#xff0c;设备实际上是在通过两组特定频率的组合来传递信息。本…

作者头像 李华
网站建设 2026/5/13 14:13:19

量子核方法:原理、挑战与指数集中问题解析

1. 量子核方法基础与核心挑战 量子核方法&#xff08;Quantum Kernel Methods, QKMs&#xff09;是量子机器学习领域的重要技术路线&#xff0c;其核心思想是通过量子特征映射将经典数据编码到高维希尔伯特空间&#xff0c;利用量子态的天然高维特性构建有效的核函数。这种方法…

作者头像 李华