mcpField
N
NestJS MCP Module

NestJS MCP Module

本地服务
github.com/bamada开发工具2476 周前更新

关于 NestJS MCP Module

NestJS MCP Module 是一个 NestJS 模块,用于通过装饰器以 NestJS 的方式实现 MCP 服务。适合已在使用 NestJS 的开发者,支持 STDIO 与 HTTP/SSE 两种传输方式。

主要功能

  1. 01使用 @McpTool、@McpResource、@McpPrompt 装饰器
  2. 02自动发现装饰的方法并注册到 MCP 服务
  3. 03支持 STDIO 与 HTTP/SSE 传输
  4. 04使用 Zod 定义工具参数与提示词参数
  5. 05提供 forRoot 与 forRootAsync 配置方式

适用场景

  1. 01在现有 NestJS 应用中暴露工具与资源给 MCP 客户端
  2. 02为命令行工具构建基于 STDIO 的 MCP 服务
  3. 03通过 HTTP/SSE 端点提供远程 MCP 服务

MCP 相关

该模块用于实现 MCP 服务,将被装饰的方法以工具、资源和提示词的形式暴露给 MCP 客户端,底层基于 @modelcontextprotocol/sdk。

配置

本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。

暂未收录安装方式

我们还没有获取到这个服务的安装包或远程地址,请按项目 README 中的说明安装。

代码仓库

工具

还没有获取到工具列表

本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。

详细介绍

NestJS MCP 模块

星标

NPM 版本 软件包许可证 NPM 下载量 CI 状态 所有贡献者 代码风格:prettier

这是一个 NestJS 模块,可无缝集成用于实现 Model Context Protocol (MCP) 服务。该模块使用熟悉的 NestJS 方法装饰器(@McpResource、@McpTool、@McpPrompt)和模式,简化了向 MCP 客户端公开资源、工具和提示词的过程。它支持多种传输层,包括 STDIO 和 HTTP/SSE。

目录

✨ 功能

  • 无缝集成 MCP: 使用 @modelcontextprotocol/sdk,轻松在 NestJS 应用程序中构建符合 MCP 规范的服务。
  • 基于方法装饰器: 在 NestJS Provider 中使用装饰器定义 MCP 资源、工具和提示词(@McpResource、@McpTool、@McpPrompt)。
  • 自动发现: 使用 NestJS 发现机制(@nestjs/core/discovery)查找带装饰器的方法。
  • 传输处理:
    • 内置支持 STDIO 传输(适用于 CLI 工具)。
    • 内置支持 HTTP/SSE 传输,通过专用控制器(McpHttpController,路径为 /api/mcp)实现。
    • 可配置传输方式。
  • Zod Schema: 使用 Zod schema 定义工具参数和提示词参数,以进行验证并确保类型安全。
  • 原生 NestJS: 使用标准 NestJS 模块、Provider 和依赖注入构建。
  • 强类型: 利用 TypeScript 实现稳健开发。
  • 可配置: 支持通过标准 forRoot 和 forRootAsync 模式进行配置。
  • Stderr 日志记录: 提供 StderrLogger,确保应用程序日志不会干扰 STDIO 传输。

📦 安装

首先,请确保已安装所需的对等依赖。然后安装该模块:

# Using npm
npm install @bamada/nestjs-mcp

# Using yarn
yarn add @bamada/nestjs-mcp

⚠️ 对等依赖

此模块依赖 NestJS 核心包、MCP SDK 和其他需要在宿主应用中安装的库。请确保 package.json 包含兼容版本:

  • @modelcontextprotocol/sdk:^1.10.2
  • @nestjs/platform-express:^10.0.0 或 ^11.0.0(如果不使用 Express,也可以选择其他平台)
  • zod:^3.0.0

未安装这些对等依赖会导致运行时错误。

🚀 快速开始

  1. 导入 McpModule: 将 McpModule 导入根 AppModule(或功能模块),并使用 .forRoot() 或 .forRootAsync() 进行配置。提供必需的 serverInfo。

    // src/app.module.ts
    import { Module } from '@nestjs/common';
    import { McpModule, TransportType } from '@bamada/nestjs-mcp';
    import { MyMcpProvider } from './my-mcp.provider';
    
    @Module({
      imports: [
        McpModule.forRoot({
          serverInfo: {
            name: 'my-awesome-mcp-app',
            version: '1.0.0',
          },
          serverOptions: {},
          transport: TransportType.SSE, // Use HTTP/SSE controller
        }),
      ],
      providers: [MyMcpProvider],
    })
    export class AppModule {}
    

以下是使用 @McpTool 装饰器定义 MCP 工具的方法:

MCP 工具示例
import {
  McpTool,
  RequestHandlerExtra,
  CallToolResult,
} from '@bamada/nestjs-mcp';
import { Injectable, Logger } from '@nestjs/common';
import { z } from 'zod';
import { Request, Notification } from '@modelcontextprotocol/sdk/types';

@Injectable()
export class MyMcpProvider {
  private readonly logger = new Logger(MyMcpProvider.name);

  @McpTool({
    name: 'add',
    description: 'Adds two numbers together.',
    paramsSchema: {
      a: z.number().describe('The first number to add'),
      b: z.number().describe('The second number to add'),
      optionalMessage: z
        .string()
        .nullable()
        .optional()
        .describe('An optional message'),
    },
  })
  addTool(
    params: { a: number; b: number; optionalMessage?: string },
    extra: RequestHandlerExtra<Request, Notification>,
  ): CallToolResult {
    const clientId =
      extra.authInfo?.clientId ?? extra.sessionId ?? 'Unknown Client';
    this.logger.log(
      `Tool 'add' called by client ${clientId} with params: ${JSON.stringify(params)}`,
    );

    const sum = params.a + params.b;
    let resultText = `The sum is ${sum}.`;
    if (params.optionalMessage) {
      resultText += ` Your message: ${params.optionalMessage}`;
    }
    return {
      content: [{ type: 'text', text: resultText }],
    };
  }
}

使用 @McpResource 装饰器定义固定 MCP 资源和模板 MCP 资源:

MCP 资源示例
import {
  RequestHandlerExtra,
  McpResource,
  ReadResourceResult,
  Variables,
} from '@bamada/nestjs-mcp';
import { Injectable, Logger } from '@nestjs/common';
import { Request, Notification } from '@modelcontextprotocol/sdk/types';

@Injectable()
export class MyMcpProvider {
  private readonly logger = new Logger(MyMcpProvider.name);

  @McpResource({
    name: 'static-config',
    uri: 'mcp://my-app/config/settings.json',
    description: 'Provides static configuration settings.',
  })
  getStaticConfig(
    uri: string,
    extra: RequestHandlerExtra<Request, Notification>,
  ): ReadResourceResult {
    this.logger.log(`Resource 'static-config' requested for URI: ${uri}`);
    const clientId =
      extra.authInfo?.clientId ?? extra.sessionId ?? 'Unknown Client';
    return {
      contents: [
        {
          type: 'application/json',
          text: JSON.stringify({
            theme: 'dark',
            featureFlags: ['newUI'],
            clientId,
          }),
          uri: uri.toString(),
        },
      ],
    };
  }

  // Template Resource
  @McpResource({
    name: 'user-profile',
    uriTemplate: 'mcp://my-app/users/{userId}/profile',
    description: 'Provides user profile data based on userId.',
  })
  getUserProfile(
    uri: string,
    variables: Variables,
    extra: RequestHandlerExtra<Request, Notification>,
  ): ReadResourceResult {
    const clientId =
      extra.authInfo?.clientId ?? extra.sessionId ?? 'Unknown Client';
    const userId = Array.isArray(variables.userId)
      ? variables.userId.join(', ')
      : String(variables.userId);
    this.logger.log(`Resource 'user-profile' requested for userId: ${userId}`);
    // Fetch user data based on userId...
    const userProfile = {
      id: userId,
      name: `User ${userId}`,
      email: `${userId}@example.com`,
      clientId,
    };
    return {
      contents: [
        {
          type: 'application/json',
          text: JSON.stringify(userProfile),
          uri: uri.toString(),
        },
      ],
    };
  }
}

使用 @McpPrompt 装饰器创建 MCP 提示词处理程序:

MCP 提示词示例
import { McpPrompt, RequestHandlerExtra } from '@bamada/nestjs-mcp';
import { Injectable, Logger } from '@nestjs/common';
import {
  Request,
  Notification,
  GetPromptResult,
} from '@modelcontextprotocol/sdk/types';

@Injectable()
export class MyMcpProvider {
  private readonly logger = new Logger(AppProvider.name);

  @McpPrompt({
    name: 'greetingPrompt',
    description: 'Generates a personalized greeting message',
    arguments: [
      {
        name: 'userName',
        description: 'The name of the person to greet',
        required: true,
      },
      {
        name: 'style',
        description: 'Greeting style (e.g., formal, casual)',
        required: false,
      },
    ],
  })
  generateGreeting(
    params: { userName: string; style?: string },
    extra: RequestHandlerExtra<Request, Notification>,
  ): GetPromptResult {
    this.logger.log(
      `Prompt 'greetingPrompt' called with params: ${JSON.stringify(params)}`,
    );
    const clientId =
      extra.authInfo?.clientId ?? extra.sessionId ?? 'Unknown Client';
    const greeting = params.style === 'formal' ? 'Greetings' : 'Hello';
    return {
      messages: [
        {
          role: 'assistant',
          content: {
            type: 'text',
            text: `${greeting}, ${params.userName}! Welcome. How can I assist you today? clientId: ${clientId}`,
          },
        },
      ],
      // Optional: context, toolCalls, toolResult etc.
    };
  }
}
  1. 使用 StderrLogger(适用于 STDIO Transport): 如果使用 TransportType.STDIO,请确保 NestJS 日志输出到 stderr,以免干扰通过 stdout 进行的 MCP 通信。

    import 'reflect-metadata';
    import { NestFactory } from '@nestjs/core';
    import { AppModule } from './app.module';
    import { StderrLogger } from '@bamada/nestjs-mcp';
    async function bootstrap() {
      const app = await NestFactory.create(AppModule, {
        // Use the StderrLogger IF using STDIO transport
        logger: new StderrLogger(),
      });
      await app.listen(3000); // Or app.init() for non-HTTP apps
      console.error('NestJS application started...'); // Log to stderr
    }
    bootstrap();
    
  2. 运行应用程序: 模块会发现带有装饰器的提供程序方法,并将其注册到底层 MCP 服务。根据配置的传输方式,它会通过 STDIO 监听,或通过 McpHttpController 暴露端点(默认:/api/mcp/sse 和 /api/mcp/messages)。

⚙️ 配置

使用 McpModule.forRoot(options) 或 McpModule.forRootAsync(options) 配置模块。

McpModuleOptions:

import { Implementation } from '@modelcontextprotocol/sdk/types';
import { ServerOptions } from '@modelcontextprotocol/sdk/server';
export enum TransportType {
  STDIO = 'stdio',
  SSE = 'sse',
  NONE = 'none',
}
export interface McpModuleOptions {
  /**
   * Required. Server implementation information.
   */
  serverInfo: Implementation;
  /**
   * Optional. Server configuration options passed to the MCP SDK's McpServer.
   * @see https://github.com/modelcontextprotocol/tsp-sdk/blob/main/server/src/server_options.ts
   */
  serverOptions?: ServerOptions;
  /**
   * Optional. Selects the transport mechanism.
   * - `STDIO`: For CLI tools, uses stdin/stdout for MCP, logs to stderr (use StderrLogger).
   * - `SSE`: Assumes HTTP/SSE transport via McpHttpController (default path /api/mcp).
   * - `NONE`: No transport is automatically managed by this module (e.g., for custom transport).
   */
  transport?: TransportType;
}

同步配置(.forRoot)

import { McpModule, TransportType } from '@bamada/nestjs-mcp';
@Module({
  imports: [
    McpModule.forRoot({
      serverInfo: { name: 'my-mcp-server', version: '0.1.0' },
      transport: TransportType.SSE,
      serverOptions: { maxConnections: 10 },
    }),
  ],
})
export class AppModule {}

异步配置(.forRootAsync)

适用于注入 ConfigService 或使用工厂函数。

import { McpModule, TransportType } from '@bamada/nestjs-mcp';
import { ConfigModule, ConfigService } from '@nestjs/config';
@Module({
  imports: [
    ConfigModule.forRoot(),
    McpModule.forRootAsync({
      imports: [ConfigModule],
      useFactory: (configService: ConfigService) => ({
        serverInfo: {
          name: configService.get<string>(
            'MCP_SERVER_NAME',
            'default-mcp-server',
          ),
          version: configService.get<string>('APP_VERSION', '0.0.1'),
        },
        transport: configService.get<TransportType>(
          'MCP_TRANSPORT',
          TransportType.SSE,
        ),
        serverOptions: {
          // Example: get options from config
          maxConnections: configService.get<number>('MCP_MAX_CONNECTIONS'),
        },
      }),
      inject: [ConfigService],
    }),
  ],
})
export class AppModule {}

📜 API 参考(装饰器)

这些装饰器应用于 NestJS 提供程序(服务)中的方法。

@McpResource(options: ResourceOptions)

将方法标记为 MCP 资源处理程序。该方法会接收请求的 uri、提取出的 variables(用于模板)和 extra 上下文,并应返回 ReadResourceResult。

  • options:符合 ResourceOptions 的对象(可以是 FixedResourceOptions 或 TemplateResourceOptions):
    • name:(string)必需。资源的唯一标识符。
    • description?:(string)可选描述。
    • uri:(string)固定资源必需。资源的确切 URI。
    • uriTemplate:(string | ResourceTemplate)模板资源必需。URI 模式(例如,/users/{id})。
    • metadata?:(ResourceMetadata)可选元数据,例如 contentType、schema 等。
@McpTool(options: ToolOptions)

将方法标记为 MCP 工具。该方法会接收经过验证的 params 对象(符合 paramsSchema)和 extra 上下文,并应返回 CallToolResult。

  • options:符合 ToolOptions 的对象:
    • name:(string)必需。工具的唯一标识符。
    • description?:(string)对工具功能的可选描述。
    • paramsSchema?:(ZodRawShape)可选。定义工具输入参数的 Zod 原始形状对象。键为参数名称,值为 Zod schema(例如,z.string()、z.number().optional())。在 Zod schema 上使用 .describe(),可为 MCP 客户端提供描述。
@McpPrompt(options: PromptType)

将方法装饰为 MCP 提示词处理程序。该方法会接收经过验证的 params 对象(与 arguments 定义相匹配)和 extra 上下文,并应返回一个 GetPromptResult。

  • options:符合 @modelcontextprotocol/sdk/types.js 中 Prompt 类型的对象:
    • name:(string)必填。提示词的唯一标识符。
    • description?:(string)可选描述。
    • arguments?:(Array)可选数组,用于定义提示词的输入参数:
      • name:(string)必填。参数名称(必须与处理程序 params 对象中的键相匹配)。
      • description?:(string)可选参数描述。
      • required?:(boolean)参数是否必填(默认值:false)。
    • input?:(PromptInputDefinition)定义预期输入格式(例如文本、图像)。
    • output?:(PromptOutputDefinition)定义预期输出格式。

🛠️ 开发与贡献

欢迎贡献!请按照以下步骤操作:

  1. **Fork 并克隆:**Fork 此仓库并将其克隆到本地。

    git clone https://github.com/YOUR_USERNAME/nestjs-mcp.git
    cd nestjs-mcp
    
  2. 安装依赖项:

    npm install
    # or
    yarn install
    
  3. **开发:**在 src 目录中进行修改。使用 npm link 或 Yarn/PNPM 工作区,在使用该项目的应用中进行本地开发。

  4. **Lint 与格式化:**确保代码质量和一致性。

    npm run lint
    npm run format
    
  5. **构建:**将 TypeScript 编译为 JavaScript。

    npm run build
    
  6. **测试:**运行测试套件。

    npm test
    # For coverage:
    npm run test:cov
    
  7. **提交并推送:**提交修改,并附上清晰的说明。

  8. **创建 Pull Request:**针对原始仓库的 main 分支提交 PR。

请通过 GitHub Issues 页面报告错误或建议新功能。

📜 许可证

本项目采用 MIT 许可证授权;详情请参阅 LICENSE 文件。

🔗 链接

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。