Community코딩 & 개발github.com

ruwanb/nestjs-mcp-server

NestJS 12 애플리케이션을 Model Context Protocol 서버로 전환합니다. @nestjs/mcp를 사용하여 @Tool, @Resource, @Prompt 데코레이터와 Zod/Standard Schema를 지원하며, 인증, 가드, 인터셉터, 파이프, 필터 등을 통합합니다.

nestjs-mcp-server란 무엇인가요?

이 스킬은 NestJS 12 애플리케이션을 Model Context Protocol(MCP) 서버로 변환하는 @nestjs/mcp 패키지를 설명합니다. 개발자는 `@Tool`, `@Resource`, `@ResourceTemplate`, `@Prompt` 데코레이터를 사용하여 NestJS 프로바이더에 MCP 프리미티브를 정의할 수 있습니다. Zod 4.2+, ArkType, Valibot 등 다양한 스키마 라이브러리를 지원하며, OAuth 2.1 리소스 서버 인증, McpContext를 통한 호출 컨텍스트 접근, 그리고 가드/인터셉터/파이프/필터 같은 NestJS 생태계의 강력한 기능을 MCP 계층에 통합할 수 있습니다. Streamable HTTP와 stdio 두 가지 전송 방식을 지원하며, 내부용 서버와 네트워크 접근 가능 서버 모두에 적합합니다. 이 스킬은 MCP 서버를 처음 구축하거나 기존 NestJS 애플리케이션에 MCP 기능을 추가하려는 개발자에게 유용합니다. 사용 예시로는 주문 조회, 주소 변경 등의 도구와 고객 데이터 리소스를 제공하며, 모든 설명은 AI 모델이 이해할 수 있도록 최적화되어야 합니다.

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

Installed? Explore more 코딩 & 개발 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

NestJS MCP 서버

npm i @nestjs/mcp @modelcontextprotocol/server zod
npm i -D @modelcontextprotocol/client          # 인메모리 테스트 클라이언트

Zod 4.2+와 ArkType은 직접 지원됩니다. Valibot은 toStandardJsonSchema()를 통해, 기존 JSON Schema는 fromJsonSchema()를 통해 사용할 수 있습니다. Zod 3 스키마는 시작 시 거부됩니다.

McpModule.forRoot({
  name: 'cat-store', version: '1.0.0',
  instructions: '변경 전에 find-order로 주문을 조회하세요.',   // 모델이 서버를 사용하는 방법을 알려줌
}),

app.listen(3000) 후에 엔드포인트는 /mcp에서 제공됩니다. npx @modelcontextprotocol/inspector로 검사할 수 있습니다. 시작 시 모듈은 프로바이더(모든 모듈 또는 include에 지정된 모듈)에서 데코레이트된 메서드를 스캔합니다. 중복된 이름, 잘못된 스키마 또는 auth 없이 선언된 scopes가 있으면 빠르게 실패합니다.

프리미티브: 프로바이더의 메서드

프리미티브데코레이터사용자호출 시 전달되는 인자
Tool (액션)@Tool({ name, description, inputSchema, outputSchema?, annotations?, scopes? })모델검증된 입력, ctx
Resource (데이터)@Resource({ uri, mimeType }) / @ResourceTemplate({ uriTemplate: 'orders://{orderId}' })클라이언트 (주로 사용자가 첨부){} 또는 템플릿 변수, ctx (ctx.uri)
Prompt (템플릿)@Prompt({ name, description, argsSchema })사용자 선택검증된 인자, ctx
@Injectable()                                          // 모듈의 providers에 나열된 일반 프로바이더
export class OrdersTools {
  constructor(private readonly orders: OrdersService) {}

  @Tool({
    name: 'find-order',
    description: '주문 ID로 주문을 찾거나, 이메일로 고객의 최신 주문을 찾습니다.',
    inputSchema: z.object({
      orderId: z.string().optional().describe('주문 ID (예: ord_123)'),
      email: z.email().optional().describe('고객의 이메일 주소'),
    }),
    annotations: { readOnlyHint: true },               // 클라이언트가 사용자에게 묻지 않고 실행 가능
  })
  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('해당 주문 없음');   // → isError: true인 도구 결과
    return found;
  }

  @Tool({
    name: 'change-address',
    description: '아직 배송되지 않은 주문의 배송 주소를 변경합니다.',
    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 + JSON 텍스트
  }
}

반환 값: 도구의 문자열은 텍스트 블록이 됩니다. 객체는 outputSchema가 있으면 structuredContent가 되고, 없으면 JSON 텍스트가 됩니다. 이미지, 오디오 또는 임베디드 리소스의 경우 CallToolResult를 직접 반환합니다. undefined는 빈 결과를 반환합니다. 리소스의 문자열은 텍스트로, Buffer/Uint8Array는 블롭(blob)으로, 객체는 JSON으로 전송됩니다. 프롬프트의 문자열은 하나의 사용자 메시지가 되고, 배열은 PromptMessage[]를 반환합니다. 도구 이름은 1~128자로 A-Z a-z 0-9 _ - . 문자만 허용되며, 기본값은 메서드 이름입니다.

이름, 설명 및 .describe() 텍스트는 모델을 위해 작성하세요. 이것이 모델이 아는 전부입니다: 도구가 무엇을 하는지, 언제 사용하는지, 무언가를 변경하는지, 각 인자의 예시 값을 제공하세요. 있는 그대로 로드되는 콘텐츠에는 리소스를 사용하고, 인자가 필요한 조회에는 도구를 사용하세요.

호출 컨텍스트

ctx: McpContext에는 다음 속성이 있습니다:

  • kind, name, uri
  • authInfo (clientId, scopes, expiresAt, extra)
  • request (플랫폼 요청; stdio에서는 undefined)
  • requestInfo
  • transport ('http' | 'stdio')
  • era ('modern' 2026-07-28 / 'legacy' 2025)
  • signal: fetch 및 DB 호출에 전달하고, 루프에서 throwIfAborted() 호출
  • reportProgress(progress, total?, message?)
  • server (SDK 컨텍스트)

사용자에게 질문(확인, 누락된 값)하려면 inputRequired({ inputRequests: { confirm: inputRequired.elicit({ message, requestedSchema }) } })를 반환하세요. 재시도 시 acceptedContent(ctx.server.mcpReq.inputResponses, 'confirm', Schema)로 응답을 읽습니다. HTTP에서는 엔드포인트가 상태 비저장(stateless)이므로 2026-07-28 클라이언트에서만 작동합니다. stdio에서는 두 시대 모두 작동합니다.

프로바이더는 싱글톤으로 유지됩니다. 요청 범위 프로바이더 대신 ctx에서 호출별 데이터를 읽으세요.

인핸서(Enhancer) 및 오류

  • 가드, 인터셉터, 파이프 및 필터는 라우트와 동일하게 실행되며, context.getType() === 'mcp'입니다. McpExecutionContext.create(context)는 getInput(), getContext(), getKind(), getName(), getRequest() 및 getAuthInfo()를 제공합니다.
  • 전역 인핸서는 MCP 호출과 이를 전달하는 HTTP 요청 모두에서 실행됩니다. HTTP 전용 인핸서는 switchToHttp()를 호출하기 전에 context.getType() === 'http'를 확인해야 합니다.
  • 전역 ValidationPipe({ transform: true })는 DTO 타입의 입력을 검증합니다. 커스텀 파라미터 데코레이터는 (input, ctx, @CurrentUser() user) 뒤에 옵니다.
  • 도구에서 발생한 오류는 모델이 복구할 수 있는 isError: true 결과가 됩니다. HttpException은 메시지를 유지하고, 다른 오류는 Internal server error가 되어 로그에 기록됩니다.
  • 리소스와 프롬프트는 JSON-RPC 오류로 응답합니다: NotFoundException → 리소스를 찾을 수 없음, BadRequestException → 잘못된 파라미터.
  • 필터: host.getType<McpContextType>() === 'mcp'를 확인하세요. 필터가 반환하는 모든 것이 결과가 됩니다. JSON-RPC 오류를 보내려면 ProtocolError를 던지세요.

인증 (네트워크 접근 가능 서버)

McpModule.forRootAsync({
  imports: [ConfigModule, AuthModule],
  inject: [ConfigService, IdentityProviderTokenVerifier],
  useFactory: (config: ConfigService, verifier: IdentityProviderTokenVerifier) => ({
    name: 'cat-store', version: config.get('VERSION'),
    auth: {
      verifier,                                               // McpTokenVerifier.verifyAccessToken(token) → AuthInfo | null 구현
      resource: config.get('MCP_RESOURCE'),                   // 정식 공개 URL = 토큰 audience
      authorizationServers: [config.get('IDENTITY_PROVIDER_URL')],
      scopesSupported: ['orders:read', 'orders:write'],
    },
  }),
}),
  • 서버는 OAuth 2.1 리소스 서버입니다: 토큰을 검증하고 절대 발급하지 않습니다.
  • /.well-known/oauth-protected-resource/mcp에서 RFC 9728 메타데이터를 제공합니다. 토큰이 없는 요청은 401과 WWW-Authenticate: Bearer 챌린지를 받으며, 이는 클라이언트의 로그인을 시작합니다.
  • 검증기(Verifier):
    • JWT: @nestjs/authentication의 JwtVerifier 사용 (서명, 발급자, audience 및 만료 확인).
    • 불투명 토큰: RFC 7662 introspection 엔드포인트 호출.
    • 반환된 AuthInfo는 resource === auth.resource이고 미래의 expiresAt을 가져야 합니다. 그렇지 않으면 401로 토큰이 거부됩니다.
    • 401 오류를 던져 토큰을 거부하고, 다른 오류는 500이 됩니다.
  • 프리미티브에 scopes가 없으면 403과 단계 업(step-up) 챌린지가 발생합니다. auth.requiredScopes는 모든 요청에 적용됩니다.
  • @nestjs/authentication ≥ 0.1.3 및 @nestjs/authorization ≥ 0.1.1과 함께 사용하는 경우:
    • 전역 가드가 각 호출을 인증하고, @CurrentUser()와 @Can(Policy, 'action')이 도구에서 작동합니다.
    • 엔드포인트에 decorators: [Public()]을 설정하여 auth가 챌린지를 처리하도록 합니다.
    • @Can()이 없는 도구는 거부됩니다.
  • 내부 전용 서버는 OAuth를 건너뛰고 엔드포인트에 decorators: [UseGuards(ApiKeyGuard)]를 사용할 수 있습니다.

전송

  • Streamable HTTP가 기본으로 제공됩니다. path: false로 비활성화할 수 있습니다. 엔드포인트는 일반 컨트롤러이므로 전역 접두사, 버전 관리(endpointVersion), 미들웨어 및 CORS가 모두 적용됩니다. 상태 비저장(stateless)이므로 고정 세션(sticky session)이 필요 없습니다. 두 프로토콜 시대 모두 제공됩니다(http.legacy: 'reject'는 2026-07-28만 제공). http.responseMode는 'auto', 'json' 또는 'sse'일 수 있습니다.
  • stdio: 로컬 프로세스를 실행하는 어시스턴트용: NestFactory.createApplicationContext(AppModule, { logger: new McpStdioLogger() })로 stdio.ts를 생성하고, await app.get(McpStdioServer).listen(); await app.close();를 실행합니다. stdout에 다른 것을 쓰면 프로토콜이 손상됩니다. 자격 증명은 process.env에서 가져옵니다. 스코프는 검사되지 않지만 가드는 실행됩니다.

전체 옵션 테이블, 프로덕션 설정, stdio 클라이언트 구성, 테스트 패턴 및 마이그레이션 맵은 references/mcp-reference.md를 참조하세요.

작업 흐름

  • 1. 설치; Zod 4.2+ 확인; name, version, instructions로 McpModule.forRoot 설정.
  • 2. 프로바이더에 모델 지향 설명, 모든 인자에 .describe(), 읽기 전용 도구에 readOnlyHint, 구조화된 결과에 outputSchema를 포함하여 도구, 리소스 및 프롬프트를 추가합니다.
  • 3. HTTP 전용 전역 인핸서가 getType()을 확인하는지 확인하고, 필요에 따라 MCP 가드 및 필터를 추가합니다.
  • 4. 네트워크 접근 가능 서버: auth(검증기, 리소스, 인증 서버, 스코프), allowedHosts/allowedOrigins, CORS 헤더 및 본문 크기 제한을 추가합니다. 로컬 서버: McpStdioLogger를 사용하여 stdio.ts를 추가합니다.
  • 5. 테스트: 메서드를 직접 단위 테스트; 테스트 검증기를 사용하여 connectMcpClient(moduleRef, { token })로 e2e 테스트; 소켓을 통해 HTTP 라우트를 테스트합니다. 빌드, 린트, 테스트 실행, 앱 시작 및 Inspector로 도구 목록을 확인합니다.

참고 파일

  • references/mcp-reference.md: 모듈 옵션, 데코레이터 옵션, HTTP 강화(Origin, Host, CORS 헤더, 본문 크기), stdio 클라이언트 구성, NestJS Observe 명명, 프로덕션 체크리스트, 테스트(토큰 및 스코프, 프로토콜 시대, McpRegistry 검사, HTTP 및 stdio 테스트), 및 @rekog/mcp-nest 1.x/2.x에서 마이그레이션. 프로덕션 구성, 테스트 작성 또는 마이그레이션 시 참조하세요.

출처: 개요 · 프리미티브 · 전송 · 테스트 · 마이그레이션

관련 스킬