Communityコーディング&開発github.com

ruwanb/nestjs-mcp-server

NestJS 12アプリケーションをModel Context Protocolサーバーに変換します。@nestjs/mcp を使用し、@Tool、@Resource、@Prompt デコレータ、Zod/Standard Schemaによるスキーマ検証、OAuth 2.1認証、ガード・インターセプター・パイプ・フィルターなどのNestJSエンハンサーをサポートします。

nestjs-mcp-server とは?

このスキルは、NestJS 12アプリケーションをModel Context Protocol (MCP) サーバーとして機能させるための公式パッケージ `@nestjs/mcp` の完全なドキュメントです。開発者は、`@Tool`、`@Resource`、`@ResourceTemplate`、`@Prompt` といったデコレータを使って、プロバイダ内のメソッドをMCPプリミティブとして宣言できます。入力スキーマはZod 4.2+を推奨し、ArkTypeやValibot、JSON Schemaもサポート。返り値やエラーハンドリングはプリミティブごとに最適化されており、ツールは `isError` 結果を返すことでモデルが回復可能なエラーを表現できます。OAuth 2.1リソースサーバー認証は `McpTokenVerifier` とスコープで実現し、JWTや不透明トークンを検証。ガード、インターセプター、パイプ、フィルターなどのNestJSエンハンサーがMCPコンテキストでも動作し、`McpContext` を介して認証情報、シグナル、進行状況報告、ユーザー入力要求(inputRequired)にアクセスできます。トランスポートはStreamable HTTP(デフォルト)とstdioの両方をサポートし、開発者は `connectMcpClient` を使ったe2eテストや、公式インスペクターでの検証が可能です。

対応✓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({ name, description, inputSchema, outputSchema?, annotations?, scopes? })モデル検証済みの入力、ctx
リソース(データ)@Resource({ uri, mimeType }) / @ResourceTemplate({ uriTemplate: 'orders://{orderId}' })クライアント(多くの場合ユーザーが添付){} またはテンプレート変数、ctx(ctx.uri)
プロンプト(テンプレート)@Prompt({ name, description, argsSchema })ユーザーが選択検証済みの引数、ctx
@Injectable()                                          // モジュールの providers にリストされた通常のプロバイダ
@Injectable()
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として送信されます。プロンプトの文字列は1つのユーザーメッセージになり、配列は 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ではエンドポイントがステートレスなため、これは2026-07-28クライアントでのみ機能します。stdioでは両方のエラで機能します。

プロバイダはシングルトンのままです。呼び出しごとのデータはリクエストスコープのプロバイダではなく、ctx から読み取ってください。

エンハンサーとエラー

  • ガード、インターセプター、パイプ、フィルターはルートと同様に動作し、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 リソースサーバーです。トークンを検証し、トークンを発行することはありません。
  • RFC 9728メタデータを /.well-known/oauth-protected-resource/mcp で提供します。トークンがないリクエストには WWW-Authenticate: Bearer チャレンジを含む401が返され、クライアントのサインインが開始されます。
  • 検証ツール:
    • JWT:@nestjs/authentication の JwtVerifier を使用します(署名、発行者、audience、有効期限をチェック)。
    • 不透明トークン:RFC 7662イントロスペクションエンドポイントを呼び出します。
    • 返される AuthInfo は resource === auth.resource を満たし、将来の expiresAt を持つ必要があります。そうでない場合、トークンは401で拒否されます。
    • トークンを拒否するには401エラーをスローします。その他のエラーは500になります。
  • プリミティブに scopes がない場合は403とステップアップチャレンジが返されます。auth.requiredScopes はすべてのリクエストに適用されます。
  • @nestjs/authentication ≥ 0.1.3 および @nestjs/authorization ≥ 0.1.1 と一緒に使用する場合:
    • それらのグローバルガードが各呼び出しを認証し、@CurrentUser() と @Can(Policy, 'action') がツール内で機能します。
    • auth がチャレンジを処理できるように、エンドポイントに decorators: [Public()] を設定します。
    • @Can() がないツールは拒否されます。
  • 内部専用サーバーはOAuthをスキップし、エンドポイントに decorators: [UseGuards(ApiKeyGuard)] を使用できます。

トランスポート

  • Streamable HTTP がデフォルトで提供されます。path: false で無効化できます。エンドポイントは通常のコントローラーであるため、グローバルプレフィックス、バージョニング(endpointVersion)、ミドルウェア、CORSがすべて適用されます。ステートレスであるため、スティッキーセッションは不要です。両方のプロトコルエラに対応します(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+ を確認。McpModule.forRoot に name、version、instructions を設定。
  • 2. プロバイダにツール、リソース、プロンプトを追加。モデル向けの説明、すべての引数に .describe()、読み取り専用ツールには readOnlyHint、構造化された結果には outputSchema を設定。
  • 3. HTTP専用のグローバルエンハンサーが getType() をチェックすることを確認。必要に応じてMCPガードとフィルターを追加。
  • 4. ネットワーク接続可能なサーバー:auth(verifier、resource、authorization servers、scopes)、allowedHosts/allowedOrigins、CORSヘッダー、ボディサイズ制限を追加。ローカルサーバー:McpStdioLogger を使用した stdio.ts を追加。
  • 5. テスト:メソッドを直接ユニットテスト。connectMcpClient(moduleRef, { token }) を使用してテスト用verifierに対してe2eテストを実行。ソケット経由でHTTPルートをテスト。ビルド、リント、テスト実行、アプリ起動、インスペクターでツール一覧表示。

参考ファイル

  • references/mcp-reference.md:モジュールオプション、デコレータオプション、HTTP強化(Origin、Host、CORSヘッダー、ボディサイズ)、stdioクライアント設定、NestJS Observe命名、本番チェックリスト、テスト(トークンとスコープ、プロトコルエラ、McpRegistryチェック、HTTPおよびstdioテスト)、@rekog/mcp-nest 1.x/2.xからの移行。本番設定、テスト作成、移行時に参照してください。

ソース:概要 · プリミティブ · トランスポート · テスト · 移行

関連スキル