@ttoss/http-server-mcp 0.26.2 → 0.27.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/MIGRATIONS.md +38 -0
- package/README.md +20 -9
- package/dist/index.cjs +8 -3
- package/dist/index.d.cts +29 -8
- package/dist/index.d.mts +29 -8
- package/dist/index.mjs +8 -3
- package/package.json +5 -3
package/MIGRATIONS.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Migrations
|
|
2
|
+
|
|
3
|
+
## `tools/list` is no longer public by default
|
|
4
|
+
|
|
5
|
+
`auth.publicMethods` now defaults to `['initialize']` instead of
|
|
6
|
+
`['initialize', 'tools/list']`. A server with `auth` configured no longer
|
|
7
|
+
serves its tool catalogue to unauthenticated callers.
|
|
8
|
+
|
|
9
|
+
```diff
|
|
10
|
+
createMcpRouter(mcpServer, {
|
|
11
|
+
auth: {
|
|
12
|
+
cognitoUserPool: { userPoolId: '...', clientId: '...' },
|
|
13
|
+
+ // Only needed if unauthenticated callers must keep listing tools.
|
|
14
|
+
+ publicMethods: ['initialize', 'tools/list'],
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**What you will observe if you miss this.** An unauthenticated `tools/list`
|
|
20
|
+
returns `401` with `WWW-Authenticate: Bearer resource_metadata="…"` instead of
|
|
21
|
+
`200` and the catalogue. It fails on the first such request rather than on
|
|
22
|
+
particular inputs, so a smoke test against a deployed server surfaces it
|
|
23
|
+
immediately.
|
|
24
|
+
|
|
25
|
+
**Who is affected.** Only consumers that configure `auth` and never set
|
|
26
|
+
`publicMethods`. Anyone already passing `publicMethods` — including
|
|
27
|
+
`publicMethods: []` — is unaffected, and so is any server without `auth`.
|
|
28
|
+
|
|
29
|
+
**Who should not restore the old value.** OAuth clients do not need it. The
|
|
30
|
+
`401` and its RFC 9728 challenge are what start the authorization flow, and a
|
|
31
|
+
client that lists tools anonymously still cannot call one; the flow reaches the
|
|
32
|
+
authorization redirect identically with `tools/list` open or closed. Restore it
|
|
33
|
+
only to serve callers that will never authenticate, and note what that exposes:
|
|
34
|
+
every tool name, description, and input schema, which for an OpenAPI-derived
|
|
35
|
+
server is a map of the whole underlying API.
|
|
36
|
+
|
|
37
|
+
The one-time startup warning that pre-announced this change is gone, since the
|
|
38
|
+
default it warned about is now the secure one.
|
package/README.md
CHANGED
|
@@ -371,7 +371,7 @@ The `WWW-Authenticate: Bearer resource_metadata="…"` header is how MCP clients
|
|
|
371
371
|
|
|
372
372
|
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:
|
|
373
373
|
|
|
374
|
-
- **`publicMethods`** — JSON-RPC methods that bypass verification, read from the request body's `method` field. Defaults to `['initialize'
|
|
374
|
+
- **`publicMethods`** — JSON-RPC methods that bypass verification, read from the request body's `method` field. Defaults to `['initialize']`, which the spec sanctions so a client can complete the lifecycle handshake before it can discover the authorization server. Pass `[]` to require a token for every method, or a custom list to change the exempt set. Adding `tools/list` serves the full tool catalogue — every tool name, description, and input schema — to unauthenticated callers; see [`publicMethods` and OAuth clients](#publicmethods-and-oauth-clients) before doing so.
|
|
375
375
|
- **`resourceMetadataUrl`** — the URL a `401` advertises as `WWW-Authenticate: Bearer resource_metadata="<url>"` (RFC 9728) instead of a bare `Bearer`, pointing MCP clients at the protected-resource metadata document. **You normally do not set it**: when the document is served — i.e. both `resourceServerUrl` and `authorizationServerUrl` are configured — it defaults to the RFC 9728 location this router serves it at, so the header and the routes cannot drift apart. When the document is not served, the header stays a bare `Bearer` rather than naming a location with no route. The field exists for the one configuration where this router deliberately does not serve the document — an `oauthServer()` in the same deployment already answers that path, so this router is mounted without `resourceServerUrl`/`authorizationServerUrl` to avoid two routers on one path, and has nothing to derive from. Compute the value with `protectedResourceMetadataUrl` from [`@ttoss/auth-core`](https://ttoss.dev/docs/modules/packages/auth-core) rather than typing it, so both halves apply the same §3.1 rule.
|
|
376
376
|
|
|
377
377
|
```typescript
|
|
@@ -383,12 +383,12 @@ createMcpRouter(mcpServer, {
|
|
|
383
383
|
resourceServerUrl: 'https://mcp.example.com',
|
|
384
384
|
authorizationServerUrl:
|
|
385
385
|
'https://cognito-idp.us-east-1.amazonaws.com/us-east-1_xxx',
|
|
386
|
-
// publicMethods defaults to ['initialize'
|
|
386
|
+
// publicMethods defaults to ['initialize'].
|
|
387
387
|
},
|
|
388
388
|
});
|
|
389
389
|
```
|
|
390
390
|
|
|
391
|
-
Every field is optional
|
|
391
|
+
Every field is optional.
|
|
392
392
|
|
|
393
393
|
### Supporting clients that connect to the bare origin
|
|
394
394
|
|
|
@@ -502,7 +502,7 @@ Creates a Koa router configured to handle MCP protocol requests.
|
|
|
502
502
|
- `auth.verifyToken` — Custom async token verifier `(token: string) => Promise<unknown>`
|
|
503
503
|
- `auth.requiredScopes` — Router-level scope guard; returns 403 if any scope is missing
|
|
504
504
|
- `auth.resourceServerUrl` + `auth.authorizationServerUrl` — Enable the protected-resource metadata document, served at both `/.well-known/oauth-protected-resource` and the RFC 9728 path-derived `/.well-known/oauth-protected-resource<path>`; the metadata `resource` is set to `resourceServerUrl + path` so clients following `resource` land on the actual MCP endpoint
|
|
505
|
-
- `auth.publicMethods` — JSON-RPC methods that bypass verification (default `['initialize'
|
|
505
|
+
- `auth.publicMethods` — JSON-RPC methods that bypass verification (default `['initialize']`)
|
|
506
506
|
- `auth.resourceMetadataUrl` — URL advertised in the RFC 9728 `WWW-Authenticate: Bearer resource_metadata="…"` header on a 401. Defaults to the location derived from `resourceServerUrl` + `path` (the one this router serves), so the header cannot drift from the routes; set it only when a separate `oauthServer()` serves the document and this router is mounted without `resourceServerUrl`/`authorizationServerUrl`
|
|
507
507
|
- `auth.resourceIndicator` — Expected `aud` value(s) (RFC 8707); rejects tokens minted for a different resource. See [Resource indicator validation](#resource-indicator-validation-rfc-8707)
|
|
508
508
|
|
|
@@ -873,14 +873,19 @@ expect(call.status).toBe(200);
|
|
|
873
873
|
|
|
874
874
|
### `publicMethods` and OAuth clients
|
|
875
875
|
|
|
876
|
-
`auth.publicMethods` defaults to `['initialize'
|
|
876
|
+
`auth.publicMethods` defaults to `['initialize']`: the handshake is reachable without a token, nothing else is.
|
|
877
877
|
|
|
878
|
-
|
|
879
|
-
- Setting `publicMethods: []` makes `initialize` return `401` + `WWW-Authenticate`, which triggers the client's OAuth discovery and PKCE flow.
|
|
878
|
+
**Adding `tools/list` is a deliberate exposure.** It serves the full tool catalogue — every name, description, and input schema — to anyone who can reach the endpoint. Those leak internal resource names and domain vocabulary, and for an OpenAPI-derived server the schemas mirror the REST surface, so the catalogue becomes an unauthenticated map of the whole API including operations the caller could never invoke. It buys an OAuth client nothing, because the `401` is what starts the authorization flow and a client that lists tools anonymously still cannot call one. Open it only to serve callers that will never authenticate:
|
|
880
879
|
|
|
881
|
-
|
|
880
|
+
```typescript
|
|
881
|
+
publicMethods: ['initialize', 'tools/list'],
|
|
882
|
+
```
|
|
883
|
+
|
|
884
|
+
**Whether `initialize` should be public depends on how tokens are issued.** Keep the default when token auth is handled outside the OAuth flow (Cognito, API keys, Bearer tokens minted elsewhere). Set `publicMethods: []` when you want OAuth clients to authenticate before anything else — `initialize` then returns `401` + `WWW-Authenticate` on the very first request, so discovery starts there rather than one step later.
|
|
882
885
|
|
|
883
|
-
|
|
886
|
+
Either way the authorization flow works. Driving the official MCP client SDK against this router shows the RFC 9728 challenge resolving to the protected-resource document, the authorization server, and the redirect, whether the `401` arrives on `initialize` (`publicMethods: []`) or on the request after it (the default). `tests/unit/tests/oauth-client-flow.test.ts` asserts it.
|
|
887
|
+
|
|
888
|
+
On the `2026-07-28` revision `initialize` does not exist — discovery is `server/discover`, which is not in the default set and therefore always requires a token. That is deliberate: RFC 9728 discovery is driven by the challenge itself.
|
|
884
889
|
|
|
885
890
|
## AWS Lambda Deployment
|
|
886
891
|
|
|
@@ -931,6 +936,12 @@ Keep the following in mind:
|
|
|
931
936
|
|
|
932
937
|
> This differs from [`awslabs/run-model-context-protocol-servers-with-aws-lambda`](https://github.com/awslabs/run-model-context-protocol-servers-with-aws-lambda), which wraps **stdio**-based MCP servers into Lambda by spawning a child process per invocation. `@ttoss/http-server-mcp` already speaks Streamable HTTP, so that wrapper is unnecessary — you deploy it like any other HTTP handler.
|
|
933
938
|
|
|
939
|
+
## Migrations
|
|
940
|
+
|
|
941
|
+
Breaking changes and what they require of consumers are listed in
|
|
942
|
+
[MIGRATIONS.md](https://github.com/ttoss/ttoss/blob/main/packages/http-server-mcp/MIGRATIONS.md),
|
|
943
|
+
newest first.
|
|
944
|
+
|
|
934
945
|
## Related Packages
|
|
935
946
|
|
|
936
947
|
- [@ttoss/http-server](https://ttoss.dev/docs/modules/packages/http-server) - HTTP server foundation
|
package/dist/index.cjs
CHANGED
|
@@ -326,8 +326,14 @@ var createGatedToolRegistrar = ({
|
|
|
326
326
|
|
|
327
327
|
//#endregion
|
|
328
328
|
//#region src/index.ts
|
|
329
|
-
/**
|
|
330
|
-
|
|
329
|
+
/**
|
|
330
|
+
* MCP lifecycle methods reachable before a client authenticates.
|
|
331
|
+
*
|
|
332
|
+
* Only `initialize`, which the MCP authorization spec sanctions: a client has
|
|
333
|
+
* to complete the handshake before it can discover the authorization server.
|
|
334
|
+
* `tools/list` is not here — see {@link McpAuthOptions.publicMethods}.
|
|
335
|
+
*/
|
|
336
|
+
var DEFAULT_PUBLIC_METHODS = ["initialize"];
|
|
331
337
|
/**
|
|
332
338
|
* Asserts that a verified token's `aud` claim includes at least one of the
|
|
333
339
|
* expected resource indicator values. `aud` may be a single string or an
|
|
@@ -547,7 +553,6 @@ var createMcpRouter = (server, options = {}) => {
|
|
|
547
553
|
})
|
|
548
554
|
} : void 0;
|
|
549
555
|
const resourceMetadataUrl = auth.resourceMetadataUrl ?? metadata?.url;
|
|
550
|
-
if (auth.publicMethods === void 0) console.warn(`[@ttoss/http-server-mcp] "auth" is configured but "publicMethods" was not set, so it defaults to ${JSON.stringify(DEFAULT_PUBLIC_METHODS)}. This means "tools/list" — the full tool catalogue, including names, descriptions, and input schemas — is served to unauthenticated callers. To require a token for "tools/list" too, set publicMethods: ['initialize']. To keep the current behaviour and silence this warning, set publicMethods: ${JSON.stringify(DEFAULT_PUBLIC_METHODS)} explicitly.`);
|
|
551
556
|
const publicMethods = new Set(auth.publicMethods ?? DEFAULT_PUBLIC_METHODS);
|
|
552
557
|
const verify = (0, _ttoss_http_server_auth.authMiddleware)({
|
|
553
558
|
strategies: ["oauth"],
|
package/dist/index.d.cts
CHANGED
|
@@ -892,11 +892,32 @@ interface McpAuthOptions {
|
|
|
892
892
|
requiredScopes?: string[];
|
|
893
893
|
/**
|
|
894
894
|
* JSON-RPC methods (read from `body.method`) that bypass verification.
|
|
895
|
-
*
|
|
896
|
-
*
|
|
897
|
-
*
|
|
898
|
-
*
|
|
899
|
-
*
|
|
895
|
+
*
|
|
896
|
+
* `initialize` is public because the MCP authorization spec sanctions it: a
|
|
897
|
+
* client completes the lifecycle handshake before it can discover the
|
|
898
|
+
* authorization server. Nothing else is, so a server with `auth` configured
|
|
899
|
+
* serves no tool metadata to an unauthenticated caller.
|
|
900
|
+
*
|
|
901
|
+
* Opening `tools/list` is a deliberate choice, not a default. It serves the
|
|
902
|
+
* full tool catalogue — every name, description, and input schema — to
|
|
903
|
+
* anyone who can reach the endpoint, which for an OpenAPI-derived server is
|
|
904
|
+
* a map of the whole underlying API. It buys an OAuth client nothing: the
|
|
905
|
+
* `401` and its RFC 9728 `WWW-Authenticate` challenge are what start the
|
|
906
|
+
* authorization flow, and a client that lists tools anonymously still
|
|
907
|
+
* cannot call one. Set it only to serve callers that will never
|
|
908
|
+
* authenticate.
|
|
909
|
+
*
|
|
910
|
+
* @default ['initialize']
|
|
911
|
+
*
|
|
912
|
+
* @example
|
|
913
|
+
* ```typescript
|
|
914
|
+
* // Restore unauthenticated tool discovery.
|
|
915
|
+
* publicMethods: ['initialize', 'tools/list'],
|
|
916
|
+
*
|
|
917
|
+
* // Require a token for the handshake too, so an OAuth client
|
|
918
|
+
* // self-discovers from its very first request.
|
|
919
|
+
* publicMethods: [],
|
|
920
|
+
* ```
|
|
900
921
|
*/
|
|
901
922
|
publicMethods?: string[];
|
|
902
923
|
/**
|
|
@@ -1114,9 +1135,9 @@ interface McpRouterOptions {
|
|
|
1114
1135
|
* OAuth / JWT authentication configuration for the MCP endpoint.
|
|
1115
1136
|
*
|
|
1116
1137
|
* When set, incoming MCP requests must include a valid Bearer token in the
|
|
1117
|
-
* `Authorization` header — except for `publicMethods` (by default
|
|
1118
|
-
* `initialize`
|
|
1119
|
-
*
|
|
1138
|
+
* `Authorization` header — except for `publicMethods` (by default just
|
|
1139
|
+
* `initialize`), which bypasses verification so a client can complete the
|
|
1140
|
+
* lifecycle handshake before authenticating. Invalid or missing tokens
|
|
1120
1141
|
* receive a `401` response with `WWW-Authenticate: Bearer` (or
|
|
1121
1142
|
* `Bearer resource_metadata="..."` when `resourceMetadataUrl` is set, per
|
|
1122
1143
|
* RFC 9728). Tokens that fail a `requiredScopes` check receive `403`.
|
package/dist/index.d.mts
CHANGED
|
@@ -893,11 +893,32 @@ interface McpAuthOptions {
|
|
|
893
893
|
requiredScopes?: string[];
|
|
894
894
|
/**
|
|
895
895
|
* JSON-RPC methods (read from `body.method`) that bypass verification.
|
|
896
|
-
*
|
|
897
|
-
*
|
|
898
|
-
*
|
|
899
|
-
*
|
|
900
|
-
*
|
|
896
|
+
*
|
|
897
|
+
* `initialize` is public because the MCP authorization spec sanctions it: a
|
|
898
|
+
* client completes the lifecycle handshake before it can discover the
|
|
899
|
+
* authorization server. Nothing else is, so a server with `auth` configured
|
|
900
|
+
* serves no tool metadata to an unauthenticated caller.
|
|
901
|
+
*
|
|
902
|
+
* Opening `tools/list` is a deliberate choice, not a default. It serves the
|
|
903
|
+
* full tool catalogue — every name, description, and input schema — to
|
|
904
|
+
* anyone who can reach the endpoint, which for an OpenAPI-derived server is
|
|
905
|
+
* a map of the whole underlying API. It buys an OAuth client nothing: the
|
|
906
|
+
* `401` and its RFC 9728 `WWW-Authenticate` challenge are what start the
|
|
907
|
+
* authorization flow, and a client that lists tools anonymously still
|
|
908
|
+
* cannot call one. Set it only to serve callers that will never
|
|
909
|
+
* authenticate.
|
|
910
|
+
*
|
|
911
|
+
* @default ['initialize']
|
|
912
|
+
*
|
|
913
|
+
* @example
|
|
914
|
+
* ```typescript
|
|
915
|
+
* // Restore unauthenticated tool discovery.
|
|
916
|
+
* publicMethods: ['initialize', 'tools/list'],
|
|
917
|
+
*
|
|
918
|
+
* // Require a token for the handshake too, so an OAuth client
|
|
919
|
+
* // self-discovers from its very first request.
|
|
920
|
+
* publicMethods: [],
|
|
921
|
+
* ```
|
|
901
922
|
*/
|
|
902
923
|
publicMethods?: string[];
|
|
903
924
|
/**
|
|
@@ -1115,9 +1136,9 @@ interface McpRouterOptions {
|
|
|
1115
1136
|
* OAuth / JWT authentication configuration for the MCP endpoint.
|
|
1116
1137
|
*
|
|
1117
1138
|
* When set, incoming MCP requests must include a valid Bearer token in the
|
|
1118
|
-
* `Authorization` header — except for `publicMethods` (by default
|
|
1119
|
-
* `initialize`
|
|
1120
|
-
*
|
|
1139
|
+
* `Authorization` header — except for `publicMethods` (by default just
|
|
1140
|
+
* `initialize`), which bypasses verification so a client can complete the
|
|
1141
|
+
* lifecycle handshake before authenticating. Invalid or missing tokens
|
|
1121
1142
|
* receive a `401` response with `WWW-Authenticate: Bearer` (or
|
|
1122
1143
|
* `Bearer resource_metadata="..."` when `resourceMetadataUrl` is set, per
|
|
1123
1144
|
* RFC 9728). Tokens that fail a `requiredScopes` check receive `403`.
|
package/dist/index.mjs
CHANGED
|
@@ -323,8 +323,14 @@ var createGatedToolRegistrar = ({
|
|
|
323
323
|
|
|
324
324
|
//#endregion
|
|
325
325
|
//#region src/index.ts
|
|
326
|
-
/**
|
|
327
|
-
|
|
326
|
+
/**
|
|
327
|
+
* MCP lifecycle methods reachable before a client authenticates.
|
|
328
|
+
*
|
|
329
|
+
* Only `initialize`, which the MCP authorization spec sanctions: a client has
|
|
330
|
+
* to complete the handshake before it can discover the authorization server.
|
|
331
|
+
* `tools/list` is not here — see {@link McpAuthOptions.publicMethods}.
|
|
332
|
+
*/
|
|
333
|
+
var DEFAULT_PUBLIC_METHODS = ["initialize"];
|
|
328
334
|
/**
|
|
329
335
|
* Asserts that a verified token's `aud` claim includes at least one of the
|
|
330
336
|
* expected resource indicator values. `aud` may be a single string or an
|
|
@@ -544,7 +550,6 @@ var createMcpRouter = (server, options = {}) => {
|
|
|
544
550
|
})
|
|
545
551
|
} : void 0;
|
|
546
552
|
const resourceMetadataUrl = auth.resourceMetadataUrl ?? metadata?.url;
|
|
547
|
-
if (auth.publicMethods === void 0) console.warn(`[@ttoss/http-server-mcp] "auth" is configured but "publicMethods" was not set, so it defaults to ${JSON.stringify(DEFAULT_PUBLIC_METHODS)}. This means "tools/list" — the full tool catalogue, including names, descriptions, and input schemas — is served to unauthenticated callers. To require a token for "tools/list" too, set publicMethods: ['initialize']. To keep the current behaviour and silence this warning, set publicMethods: ${JSON.stringify(DEFAULT_PUBLIC_METHODS)} explicitly.`);
|
|
548
553
|
const publicMethods = new Set(auth.publicMethods ?? DEFAULT_PUBLIC_METHODS);
|
|
549
554
|
const verify = authMiddleware({
|
|
550
555
|
strategies: ["oauth"],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ttoss/http-server-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.27.0",
|
|
4
4
|
"description": "Model Context Protocol (MCP) server integration for @ttoss/http-server",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
}
|
|
31
31
|
},
|
|
32
32
|
"files": [
|
|
33
|
+
"MIGRATIONS.md",
|
|
33
34
|
"dist"
|
|
34
35
|
],
|
|
35
36
|
"dependencies": {
|
|
@@ -37,10 +38,11 @@
|
|
|
37
38
|
"@modelcontextprotocol/server": "^2.0.0",
|
|
38
39
|
"zod": "^4.4.3",
|
|
39
40
|
"@ttoss/auth-core": "^0.14.1",
|
|
40
|
-
"@ttoss/http-server
|
|
41
|
-
"@ttoss/http-server": "^0.
|
|
41
|
+
"@ttoss/http-server": "^0.8.1",
|
|
42
|
+
"@ttoss/http-server-auth": "^0.6.4"
|
|
42
43
|
},
|
|
43
44
|
"devDependencies": {
|
|
45
|
+
"@modelcontextprotocol/client": "^2.0.0",
|
|
44
46
|
"@types/koa": "^3.0.3",
|
|
45
47
|
"jest": "^30.4.2",
|
|
46
48
|
"supertest": "^7.2.2",
|