@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 +28 -1
- package/dist/index.cjs +71 -47
- package/dist/index.d.cts +79 -57
- package/dist/index.d.mts +79 -57
- package/dist/index.mjs +71 -47
- package/package.json +1 -1
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.
|
|
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,
|
|
1112
|
-
*
|
|
1113
|
-
*
|
|
1114
|
-
*
|
|
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,
|
|
1112
|
-
*
|
|
1113
|
-
*
|
|
1114
|
-
*
|
|
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
|
*
|