@ttoss/http-server-mcp 0.26.0 → 0.26.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.
- package/README.md +18 -9
- package/dist/index.cjs +20 -11
- package/dist/index.d.cts +12 -2
- package/dist/index.d.mts +12 -2
- package/dist/index.mjs +20 -11
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -305,6 +305,17 @@ const mcpRouter = createMcpRouter(mcpServer, {
|
|
|
305
305
|
|
|
306
306
|
MCP clients (Claude, Cursor, etc.) fetch `/.well-known/oauth-protected-resource` to discover which authorization server issues tokens for your MCP server. The endpoint must be **unauthenticated** — MCP clients call it before they have a token.
|
|
307
307
|
|
|
308
|
+
The document is served at **two** locations, both unauthenticated:
|
|
309
|
+
|
|
310
|
+
| Location | Who fetches it |
|
|
311
|
+
| --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
312
|
+
| `/.well-known/oauth-protected-resource` | Clients following the `resource_metadata` value in the `WWW-Authenticate` header |
|
|
313
|
+
| `/.well-known/oauth-protected-resource<path>` | Clients applying RFC 9728 §3.1, which derives the URL from the resource identifier's path — e.g. `/.well-known/oauth-protected-resource/mcp` for the default mount |
|
|
314
|
+
|
|
315
|
+
Serving only the root location makes a spec-following client fail discovery outright. When `path` is `'/'` the derivation produces the root itself, so only one route is registered.
|
|
316
|
+
|
|
317
|
+
Both the locations and the document come from `protectedResourceMetadataPaths` / `protectedResourceMetadataDocument` in [`@ttoss/auth-core`](https://ttoss.dev/docs/modules/packages/auth-core), which every package here shares — so this router, `oauthServer`, `createProtectedResourceMetadataMiddleware`, and `getWwwAuthenticateHeader` cannot disagree about where the document lives.
|
|
318
|
+
|
|
308
319
|
**With the built-in `auth` option** — add `resourceServerUrl` and `authorizationServerUrl`:
|
|
309
320
|
|
|
310
321
|
```typescript
|
|
@@ -361,25 +372,23 @@ The `WWW-Authenticate: Bearer resource_metadata="…"` header is how MCP clients
|
|
|
361
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:
|
|
362
373
|
|
|
363
374
|
- **`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. Leaving it unset serves the full tool catalogue — every tool name, description, and input schema — to unauthenticated callers, and logs a one-time warning explaining that and how to close it. Set `publicMethods` explicitly (even to the same default) to silence the warning.
|
|
364
|
-
- **`resourceMetadataUrl`** —
|
|
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. Set it only to point at a document served elsewhere. When the document is not served, the header stays a bare `Bearer` rather than naming a location with no route.
|
|
365
376
|
|
|
366
377
|
```typescript
|
|
367
378
|
createMcpRouter(mcpServer, {
|
|
368
379
|
auth: {
|
|
369
380
|
cognitoUserPool: { userPoolId: '...', clientId: '...' },
|
|
370
|
-
//
|
|
381
|
+
// Serves the metadata document (unauthenticated) and points 401s at it
|
|
382
|
+
// for auto-discovery — resourceMetadataUrl is derived from these two.
|
|
371
383
|
resourceServerUrl: 'https://mcp.example.com',
|
|
372
384
|
authorizationServerUrl:
|
|
373
385
|
'https://cognito-idp.us-east-1.amazonaws.com/us-east-1_xxx',
|
|
374
|
-
// ...and point 401s at it for auto-discovery.
|
|
375
|
-
resourceMetadataUrl:
|
|
376
|
-
'https://mcp.example.com/.well-known/oauth-protected-resource',
|
|
377
386
|
// publicMethods defaults to ['initialize', 'tools/list'].
|
|
378
387
|
},
|
|
379
388
|
});
|
|
380
389
|
```
|
|
381
390
|
|
|
382
|
-
|
|
391
|
+
Every field is optional, and the `publicMethods` default matches what MCP clients expect for discovery.
|
|
383
392
|
|
|
384
393
|
### Supporting clients that connect to the bare origin
|
|
385
394
|
|
|
@@ -399,7 +408,7 @@ createMcpRouter(mcpServer, {
|
|
|
399
408
|
});
|
|
400
409
|
```
|
|
401
410
|
|
|
402
|
-
The discovery
|
|
411
|
+
The discovery endpoints (both locations above) remain publicly accessible even when `aliases` includes `'/'`. Aliases themselves get no derived location — the metadata `resource` always names the primary `path`.
|
|
403
412
|
|
|
404
413
|
## Gated Tool Registrar
|
|
405
414
|
|
|
@@ -492,9 +501,9 @@ Creates a Koa router configured to handle MCP protocol requests.
|
|
|
492
501
|
- `auth.cognitoUserPool` — Cognito user pool config (`userPoolId`, `clientId`, `tokenUse`)
|
|
493
502
|
- `auth.verifyToken` — Custom async token verifier `(token: string) => Promise<unknown>`
|
|
494
503
|
- `auth.requiredScopes` — Router-level scope guard; returns 403 if any scope is missing
|
|
495
|
-
- `auth.resourceServerUrl` + `auth.authorizationServerUrl` — Enable `/.well-known/oauth-protected-resource
|
|
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
|
|
496
505
|
- `auth.publicMethods` — JSON-RPC methods that bypass verification (default `['initialize', 'tools/list']`)
|
|
497
|
-
- `auth.resourceMetadataUrl` —
|
|
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 to point at a document served elsewhere
|
|
498
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)
|
|
499
508
|
|
|
500
509
|
**Returns:** `Router` — Koa router instance
|
package/dist/index.cjs
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, {
|
|
3
3
|
value: 'Module'
|
|
4
4
|
});
|
|
5
|
+
let _ttoss_auth_core = require("@ttoss/auth-core");
|
|
5
6
|
let _ttoss_auth_core_amazon_cognito = require("@ttoss/auth-core/amazon-cognito");
|
|
6
7
|
let _ttoss_http_server = require("@ttoss/http-server");
|
|
7
8
|
let _ttoss_http_server_auth = require("@ttoss/http-server-auth");
|
|
@@ -531,6 +532,21 @@ var createMcpRouter = (server, options = {}) => {
|
|
|
531
532
|
let authCheck;
|
|
532
533
|
if (auth) {
|
|
533
534
|
const verifyToken = buildVerifyToken(auth);
|
|
535
|
+
const base = auth.resourceServerUrl?.replace(/\/$/, "");
|
|
536
|
+
const resourceUrl = base === void 0 || path === "/" ? base : `${base}${path}`;
|
|
537
|
+
const metadata = resourceUrl !== void 0 && auth.authorizationServerUrl !== void 0 ? {
|
|
538
|
+
document: (0, _ttoss_auth_core.protectedResourceMetadataDocument)({
|
|
539
|
+
resource: resourceUrl,
|
|
540
|
+
authorizationServers: [auth.authorizationServerUrl]
|
|
541
|
+
}),
|
|
542
|
+
paths: (0, _ttoss_auth_core.protectedResourceMetadataPaths)({
|
|
543
|
+
resource: resourceUrl
|
|
544
|
+
}),
|
|
545
|
+
url: (0, _ttoss_auth_core.protectedResourceMetadataUrl)({
|
|
546
|
+
resource: resourceUrl
|
|
547
|
+
})
|
|
548
|
+
} : void 0;
|
|
549
|
+
const resourceMetadataUrl = auth.resourceMetadataUrl ?? metadata?.url;
|
|
534
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.`);
|
|
535
551
|
const publicMethods = new Set(auth.publicMethods ?? DEFAULT_PUBLIC_METHODS);
|
|
536
552
|
const verify = (0, _ttoss_http_server_auth.authMiddleware)({
|
|
@@ -541,7 +557,7 @@ var createMcpRouter = (server, options = {}) => {
|
|
|
541
557
|
},
|
|
542
558
|
requiredScopes: auth.requiredScopes
|
|
543
559
|
},
|
|
544
|
-
resourceMetadataUrl
|
|
560
|
+
resourceMetadataUrl
|
|
545
561
|
});
|
|
546
562
|
authCheck = async (ctx, next) => {
|
|
547
563
|
const method = ctx.request.body?.method;
|
|
@@ -551,16 +567,9 @@ var createMcpRouter = (server, options = {}) => {
|
|
|
551
567
|
}
|
|
552
568
|
await verify(ctx, next);
|
|
553
569
|
};
|
|
554
|
-
if (
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
router.get("/.well-known/oauth-protected-resource", ctx => {
|
|
558
|
-
ctx.body = {
|
|
559
|
-
resource: resourceUrl,
|
|
560
|
-
authorization_servers: [auth.authorizationServerUrl]
|
|
561
|
-
};
|
|
562
|
-
});
|
|
563
|
-
}
|
|
570
|
+
if (metadata) for (const metadataPath of metadata.paths) router.get(metadataPath, ctx => {
|
|
571
|
+
ctx.body = metadata.document;
|
|
572
|
+
});
|
|
564
573
|
}
|
|
565
574
|
const handleWithContext = async (ctx, body) => {
|
|
566
575
|
const apiHeaders = getApiHeaders ? getApiHeaders(ctx) : {};
|
package/dist/index.d.cts
CHANGED
|
@@ -900,8 +900,18 @@ interface McpAuthOptions {
|
|
|
900
900
|
*/
|
|
901
901
|
publicMethods?: string[];
|
|
902
902
|
/**
|
|
903
|
-
*
|
|
904
|
-
* (RFC 9728) so MCP clients can discover the authorization
|
|
903
|
+
* URL advertised in `WWW-Authenticate: Bearer resource_metadata="…"`
|
|
904
|
+
* (RFC 9728) on a `401`, so MCP clients can discover the authorization
|
|
905
|
+
* server.
|
|
906
|
+
*
|
|
907
|
+
* Defaults to the location derived from `resourceServerUrl` + `path` — the
|
|
908
|
+
* same location this router serves the document at — so the header cannot
|
|
909
|
+
* drift from the routes. The default applies only when the document is
|
|
910
|
+
* actually served (both `resourceServerUrl` and `authorizationServerUrl`
|
|
911
|
+
* set); otherwise the header stays a bare `Bearer` rather than advertising a
|
|
912
|
+
* location with no route. Set it only to point at a document served
|
|
913
|
+
* elsewhere; a hand-written value that names a location nothing serves fails
|
|
914
|
+
* discovery silently.
|
|
905
915
|
*/
|
|
906
916
|
resourceMetadataUrl?: string;
|
|
907
917
|
/**
|
package/dist/index.d.mts
CHANGED
|
@@ -901,8 +901,18 @@ interface McpAuthOptions {
|
|
|
901
901
|
*/
|
|
902
902
|
publicMethods?: string[];
|
|
903
903
|
/**
|
|
904
|
-
*
|
|
905
|
-
* (RFC 9728) so MCP clients can discover the authorization
|
|
904
|
+
* URL advertised in `WWW-Authenticate: Bearer resource_metadata="…"`
|
|
905
|
+
* (RFC 9728) on a `401`, so MCP clients can discover the authorization
|
|
906
|
+
* server.
|
|
907
|
+
*
|
|
908
|
+
* Defaults to the location derived from `resourceServerUrl` + `path` — the
|
|
909
|
+
* same location this router serves the document at — so the header cannot
|
|
910
|
+
* drift from the routes. The default applies only when the document is
|
|
911
|
+
* actually served (both `resourceServerUrl` and `authorizationServerUrl`
|
|
912
|
+
* set); otherwise the header stays a bare `Bearer` rather than advertising a
|
|
913
|
+
* location with no route. Set it only to point at a document served
|
|
914
|
+
* elsewhere; a hand-written value that names a location nothing serves fails
|
|
915
|
+
* discovery silently.
|
|
906
916
|
*/
|
|
907
917
|
resourceMetadataUrl?: string;
|
|
908
918
|
/**
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/** Powered by @ttoss/config. https://ttoss.dev/docs/modules/packages/config/ */
|
|
2
|
+
import { protectedResourceMetadataDocument, protectedResourceMetadataPaths, protectedResourceMetadataUrl } from "@ttoss/auth-core";
|
|
2
3
|
import { CognitoJwtVerifier } from "@ttoss/auth-core/amazon-cognito";
|
|
3
4
|
import { Router } from "@ttoss/http-server";
|
|
4
5
|
import { authMiddleware } from "@ttoss/http-server-auth";
|
|
@@ -528,6 +529,21 @@ var createMcpRouter = (server, options = {}) => {
|
|
|
528
529
|
let authCheck;
|
|
529
530
|
if (auth) {
|
|
530
531
|
const verifyToken = buildVerifyToken(auth);
|
|
532
|
+
const base = auth.resourceServerUrl?.replace(/\/$/, "");
|
|
533
|
+
const resourceUrl = base === void 0 || path === "/" ? base : `${base}${path}`;
|
|
534
|
+
const metadata = resourceUrl !== void 0 && auth.authorizationServerUrl !== void 0 ? {
|
|
535
|
+
document: protectedResourceMetadataDocument({
|
|
536
|
+
resource: resourceUrl,
|
|
537
|
+
authorizationServers: [auth.authorizationServerUrl]
|
|
538
|
+
}),
|
|
539
|
+
paths: protectedResourceMetadataPaths({
|
|
540
|
+
resource: resourceUrl
|
|
541
|
+
}),
|
|
542
|
+
url: protectedResourceMetadataUrl({
|
|
543
|
+
resource: resourceUrl
|
|
544
|
+
})
|
|
545
|
+
} : void 0;
|
|
546
|
+
const resourceMetadataUrl = auth.resourceMetadataUrl ?? metadata?.url;
|
|
531
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.`);
|
|
532
548
|
const publicMethods = new Set(auth.publicMethods ?? DEFAULT_PUBLIC_METHODS);
|
|
533
549
|
const verify = authMiddleware({
|
|
@@ -538,7 +554,7 @@ var createMcpRouter = (server, options = {}) => {
|
|
|
538
554
|
},
|
|
539
555
|
requiredScopes: auth.requiredScopes
|
|
540
556
|
},
|
|
541
|
-
resourceMetadataUrl
|
|
557
|
+
resourceMetadataUrl
|
|
542
558
|
});
|
|
543
559
|
authCheck = async (ctx, next) => {
|
|
544
560
|
const method = ctx.request.body?.method;
|
|
@@ -548,16 +564,9 @@ var createMcpRouter = (server, options = {}) => {
|
|
|
548
564
|
}
|
|
549
565
|
await verify(ctx, next);
|
|
550
566
|
};
|
|
551
|
-
if (
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
router.get("/.well-known/oauth-protected-resource", ctx => {
|
|
555
|
-
ctx.body = {
|
|
556
|
-
resource: resourceUrl,
|
|
557
|
-
authorization_servers: [auth.authorizationServerUrl]
|
|
558
|
-
};
|
|
559
|
-
});
|
|
560
|
-
}
|
|
567
|
+
if (metadata) for (const metadataPath of metadata.paths) router.get(metadataPath, ctx => {
|
|
568
|
+
ctx.body = metadata.document;
|
|
569
|
+
});
|
|
561
570
|
}
|
|
562
571
|
const handleWithContext = async (ctx, body) => {
|
|
563
572
|
const apiHeaders = getApiHeaders ? getApiHeaders(ctx) : {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ttoss/http-server-mcp",
|
|
3
|
-
"version": "0.26.
|
|
3
|
+
"version": "0.26.1",
|
|
4
4
|
"description": "Model Context Protocol (MCP) server integration for @ttoss/http-server",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -36,9 +36,9 @@
|
|
|
36
36
|
"@modelcontextprotocol/node": "^2.0.0",
|
|
37
37
|
"@modelcontextprotocol/server": "^2.0.0",
|
|
38
38
|
"zod": "^4.4.3",
|
|
39
|
-
"@ttoss/
|
|
40
|
-
"@ttoss/http-server-auth": "^0.6.
|
|
41
|
-
"@ttoss/
|
|
39
|
+
"@ttoss/auth-core": "^0.14.1",
|
|
40
|
+
"@ttoss/http-server-auth": "^0.6.4",
|
|
41
|
+
"@ttoss/http-server": "^0.8.1"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@types/koa": "^3.0.3",
|