CommunityProgramación y desarrollogithub.com

ruwanb/nestjs-mcp-server

Convierte una aplicación NestJS 12 en un servidor del Protocolo de Contexto de Modelo (MCP) con @nestjs/mcp — McpModule.forRoot/forRootAsync, @Tool con esquemas Zod o Standard Schema y anotaciones, @Resource y @ResourceTemplate, @Prompt, McpContext (authInfo, señal, reportProgress, obtención de inputRequired), guards, interceptores, pipes y filtros con McpExecutionContext, errores de herramienta vs errores JSON-RPC, autenticación de servidor de recursos OAuth 2.1 con McpTokenVerifier, ámbitos e integración con @nestjs/authentication.

¿Qué es nestjs-mcp-server?

Este skill transforma aplicaciones NestJS 12 en servidores del Model Context Protocol (MCP), permitiendo que modelos de IA interactúen con la aplicación a través de herramientas, recursos y plantillas. Soporta decoradores como @Tool, @Resource, @ResourceTemplate y @Prompt, con validación de entrada mediante Zod (4.2+), ArkType, Valibot o esquemas JSON estándar. Incluye manejo de contexto con McpContext, que proporciona información de autenticación, señales de cancelación y notificaciones de progreso. Además, ofrece seguridad OAuth 2.1 como servidor de recursos, con verificación de tokens JWT u opacos, y soporte para ámbitos. Es compatible con transportes HTTP streamable y stdio, y se integra perfectamente con los guards, interceptores, pipes y filtros de NestJS, permitiendo una depuración y control de errores exhaustivos. Ideal para desarrolladores que desean exponer la lógica de negocio de NestJS como capacidades consumibles por modelos de lenguaje.

Compatible con✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/ruwanb/nestjs-boost/tree/HEAD/skills/nestjs-mcp-server

Installed? Explore more Programación y desarrollo skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

Servidor NestJS MCP

npm i @nestjs/mcp @modelcontextprotocol/server zod
npm i -D @modelcontextprotocol/client          # cliente de prueba en memoria

Zod 4.2+ y ArkType funcionan directamente; Valibot a través de toStandardJsonSchema(); un esquema JSON existente mediante fromJsonSchema(). Los esquemas de Zod 3 son rechazados al inicio.

McpModule.forRoot({
  name: 'cat-store', version: '1.0.0',
  instructions: 'Busca los pedidos con find-order antes de modificarlos.',   // le indica al modelo cómo usar el servidor
}),

app.listen(3000) luego sirve el endpoint en /mcp. Inspecciónalo con npx @modelcontextprotocol/inspector. Al iniciar, el módulo escanea los providers (todos los módulos, o solo include) en busca de métodos decorados. Falla rápidamente en nombres duplicados, esquemas inválidos o scopes declarados sin auth.

Primitivas: métodos en providers

PrimitivaDecoradorQuién la usaSe llama con
Herramienta (una acción)@Tool({ name, description, inputSchema, outputSchema?, annotations?, scopes? })el modeloentrada validada, ctx
Recurso (datos)@Resource({ uri, mimeType }) / @ResourceTemplate({ uriTemplate: 'orders://{orderId}' })el cliente, a menudo adjuntado por el usuario{} o las variables de la plantilla, ctx (ctx.uri)
Plantilla (un prompt)@Prompt({ name, description, argsSchema })el usuario elige unaargs validados, ctx
@Injectable()                                          // un provider simple, listado en los providers de un módulo
export class OrdersTools {
  constructor(private readonly orders: OrdersService) {}

  @Tool({
    name: 'find-order',
    description: 'Encuentra un pedido por su id, o los últimos pedidos de un cliente por email',
    inputSchema: z.object({
      orderId: z.string().optional().describe('El id del pedido, por ejemplo ord_123'),
      email: z.email().optional().describe('La dirección de correo electrónico del cliente'),
    }),
    annotations: { readOnlyHint: true },               // el cliente puede ejecutarlo sin preguntar al usuario
  })
  async find({ orderId, email }: { orderId?: string; email?: string }) {
    const found = orderId ? [await this.orders.findById(orderId)] : await this.orders.latestFor(email!);
    if (!found[0]) throw new NotFoundException('No existe tal pedido');   // → resultado de herramienta con isError: true
    return found;
  }

  @Tool({
    name: 'change-address',
    description: 'Cambia la dirección de entrega de un pedido que aún no se ha enviado',
    inputSchema: z.object({ orderId: z.string(), address: z.string().min(10) }),
    outputSchema: z.object({ orderId: z.string(), address: z.string(), status: z.string() }),
    scopes: ['orders:write'],
  })
  async changeAddress({ orderId, address }: { orderId: string; address: string }, ctx: McpContext) {
    const o = await this.orders.changeAddress(orderId, address, ctx.authInfo?.clientId);
    return { orderId: o.id, address: o.address, status: o.status };   // → structuredContent + texto JSON
  }
}

Valores de retorno: el string de una herramienta se convierte en un bloque de texto. Un objeto se convierte en structuredContent si hay un outputSchema, y en texto JSON en caso contrario. Devuelve un CallToolResult tú mismo para imágenes, audio o recursos incrustados; undefined da un resultado vacío. El string de un recurso se envía como texto, un Buffer/Uint8Array como un blob, un objeto como JSON. El string de una plantilla se convierte en un mensaje de usuario; un array devuelve PromptMessage[]. Los nombres de las herramientas tienen entre 1 y 128 caracteres de A-Z a-z 0-9 _ - . y por defecto toman el nombre del método.

Escribe nombres, descripciones y textos .describe() para el modelo. Es todo lo que conoce: di qué hace la herramienta, cuándo usarla, si cambia algo, y proporciona un valor de ejemplo para cada argumento. Usa recursos para contenido que se carga tal cual; usa herramientas para búsquedas que toman argumentos.

Contexto de llamada

ctx: McpContext tiene estas propiedades:

  • kind, name, uri
  • authInfo (clientId, scopes, expiresAt, extra)
  • request (la solicitud de la plataforma; undefined sobre stdio)
  • requestInfo
  • transport ('http' | 'stdio')
  • era ('modern' 2026-07-28 / 'legacy' 2025)
  • signal: pásalo a fetch y a llamadas a BD; llama a throwIfAborted() en bucles
  • reportProgress(progress, total?, message?)
  • server (contexto del SDK)

Para preguntar al usuario (confirmaciones, valores faltantes), devuelve inputRequired({ inputRequests: { confirm: inputRequired.elicit({ message, requestedSchema }) } }). En el reintento, lee la respuesta con acceptedContent(ctx.server.mcpReq.inputResponses, 'confirm', Schema). Sobre HTTP esto solo funciona para clientes de 2026-07-28, porque el endpoint no tiene estado. Sobre stdio funciona para ambas eras.

Los providers siguen siendo singletons. Lee los datos por llamada de ctx, no de providers con ámbito de solicitud.

Mejoradores y errores

  • Guards, interceptores, pipes y filtros se ejecutan como en las rutas, con context.getType() === 'mcp'. McpExecutionContext.create(context) proporciona getInput(), getContext(), getKind(), getName(), getRequest() y getAuthInfo().
  • Los mejoradores globales se ejecutan en las llamadas MCP y en la solicitud HTTP portadora. Los mejoradores solo HTTP deben verificar context.getType() === 'http' antes de llamar a switchToHttp().
  • Un ValidationPipe({ transform: true }) global valida la entrada tipada por DTO. Los decoradores de parámetros personalizados van después de (input, ctx, @CurrentUser() user).
  • Los errores lanzados por herramientas se convierten en resultados isError: true de los que el modelo puede recuperarse. Un HttpException conserva su mensaje; cualquier otro error se convierte en Error interno del servidor y se registra.
  • Los recursos y plantillas responden con errores JSON-RPC: NotFoundException → recurso no encontrado, BadRequestException → parámetros inválidos.
  • Filtros: verifica host.getType<McpContextType>() === 'mcp'. Lo que devuelva el filtro se convierte en el resultado; para enviar un error JSON-RPC, lanza un ProtocolError.

Autorización (servidores accesibles por red)

McpModule.forRootAsync({
  imports: [ConfigModule, AuthModule],
  inject: [ConfigService, IdentityProviderTokenVerifier],
  useFactory: (config: ConfigService, verifier: IdentityProviderTokenVerifier) => ({
    name: 'cat-store', version: config.get('VERSION'),
    auth: {
      verifier,                                               // implementa McpTokenVerifier.verifyAccessToken(token) → AuthInfo | null
      resource: config.get('MCP_RESOURCE'),                   // URL pública canónica = audiencia del token
      authorizationServers: [config.get('IDENTITY_PROVIDER_URL')],
      scopesSupported: ['orders:read', 'orders:write'],
    },
  }),
}),
  • El servidor es un servidor de recursos OAuth 2.1: verifica tokens y nunca los emite.
  • Sirve metadatos RFC 9728 en /.well-known/oauth-protected-resource/mcp. Las solicitudes sin token reciben un 401 con un desafío WWW-Authenticate: Bearer, que inicia el inicio de sesión del cliente.
  • Verificadores:
    • JWT: usa JwtVerifier de @nestjs/authentication (verifica firma, emisor, audiencia y caducidad).
    • Tokens opacos: llama al endpoint de introspección RFC 7662.
    • El AuthInfo devuelto debe contener resource === auth.resource y un expiresAt futuro; de lo contrario, el token es rechazado con 401.
    • Lanza un error 401 para rechazar un token; cualquier otro error se convierte en 500.
  • scopes faltantes en una primitiva → 403 con desafío de escalado. auth.requiredScopes se aplica a cada solicitud.
  • Con @nestjs/authentication ≥ 0.1.3 y @nestjs/authorization ≥ 0.1.1:
    • Sus guards globales autentican cada llamada, y @CurrentUser() y @Can(Policy, 'action') funcionan en las herramientas.
    • Establece decorators: [Public()] en el endpoint para que auth maneje el desafío.
    • Una herramienta sin @Can() es denegada.
  • Los servidores solo internos pueden omitir OAuth y usar decorators: [UseGuards(ApiKeyGuard)] en el endpoint.

Transportes

  • HTTP streamable se sirve por defecto; path: false lo desactiva. El endpoint es un controlador normal, por lo que se aplican el prefijo global, el versionado (endpointVersion), el middleware y CORS. No tiene estado: no se necesitan sesiones adhesivas. Se sirven ambas eras del protocolo (http.legacy: 'reject' sirve solo 2026-07-28). http.responseMode puede ser 'auto', 'json' o 'sse'.
  • stdio, para asistentes que lanzan un proceso local: crea stdio.ts con NestFactory.createApplicationContext(AppModule, { logger: new McpStdioLogger() }), luego await app.get(McpStdioServer).listen(); await app.close();. Cualquier otra cosa escrita en stdout rompe el protocolo. Las credenciales provienen de process.env; los ámbitos no se verifican, pero los guards se ejecutan.

Consulta references/mcp-reference.md para la tabla completa de opciones, configuraciones de producción, la configuración del cliente stdio, patrones de prueba y el mapa de migración.

Flujo de trabajo

  • 1. Instala; verifica Zod 4.2+; McpModule.forRoot con name, version, instructions.
  • 2. Añade herramientas, recursos y plantillas en providers, con descripciones orientadas al modelo, .describe() en cada argumento, readOnlyHint en herramientas de solo lectura, y outputSchema cuando el resultado sea estructurado.
  • 3. Asegúrate de que los mejoradores globales solo HTTP verifiquen getType(); añade guards y filtros MCP según sea necesario.
  • 4. Servidores accesibles por red: añade auth (verificador, recurso, servidores de autorización, ámbitos), allowedHosts/allowedOrigins, cabeceras CORS y el límite del body. Servidores locales: añade stdio.ts con McpStdioLogger.
  • 5. Pruebas: prueba métodos unitariamente directamente; ejecuta pruebas e2e con connectMcpClient(moduleRef, { token }) contra un verificador de prueba; prueba la ruta HTTP a través de un socket. Compila, lint, ejecuta pruebas, inicia la aplicación y lista las herramientas con el Inspector.

Archivos de referencia

  • references/mcp-reference.md: opciones del módulo, opciones de decoradores, endurecimiento HTTP (Origin, Host, cabeceras CORS, tamaño del body), configuración del cliente stdio, convención de nombres Observe de NestJS, lista de verificación de producción, pruebas (tokens y ámbitos, eras del protocolo, comprobaciones de McpRegistry, pruebas HTTP y stdio) y migración desde @rekog/mcp-nest 1.x/2.x. Léelo al configurar producción, escribir pruebas o migrar.

Fuentes: Visión general · Primitivas · Transportes · Pruebas · Migración

Skills relacionados