@ttoss/http-server-mcp 0.19.0 → 0.20.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 +72 -0
- package/dist/index.cjs +110 -13
- package/dist/index.d.cts +103 -10
- package/dist/index.d.mts +107 -13
- package/dist/index.mjs +110 -14
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -366,6 +366,56 @@ createMcpRouter(mcpServer, {
|
|
|
366
366
|
|
|
367
367
|
The discovery endpoint (`/.well-known/oauth-protected-resource`) remains publicly accessible even when `aliases` includes `'/'`.
|
|
368
368
|
|
|
369
|
+
## Gated Tool Registrar
|
|
370
|
+
|
|
371
|
+
`createGatedToolRegistrar` wraps `server.registerTool` with a consistent authentication and authorization pipeline so you don't repeat the same boilerplate in every handler.
|
|
372
|
+
|
|
373
|
+
Every registered tool automatically:
|
|
374
|
+
|
|
375
|
+
1. Resolves the caller's identity (defaults to `getIdentity()` from the request context).
|
|
376
|
+
2. Checks that the caller holds a required OAuth scope — returns an `isError` result (not a throw) when the scope is absent.
|
|
377
|
+
3. Runs any extra `gates` in order; a throwing gate rejects the call.
|
|
378
|
+
4. Merges `buildContext` output into the handler args.
|
|
379
|
+
5. Wraps `null`/`undefined` results in a "Not found" error result.
|
|
380
|
+
6. Calls `onError` on throw before rethrowing, for telemetry.
|
|
381
|
+
|
|
382
|
+
```typescript
|
|
383
|
+
import {
|
|
384
|
+
createGatedToolRegistrar,
|
|
385
|
+
getIdentity,
|
|
386
|
+
McpServer,
|
|
387
|
+
} from '@ttoss/http-server-mcp';
|
|
388
|
+
import { z } from 'zod';
|
|
389
|
+
|
|
390
|
+
const { register } = createGatedToolRegistrar({
|
|
391
|
+
server,
|
|
392
|
+
resolveIdentity: () => {
|
|
393
|
+
const jwt = getIdentity<{ sub: string; scope: string }>();
|
|
394
|
+
const scopes = jwt?.scope?.split(' ') ?? [];
|
|
395
|
+
return { userId: jwt!.sub, scopes };
|
|
396
|
+
},
|
|
397
|
+
buildContext: ({ userId }) => ({ tenantId: lookupTenant(userId) }),
|
|
398
|
+
onError: (err, { handler, identity }) => {
|
|
399
|
+
logger.error({ err, handler, userId: identity.userId });
|
|
400
|
+
},
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
register({
|
|
404
|
+
name: 'list-campaigns',
|
|
405
|
+
description: 'List all ad campaigns for the caller.',
|
|
406
|
+
requiredScope: 'campaigns:read',
|
|
407
|
+
inputSchema: { limit: z.number().optional() },
|
|
408
|
+
method: async ({ userId, tenantId, limit }) =>
|
|
409
|
+
fetchCampaigns(tenantId, limit),
|
|
410
|
+
});
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
**`resolveIdentity`** is called once per invocation. Omit it to use the default `getIdentity()` from the MCP request context.
|
|
414
|
+
|
|
415
|
+
**`gates`** are async functions that receive the resolved identity and throw to reject. Use them for additional checks that span multiple tools (rate limiting, IP allowlists, etc.).
|
|
416
|
+
|
|
417
|
+
**`buildContext`** injects request-scoped values (DB clients, tenant IDs) into every handler call without threading them through each individual tool.
|
|
418
|
+
|
|
369
419
|
## Issuing tokens for MCP clients
|
|
370
420
|
|
|
371
421
|
The `auth` option above covers the **resource-server** half of MCP authorization — it verifies tokens issued by an external authorization server (Cognito, Auth0, …). To make your own first-party server _issue_ the tokens an MCP client runs the full OAuth flow against, add the [`@ttoss/http-server-auth`](https://ttoss.dev/docs/modules/packages/http-server-auth) plugin's `oauthServer()` and pair it with `createMcpRouter({ auth: { verifyToken } })` so one deployment both issues and verifies tokens. See the [OAuth Authorization Server](https://ttoss.dev/docs/engineering/guidelines/oauth-authorization-server) guideline.
|
|
@@ -446,6 +496,28 @@ Returns the `WWW-Authenticate` header value for a 401 response, formatted per th
|
|
|
446
496
|
|
|
447
497
|
**Returns:** `string` — The full `WWW-Authenticate` header value
|
|
448
498
|
|
|
499
|
+
### `createGatedToolRegistrar(options)`
|
|
500
|
+
|
|
501
|
+
Factory that returns a `register` helper for tools that require authentication and a specific OAuth scope.
|
|
502
|
+
|
|
503
|
+
**Parameters (`options`):**
|
|
504
|
+
|
|
505
|
+
- `server` (`McpServer`) — The MCP server to register tools on.
|
|
506
|
+
- `resolveIdentity` (`() => ToolIdentity`, optional) — Called once per invocation to resolve `{ userId, scopes? }`. Defaults to `getIdentity()` from the request context.
|
|
507
|
+
- `gates` (`Array<(identity: ToolIdentity) => void | Promise<void>>`, optional) — Additional guards run after the scope check, in order. Throw to reject the call.
|
|
508
|
+
- `buildContext` (`(identity: ToolIdentity) => Record<string, unknown>`, optional) — Produces extra key-value pairs merged into every handler's args.
|
|
509
|
+
- `onError` (`(error, ctx) => void | Promise<void>`, optional) — Called when a handler throws, before the error is rethrown. Use for logging/telemetry.
|
|
510
|
+
|
|
511
|
+
**Returns:** `{ register: (def: GatedToolDef) => void }`
|
|
512
|
+
|
|
513
|
+
The `GatedToolDef` passed to `register` has:
|
|
514
|
+
|
|
515
|
+
- `name` — Tool name.
|
|
516
|
+
- `description` — Human-readable description.
|
|
517
|
+
- `requiredScope` — Single scope that must appear in `identity.scopes`.
|
|
518
|
+
- `inputSchema` — Zod field map or `ZodObject`, forwarded to `server.registerTool`.
|
|
519
|
+
- `method` — Async handler. Receives merged call args + `buildContext` output.
|
|
520
|
+
|
|
449
521
|
### `registerToolFromSchema(server, params)`
|
|
450
522
|
|
|
451
523
|
Registers a tool using a **plain JSON Schema** object for `inputSchema` instead of a Zod shape.
|
package/dist/index.cjs
CHANGED
|
@@ -2,14 +2,122 @@
|
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, {
|
|
3
3
|
value: 'Module'
|
|
4
4
|
});
|
|
5
|
-
let node_async_hooks = require("node:async_hooks");
|
|
6
5
|
let _modelcontextprotocol_sdk_server_streamableHttp_js = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
7
6
|
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 _ttoss_http_server_auth = require("@ttoss/http-server-auth");
|
|
10
9
|
let zod = require("zod");
|
|
10
|
+
let node_async_hooks = require("node:async_hooks");
|
|
11
11
|
let _modelcontextprotocol_sdk_server_mcp_js = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
12
12
|
|
|
13
|
+
//#region src/context.ts
|
|
14
|
+
var requestContextStore = new node_async_hooks.AsyncLocalStorage();
|
|
15
|
+
/**
|
|
16
|
+
* Returns the verified JWT payload for the current MCP request.
|
|
17
|
+
* Only available inside a tool handler when `auth` is configured on the router.
|
|
18
|
+
*
|
|
19
|
+
* Accepts an optional type parameter to avoid casting at call sites:
|
|
20
|
+
* `getIdentity<{ sub: string; email: string }>()` returns `T | undefined`.
|
|
21
|
+
* Omitting the type parameter keeps the return type as `unknown | undefined`.
|
|
22
|
+
*/
|
|
23
|
+
var getIdentity = () => {
|
|
24
|
+
return requestContextStore.getStore()?.identity;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
//#endregion
|
|
28
|
+
//#region src/createGatedToolRegistrar.ts
|
|
29
|
+
var toolError = message => {
|
|
30
|
+
return {
|
|
31
|
+
content: [{
|
|
32
|
+
type: "text",
|
|
33
|
+
text: JSON.stringify({
|
|
34
|
+
error: message
|
|
35
|
+
})
|
|
36
|
+
}],
|
|
37
|
+
isError: true
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Factory that returns a `register` helper for MCP tools that require
|
|
42
|
+
* authentication and a specific OAuth scope.
|
|
43
|
+
*
|
|
44
|
+
* Every registered tool automatically:
|
|
45
|
+
* 1. Resolves the caller identity (default: `getIdentity()`). Returns an
|
|
46
|
+
* `isError` result when the identity is absent (unauthenticated request).
|
|
47
|
+
* 2. Checks `requiredScope` — returns an `isError` result (not a throw) when
|
|
48
|
+
* the scope is absent, consistent with how MCP surfaces authorization errors.
|
|
49
|
+
* Scope semantics are intentionally simple (`Array.includes`). The package's
|
|
50
|
+
* `checkScopes` helper is not reused here because it throws rather than
|
|
51
|
+
* returning an `isError` result, and it reads identity from context itself.
|
|
52
|
+
* 3. Runs any extra `gates` in order; a throwing gate rejects the call.
|
|
53
|
+
* Gates own their own error handling; `onError` covers the tool handler only.
|
|
54
|
+
* 4. Merges `buildContext` output into the handler args.
|
|
55
|
+
* 5. Wraps `null`/`undefined` results in a "Not found" error result.
|
|
56
|
+
* 6. Calls `onError` on handler throw before rethrowing.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```typescript
|
|
60
|
+
* const { register } = createGatedToolRegistrar({
|
|
61
|
+
* server,
|
|
62
|
+
* resolveIdentity: () => {
|
|
63
|
+
* const jwt = getIdentity<{ sub: string; scopes: string[] }>();
|
|
64
|
+
* return { userId: jwt!.sub, scopes: jwt!.scopes };
|
|
65
|
+
* },
|
|
66
|
+
* });
|
|
67
|
+
*
|
|
68
|
+
* register({
|
|
69
|
+
* name: 'list-campaigns',
|
|
70
|
+
* description: 'List all ad campaigns for the caller.',
|
|
71
|
+
* requiredScope: 'campaigns:read',
|
|
72
|
+
* inputSchema: { limit: z.number().optional() },
|
|
73
|
+
* method: async ({ userId, limit }) => fetchCampaigns(userId, limit),
|
|
74
|
+
* });
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
var createGatedToolRegistrar = ({
|
|
78
|
+
server,
|
|
79
|
+
resolveIdentity = getIdentity,
|
|
80
|
+
gates = [],
|
|
81
|
+
onError,
|
|
82
|
+
buildContext
|
|
83
|
+
}) => {
|
|
84
|
+
const register = def => {
|
|
85
|
+
server.registerTool(def.name, {
|
|
86
|
+
description: def.description,
|
|
87
|
+
inputSchema: def.inputSchema
|
|
88
|
+
}, async args => {
|
|
89
|
+
const identity = resolveIdentity();
|
|
90
|
+
if (!identity) return toolError("Unauthorized");
|
|
91
|
+
if (!(Array.isArray(identity.scopes) ? identity.scopes : []).includes(def.requiredScope)) return toolError(`This tool requires the "${def.requiredScope}" scope.`);
|
|
92
|
+
for (const gate of gates) await gate(identity);
|
|
93
|
+
const extra = buildContext ? buildContext(identity) : {};
|
|
94
|
+
try {
|
|
95
|
+
const result = await def.method({
|
|
96
|
+
...args,
|
|
97
|
+
...extra
|
|
98
|
+
});
|
|
99
|
+
if (result == null) return toolError("Not found");
|
|
100
|
+
return {
|
|
101
|
+
content: [{
|
|
102
|
+
type: "text",
|
|
103
|
+
text: JSON.stringify(result)
|
|
104
|
+
}]
|
|
105
|
+
};
|
|
106
|
+
} catch (error) {
|
|
107
|
+
if (onError) await onError(error, {
|
|
108
|
+
handler: def.name,
|
|
109
|
+
identity
|
|
110
|
+
});
|
|
111
|
+
throw error;
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
};
|
|
115
|
+
return {
|
|
116
|
+
register
|
|
117
|
+
};
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
//#endregion
|
|
13
121
|
//#region src/index.ts
|
|
14
122
|
/** MCP lifecycle/discovery methods reachable before a client authenticates. */
|
|
15
123
|
var DEFAULT_PUBLIC_METHODS = ["initialize", "tools/list"];
|
|
@@ -27,7 +135,6 @@ var buildVerifyToken = auth => {
|
|
|
27
135
|
if (auth.verifyToken) return auth.verifyToken;
|
|
28
136
|
throw new Error("McpAuthOptions requires either cognitoUserPool or verifyToken");
|
|
29
137
|
};
|
|
30
|
-
var requestContextStore = new node_async_hooks.AsyncLocalStorage();
|
|
31
138
|
/**
|
|
32
139
|
* Generic HTTP helper for use inside MCP tool handlers.
|
|
33
140
|
*
|
|
@@ -115,17 +222,6 @@ var apiCall = async (method, url, options) => {
|
|
|
115
222
|
return response.text();
|
|
116
223
|
};
|
|
117
224
|
/**
|
|
118
|
-
* Returns the verified JWT payload for the current MCP request.
|
|
119
|
-
* Only available inside a tool handler when `auth` is configured on the router.
|
|
120
|
-
*
|
|
121
|
-
* Accepts an optional type parameter to avoid casting at call sites:
|
|
122
|
-
* `getIdentity<{ sub: string; email: string }>()` returns `T | undefined`.
|
|
123
|
-
* Omitting the type parameter keeps the return type as `unknown | undefined`.
|
|
124
|
-
*/
|
|
125
|
-
var getIdentity = () => {
|
|
126
|
-
return requestContextStore.getStore()?.identity;
|
|
127
|
-
};
|
|
128
|
-
/**
|
|
129
225
|
* Asserts that the current request's token contains all required scopes.
|
|
130
226
|
* Throws if any scope is missing — the MCP SDK catches this and returns a
|
|
131
227
|
* tool error to the client. Use inside tool handlers for per-tool authorization.
|
|
@@ -414,6 +510,7 @@ Object.defineProperty(exports, 'McpServer', {
|
|
|
414
510
|
});
|
|
415
511
|
exports.apiCall = apiCall;
|
|
416
512
|
exports.checkScopes = checkScopes;
|
|
513
|
+
exports.createGatedToolRegistrar = createGatedToolRegistrar;
|
|
417
514
|
exports.createMcpRouter = createMcpRouter;
|
|
418
515
|
exports.getIdentity = getIdentity;
|
|
419
516
|
exports.registerToolFromSchema = registerToolFromSchema;
|
package/dist/index.d.cts
CHANGED
|
@@ -665,6 +665,108 @@ declare namespace Application {
|
|
|
665
665
|
const HttpError: typeof HttpErrors.HttpError;
|
|
666
666
|
}
|
|
667
667
|
//#endregion
|
|
668
|
+
//#region src/context.d.ts
|
|
669
|
+
/**
|
|
670
|
+
* Returns the verified JWT payload for the current MCP request.
|
|
671
|
+
* Only available inside a tool handler when `auth` is configured on the router.
|
|
672
|
+
*
|
|
673
|
+
* Accepts an optional type parameter to avoid casting at call sites:
|
|
674
|
+
* `getIdentity<{ sub: string; email: string }>()` returns `T | undefined`.
|
|
675
|
+
* Omitting the type parameter keeps the return type as `unknown | undefined`.
|
|
676
|
+
*/
|
|
677
|
+
declare const getIdentity: <T = unknown>() => T | undefined;
|
|
678
|
+
//#endregion
|
|
679
|
+
//#region src/createGatedToolRegistrar.d.ts
|
|
680
|
+
/** Resolved identity for a gated tool call. */
|
|
681
|
+
type ToolIdentity = {
|
|
682
|
+
userId: string;
|
|
683
|
+
scopes?: string[];
|
|
684
|
+
};
|
|
685
|
+
/** Definition for a single gated tool. */
|
|
686
|
+
type GatedToolDef = {
|
|
687
|
+
/** Tool name as registered with the MCP server. */name: string; /** Human-readable tool description. */
|
|
688
|
+
description: string; /** The single scope that must be present on the caller's token. */
|
|
689
|
+
requiredScope: string; /** Zod field map or ZodObject — passed through to `server.registerTool`. */
|
|
690
|
+
inputSchema: unknown; /** The tool handler. Receives merged call args + `buildContext` output. */
|
|
691
|
+
method: (args: Record<string, unknown>) => Promise<unknown>;
|
|
692
|
+
};
|
|
693
|
+
/** Options for {@link createGatedToolRegistrar}. */
|
|
694
|
+
type CreateGatedToolRegistrarOptions = {
|
|
695
|
+
/** The MCP server instance to register tools on. */server: McpServer$1;
|
|
696
|
+
/**
|
|
697
|
+
* Called once per tool invocation to resolve the caller's identity.
|
|
698
|
+
* Defaults to `getIdentity()` from the request context.
|
|
699
|
+
*/
|
|
700
|
+
resolveIdentity?: () => ToolIdentity;
|
|
701
|
+
/**
|
|
702
|
+
* Additional authorization gates run after the scope check, in order.
|
|
703
|
+
* Throw to reject the call; return (or resolve) to continue.
|
|
704
|
+
* Gates own their own error handling — `onError` covers the tool handler only.
|
|
705
|
+
*/
|
|
706
|
+
gates?: Array<(identity: ToolIdentity) => void | Promise<void>>;
|
|
707
|
+
/**
|
|
708
|
+
* Called when the tool **handler** throws. Use for error reporting/telemetry.
|
|
709
|
+
* The error is always rethrown after this hook completes.
|
|
710
|
+
* Note: scope-check failures and gate rejections do not trigger `onError`.
|
|
711
|
+
*/
|
|
712
|
+
onError?: (error: unknown, ctx: {
|
|
713
|
+
handler: string;
|
|
714
|
+
identity: ToolIdentity;
|
|
715
|
+
}) => void | Promise<void>;
|
|
716
|
+
/**
|
|
717
|
+
* Called once per invocation to produce extra key-value pairs that are
|
|
718
|
+
* merged into the handler args. Use to inject request-scoped context
|
|
719
|
+
* (e.g. a database client keyed to the caller's tenant).
|
|
720
|
+
*/
|
|
721
|
+
buildContext?: (identity: ToolIdentity) => Record<string, unknown>;
|
|
722
|
+
};
|
|
723
|
+
/**
|
|
724
|
+
* Factory that returns a `register` helper for MCP tools that require
|
|
725
|
+
* authentication and a specific OAuth scope.
|
|
726
|
+
*
|
|
727
|
+
* Every registered tool automatically:
|
|
728
|
+
* 1. Resolves the caller identity (default: `getIdentity()`). Returns an
|
|
729
|
+
* `isError` result when the identity is absent (unauthenticated request).
|
|
730
|
+
* 2. Checks `requiredScope` — returns an `isError` result (not a throw) when
|
|
731
|
+
* the scope is absent, consistent with how MCP surfaces authorization errors.
|
|
732
|
+
* Scope semantics are intentionally simple (`Array.includes`). The package's
|
|
733
|
+
* `checkScopes` helper is not reused here because it throws rather than
|
|
734
|
+
* returning an `isError` result, and it reads identity from context itself.
|
|
735
|
+
* 3. Runs any extra `gates` in order; a throwing gate rejects the call.
|
|
736
|
+
* Gates own their own error handling; `onError` covers the tool handler only.
|
|
737
|
+
* 4. Merges `buildContext` output into the handler args.
|
|
738
|
+
* 5. Wraps `null`/`undefined` results in a "Not found" error result.
|
|
739
|
+
* 6. Calls `onError` on handler throw before rethrowing.
|
|
740
|
+
*
|
|
741
|
+
* @example
|
|
742
|
+
* ```typescript
|
|
743
|
+
* const { register } = createGatedToolRegistrar({
|
|
744
|
+
* server,
|
|
745
|
+
* resolveIdentity: () => {
|
|
746
|
+
* const jwt = getIdentity<{ sub: string; scopes: string[] }>();
|
|
747
|
+
* return { userId: jwt!.sub, scopes: jwt!.scopes };
|
|
748
|
+
* },
|
|
749
|
+
* });
|
|
750
|
+
*
|
|
751
|
+
* register({
|
|
752
|
+
* name: 'list-campaigns',
|
|
753
|
+
* description: 'List all ad campaigns for the caller.',
|
|
754
|
+
* requiredScope: 'campaigns:read',
|
|
755
|
+
* inputSchema: { limit: z.number().optional() },
|
|
756
|
+
* method: async ({ userId, limit }) => fetchCampaigns(userId, limit),
|
|
757
|
+
* });
|
|
758
|
+
* ```
|
|
759
|
+
*/
|
|
760
|
+
declare const createGatedToolRegistrar: ({
|
|
761
|
+
server,
|
|
762
|
+
resolveIdentity,
|
|
763
|
+
gates,
|
|
764
|
+
onError,
|
|
765
|
+
buildContext
|
|
766
|
+
}: CreateGatedToolRegistrarOptions) => {
|
|
767
|
+
register: (def: GatedToolDef) => void;
|
|
768
|
+
};
|
|
769
|
+
//#endregion
|
|
668
770
|
//#region src/index.d.ts
|
|
669
771
|
type Context$1 = Application.Context;
|
|
670
772
|
/** Amazon Cognito user pool configuration for JWT verification. */
|
|
@@ -787,15 +889,6 @@ interface ApiCallOptions {
|
|
|
787
889
|
* ```
|
|
788
890
|
*/
|
|
789
891
|
declare const apiCall: (method: string, url: string, options?: ApiCallOptions) => Promise<unknown>;
|
|
790
|
-
/**
|
|
791
|
-
* Returns the verified JWT payload for the current MCP request.
|
|
792
|
-
* Only available inside a tool handler when `auth` is configured on the router.
|
|
793
|
-
*
|
|
794
|
-
* Accepts an optional type parameter to avoid casting at call sites:
|
|
795
|
-
* `getIdentity<{ sub: string; email: string }>()` returns `T | undefined`.
|
|
796
|
-
* Omitting the type parameter keeps the return type as `unknown | undefined`.
|
|
797
|
-
*/
|
|
798
|
-
declare const getIdentity: <T = unknown>() => T | undefined;
|
|
799
892
|
/**
|
|
800
893
|
* Asserts that the current request's token contains all required scopes.
|
|
801
894
|
* Throws if any scope is missing — the MCP SDK catches this and returns a
|
|
@@ -1025,4 +1118,4 @@ interface RegisterToolFromSchemaParams {
|
|
|
1025
1118
|
*/
|
|
1026
1119
|
declare const registerToolFromSchema: (server: McpServer$1, params: RegisterToolFromSchemaParams) => void;
|
|
1027
1120
|
//#endregion
|
|
1028
|
-
export { ApiCallOptions, CognitoUserPoolConfig, JsonObjectSchema, McpAuthOptions, McpRouterOptions, McpServer, RegisterToolFromSchemaParams, apiCall, checkScopes, createMcpRouter, getIdentity, registerToolFromSchema, z };
|
|
1121
|
+
export { ApiCallOptions, CognitoUserPoolConfig, type CreateGatedToolRegistrarOptions, type GatedToolDef, JsonObjectSchema, McpAuthOptions, McpRouterOptions, McpServer, RegisterToolFromSchemaParams, type ToolIdentity, apiCall, checkScopes, createGatedToolRegistrar, createMcpRouter, getIdentity, registerToolFromSchema, z };
|
package/dist/index.d.mts
CHANGED
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
/// <reference types="node" />
|
|
3
3
|
import { Router } from "@ttoss/http-server";
|
|
4
4
|
import { z } from "zod";
|
|
5
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
5
6
|
import { McpServer, McpServer as McpServer$1 } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
7
|
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
7
8
|
import accepts from "accepts";
|
|
8
|
-
import { AsyncLocalStorage } from "async_hooks";
|
|
9
|
+
import { AsyncLocalStorage as AsyncLocalStorage$1 } from "async_hooks";
|
|
9
10
|
import Cookies from "cookies";
|
|
10
11
|
import { EventEmitter } from "events";
|
|
11
12
|
import { IncomingHttpHeaders, IncomingMessage, OutgoingHttpHeaders, Server, ServerResponse } from "http";
|
|
@@ -403,7 +404,7 @@ declare class Application<StateT = Application.DefaultState, ContextT = Applicat
|
|
|
403
404
|
response: Application.BaseResponse;
|
|
404
405
|
silent: boolean;
|
|
405
406
|
keys: Keygrip | string[];
|
|
406
|
-
ctxStorage: AsyncLocalStorage<ContextT> | undefined;
|
|
407
|
+
ctxStorage: AsyncLocalStorage$1<ContextT> | undefined;
|
|
407
408
|
/**
|
|
408
409
|
* @param {object} [options] Application options
|
|
409
410
|
* @param {string} [options.env='development'] Environment
|
|
@@ -421,7 +422,7 @@ declare class Application<StateT = Application.DefaultState, ContextT = Applicat
|
|
|
421
422
|
subdomainOffset?: number | undefined;
|
|
422
423
|
proxyIpHeader?: string | undefined;
|
|
423
424
|
maxIpsCount?: number | undefined;
|
|
424
|
-
asyncLocalStorage?: boolean | AsyncLocalStorage<ContextT> | undefined;
|
|
425
|
+
asyncLocalStorage?: boolean | AsyncLocalStorage$1<ContextT> | undefined;
|
|
425
426
|
});
|
|
426
427
|
/**
|
|
427
428
|
* Shorthand for:
|
|
@@ -665,6 +666,108 @@ declare namespace Application {
|
|
|
665
666
|
const HttpError: typeof HttpErrors.HttpError;
|
|
666
667
|
}
|
|
667
668
|
//#endregion
|
|
669
|
+
//#region src/context.d.ts
|
|
670
|
+
/**
|
|
671
|
+
* Returns the verified JWT payload for the current MCP request.
|
|
672
|
+
* Only available inside a tool handler when `auth` is configured on the router.
|
|
673
|
+
*
|
|
674
|
+
* Accepts an optional type parameter to avoid casting at call sites:
|
|
675
|
+
* `getIdentity<{ sub: string; email: string }>()` returns `T | undefined`.
|
|
676
|
+
* Omitting the type parameter keeps the return type as `unknown | undefined`.
|
|
677
|
+
*/
|
|
678
|
+
declare const getIdentity: <T = unknown>() => T | undefined;
|
|
679
|
+
//#endregion
|
|
680
|
+
//#region src/createGatedToolRegistrar.d.ts
|
|
681
|
+
/** Resolved identity for a gated tool call. */
|
|
682
|
+
type ToolIdentity = {
|
|
683
|
+
userId: string;
|
|
684
|
+
scopes?: string[];
|
|
685
|
+
};
|
|
686
|
+
/** Definition for a single gated tool. */
|
|
687
|
+
type GatedToolDef = {
|
|
688
|
+
/** Tool name as registered with the MCP server. */name: string; /** Human-readable tool description. */
|
|
689
|
+
description: string; /** The single scope that must be present on the caller's token. */
|
|
690
|
+
requiredScope: string; /** Zod field map or ZodObject — passed through to `server.registerTool`. */
|
|
691
|
+
inputSchema: unknown; /** The tool handler. Receives merged call args + `buildContext` output. */
|
|
692
|
+
method: (args: Record<string, unknown>) => Promise<unknown>;
|
|
693
|
+
};
|
|
694
|
+
/** Options for {@link createGatedToolRegistrar}. */
|
|
695
|
+
type CreateGatedToolRegistrarOptions = {
|
|
696
|
+
/** The MCP server instance to register tools on. */server: McpServer$1;
|
|
697
|
+
/**
|
|
698
|
+
* Called once per tool invocation to resolve the caller's identity.
|
|
699
|
+
* Defaults to `getIdentity()` from the request context.
|
|
700
|
+
*/
|
|
701
|
+
resolveIdentity?: () => ToolIdentity;
|
|
702
|
+
/**
|
|
703
|
+
* Additional authorization gates run after the scope check, in order.
|
|
704
|
+
* Throw to reject the call; return (or resolve) to continue.
|
|
705
|
+
* Gates own their own error handling — `onError` covers the tool handler only.
|
|
706
|
+
*/
|
|
707
|
+
gates?: Array<(identity: ToolIdentity) => void | Promise<void>>;
|
|
708
|
+
/**
|
|
709
|
+
* Called when the tool **handler** throws. Use for error reporting/telemetry.
|
|
710
|
+
* The error is always rethrown after this hook completes.
|
|
711
|
+
* Note: scope-check failures and gate rejections do not trigger `onError`.
|
|
712
|
+
*/
|
|
713
|
+
onError?: (error: unknown, ctx: {
|
|
714
|
+
handler: string;
|
|
715
|
+
identity: ToolIdentity;
|
|
716
|
+
}) => void | Promise<void>;
|
|
717
|
+
/**
|
|
718
|
+
* Called once per invocation to produce extra key-value pairs that are
|
|
719
|
+
* merged into the handler args. Use to inject request-scoped context
|
|
720
|
+
* (e.g. a database client keyed to the caller's tenant).
|
|
721
|
+
*/
|
|
722
|
+
buildContext?: (identity: ToolIdentity) => Record<string, unknown>;
|
|
723
|
+
};
|
|
724
|
+
/**
|
|
725
|
+
* Factory that returns a `register` helper for MCP tools that require
|
|
726
|
+
* authentication and a specific OAuth scope.
|
|
727
|
+
*
|
|
728
|
+
* Every registered tool automatically:
|
|
729
|
+
* 1. Resolves the caller identity (default: `getIdentity()`). Returns an
|
|
730
|
+
* `isError` result when the identity is absent (unauthenticated request).
|
|
731
|
+
* 2. Checks `requiredScope` — returns an `isError` result (not a throw) when
|
|
732
|
+
* the scope is absent, consistent with how MCP surfaces authorization errors.
|
|
733
|
+
* Scope semantics are intentionally simple (`Array.includes`). The package's
|
|
734
|
+
* `checkScopes` helper is not reused here because it throws rather than
|
|
735
|
+
* returning an `isError` result, and it reads identity from context itself.
|
|
736
|
+
* 3. Runs any extra `gates` in order; a throwing gate rejects the call.
|
|
737
|
+
* Gates own their own error handling; `onError` covers the tool handler only.
|
|
738
|
+
* 4. Merges `buildContext` output into the handler args.
|
|
739
|
+
* 5. Wraps `null`/`undefined` results in a "Not found" error result.
|
|
740
|
+
* 6. Calls `onError` on handler throw before rethrowing.
|
|
741
|
+
*
|
|
742
|
+
* @example
|
|
743
|
+
* ```typescript
|
|
744
|
+
* const { register } = createGatedToolRegistrar({
|
|
745
|
+
* server,
|
|
746
|
+
* resolveIdentity: () => {
|
|
747
|
+
* const jwt = getIdentity<{ sub: string; scopes: string[] }>();
|
|
748
|
+
* return { userId: jwt!.sub, scopes: jwt!.scopes };
|
|
749
|
+
* },
|
|
750
|
+
* });
|
|
751
|
+
*
|
|
752
|
+
* register({
|
|
753
|
+
* name: 'list-campaigns',
|
|
754
|
+
* description: 'List all ad campaigns for the caller.',
|
|
755
|
+
* requiredScope: 'campaigns:read',
|
|
756
|
+
* inputSchema: { limit: z.number().optional() },
|
|
757
|
+
* method: async ({ userId, limit }) => fetchCampaigns(userId, limit),
|
|
758
|
+
* });
|
|
759
|
+
* ```
|
|
760
|
+
*/
|
|
761
|
+
declare const createGatedToolRegistrar: ({
|
|
762
|
+
server,
|
|
763
|
+
resolveIdentity,
|
|
764
|
+
gates,
|
|
765
|
+
onError,
|
|
766
|
+
buildContext
|
|
767
|
+
}: CreateGatedToolRegistrarOptions) => {
|
|
768
|
+
register: (def: GatedToolDef) => void;
|
|
769
|
+
};
|
|
770
|
+
//#endregion
|
|
668
771
|
//#region src/index.d.ts
|
|
669
772
|
type Context$1 = Application.Context;
|
|
670
773
|
/** Amazon Cognito user pool configuration for JWT verification. */
|
|
@@ -787,15 +890,6 @@ interface ApiCallOptions {
|
|
|
787
890
|
* ```
|
|
788
891
|
*/
|
|
789
892
|
declare const apiCall: (method: string, url: string, options?: ApiCallOptions) => Promise<unknown>;
|
|
790
|
-
/**
|
|
791
|
-
* Returns the verified JWT payload for the current MCP request.
|
|
792
|
-
* Only available inside a tool handler when `auth` is configured on the router.
|
|
793
|
-
*
|
|
794
|
-
* Accepts an optional type parameter to avoid casting at call sites:
|
|
795
|
-
* `getIdentity<{ sub: string; email: string }>()` returns `T | undefined`.
|
|
796
|
-
* Omitting the type parameter keeps the return type as `unknown | undefined`.
|
|
797
|
-
*/
|
|
798
|
-
declare const getIdentity: <T = unknown>() => T | undefined;
|
|
799
893
|
/**
|
|
800
894
|
* Asserts that the current request's token contains all required scopes.
|
|
801
895
|
* Throws if any scope is missing — the MCP SDK catches this and returns a
|
|
@@ -1025,4 +1119,4 @@ interface RegisterToolFromSchemaParams {
|
|
|
1025
1119
|
*/
|
|
1026
1120
|
declare const registerToolFromSchema: (server: McpServer$1, params: RegisterToolFromSchemaParams) => void;
|
|
1027
1121
|
//#endregion
|
|
1028
|
-
export { ApiCallOptions, CognitoUserPoolConfig, JsonObjectSchema, McpAuthOptions, McpRouterOptions, McpServer, RegisterToolFromSchemaParams, apiCall, checkScopes, createMcpRouter, getIdentity, registerToolFromSchema, z };
|
|
1122
|
+
export { ApiCallOptions, CognitoUserPoolConfig, type CreateGatedToolRegistrarOptions, type GatedToolDef, JsonObjectSchema, McpAuthOptions, McpRouterOptions, McpServer, RegisterToolFromSchemaParams, type ToolIdentity, apiCall, checkScopes, createGatedToolRegistrar, createMcpRouter, getIdentity, registerToolFromSchema, z };
|
package/dist/index.mjs
CHANGED
|
@@ -1,12 +1,120 @@
|
|
|
1
1
|
/** Powered by @ttoss/config. https://ttoss.dev/docs/modules/packages/config/ */
|
|
2
|
-
import { AsyncLocalStorage } from "node:async_hooks";
|
|
3
2
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
4
3
|
import { CognitoJwtVerifier } from "@ttoss/auth-core/amazon-cognito";
|
|
5
4
|
import { Router } from "@ttoss/http-server";
|
|
6
5
|
import { authMiddleware } from "@ttoss/http-server-auth";
|
|
7
6
|
import { z, z as z$1 } from "zod";
|
|
7
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
8
8
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
9
9
|
|
|
10
|
+
//#region src/context.ts
|
|
11
|
+
var requestContextStore = new AsyncLocalStorage();
|
|
12
|
+
/**
|
|
13
|
+
* Returns the verified JWT payload for the current MCP request.
|
|
14
|
+
* Only available inside a tool handler when `auth` is configured on the router.
|
|
15
|
+
*
|
|
16
|
+
* Accepts an optional type parameter to avoid casting at call sites:
|
|
17
|
+
* `getIdentity<{ sub: string; email: string }>()` returns `T | undefined`.
|
|
18
|
+
* Omitting the type parameter keeps the return type as `unknown | undefined`.
|
|
19
|
+
*/
|
|
20
|
+
var getIdentity = () => {
|
|
21
|
+
return requestContextStore.getStore()?.identity;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
//#endregion
|
|
25
|
+
//#region src/createGatedToolRegistrar.ts
|
|
26
|
+
var toolError = message => {
|
|
27
|
+
return {
|
|
28
|
+
content: [{
|
|
29
|
+
type: "text",
|
|
30
|
+
text: JSON.stringify({
|
|
31
|
+
error: message
|
|
32
|
+
})
|
|
33
|
+
}],
|
|
34
|
+
isError: true
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Factory that returns a `register` helper for MCP tools that require
|
|
39
|
+
* authentication and a specific OAuth scope.
|
|
40
|
+
*
|
|
41
|
+
* Every registered tool automatically:
|
|
42
|
+
* 1. Resolves the caller identity (default: `getIdentity()`). Returns an
|
|
43
|
+
* `isError` result when the identity is absent (unauthenticated request).
|
|
44
|
+
* 2. Checks `requiredScope` — returns an `isError` result (not a throw) when
|
|
45
|
+
* the scope is absent, consistent with how MCP surfaces authorization errors.
|
|
46
|
+
* Scope semantics are intentionally simple (`Array.includes`). The package's
|
|
47
|
+
* `checkScopes` helper is not reused here because it throws rather than
|
|
48
|
+
* returning an `isError` result, and it reads identity from context itself.
|
|
49
|
+
* 3. Runs any extra `gates` in order; a throwing gate rejects the call.
|
|
50
|
+
* Gates own their own error handling; `onError` covers the tool handler only.
|
|
51
|
+
* 4. Merges `buildContext` output into the handler args.
|
|
52
|
+
* 5. Wraps `null`/`undefined` results in a "Not found" error result.
|
|
53
|
+
* 6. Calls `onError` on handler throw before rethrowing.
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```typescript
|
|
57
|
+
* const { register } = createGatedToolRegistrar({
|
|
58
|
+
* server,
|
|
59
|
+
* resolveIdentity: () => {
|
|
60
|
+
* const jwt = getIdentity<{ sub: string; scopes: string[] }>();
|
|
61
|
+
* return { userId: jwt!.sub, scopes: jwt!.scopes };
|
|
62
|
+
* },
|
|
63
|
+
* });
|
|
64
|
+
*
|
|
65
|
+
* register({
|
|
66
|
+
* name: 'list-campaigns',
|
|
67
|
+
* description: 'List all ad campaigns for the caller.',
|
|
68
|
+
* requiredScope: 'campaigns:read',
|
|
69
|
+
* inputSchema: { limit: z.number().optional() },
|
|
70
|
+
* method: async ({ userId, limit }) => fetchCampaigns(userId, limit),
|
|
71
|
+
* });
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
var createGatedToolRegistrar = ({
|
|
75
|
+
server,
|
|
76
|
+
resolveIdentity = getIdentity,
|
|
77
|
+
gates = [],
|
|
78
|
+
onError,
|
|
79
|
+
buildContext
|
|
80
|
+
}) => {
|
|
81
|
+
const register = def => {
|
|
82
|
+
server.registerTool(def.name, {
|
|
83
|
+
description: def.description,
|
|
84
|
+
inputSchema: def.inputSchema
|
|
85
|
+
}, async args => {
|
|
86
|
+
const identity = resolveIdentity();
|
|
87
|
+
if (!identity) return toolError("Unauthorized");
|
|
88
|
+
if (!(Array.isArray(identity.scopes) ? identity.scopes : []).includes(def.requiredScope)) return toolError(`This tool requires the "${def.requiredScope}" scope.`);
|
|
89
|
+
for (const gate of gates) await gate(identity);
|
|
90
|
+
const extra = buildContext ? buildContext(identity) : {};
|
|
91
|
+
try {
|
|
92
|
+
const result = await def.method({
|
|
93
|
+
...args,
|
|
94
|
+
...extra
|
|
95
|
+
});
|
|
96
|
+
if (result == null) return toolError("Not found");
|
|
97
|
+
return {
|
|
98
|
+
content: [{
|
|
99
|
+
type: "text",
|
|
100
|
+
text: JSON.stringify(result)
|
|
101
|
+
}]
|
|
102
|
+
};
|
|
103
|
+
} catch (error) {
|
|
104
|
+
if (onError) await onError(error, {
|
|
105
|
+
handler: def.name,
|
|
106
|
+
identity
|
|
107
|
+
});
|
|
108
|
+
throw error;
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
};
|
|
112
|
+
return {
|
|
113
|
+
register
|
|
114
|
+
};
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
//#endregion
|
|
10
118
|
//#region src/index.ts
|
|
11
119
|
/** MCP lifecycle/discovery methods reachable before a client authenticates. */
|
|
12
120
|
var DEFAULT_PUBLIC_METHODS = ["initialize", "tools/list"];
|
|
@@ -24,7 +132,6 @@ var buildVerifyToken = auth => {
|
|
|
24
132
|
if (auth.verifyToken) return auth.verifyToken;
|
|
25
133
|
throw new Error("McpAuthOptions requires either cognitoUserPool or verifyToken");
|
|
26
134
|
};
|
|
27
|
-
var requestContextStore = new AsyncLocalStorage();
|
|
28
135
|
/**
|
|
29
136
|
* Generic HTTP helper for use inside MCP tool handlers.
|
|
30
137
|
*
|
|
@@ -112,17 +219,6 @@ var apiCall = async (method, url, options) => {
|
|
|
112
219
|
return response.text();
|
|
113
220
|
};
|
|
114
221
|
/**
|
|
115
|
-
* Returns the verified JWT payload for the current MCP request.
|
|
116
|
-
* Only available inside a tool handler when `auth` is configured on the router.
|
|
117
|
-
*
|
|
118
|
-
* Accepts an optional type parameter to avoid casting at call sites:
|
|
119
|
-
* `getIdentity<{ sub: string; email: string }>()` returns `T | undefined`.
|
|
120
|
-
* Omitting the type parameter keeps the return type as `unknown | undefined`.
|
|
121
|
-
*/
|
|
122
|
-
var getIdentity = () => {
|
|
123
|
-
return requestContextStore.getStore()?.identity;
|
|
124
|
-
};
|
|
125
|
-
/**
|
|
126
222
|
* Asserts that the current request's token contains all required scopes.
|
|
127
223
|
* Throws if any scope is missing — the MCP SDK catches this and returns a
|
|
128
224
|
* tool error to the client. Use inside tool handlers for per-tool authorization.
|
|
@@ -403,4 +499,4 @@ var registerToolFromSchema = (server, params) => {
|
|
|
403
499
|
};
|
|
404
500
|
|
|
405
501
|
//#endregion
|
|
406
|
-
export { McpServer, apiCall, checkScopes, createMcpRouter, getIdentity, registerToolFromSchema, z };
|
|
502
|
+
export { McpServer, apiCall, checkScopes, createGatedToolRegistrar, createMcpRouter, getIdentity, registerToolFromSchema, z };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ttoss/http-server-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
4
4
|
"description": "Model Context Protocol (MCP) server integration for @ttoss/http-server",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -35,9 +35,9 @@
|
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
37
37
|
"zod": "^4.4.3",
|
|
38
|
-
"@ttoss/auth-core": "^0.
|
|
39
|
-
"@ttoss/http-server
|
|
40
|
-
"@ttoss/http-server": "^0.7
|
|
38
|
+
"@ttoss/auth-core": "^0.10.0",
|
|
39
|
+
"@ttoss/http-server": "^0.7.1",
|
|
40
|
+
"@ttoss/http-server-auth": "^0.3.7"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@types/koa": "^3.0.3",
|