@ttoss/http-server-mcp 0.15.0 → 0.16.0

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.
package/README.md CHANGED
@@ -129,7 +129,7 @@ const data = await apiCall('GET', 'https://partner.api.com/data', {
129
129
 
130
130
  ## Authentication
131
131
 
132
- `createMcpRouter` supports OAuth 2.0 Bearer token authentication via the `auth` option. Every incoming MCP request must include a valid `Authorization: Bearer <token>` header — invalid or missing tokens receive a `401 Unauthorized` response.
132
+ `createMcpRouter` supports OAuth 2.0 Bearer token authentication via the `auth` option. Incoming MCP requests must include a valid `Authorization: Bearer <token>` header — invalid or missing tokens receive a `401 Unauthorized` response. The MCP lifecycle methods `initialize` and `tools/list` are exempt by default so clients can discover the server before authenticating (see [Public methods and discovery](#public-methods-and-discovery)).
133
133
 
134
134
  ```mermaid
135
135
  sequenceDiagram
@@ -315,6 +315,31 @@ app.use(createMcpRouter(mcpServer).routes());
315
315
 
316
316
  The `WWW-Authenticate: Bearer resource_metadata="…"` header is how MCP clients bootstrap OAuth discovery after their first unauthorized request.
317
317
 
318
+ ### Public methods and discovery
319
+
320
+ The two behaviors the [MCP authorization spec](https://spec.modelcontextprotocol.io/specification/2025-03-26/basic/authorization/) requires for client bootstrapping are built into the `auth` option, so you no longer need the hand-rolled middleware shown above:
321
+
322
+ - **`publicMethods`** — JSON-RPC methods that bypass verification, read from the request body's `method` field. Defaults to `['initialize', 'tools/list']` so clients can discover the server before authenticating. Pass `[]` to require a token for every method, or a custom list to change the exempt set.
323
+ - **`resourceMetadataUrl`** — when set, a `401` responds with `WWW-Authenticate: Bearer resource_metadata="<resourceMetadataUrl>"` (RFC 9728) instead of a bare `Bearer`, pointing MCP clients at the protected-resource metadata document. When omitted, the header falls back to `Bearer`.
324
+
325
+ ```typescript
326
+ createMcpRouter(mcpServer, {
327
+ auth: {
328
+ cognitoUserPool: { userPoolId: '...', clientId: '...' },
329
+ // Serve the metadata document (unauthenticated)...
330
+ resourceServerUrl: 'https://mcp.example.com',
331
+ authorizationServerUrl:
332
+ 'https://cognito-idp.us-east-1.amazonaws.com/us-east-1_xxx',
333
+ // ...and point 401s at it for auto-discovery.
334
+ resourceMetadataUrl:
335
+ 'https://mcp.example.com/.well-known/oauth-protected-resource',
336
+ // publicMethods defaults to ['initialize', 'tools/list'].
337
+ },
338
+ });
339
+ ```
340
+
341
+ Both fields are optional. Omitting `resourceMetadataUrl` keeps the bare `Bearer` header, and the `publicMethods` default matches what MCP clients expect for discovery.
342
+
318
343
  ## OAuth 2.1 Authorization Server
319
344
 
320
345
  The `auth` option above covers the **resource-server** half of MCP authorization — it verifies tokens issued by an external authorization server (Cognito, Auth0, …). When you want your own first-party server to _issue_ the tokens, `createMcpAuthServer` provides the **authorization-server** half: the `/authorize`, `/token`, `/register`, and discovery endpoints an MCP client (Claude, Cursor, VS Code) auto-discovers and runs the full OAuth 2.1 flow against.
@@ -403,6 +428,8 @@ Creates a Koa router configured to handle MCP protocol requests.
403
428
  - `auth.verifyToken` — Custom async token verifier `(token: string) => Promise<unknown>`
404
429
  - `auth.requiredScopes` — Router-level scope guard; returns 403 if any scope is missing
405
430
  - `auth.resourceServerUrl` + `auth.authorizationServerUrl` — Enable `/.well-known/oauth-protected-resource`
431
+ - `auth.publicMethods` — JSON-RPC methods that bypass verification (default `['initialize', 'tools/list']`)
432
+ - `auth.resourceMetadataUrl` — Emit RFC 9728 `WWW-Authenticate: Bearer resource_metadata="…"` on 401
406
433
 
407
434
  **Returns:** `Router` — Koa router instance
408
435
 
package/dist/index.cjs CHANGED
@@ -4,12 +4,82 @@ Object.defineProperty(exports, Symbol.toStringTag, {
4
4
  });
5
5
  let node_async_hooks = require("node:async_hooks");
6
6
  let _modelcontextprotocol_sdk_server_streamableHttp_js = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
7
- let _ttoss_auth_core_amazon_cognito = require("@ttoss/auth-core/amazon-cognito");
8
7
  let _ttoss_http_server = require("@ttoss/http-server");
9
8
  let zod = require("zod");
9
+ let _ttoss_auth_core_amazon_cognito = require("@ttoss/auth-core/amazon-cognito");
10
10
  let node_crypto = require("node:crypto");
11
11
  let _modelcontextprotocol_sdk_server_mcp_js = require("@modelcontextprotocol/sdk/server/mcp.js");
12
12
 
13
+ //#region src/auth.ts
14
+ /** MCP lifecycle/discovery methods reachable before a client authenticates. */
15
+ var DEFAULT_PUBLIC_METHODS = ["initialize", "tools/list"];
16
+ /**
17
+ * Builds the token verifier from the auth options, preferring an explicit
18
+ * `verifyToken` and otherwise creating a `CognitoJwtVerifier`.
19
+ */
20
+ var buildTokenVerifier = auth => {
21
+ if (auth.cognitoUserPool) {
22
+ const v = _ttoss_auth_core_amazon_cognito.CognitoJwtVerifier.create({
23
+ tokenUse: "access",
24
+ ...auth.cognitoUserPool
25
+ });
26
+ return t => {
27
+ return v.verify(t);
28
+ };
29
+ }
30
+ if (auth.verifyToken) return auth.verifyToken;
31
+ throw new Error("McpAuthOptions requires either cognitoUserPool or verifyToken");
32
+ };
33
+ /** Returns true when the identity carries every required scope (or none are required). */
34
+ var hasRequiredScopes = (requiredScopes, identity) => {
35
+ if (!requiredScopes?.length) return true;
36
+ const tokenScopes = (identity?.scope ?? "").split(" ");
37
+ return requiredScopes.every(s => {
38
+ return tokenScopes.includes(s);
39
+ });
40
+ };
41
+ /**
42
+ * Registers the token-verification middleware (with public-method bypass and
43
+ * RFC 9728 discovery) and, when configured, the OAuth protected-resource
44
+ * metadata endpoint on the given router.
45
+ */
46
+ var registerAuthRoutes = (router, path, auth, tokenVerifier) => {
47
+ const publicMethods = new Set(auth.publicMethods ?? DEFAULT_PUBLIC_METHODS);
48
+ const unauthorizedHeader = auth.resourceMetadataUrl ? `Bearer resource_metadata="${auth.resourceMetadataUrl}"` : "Bearer";
49
+ router.use(path, async (ctx, next) => {
50
+ const method = ctx.request.body?.method;
51
+ if (publicMethods.has(method ?? "")) {
52
+ await next();
53
+ return;
54
+ }
55
+ const authHeader = ctx.headers.authorization;
56
+ const token = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : "";
57
+ let identity;
58
+ try {
59
+ identity = await tokenVerifier(token);
60
+ } catch {
61
+ ctx.status = 401;
62
+ ctx.set("WWW-Authenticate", unauthorizedHeader);
63
+ ctx.body = "Unauthorized";
64
+ return;
65
+ }
66
+ if (!hasRequiredScopes(auth.requiredScopes, identity)) {
67
+ ctx.status = 403;
68
+ ctx.body = "Forbidden";
69
+ return;
70
+ }
71
+ ctx.state.identity = identity;
72
+ await next();
73
+ });
74
+ if (auth.resourceServerUrl && auth.authorizationServerUrl) router.get("/.well-known/oauth-protected-resource", ctx => {
75
+ ctx.body = {
76
+ resource: auth.resourceServerUrl,
77
+ authorization_servers: [auth.authorizationServerUrl]
78
+ };
79
+ });
80
+ };
81
+
82
+ //#endregion
13
83
  //#region src/authServerHandlers.ts
14
84
  var base64UrlEncode = buffer => {
15
85
  return buffer.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
@@ -496,52 +566,6 @@ var checkScopes = required => {
496
566
  return !tokenScopes.includes(s);
497
567
  }).length > 0) throw new Error(`Insufficient scopes. Required: ${required.join(", ")}`);
498
568
  };
499
- var buildTokenVerifier = auth => {
500
- if (auth.cognitoUserPool) {
501
- const v = _ttoss_auth_core_amazon_cognito.CognitoJwtVerifier.create({
502
- tokenUse: "access",
503
- ...auth.cognitoUserPool
504
- });
505
- return t => {
506
- return v.verify(t);
507
- };
508
- }
509
- if (auth.verifyToken) return auth.verifyToken;
510
- throw new Error("McpAuthOptions requires either cognitoUserPool or verifyToken");
511
- };
512
- var registerAuthRoutes = (router, path, auth, tokenVerifier) => {
513
- router.use(path, async (ctx, next) => {
514
- const authHeader = ctx.headers.authorization;
515
- const token = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : "";
516
- let identity;
517
- try {
518
- identity = await tokenVerifier(token);
519
- } catch {
520
- ctx.status = 401;
521
- ctx.set("WWW-Authenticate", "Bearer");
522
- ctx.body = "Unauthorized";
523
- return;
524
- }
525
- if (auth.requiredScopes?.length) {
526
- const tokenScopes = (identity?.scope ?? "").split(" ");
527
- if (auth.requiredScopes.filter(s => {
528
- return !tokenScopes.includes(s);
529
- }).length > 0) {
530
- ctx.status = 403;
531
- ctx.body = "Forbidden";
532
- return;
533
- }
534
- }
535
- ctx.state.identity = identity;
536
- await next();
537
- });
538
- if (auth.resourceServerUrl && auth.authorizationServerUrl) router.get("/.well-known/oauth-protected-resource", ctx => {
539
- ctx.body = {
540
- resource: auth.resourceServerUrl,
541
- authorization_servers: [auth.authorizationServerUrl]
542
- };
543
- });
544
- };
545
569
  /**
546
570
  * Creates a Koa router configured to handle MCP protocol requests
547
571
  *
package/dist/index.d.cts CHANGED
@@ -665,6 +665,77 @@ declare namespace Application {
665
665
  const HttpError: typeof HttpErrors.HttpError;
666
666
  }
667
667
  //#endregion
668
+ //#region src/auth.d.ts
669
+ /** Amazon Cognito user pool configuration for JWT verification. */
670
+ interface CognitoUserPoolConfig {
671
+ /** The Cognito User Pool ID (e.g. `us-east-1_abc123`). */
672
+ userPoolId: string;
673
+ /**
674
+ * Which token type to verify.
675
+ * @default 'access'
676
+ */
677
+ tokenUse?: 'access' | 'id';
678
+ /** The app client ID registered in the User Pool. */
679
+ clientId: string;
680
+ }
681
+ /**
682
+ * Authentication options for the MCP router.
683
+ *
684
+ * Supply either `cognitoUserPool` (uses `CognitoJwtVerifier` from
685
+ * `@ttoss/auth-core`) or a custom `verifyToken` function — not both.
686
+ */
687
+ interface McpAuthOptions {
688
+ /**
689
+ * Amazon Cognito user pool config. When provided, the router creates a
690
+ * `CognitoJwtVerifier` and validates every incoming Bearer token against it.
691
+ */
692
+ cognitoUserPool?: CognitoUserPoolConfig;
693
+ /**
694
+ * Custom token verifier for non-Cognito providers (Auth0, Keycloak, …).
695
+ * Receives the raw Bearer token string. Should resolve with the verified
696
+ * payload or reject/throw on failure.
697
+ */
698
+ verifyToken?: (token: string) => Promise<unknown>;
699
+ /**
700
+ * Router-level scope guard. All listed scopes must be present on the token
701
+ * for any MCP request to be allowed. Returns 403 if any scope is missing.
702
+ *
703
+ * Cognito encodes scopes as a space-separated string in `payload.scope`.
704
+ *
705
+ * @example ['mcp:access']
706
+ */
707
+ requiredScopes?: string[];
708
+ /**
709
+ * URL of this MCP server, used in the OAuth Protected Resource Metadata
710
+ * response (`/.well-known/oauth-protected-resource`). Both this field and
711
+ * `authorizationServerUrl` must be provided to enable the endpoint.
712
+ */
713
+ resourceServerUrl?: string;
714
+ /**
715
+ * URL of the OAuth Authorization Server that issues tokens for this resource.
716
+ * Enables `/.well-known/oauth-protected-resource` for MCP client auto-discovery
717
+ * (RFC 9728) when combined with `resourceServerUrl`.
718
+ */
719
+ authorizationServerUrl?: string;
720
+ /**
721
+ * JSON-RPC methods (read from `body.method`) that bypass token verification,
722
+ * so MCP clients can discover the server before authenticating. Set to an
723
+ * empty array to require a token for every method.
724
+ *
725
+ * @default ['initialize', 'tools/list']
726
+ */
727
+ publicMethods?: string[];
728
+ /**
729
+ * When set, a `401` from verification responds with
730
+ * `WWW-Authenticate: Bearer resource_metadata="<resourceMetadataUrl>"`
731
+ * (RFC 9728) so MCP clients can auto-discover the authorization server.
732
+ * When omitted, the header falls back to a bare `Bearer`.
733
+ *
734
+ * @example 'https://mcp.example.com/.well-known/oauth-protected-resource'
735
+ */
736
+ resourceMetadataUrl?: string;
737
+ }
738
+ //#endregion
668
739
  //#region src/authServerTypes.d.ts
669
740
  type Context$1 = Application.Context;
670
741
  /**
@@ -1004,58 +1075,6 @@ declare const getIdentity: () => unknown;
1004
1075
  * ```
1005
1076
  */
1006
1077
  declare const checkScopes: (required: string[]) => void;
1007
- /** Amazon Cognito user pool configuration for JWT verification. */
1008
- interface CognitoUserPoolConfig {
1009
- /** The Cognito User Pool ID (e.g. `us-east-1_abc123`). */
1010
- userPoolId: string;
1011
- /**
1012
- * Which token type to verify.
1013
- * @default 'access'
1014
- */
1015
- tokenUse?: 'access' | 'id';
1016
- /** The app client ID registered in the User Pool. */
1017
- clientId: string;
1018
- }
1019
- /**
1020
- * Authentication options for the MCP router.
1021
- *
1022
- * Supply either `cognitoUserPool` (uses `CognitoJwtVerifier` from
1023
- * `@ttoss/auth-core`) or a custom `verifyToken` function — not both.
1024
- */
1025
- interface McpAuthOptions {
1026
- /**
1027
- * Amazon Cognito user pool config. When provided, the router creates a
1028
- * `CognitoJwtVerifier` and validates every incoming Bearer token against it.
1029
- */
1030
- cognitoUserPool?: CognitoUserPoolConfig;
1031
- /**
1032
- * Custom token verifier for non-Cognito providers (Auth0, Keycloak, …).
1033
- * Receives the raw Bearer token string. Should resolve with the verified
1034
- * payload or reject/throw on failure.
1035
- */
1036
- verifyToken?: (token: string) => Promise<unknown>;
1037
- /**
1038
- * Router-level scope guard. All listed scopes must be present on the token
1039
- * for any MCP request to be allowed. Returns 403 if any scope is missing.
1040
- *
1041
- * Cognito encodes scopes as a space-separated string in `payload.scope`.
1042
- *
1043
- * @example ['mcp:access']
1044
- */
1045
- requiredScopes?: string[];
1046
- /**
1047
- * URL of this MCP server, used in the OAuth Protected Resource Metadata
1048
- * response (`/.well-known/oauth-protected-resource`). Both this field and
1049
- * `authorizationServerUrl` must be provided to enable the endpoint.
1050
- */
1051
- resourceServerUrl?: string;
1052
- /**
1053
- * URL of the OAuth Authorization Server that issues tokens for this resource.
1054
- * Enables `/.well-known/oauth-protected-resource` for MCP client auto-discovery
1055
- * (RFC 9728) when combined with `resourceServerUrl`.
1056
- */
1057
- authorizationServerUrl?: string;
1058
- }
1059
1078
  /**
1060
1079
  * Options for configuring the MCP router
1061
1080
  */
@@ -1108,10 +1127,13 @@ interface McpRouterOptions {
1108
1127
  /**
1109
1128
  * OAuth / JWT authentication configuration for the MCP endpoint.
1110
1129
  *
1111
- * When set, every incoming MCP request must include a valid Bearer token in
1112
- * the `Authorization` header. Invalid or missing tokens receive a `401`
1113
- * response with `WWW-Authenticate: Bearer`. Tokens that fail a
1114
- * `requiredScopes` check receive `403`.
1130
+ * When set, incoming MCP requests must include a valid Bearer token in the
1131
+ * `Authorization` header except for `publicMethods` (by default
1132
+ * `initialize` and `tools/list`), which bypass verification so clients can
1133
+ * discover the server before authenticating. Invalid or missing tokens
1134
+ * receive a `401` response with `WWW-Authenticate: Bearer` (or
1135
+ * `Bearer resource_metadata="..."` when `resourceMetadataUrl` is set, per
1136
+ * RFC 9728). Tokens that fail a `requiredScopes` check receive `403`.
1115
1137
  *
1116
1138
  * The verified token payload is accessible inside tool handlers via
1117
1139
  * {@link getIdentity}. Fine-grained per-tool scope checks can be done with
@@ -1312,4 +1334,4 @@ declare const createProtectedResourceMetadataMiddleware: (args: {
1312
1334
  authorizationServers: string[];
1313
1335
  }) => Application.Middleware;
1314
1336
  //#endregion
1315
- export { ApiCallOptions, AuthCodeStore, AuthorizeRequest, ClientStore, CognitoUserPoolConfig, Context$1 as Context, IssueTokensArgs, IssuedTokens, JsonObjectSchema, McpAuthOptions, McpAuthServerOptions, McpRouterOptions, McpServer, OAuthClient, OAuthClientMetadata, OnAuthorizeArgs, OnAuthorizeResult, OnRefreshTokenArgs, OnRefreshTokenResult, RegisterToolFromSchemaParams, StoredAuthorizationCode, apiCall, checkScopes, createMcpAuthServer, createMcpRouter, createProtectedResourceMetadataMiddleware, getIdentity, getWwwAuthenticateHeader, registerToolFromSchema, z };
1337
+ export { ApiCallOptions, AuthCodeStore, AuthorizeRequest, ClientStore, type CognitoUserPoolConfig, Context$1 as Context, IssueTokensArgs, IssuedTokens, JsonObjectSchema, type McpAuthOptions, McpAuthServerOptions, McpRouterOptions, McpServer, OAuthClient, OAuthClientMetadata, OnAuthorizeArgs, OnAuthorizeResult, OnRefreshTokenArgs, OnRefreshTokenResult, RegisterToolFromSchemaParams, StoredAuthorizationCode, apiCall, checkScopes, createMcpAuthServer, createMcpRouter, createProtectedResourceMetadataMiddleware, getIdentity, getWwwAuthenticateHeader, registerToolFromSchema, z };
package/dist/index.d.mts CHANGED
@@ -665,6 +665,77 @@ declare namespace Application {
665
665
  const HttpError: typeof HttpErrors.HttpError;
666
666
  }
667
667
  //#endregion
668
+ //#region src/auth.d.ts
669
+ /** Amazon Cognito user pool configuration for JWT verification. */
670
+ interface CognitoUserPoolConfig {
671
+ /** The Cognito User Pool ID (e.g. `us-east-1_abc123`). */
672
+ userPoolId: string;
673
+ /**
674
+ * Which token type to verify.
675
+ * @default 'access'
676
+ */
677
+ tokenUse?: 'access' | 'id';
678
+ /** The app client ID registered in the User Pool. */
679
+ clientId: string;
680
+ }
681
+ /**
682
+ * Authentication options for the MCP router.
683
+ *
684
+ * Supply either `cognitoUserPool` (uses `CognitoJwtVerifier` from
685
+ * `@ttoss/auth-core`) or a custom `verifyToken` function — not both.
686
+ */
687
+ interface McpAuthOptions {
688
+ /**
689
+ * Amazon Cognito user pool config. When provided, the router creates a
690
+ * `CognitoJwtVerifier` and validates every incoming Bearer token against it.
691
+ */
692
+ cognitoUserPool?: CognitoUserPoolConfig;
693
+ /**
694
+ * Custom token verifier for non-Cognito providers (Auth0, Keycloak, …).
695
+ * Receives the raw Bearer token string. Should resolve with the verified
696
+ * payload or reject/throw on failure.
697
+ */
698
+ verifyToken?: (token: string) => Promise<unknown>;
699
+ /**
700
+ * Router-level scope guard. All listed scopes must be present on the token
701
+ * for any MCP request to be allowed. Returns 403 if any scope is missing.
702
+ *
703
+ * Cognito encodes scopes as a space-separated string in `payload.scope`.
704
+ *
705
+ * @example ['mcp:access']
706
+ */
707
+ requiredScopes?: string[];
708
+ /**
709
+ * URL of this MCP server, used in the OAuth Protected Resource Metadata
710
+ * response (`/.well-known/oauth-protected-resource`). Both this field and
711
+ * `authorizationServerUrl` must be provided to enable the endpoint.
712
+ */
713
+ resourceServerUrl?: string;
714
+ /**
715
+ * URL of the OAuth Authorization Server that issues tokens for this resource.
716
+ * Enables `/.well-known/oauth-protected-resource` for MCP client auto-discovery
717
+ * (RFC 9728) when combined with `resourceServerUrl`.
718
+ */
719
+ authorizationServerUrl?: string;
720
+ /**
721
+ * JSON-RPC methods (read from `body.method`) that bypass token verification,
722
+ * so MCP clients can discover the server before authenticating. Set to an
723
+ * empty array to require a token for every method.
724
+ *
725
+ * @default ['initialize', 'tools/list']
726
+ */
727
+ publicMethods?: string[];
728
+ /**
729
+ * When set, a `401` from verification responds with
730
+ * `WWW-Authenticate: Bearer resource_metadata="<resourceMetadataUrl>"`
731
+ * (RFC 9728) so MCP clients can auto-discover the authorization server.
732
+ * When omitted, the header falls back to a bare `Bearer`.
733
+ *
734
+ * @example 'https://mcp.example.com/.well-known/oauth-protected-resource'
735
+ */
736
+ resourceMetadataUrl?: string;
737
+ }
738
+ //#endregion
668
739
  //#region src/authServerTypes.d.ts
669
740
  type Context$1 = Application.Context;
670
741
  /**
@@ -1004,58 +1075,6 @@ declare const getIdentity: () => unknown;
1004
1075
  * ```
1005
1076
  */
1006
1077
  declare const checkScopes: (required: string[]) => void;
1007
- /** Amazon Cognito user pool configuration for JWT verification. */
1008
- interface CognitoUserPoolConfig {
1009
- /** The Cognito User Pool ID (e.g. `us-east-1_abc123`). */
1010
- userPoolId: string;
1011
- /**
1012
- * Which token type to verify.
1013
- * @default 'access'
1014
- */
1015
- tokenUse?: 'access' | 'id';
1016
- /** The app client ID registered in the User Pool. */
1017
- clientId: string;
1018
- }
1019
- /**
1020
- * Authentication options for the MCP router.
1021
- *
1022
- * Supply either `cognitoUserPool` (uses `CognitoJwtVerifier` from
1023
- * `@ttoss/auth-core`) or a custom `verifyToken` function — not both.
1024
- */
1025
- interface McpAuthOptions {
1026
- /**
1027
- * Amazon Cognito user pool config. When provided, the router creates a
1028
- * `CognitoJwtVerifier` and validates every incoming Bearer token against it.
1029
- */
1030
- cognitoUserPool?: CognitoUserPoolConfig;
1031
- /**
1032
- * Custom token verifier for non-Cognito providers (Auth0, Keycloak, …).
1033
- * Receives the raw Bearer token string. Should resolve with the verified
1034
- * payload or reject/throw on failure.
1035
- */
1036
- verifyToken?: (token: string) => Promise<unknown>;
1037
- /**
1038
- * Router-level scope guard. All listed scopes must be present on the token
1039
- * for any MCP request to be allowed. Returns 403 if any scope is missing.
1040
- *
1041
- * Cognito encodes scopes as a space-separated string in `payload.scope`.
1042
- *
1043
- * @example ['mcp:access']
1044
- */
1045
- requiredScopes?: string[];
1046
- /**
1047
- * URL of this MCP server, used in the OAuth Protected Resource Metadata
1048
- * response (`/.well-known/oauth-protected-resource`). Both this field and
1049
- * `authorizationServerUrl` must be provided to enable the endpoint.
1050
- */
1051
- resourceServerUrl?: string;
1052
- /**
1053
- * URL of the OAuth Authorization Server that issues tokens for this resource.
1054
- * Enables `/.well-known/oauth-protected-resource` for MCP client auto-discovery
1055
- * (RFC 9728) when combined with `resourceServerUrl`.
1056
- */
1057
- authorizationServerUrl?: string;
1058
- }
1059
1078
  /**
1060
1079
  * Options for configuring the MCP router
1061
1080
  */
@@ -1108,10 +1127,13 @@ interface McpRouterOptions {
1108
1127
  /**
1109
1128
  * OAuth / JWT authentication configuration for the MCP endpoint.
1110
1129
  *
1111
- * When set, every incoming MCP request must include a valid Bearer token in
1112
- * the `Authorization` header. Invalid or missing tokens receive a `401`
1113
- * response with `WWW-Authenticate: Bearer`. Tokens that fail a
1114
- * `requiredScopes` check receive `403`.
1130
+ * When set, incoming MCP requests must include a valid Bearer token in the
1131
+ * `Authorization` header except for `publicMethods` (by default
1132
+ * `initialize` and `tools/list`), which bypass verification so clients can
1133
+ * discover the server before authenticating. Invalid or missing tokens
1134
+ * receive a `401` response with `WWW-Authenticate: Bearer` (or
1135
+ * `Bearer resource_metadata="..."` when `resourceMetadataUrl` is set, per
1136
+ * RFC 9728). Tokens that fail a `requiredScopes` check receive `403`.
1115
1137
  *
1116
1138
  * The verified token payload is accessible inside tool handlers via
1117
1139
  * {@link getIdentity}. Fine-grained per-tool scope checks can be done with
@@ -1312,4 +1334,4 @@ declare const createProtectedResourceMetadataMiddleware: (args: {
1312
1334
  authorizationServers: string[];
1313
1335
  }) => Application.Middleware;
1314
1336
  //#endregion
1315
- export { ApiCallOptions, AuthCodeStore, AuthorizeRequest, ClientStore, CognitoUserPoolConfig, Context$1 as Context, IssueTokensArgs, IssuedTokens, JsonObjectSchema, McpAuthOptions, McpAuthServerOptions, McpRouterOptions, McpServer, OAuthClient, OAuthClientMetadata, OnAuthorizeArgs, OnAuthorizeResult, OnRefreshTokenArgs, OnRefreshTokenResult, RegisterToolFromSchemaParams, StoredAuthorizationCode, apiCall, checkScopes, createMcpAuthServer, createMcpRouter, createProtectedResourceMetadataMiddleware, getIdentity, getWwwAuthenticateHeader, registerToolFromSchema, z };
1337
+ export { ApiCallOptions, AuthCodeStore, AuthorizeRequest, ClientStore, type CognitoUserPoolConfig, Context$1 as Context, IssueTokensArgs, IssuedTokens, JsonObjectSchema, type McpAuthOptions, McpAuthServerOptions, McpRouterOptions, McpServer, OAuthClient, OAuthClientMetadata, OnAuthorizeArgs, OnAuthorizeResult, OnRefreshTokenArgs, OnRefreshTokenResult, RegisterToolFromSchemaParams, StoredAuthorizationCode, apiCall, checkScopes, createMcpAuthServer, createMcpRouter, createProtectedResourceMetadataMiddleware, getIdentity, getWwwAuthenticateHeader, registerToolFromSchema, z };
package/dist/index.mjs CHANGED
@@ -1,12 +1,82 @@
1
1
  /** Powered by @ttoss/config. https://ttoss.dev/docs/modules/packages/config/ */
2
2
  import { AsyncLocalStorage } from "node:async_hooks";
3
3
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
4
- import { CognitoJwtVerifier } from "@ttoss/auth-core/amazon-cognito";
5
4
  import { Router } from "@ttoss/http-server";
6
5
  import { z, z as z$1 } from "zod";
6
+ import { CognitoJwtVerifier } from "@ttoss/auth-core/amazon-cognito";
7
7
  import { createHash, randomBytes } from "node:crypto";
8
8
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
9
9
 
10
+ //#region src/auth.ts
11
+ /** MCP lifecycle/discovery methods reachable before a client authenticates. */
12
+ var DEFAULT_PUBLIC_METHODS = ["initialize", "tools/list"];
13
+ /**
14
+ * Builds the token verifier from the auth options, preferring an explicit
15
+ * `verifyToken` and otherwise creating a `CognitoJwtVerifier`.
16
+ */
17
+ var buildTokenVerifier = auth => {
18
+ if (auth.cognitoUserPool) {
19
+ const v = CognitoJwtVerifier.create({
20
+ tokenUse: "access",
21
+ ...auth.cognitoUserPool
22
+ });
23
+ return t => {
24
+ return v.verify(t);
25
+ };
26
+ }
27
+ if (auth.verifyToken) return auth.verifyToken;
28
+ throw new Error("McpAuthOptions requires either cognitoUserPool or verifyToken");
29
+ };
30
+ /** Returns true when the identity carries every required scope (or none are required). */
31
+ var hasRequiredScopes = (requiredScopes, identity) => {
32
+ if (!requiredScopes?.length) return true;
33
+ const tokenScopes = (identity?.scope ?? "").split(" ");
34
+ return requiredScopes.every(s => {
35
+ return tokenScopes.includes(s);
36
+ });
37
+ };
38
+ /**
39
+ * Registers the token-verification middleware (with public-method bypass and
40
+ * RFC 9728 discovery) and, when configured, the OAuth protected-resource
41
+ * metadata endpoint on the given router.
42
+ */
43
+ var registerAuthRoutes = (router, path, auth, tokenVerifier) => {
44
+ const publicMethods = new Set(auth.publicMethods ?? DEFAULT_PUBLIC_METHODS);
45
+ const unauthorizedHeader = auth.resourceMetadataUrl ? `Bearer resource_metadata="${auth.resourceMetadataUrl}"` : "Bearer";
46
+ router.use(path, async (ctx, next) => {
47
+ const method = ctx.request.body?.method;
48
+ if (publicMethods.has(method ?? "")) {
49
+ await next();
50
+ return;
51
+ }
52
+ const authHeader = ctx.headers.authorization;
53
+ const token = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : "";
54
+ let identity;
55
+ try {
56
+ identity = await tokenVerifier(token);
57
+ } catch {
58
+ ctx.status = 401;
59
+ ctx.set("WWW-Authenticate", unauthorizedHeader);
60
+ ctx.body = "Unauthorized";
61
+ return;
62
+ }
63
+ if (!hasRequiredScopes(auth.requiredScopes, identity)) {
64
+ ctx.status = 403;
65
+ ctx.body = "Forbidden";
66
+ return;
67
+ }
68
+ ctx.state.identity = identity;
69
+ await next();
70
+ });
71
+ if (auth.resourceServerUrl && auth.authorizationServerUrl) router.get("/.well-known/oauth-protected-resource", ctx => {
72
+ ctx.body = {
73
+ resource: auth.resourceServerUrl,
74
+ authorization_servers: [auth.authorizationServerUrl]
75
+ };
76
+ });
77
+ };
78
+
79
+ //#endregion
10
80
  //#region src/authServerHandlers.ts
11
81
  var base64UrlEncode = buffer => {
12
82
  return buffer.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
@@ -493,52 +563,6 @@ var checkScopes = required => {
493
563
  return !tokenScopes.includes(s);
494
564
  }).length > 0) throw new Error(`Insufficient scopes. Required: ${required.join(", ")}`);
495
565
  };
496
- var buildTokenVerifier = auth => {
497
- if (auth.cognitoUserPool) {
498
- const v = CognitoJwtVerifier.create({
499
- tokenUse: "access",
500
- ...auth.cognitoUserPool
501
- });
502
- return t => {
503
- return v.verify(t);
504
- };
505
- }
506
- if (auth.verifyToken) return auth.verifyToken;
507
- throw new Error("McpAuthOptions requires either cognitoUserPool or verifyToken");
508
- };
509
- var registerAuthRoutes = (router, path, auth, tokenVerifier) => {
510
- router.use(path, async (ctx, next) => {
511
- const authHeader = ctx.headers.authorization;
512
- const token = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : "";
513
- let identity;
514
- try {
515
- identity = await tokenVerifier(token);
516
- } catch {
517
- ctx.status = 401;
518
- ctx.set("WWW-Authenticate", "Bearer");
519
- ctx.body = "Unauthorized";
520
- return;
521
- }
522
- if (auth.requiredScopes?.length) {
523
- const tokenScopes = (identity?.scope ?? "").split(" ");
524
- if (auth.requiredScopes.filter(s => {
525
- return !tokenScopes.includes(s);
526
- }).length > 0) {
527
- ctx.status = 403;
528
- ctx.body = "Forbidden";
529
- return;
530
- }
531
- }
532
- ctx.state.identity = identity;
533
- await next();
534
- });
535
- if (auth.resourceServerUrl && auth.authorizationServerUrl) router.get("/.well-known/oauth-protected-resource", ctx => {
536
- ctx.body = {
537
- resource: auth.resourceServerUrl,
538
- authorization_servers: [auth.authorizationServerUrl]
539
- };
540
- });
541
- };
542
566
  /**
543
567
  * Creates a Koa router configured to handle MCP protocol requests
544
568
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ttoss/http-server-mcp",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "description": "Model Context Protocol (MCP) server integration for @ttoss/http-server",
5
5
  "keywords": [
6
6
  "ai",