Community程式設計與開發github.com

ruwanb/nestjs-mcp-server

將 NestJS 12 應用程式轉換為模型上下文協定伺服器,支援 @nestjs/mcp 模組與 @Tool、@Resource、@Prompt 等裝飾器,並整合 Zod 或標準結構描述驗證、OAuth 2.1 授權與攔截器。

nestjs-mcp-server 是什麼?

此技能將 NestJS 12 應用程式包裝為符合模型上下文協定 (MCP) 的伺服器,透過 @nestjs/mcp 模組提供 @Tool、@Resource 和 @Prompt 等裝飾器,讓開發者能以宣告式方式定義 AI 模型可呼叫的介面。它支援 Zod 4.2+、ArkType 與 Valibot 等結構描述驗證庫,並內建完整的增強器系統(守衛、攔截器、管道、過濾器)以及 OAuth 2.1 資源伺服器授權(含範圍驗證)。開發者可透過 McpContext 存取呼叫上下文、訊號與進度回報,並使用 inputRequired 與使用者互動。此技能適用於 Claude Code、Cursor 等支援 MCP 的平台,預設使用 Streamable HTTP 傳輸,也支援 stdio 模式以利本機測試。詳細文件包含生產環境配置、測試模式與從舊版遷移的指引。

相容平台✓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 中提問

開啟一個已預先載入此 Agent Skill 的新對話。

說明文件

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 查詢訂單,或根據客戶 Email 查詢最新訂單',
    inputSchema: z.object({
      orderId: z.string().optional().describe('訂單 ID,例如 ord_123'),
      email: z.email().optional().describe('客戶的 Email 位址'),
    }),
    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() 文字。 模型只知道這些資訊:說明工具的功能、使用時機、是否會變更資料,並為每個引數提供範例值。使用 Resource 處理直接載入的內容;使用 Tool 處理需要引數的查詢。

呼叫上下文

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 和資料庫呼叫;在迴圈中呼叫 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 並記錄下來。
  • Resource 與 Prompt 會以 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 = 權杖的目標對象
      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 挑戰,從而啟動客戶端的登入流程。
  • 驗證器:
    • JWT:使用 @nestjs/authentication 的 JwtVerifier(檢查簽章、發行者、目標對象與到期時間)。
    • 不透明權杖:呼叫 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') 可在工具中運作。
    • 在端點上設定 decorators: [Public()],讓 auth 處理挑戰。
    • 沒有 @Can() 的工具會被拒絕。
  • 僅供內部使用的伺服器可以跳過 OAuth,改為在端點上使用 decorators: [UseGuards(ApiKeyGuard)]。

傳輸方式

  • Streamable HTTP 為預設方式;設定 path: false 可停用它。端點是一個一般控制器,因此全域前置詞、版本化(endpointVersion)、中介軟體與 CORS 都適用。它是無狀態的:不需要黏性工作階段。兩種協議時期都會提供服務(http.legacy: 'reject' 只提供 2026-07-28 版本)。http.responseMode 可以是 'auto'、'json' 或 'sse'。
  • stdio,適用於啟動本機程序的輔助工具:建立 stdio.ts,使用 NestFactory.createApplicationContext(AppModule, { logger: new McpStdioLogger() }),然後執行 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 }) 搭配測試驗證器進行端到端測試;透過 Socket 測試 HTTP 路由。建置、檢查、執行測試、啟動應用程式並使用 Inspector 列出工具。

參考檔案

  • references/mcp-reference.md:模組選項、裝飾器選項、HTTP 強化(Origin、Host、CORS 標頭、主體大小)、stdio 客戶端設定、NestJS Observe 命名、生產檢查清單、測試(權杖與範圍、協議時期、McpRegistry 檢查、HTTP 與 stdio 測試),以及從 @rekog/mcp-nest 1.x/2.x 遷移。在設定生產環境、撰寫測試或進行遷移時閱讀。

來源:概覽 · 基本元件 · 傳輸方式 · 測試 · 遷移

相關技能