ruwanb/nestjs-mcp-server

Transforma uma aplicação NestJS 12 em um servidor do Model Context Protocol com @nestjs/mcp — McpModule.forRoot/forRootAsync, @Tool com entrada/saída Zod ou Standard Schema e anotações, @Resource e @ResourceTemplate, @Prompt, McpContext (authInfo, sinal, reportProgress, inputRequired elicitation), guards, interceptors, pipes e filtros com McpExecutionContext, erros de ferramentas vs erros JSON-RPC, autenticação OAuth 2.1 com McpTokenVerifier, escopos e integração @nestjs/authentication.

O que é nestjs-mcp-server?

Este skill transforma uma aplicação NestJS 12 em um servidor do Model Context Protocol (MCP) usando o módulo @nestjs/mcp. Ele permite que você exponha ferramentas (ações), recursos (dados) e prompts (modelos) através do protocolo MCP, que podem ser consumidos por assistentes de IA como Claude Code, Cursor ou Codex. Você define provedores (serviços) comuns, decora métodos com @Tool, @Resource, @ResourceTemplate e @Prompt, e o módulo gerencia automaticamente a validação de entrada (usando Zod 4.2+, Standard Schema, ArkType, Valibot ou JSON Schema via fromJsonSchema()), o contexto de chamada (McpContext com authInfo, sinal de aborto, relatório de progresso e elicitação de entrada), e os transportes (HTTP SSE, Streamable HTTP ou stdio). Inclui suporte completo a OAuth 2.1 resource server, guards, interceptors, pipes, filtros, tratamento diferenciado de erros (ferramentas retornam isError, recursos/prompts usam erros JSON-RPC), e integração com @nestjs/authentication e @nestjs/authorization. O servidor falha rapidamente em nomes duplicados ou esquemas inválidos, e fornece um inspector MCP para teste. É útil para desenvolvedores NestJS que desejam expor seus serviços de forma padronizada para agentes de IA, mantendo a segurança e a estrutura familiar do framework.

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

Installed? Explore more Programação e Desenvolvimento skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

Servidor MCP NestJS

npm i @nestjs/mcp @modelcontextprotocol/server zod
npm i -D @modelcontextprotocol/client          # cliente de teste em memória

Zod 4.2+ e ArkType funcionam diretamente; Valibot através de toStandardJsonSchema(); um JSON Schema existente via fromJsonSchema(). Esquemas do Zod 3 são recusados na inicialização.

McpModule.forRoot({
  name: 'cat-store', version: '1.0.0',
  instructions: 'Consulte pedidos com find-order antes de alterá-los.',   // informa ao modelo como usar o servidor
}),

app.listen(3000) então expõe o endpoint em /mcp. Inspecione-o com npx @modelcontextprotocol/inspector. Na inicialização, o módulo escaneia os provedores (todos os módulos, ou apenas include) em busca de métodos decorados. Ele falha rapidamente em nomes duplicados, esquemas inválidos ou scopes declarados sem auth.

Primitivas: métodos em provedores

PrimitivaDecoradorQuem usaChamada com
Ferramenta (uma ação)@Tool({ name, description, inputSchema, outputSchema?, annotations?, scopes? })o modeloentrada validada, ctx
Recurso (dados)@Resource({ uri, mimeType }) / @ResourceTemplate({ uriTemplate: 'orders://{orderId}' })o cliente, muitas vezes anexado pelo usuário{} ou as variáveis do template, ctx (ctx.uri)
Prompt (um modelo)@Prompt({ name, description, argsSchema })o usuário escolhe umargumentos validados, ctx
@Injectable()                                          // um provedor comum, listado em providers de um módulo
export class OrdersTools {
  constructor(private readonly orders: OrdersService) {}

  @Tool({
    name: 'find-order',
    description: 'Encontre um pedido pelo seu id, ou os pedidos mais recentes de um cliente pelo email',
    inputSchema: z.object({
      orderId: z.string().optional().describe('O id do pedido, como ord_123'),
      email: z.email().optional().describe('O endereço de email do cliente'),
    }),
    annotations: { readOnlyHint: true },               // o cliente pode executá-lo sem perguntar ao usuário
  })
  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('Nenhum pedido encontrado');   // → resultado da ferramenta com isError: true
    return found;
  }

  @Tool({
    name: 'change-address',
    description: 'Altere o endereço de entrega de um pedido que ainda não foi 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: uma string de ferramenta se torna um bloco de texto. Um objeto se torna structuredContent se houver um outputSchema, e texto JSON caso contrário. Retorne um CallToolResult você mesmo para imagens, áudio ou recursos incorporados; undefined dá um resultado vazio. A string de um recurso é enviada como texto, um Buffer/Uint8Array como blob, um objeto como JSON. A string de um prompt se torna uma mensagem de usuário; um array retorna PromptMessage[]. Nomes de ferramentas têm 1–128 caracteres de A-Z a-z 0-9 _ - . e, por padrão, são o nome do método.

Escreva nomes, descrições e textos .describe() para o modelo. São tudo o que ele conhece: diga o que a ferramenta faz, quando usá-la, se ela altera algo, e dê um valor de exemplo para cada argumento. Use recursos para conteúdo carregado como está; use ferramentas para consultas que recebem argumentos.

Contexto de chamada

ctx: McpContext tem estas propriedades:

  • kind, name, uri
  • authInfo (clientId, scopes, expiresAt, extra)
  • request (a requisição da plataforma; undefined sobre stdio)
  • requestInfo
  • transport ('http' | 'stdio')
  • era ('modern' 2026-07-28 / 'legacy' 2025)
  • signal: passe para fetch e chamadas de banco de dados; chame throwIfAborted() em loops
  • reportProgress(progress, total?, message?)
  • server (contexto do SDK)

Para perguntar ao usuário (confirmações, valores ausentes), retorne inputRequired({ inputRequests: { confirm: inputRequired.elicit({ message, requestedSchema }) } }). Na repetição, leia a resposta com acceptedContent(ctx.server.mcpReq.inputResponses, 'confirm', Schema). Sobre HTTP, isso só funciona para clientes 2026-07-28, porque o endpoint é sem estado. Sobre stdio, funciona para ambas as eras.

Provedores permanecem singletons. Leia dados por chamada de ctx, não de provedores com escopo de requisição.

Aprimoradores e erros

  • Guards, interceptors, pipes e filtros funcionam como em rotas, com context.getType() === 'mcp'. McpExecutionContext.create(context) fornece getInput(), getContext(), getKind(), getName(), getRequest() e getAuthInfo().
  • Aprimoradores globais executam em chamadas MCP e na requisição HTTP que as transporta. Aprimoradores exclusivos HTTP devem verificar context.getType() === 'http' antes de chamar switchToHttp().
  • Um ValidationPipe({ transform: true }) global valida a entrada tipada por DTO. Decoradores personalizados de parâmetro vêm depois de (input, ctx, @CurrentUser() user).
  • Erros lançados por ferramentas se tornam resultados isError: true dos quais o modelo pode se recuperar. Uma HttpException mantém sua mensagem; qualquer outro erro se torna Internal server error e é registrado.
  • Recursos e prompts respondem com erros JSON-RPC: NotFoundException → recurso não encontrado, BadRequestException → parâmetros inválidos.
  • Filtros: verifique host.getType<McpContextType>() === 'mcp'. O que o filtro retornar se torna o resultado; para enviar um erro JSON-RPC, lance um ProtocolError.

Autorização (servidores acessíveis por rede)

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 = público-alvo do token
      authorizationServers: [config.get('IDENTITY_PROVIDER_URL')],
      scopesSupported: ['orders:read', 'orders:write'],
    },
  }),
}),
  • O servidor é um servidor de recursos OAuth 2.1: ele verifica tokens e nunca os emite.
  • Ele serve metadados RFC 9728 em /.well-known/oauth-protected-resource/mcp. Requisições sem token recebem um 401 com um desafio WWW-Authenticate: Bearer, que inicia o login do cliente.
  • Verificadores:
    • JWTs: use JwtVerifier do @nestjs/authentication (verifica assinatura, emissor, público e expiração).
    • Tokens opacos: chame o endpoint de introspecção RFC 7662.
    • O AuthInfo retornado deve carregar resource === auth.resource e um expiresAt futuro, caso contrário o token é recusado com 401.
    • Lance um erro 401 para recusar um token; qualquer outro erro se torna 500.
  • Ausência de scopes em uma primitiva → 403 com desafio de step-up. auth.requiredScopes se aplica a todas as requisições.
  • Com @nestjs/authentication ≥ 0.1.3 e @nestjs/authorization ≥ 0.1.1:
    • Seus guards globais autenticam cada chamada, e @CurrentUser() e @Can(Policy, 'action') funcionam em ferramentas.
    • Defina decorators: [Public()] no endpoint para que auth lide com o desafio.
    • Uma ferramenta sem @Can() é negada.
  • Servidores exclusivamente internos podem pular OAuth e usar decorators: [UseGuards(ApiKeyGuard)] no endpoint.

Transportes

  • HTTP Streamable é servido por padrão; path: false desabilita. O endpoint é um controlador normal, então prefixo global, versionamento (endpointVersion), middleware e CORS se aplicam. É sem estado: não são necessárias sessões adesivas. Ambas as eras do protocolo são servidas (http.legacy: 'reject' serve apenas 2026-07-28). http.responseMode pode ser 'auto', 'json' ou 'sse'.
  • stdio, para assistentes que iniciam um processo local: crie stdio.ts com NestFactory.createApplicationContext(AppModule, { logger: new McpStdioLogger() }), depois await app.get(McpStdioServer).listen(); await app.close();. Qualquer outra coisa escrita no stdout quebra o protocolo. Credenciais vêm de process.env; escopos não são verificados, mas guards executam.

Consulte references/mcp-reference.md para a tabela completa de opções, configurações de produção, configuração do cliente stdio, padrões de teste e mapa de migração.

Fluxo de trabalho

  • 1. Instalar; verificar Zod 4.2+; McpModule.forRoot com name, version, instructions.
  • 2. Adicionar ferramentas, recursos e prompts em provedores, com descrições orientadas ao modelo, .describe() em cada argumento, readOnlyHint em ferramentas somente leitura, e outputSchema quando o resultado é estruturado.
  • 3. Garantir que aprimoradores globais exclusivos HTTP verifiquem getType(); adicionar guards e filtros MCP conforme necessário.
  • 4. Servidores acessíveis por rede: adicionar auth (verificador, recurso, servidores de autorização, escopos), allowedHosts/allowedOrigins, cabeçalhos CORS e limite de corpo. Servidores locais: adicionar stdio.ts com McpStdioLogger.
  • 5. Testes: testar métodos unitariamente; executar e2e com connectMcpClient(moduleRef, { token }) contra um verificador de teste; testar a rota HTTP sobre um socket. Compilar, lintar, executar testes, iniciar o app e listar ferramentas com o Inspector.

Arquivos de referência

  • references/mcp-reference.md: opções do módulo, opções do decorador, endurecimento HTTP (Origin, Host, cabeçalhos CORS, tamanho do corpo), configuração do cliente stdio, nomenclatura NestJS Observe, checklist de produção, testes (tokens e escopos, eras do protocolo, verificações do McpRegistry, testes HTTP e stdio), e migração de @rekog/mcp-nest 1.x/2.x. Leia ao configurar produção, escrever testes ou migrar.

Fontes: Visão geral · Primitivas · Transportes · Testes · Migração

Habilidades Relacionadas