mcp-expose 1.0.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/CHANGELOG.md +36 -1
  2. package/README.md +181 -32
  3. package/dist/adonisjs/index.cjs +366 -6
  4. package/dist/adonisjs/index.cjs.map +1 -1
  5. package/dist/adonisjs/index.d.cts +2 -1
  6. package/dist/adonisjs/index.d.ts +2 -1
  7. package/dist/adonisjs/index.js +366 -6
  8. package/dist/adonisjs/index.js.map +1 -1
  9. package/dist/express/index.cjs +364 -7
  10. package/dist/express/index.cjs.map +1 -1
  11. package/dist/express/index.d.cts +4 -2
  12. package/dist/express/index.d.ts +4 -2
  13. package/dist/express/index.js +364 -7
  14. package/dist/express/index.js.map +1 -1
  15. package/dist/fastify/index.cjs +373 -6
  16. package/dist/fastify/index.cjs.map +1 -1
  17. package/dist/fastify/index.d.cts +2 -1
  18. package/dist/fastify/index.d.ts +2 -1
  19. package/dist/fastify/index.js +373 -6
  20. package/dist/fastify/index.js.map +1 -1
  21. package/dist/hono/index.cjs +367 -6
  22. package/dist/hono/index.cjs.map +1 -1
  23. package/dist/hono/index.d.cts +2 -1
  24. package/dist/hono/index.d.ts +2 -1
  25. package/dist/hono/index.js +367 -6
  26. package/dist/hono/index.js.map +1 -1
  27. package/dist/index.cjs +371 -31
  28. package/dist/index.cjs.map +1 -1
  29. package/dist/index.d.cts +10 -5
  30. package/dist/index.d.ts +10 -5
  31. package/dist/index.js +367 -31
  32. package/dist/index.js.map +1 -1
  33. package/dist/koa/index.cjs +366 -6
  34. package/dist/koa/index.cjs.map +1 -1
  35. package/dist/koa/index.d.cts +2 -1
  36. package/dist/koa/index.d.ts +2 -1
  37. package/dist/koa/index.js +366 -6
  38. package/dist/koa/index.js.map +1 -1
  39. package/dist/nestjs/index.cjs +381 -7
  40. package/dist/nestjs/index.cjs.map +1 -1
  41. package/dist/nestjs/index.d.cts +21 -7
  42. package/dist/nestjs/index.d.ts +21 -7
  43. package/dist/nestjs/index.js +382 -8
  44. package/dist/nestjs/index.js.map +1 -1
  45. package/dist/oauth/index.cjs +214 -0
  46. package/dist/oauth/index.cjs.map +1 -0
  47. package/dist/oauth/index.d.cts +69 -0
  48. package/dist/oauth/index.d.ts +69 -0
  49. package/dist/oauth/index.js +177 -0
  50. package/dist/oauth/index.js.map +1 -0
  51. package/dist/oauth-8RmQamGP.d.cts +75 -0
  52. package/dist/oauth-8RmQamGP.d.ts +75 -0
  53. package/dist/{server-fzkb-wJo.d.ts → server--mQVQq3A.d.cts} +81 -1
  54. package/dist/{server-fzkb-wJo.d.cts → server-BPDTsT2o.d.ts} +81 -1
  55. package/package.json +22 -2
package/CHANGELOG.md CHANGED
@@ -6,6 +6,39 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [1.1.1] - 2026-09-28
10
+
11
+ ### Added
12
+
13
+ - `transformResponse` hook for route tools, on every marker and adapter, plus a server-level default. It shapes what
14
+ the agent sees from a route's response (hide or format fields, add hints) without turning the route into a custom
15
+ tool. The hook receives the status, headers, body and parsed JSON, the tool arguments and the MCP call context.
16
+ `ctx.callRoute()` enriches the response with other routes of the app, using the same credentials and the same
17
+ middleware, guards and validation. Placeholders are URL-encoded, and `.` / `..` segments are rejected.
18
+ - `ResponseTransform`, `ResponseTransformContext` and `RouteToolResponse` types.
19
+
20
+ ### Fixed
21
+
22
+ - A tool handler or hook that returns plain data with its own `content` array (for example a CMS post) is no longer
23
+ mistaken for an MCP `ToolResult`; only `content` arrays of MCP content items (`{ type: ... }`) are.
24
+
25
+ ## [1.1.0] - 2026-09-27
26
+
27
+ ### Added
28
+
29
+ - **OAuth 2.1 support** following the MCP authorization spec, on every adapter, enabled with the new `oauth` server option:
30
+ - Protected Resource Metadata (RFC 9728) served at `/.well-known/oauth-protected-resource[/<path>]`, public and CORS-enabled.
31
+ - `401` / `403` responses with `WWW-Authenticate` challenges (`resource_metadata`, `scope`, `invalid_token`,
32
+ `insufficient_scope`), so MCP clients discover the authorization server and sign the user in by themselves.
33
+ - Audience-checked token verification with `requiredScopes`; the verified token is available to tools as `ctx.auth`.
34
+ - Per-tool `scopes` with `403 insufficient_scope` step-up.
35
+ - New `mcp-expose/oauth` entry point: `jwtVerifier()` (JWKS, via the optional `jose` peer dependency),
36
+ `introspectionVerifier()` (RFC 7662) and `discoverAuthorizationServer()`.
37
+ - NestJS: `decorators` option to add decorators (such as `Public()`) to the MCP controller.
38
+ - E2E: every scenario (each supported framework major) also runs in OAuth mode with `jwtVerifier`, and a new
39
+ scenario has the official MCP SDK client register dynamically, sign in with authorization code + PKCE and a
40
+ resource indicator, and step up scopes.
41
+
9
42
  ## [1.0.0] - 2026-09-25
10
43
 
11
44
  First stable release. The public API (`mcp-expose` and `mcp-expose/<framework>` exports, tool and server
@@ -35,5 +68,7 @@ options, default tool naming) now follows Semantic Versioning.
35
68
  - Path parameters that are empty, `.` or `..` are rejected, so an agent cannot use them to reach routes
36
69
  that were never exposed as tools (for example `/orders/../admin`).
37
70
 
38
- [Unreleased]: https://github.com/NITINKACHHADIYA/Node-MCP/compare/v1.0.0...HEAD
71
+ [Unreleased]: https://github.com/NITINKACHHADIYA/Node-MCP/compare/v1.1.1...HEAD
72
+ [1.1.1]: https://github.com/NITINKACHHADIYA/Node-MCP/releases/tag/v1.1.1
73
+ [1.1.0]: https://github.com/NITINKACHHADIYA/Node-MCP/releases/tag/v1.1.0
39
74
  [1.0.0]: https://github.com/NITINKACHHADIYA/Node-MCP/releases/tag/v1.0.0
package/README.md CHANGED
@@ -32,6 +32,7 @@ router.get('/orders/:id', [OrdersController, 'show']).use(middleware.auth()).mcp
32
32
  - Zero runtime dependencies. Dual ESM/CJS. Node.js 20, 22 and 24, plus Bun, Deno and Workers for Hono.
33
33
  - Supports the current **and the two previous major versions** of every framework, verified end to end.
34
34
  - Speaks MCP Streamable HTTP (protocol `2024-11-05` → `2025-11-25`), stateless, so it scales horizontally and runs serverless.
35
+ - Built-in [OAuth 2.1](#oauth-sign-in-from-ai-clients): AI clients discover your identity provider and sign the user in by themselves (Auth0, Okta, Keycloak, Entra ID, Clerk, Cognito, ...).
35
36
 
36
37
  ---
37
38
 
@@ -44,14 +45,16 @@ router.get('/orders/:id', [OrdersController, 'show']).use(middleware.auth()).mcp
44
45
  5. [Framework guides](#framework-guides)
45
46
  - [NestJS](#nestjs) · [Express](#express) · [Fastify](#fastify) · [Koa](#koa) · [Hono](#hono) · [AdonisJS](#adonisjs) · [Any API via OpenAPI](#any-api-via-openapi-standalone-gateway)
46
47
  6. [Connect an AI client](#connect-an-ai-client)
47
- 7. [Defining tool inputs (schemas)](#defining-tool-inputs-schemas)
48
- 8. [Configuration reference](#configuration-reference)
49
- 9. [Custom (non-HTTP) tools](#custom-non-http-tools)
50
- 10. [Security checklist](#security-checklist)
51
- 11. [Writing tools agents use well](#writing-tools-agents-use-well)
52
- 12. [Limitations and roadmap](#limitations-and-roadmap)
53
- 13. [Development](#development)
54
- 14. [Versioning and support](#versioning-and-support)
48
+ 7. [OAuth: sign in from AI clients](#oauth-sign-in-from-ai-clients)
49
+ 8. [Defining tool inputs (schemas)](#defining-tool-inputs-schemas)
50
+ 9. [Shaping and enriching responses](#shaping-and-enriching-responses)
51
+ 10. [Configuration reference](#configuration-reference)
52
+ 11. [Custom (non-HTTP) tools](#custom-non-http-tools)
53
+ 12. [Security checklist](#security-checklist)
54
+ 13. [Writing tools agents use well](#writing-tools-agents-use-well)
55
+ 14. [Limitations and roadmap](#limitations-and-roadmap)
56
+ 15. [Development](#development)
57
+ 16. [Versioning and support](#versioning-and-support)
55
58
 
56
59
  ---
57
60
 
@@ -466,7 +469,8 @@ This also works with `@nestjs/swagger`, `@fastify/swagger`, tsoa and hono-openap
466
469
  ## Connect an AI client
467
470
 
468
471
  Start your app, then point a client at `http://localhost:3000/mcp`. Pass the same credentials a normal API
469
- client would use. They are forwarded to your routes.
472
+ client would use. They are forwarded to your routes. (With the [`oauth` option](#oauth-sign-in-from-ai-clients)
473
+ you skip the header: the client signs the user in by itself.)
470
474
 
471
475
  **Claude Code**
472
476
 
@@ -534,6 +538,89 @@ curl -s localhost:3000/mcp -H 'content-type: application/json' \
534
538
 
535
539
  ---
536
540
 
541
+ ## OAuth: sign in from AI clients
542
+
543
+ Pasting a token into a client config works for development. For real users, turn on the `oauth` option:
544
+ mcp-expose then implements the [MCP authorization spec](https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization),
545
+ so Claude, Cursor, VS Code and other clients open your normal login page, get a token for the user, and
546
+ send it on every call. Nothing is pasted by hand.
547
+
548
+ ```
549
+ AI client ── POST /mcp (no token) ─────────────▶ 401 WWW-Authenticate: Bearer resource_metadata=".../.well-known/oauth-protected-resource/mcp"
550
+ AI client ── GET /.well-known/oauth-protected-resource/mcp ─▶ { resource, authorization_servers: ["https://login.example.com"] }
551
+ AI client ◀──── login in the browser (your IdP: Auth0, Okta, Keycloak, Entra ID, Clerk, Cognito, ...) ────▶ access token
552
+ AI client ── POST /mcp Authorization: Bearer <token> ─▶ mcp-expose checks the token ─▶ your route (token forwarded, guards run)
553
+ ```
554
+
555
+ mcp-expose is the **resource server**. Your identity provider stays the **authorization server**, so user
556
+ accounts, login pages and MFA stay where they are.
557
+
558
+ ### 1. Configure it
559
+
560
+ ```ts
561
+ import { jwtVerifier } from 'mcp-expose/oauth'; // npm install jose
562
+
563
+ const oauth = {
564
+ // The public URL of your MCP endpoint. Tokens must be issued for exactly this audience.
565
+ resource: 'https://api.example.com/mcp',
566
+ authorizationServers: ['https://example.auth0.com/'],
567
+ requiredScopes: ['mcp:tools'], // optional: needed for any MCP access
568
+ verifyToken: jwtVerifier({ issuer: 'https://example.auth0.com/' }),
569
+ };
570
+ ```
571
+
572
+ Pass it as `oauth` to any adapter:
573
+
574
+ ```ts
575
+ McpModule.forRoot({ name: 'shop-api', oauth, decorators: [Public()] }); // NestJS (see note below)
576
+ mountMcp(app, { name: 'shop-api', oauth }); // Express, Koa, Hono, AdonisJS
577
+ await app.register(fastifyMcp, { name: 'shop-api', oauth }); // Fastify
578
+ ```
579
+
580
+ That's it. The adapter:
581
+
582
+ - serves the Protected Resource Metadata ([RFC 9728](https://www.rfc-editor.org/rfc/rfc9728)) at `/.well-known/oauth-protected-resource/mcp` (and `/.well-known/oauth-protected-resource`), public and CORS-enabled, outside any global prefix, guard or MCP middleware;
583
+ - answers requests without a valid token with `401` and a `WWW-Authenticate` challenge that starts the client's login;
584
+ - rejects tokens that were not issued for this resource (audience check), which blocks token passthrough from other services;
585
+ - forwards the verified `Authorization` header to your routes, so your guards see the same user;
586
+ - exposes the verified token to custom tools as `ctx.auth` (`subject`, `scopes`, `clientId`, `claims`).
587
+
588
+ ### 2. Per-tool scopes (step-up)
589
+
590
+ ```ts
591
+ @Delete(':id')
592
+ @McpTool({ name: 'cancel_order', scopes: ['orders:write'] })
593
+ cancel(@Param('id') id: string) { ... }
594
+ ```
595
+
596
+ A token without `orders:write` gets `403 insufficient_scope` naming the scopes it needs (plus the ones it already has, so the new token doesn't lose access). MCP clients then ask
597
+ the user to approve the extra access and retry. All tool scopes are advertised in `scopes_supported`.
598
+ `scopes` works on every marker (`mcpTool()`, `config.mcp`, `.mcp()`, `defineTool()`).
599
+
600
+ ### Token verifiers
601
+
602
+ | Verifier | Use it for |
603
+ | ------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
604
+ | `jwtVerifier({ issuer })` | JWT access tokens. Keys come from the issuer's JWKS (found through its metadata, or set `jwksUri` / `jwks`). Checks signature (asymmetric algorithms only), `iss`, `aud`, `exp`, `nbf`. Needs `jose`. |
605
+ | `introspectionVerifier({ issuer, clientId, clientSecret })` | Opaque tokens, via [RFC 7662](https://www.rfc-editor.org/rfc/rfc7662) introspection (Keycloak, Okta, ORY, Authlete, ...). Checks `active`, `aud` and `iss`, and caches results for 60 s. |
606
+ | `verifyToken: async (token, { resource }) => AuthInfo \| undefined` | Anything else, such as your existing session or API-key lookup. Return `undefined` to reject the token. |
607
+
608
+ `jwtVerifier` checks that `aud` equals `resource` by default. If your provider uses another audience
609
+ identifier (for example an Auth0 API identifier), set `audience`.
610
+
611
+ ### Provider notes
612
+
613
+ - **The authorization server must support the MCP client flow:** authorization code with PKCE, and ideally
614
+ dynamic client registration ([RFC 7591](https://www.rfc-editor.org/rfc/rfc7591)) or client ID metadata documents,
615
+ so clients can register themselves. Many providers support this (for example Auth0, Keycloak, WorkOS and Stytch); with others, pre-register the client and give users its client ID.
616
+ - **Your routes must accept the same token.** It is forwarded unchanged. If your API normally checks a different
617
+ audience, allow the MCP resource too, or set `forwardHeaders` to drop `authorization` and authorise
618
+ with a custom tool using `ctx.auth`.
619
+ - **NestJS global guards** (an `APP_GUARD` that requires a session) run before the MCP controller and would answer 401 without the
620
+ OAuth challenge. Pass your "public" decorator with `decorators: [Public()]` so the guard lets MCP requests through;
621
+ mcp-expose checks the token itself. Likewise, don't also put auth `middleware`, `guards`, `routeOptions` hooks or `configureRoute` middleware on the MCP endpoint.
622
+ - **Behind a proxy**, `resource` must be the public URL the client connects to (`https://...`), not the internal one.
623
+
537
624
  ## Defining tool inputs (schemas)
538
625
 
539
626
  The agent sees **one flat object** of arguments. mcp-expose maps each argument back to the right place:
@@ -562,28 +649,88 @@ Sources, from highest to lowest precedence:
562
649
 
563
650
  Your app's own validation always runs as well. The schema tells the agent what to send, and your API decides what it accepts.
564
651
 
652
+ ## Shaping and enriching responses
653
+
654
+ By default the agent gets the route's response as it is. Add `transformResponse` to a route tool to change
655
+ what the agent sees: hide internal fields, format values, add hints, or pull in related data from other
656
+ routes. The route stays a route tool; there is no need to rewrite it as a custom tool.
657
+
658
+ ```ts
659
+ @Get(':id')
660
+ @McpTool({
661
+ name: 'get_order',
662
+ transformResponse: async (res, ctx) => {
663
+ if (!res.ok) return undefined; // keep the default error result
664
+ const { internalNotes, ...order } = res.json as Order;
665
+ // Another route of this app, with the same user's credentials, guards and pipes:
666
+ const customer = await ctx.callRoute({ path: '/api/customers/:id', params: { id: order.customerId } });
667
+ return { ...order, total: formatMoney(order.total), customer: customer.ok ? customer.json : null };
668
+ },
669
+ })
670
+ findOne(@Param('id') id: string) { ... }
671
+ ```
672
+
673
+ It works the same with every marker: `mcpTool({ transformResponse })`, `config: { mcp: { transformResponse } }`,
674
+ `.mcp({ transformResponse })`, and `toolsFromOpenApi(doc, dispatch, { transformResponse })`.
675
+
676
+ **What the hook receives**
677
+
678
+ - `res`: `status`, `headers`, the raw `body`, the parsed `json` (for JSON responses) and `ok` (status below 400).
679
+ - `ctx.args`: the tool arguments. `ctx.request`: the internal request (method, path, query, body).
680
+ - `ctx.toolContext`: the MCP request's headers, client IP and, with [OAuth](#oauth-sign-in-from-ai-clients), `auth`.
681
+ - `ctx.defaultResult()`: the result mcp-expose would return without the hook.
682
+ - `ctx.callRoute({ method, path, params, query, body, headers })`: calls another route of the same app.
683
+ - It forwards the same credentials (`Authorization`, cookies) and runs through the same middleware, guards and validation.
684
+ - `path` is the full path as your server sees it, including any global prefix or router mount path. Fill `:id` / `{id}`
685
+ placeholders from `params`: values are URL-encoded, and `.` or `..` are rejected, so agent input can't redirect the call.
686
+
687
+ **What it returns**
688
+
689
+ | Return value | Result |
690
+ | -------------------------- | --------------------------------------------------------------------------------------------------- |
691
+ | a string or any JSON value | Sent to the agent (objects also as `structuredContent`). Still an error result if the route failed. |
692
+ | a full `ToolResult` | Used as is, so you control `isError` (e.g. `{ ...ctx.defaultResult(), content: [...] }`). |
693
+ | `undefined` | The default result. |
694
+ | a thrown error | An error result with the message, which the agent sees. |
695
+
696
+ `maxResponseChars` still applies to whatever the hook returns.
697
+
698
+ **For every tool at once**, set `transformResponse` in the server options, e.g. to strip fields everywhere:
699
+
700
+ ```ts
701
+ mountMcp(app, {
702
+ name: 'shop-api',
703
+ transformResponse: (res) => (res.ok ? omitDeep(res.json, ['internalNotes', 'passwordHash']) : undefined),
704
+ });
705
+ ```
706
+
707
+ A tool's own `transformResponse` replaces the server-level one.
708
+
565
709
  ## Configuration reference
566
710
 
567
711
  ### Server options (all adapters)
568
712
 
569
- | Option | Type | Default | Description |
570
- | ------------------ | --------------------- | ---------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
571
- | `name` | `string` | required | Server name shown to clients. |
572
- | `version` | `string` | `'1.0.0'` | Server version. |
573
- | `instructions` | `string` | none | Guidance for the model on how to use the tools together. |
574
- | `path` | `string` | `'/mcp'` | Endpoint path. |
575
- | `allowedOrigins` | `string[] \| '*'` | `[]` | Browser origins allowed to call the endpoint. Requests without `Origin` (CLIs, IDEs, servers) are always allowed. |
576
- | `forwardHeaders` | `string[]` | `['authorization','cookie','x-api-key','accept-language']` | Headers copied from the MCP request to the internal API call. |
577
- | `maxResponseChars` | `number` | `100000` | Longer API responses are truncated before reaching the model. |
578
- | `tools` | `McpToolDefinition[]` | `[]` | Extra hand-written tools. |
579
- | `baseUrl` | `string` | loopback | _Express/Koa/Nest/Adonis._ Where internal calls go. Set it for HTTPS with self-signed certs, unix sockets, or a separate API host. |
580
- | `routes` | `{method,path,...}[]` | `[]` | _Express/Koa/Hono._ Expose routes without editing them. |
581
- | `routers` | see guide | none | _Express:_ `{ '/prefix': router }`. _Koa:_ `[router]`. |
582
- | `middleware` | `Middleware[]` | `[]` | _Express._ Middleware in front of `/mcp`, such as auth. |
583
- | `guards` | `CanActivate[]` | `[]` | _NestJS._ Guards on the MCP controller. |
584
- | `pathPrefix` | `string` | none | _NestJS._ Extra prefix for tool routes. Global prefix and URI versioning are automatic. |
585
- | `routeOptions` | `object` | none | _Fastify._ Extra route options for `/mcp`, such as `onRequest` hooks. |
586
- | `configureRoute` | `(route) => void` | none | _AdonisJS._ Configure the MCP route, e.g. add middleware. |
713
+ | Option | Type | Default | Description |
714
+ | ------------------- | --------------------- | ---------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
715
+ | `name` | `string` | required | Server name shown to clients. |
716
+ | `version` | `string` | `'1.0.0'` | Server version. |
717
+ | `instructions` | `string` | none | Guidance for the model on how to use the tools together. |
718
+ | `path` | `string` | `'/mcp'` | Endpoint path. |
719
+ | `allowedOrigins` | `string[] \| '*'` | `[]` | Browser origins allowed to call the endpoint. Requests without `Origin` (CLIs, IDEs, servers) are always allowed. |
720
+ | `forwardHeaders` | `string[]` | `['authorization','cookie','x-api-key','accept-language']` | Headers copied from the MCP request to the internal API call. |
721
+ | `maxResponseChars` | `number` | `100000` | Longer API responses are truncated before reaching the model. |
722
+ | `oauth` | `OAuthOptions` | none | Protect the endpoint with OAuth 2.1. See [OAuth](#oauth-sign-in-from-ai-clients). |
723
+ | `transformResponse` | `ResponseTransform` | none | Default response hook for route tools without their own. See [Shaping responses](#shaping-and-enriching-responses). |
724
+ | `tools` | `McpToolDefinition[]` | `[]` | Extra hand-written tools. |
725
+ | `baseUrl` | `string` | loopback | _Express/Koa/Nest/Adonis._ Where internal calls go. Set it for HTTPS with self-signed certs, unix sockets, or a separate API host. |
726
+ | `routes` | `{method,path,...}[]` | `[]` | _Express/Koa/Hono._ Expose routes without editing them. |
727
+ | `routers` | see guide | none | _Express:_ `{ '/prefix': router }`. _Koa:_ `[router]`. |
728
+ | `middleware` | `Middleware[]` | `[]` | _Express._ Middleware in front of `/mcp`, such as auth. |
729
+ | `guards` | `CanActivate[]` | `[]` | _NestJS._ Guards on the MCP controller. |
730
+ | `decorators` | `ClassDecorator[]` | `[]` | _NestJS._ Extra decorators on the MCP controller, e.g. `[Public()]` for global guards. |
731
+ | `pathPrefix` | `string` | none | _NestJS._ Extra prefix for tool routes. Global prefix and URI versioning are automatic. |
732
+ | `routeOptions` | `object` | none | _Fastify._ Extra route options for `/mcp`, such as `onRequest` hooks. |
733
+ | `configureRoute` | `(route) => void` | none | _AdonisJS._ Configure the MCP route, e.g. add middleware. |
587
734
 
588
735
  ### Tool options (`@McpTool()`, `mcpTool()`, `config.mcp`, `.mcp()`)
589
736
 
@@ -595,6 +742,8 @@ Your app's own validation always runs as well. The schema tells the agent what t
595
742
  | `input` / `params` / `query` / `body` | Schemas, see [above](#defining-tool-inputs-schemas). |
596
743
  | `annotations` | MCP hints: `readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`. Defaults come from the HTTP method (GET → read-only, DELETE → destructive). |
597
744
  | `headers` | Static headers added to the internal request. |
745
+ | `scopes` | OAuth scopes the token needs to call this tool (with the `oauth` option). Missing scopes answer `403 insufficient_scope`. |
746
+ | `transformResponse` | Shape or enrich what the agent sees from this route's response. See [Shaping and enriching responses](#shaping-and-enriching-responses). |
598
747
 
599
748
  Each internal request also carries `X-Mcp-Tool: <tool name>`, so you can log or meter agent traffic separately.
600
749
 
@@ -616,12 +765,12 @@ const convert = defineTool({
616
765
  mountMcp(app, { name: 'shop-api', tools: [convert] });
617
766
  ```
618
767
 
619
- Handlers can return a string, any JSON value, or a full MCP `ToolResult`. `ctx.headers` holds the MCP request's headers, so you can authenticate there too.
768
+ Handlers can return a string, any JSON value, or a full MCP `ToolResult`. `ctx.headers` holds the MCP request's headers, so you can authenticate there too. With the `oauth` option, `ctx.auth` holds the verified token (`subject`, `scopes`, `clientId`, `claims`).
620
769
 
621
770
  ## Security checklist
622
771
 
623
772
  - **Opt-in only.** Nothing is exposed unless you mark it. Review marked routes the way you review a public API, because an LLM can call them with any arguments.
624
- - **Use per-user credentials.** Clients send their own token, the token is forwarded, and your guards authorise the call. Avoid one shared super-token.
773
+ - **Use per-user credentials.** Clients send their own token, the token is forwarded, and your guards authorise the call. Avoid one shared super-token. For remote servers, prefer the [`oauth` option](#oauth-sign-in-from-ai-clients): users sign in through your identity provider, tokens are audience-checked, and sensitive tools can require extra `scopes`.
625
774
  - **Protect discovery too** if tool names are sensitive (`guards`, `middleware`, `routeOptions`).
626
775
  - **Rate limits and IPs:** the agent IP is sent as `X-Forwarded-For`. For loopback adapters, trust loopback only: Express `app.set('trust proxy', 'loopback')`, Koa `app.proxy = true` behind a proxy that overwrites the header, Nest (Express) `app.set('trust proxy', 'loopback')`. Fastify `inject()` sets the IP directly.
627
776
  - **Browser access:** keep `allowedOrigins` empty unless a browser app must call `/mcp` directly.
@@ -649,9 +798,9 @@ Current scope (1.x):
649
798
 
650
799
  Planned (non-breaking, 1.x minor releases):
651
800
 
652
- - OAuth 2.1 protected-resource metadata (RFC 9728) helpers for remote MCP auth, and per-user tool lists
801
+ - Per-user tool lists (hide tools the token's scopes can't call)
653
802
  - Next.js route handlers, Hapi and Elysia adapters
654
- - Structured output schemas, and binary/file responses
803
+ - Structured output schemas (`outputSchema`), and binary/file responses
655
804
  - Streaming long-running responses over SSE
656
805
  - CLI to preview generated tools (`npx mcp-expose inspect`)
657
806
  - Resources from GET routes, and prompts
@@ -663,7 +812,7 @@ Contributions are welcome. See [Development](#development).
663
812
  ```bash
664
813
  npm install
665
814
  npm test # vitest: core + all six adapters (real servers, real HTTP)
666
- npm run test:e2e # pack → install into 18 fresh framework projects → official MCP SDK client
815
+ npm run test:e2e # pack → install into 19 fresh framework projects → official MCP SDK client (with and without OAuth)
667
816
  npm run typecheck
668
817
  npm run build # ESM + CJS + .d.ts into dist/
669
818