@webpieces/mcp-server 0.4.886 → 0.4.888
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 +46 -0
- package/package.json +6 -6
- package/src/McpAuth.d.ts +5 -0
- package/src/McpAuth.js +10 -0
- package/src/McpAuth.js.map +1 -1
- package/src/McpAuthorityCompileAssertions.d.ts +5 -1
- package/src/McpAuthorityCompileAssertions.js +9 -1
- package/src/McpAuthorityCompileAssertions.js.map +1 -1
- package/src/McpToolRegistry.d.ts +3 -2
- package/src/McpToolRegistry.js +18 -4
- package/src/McpToolRegistry.js.map +1 -1
- package/src/WpMcpServer.d.ts +1 -0
- package/src/WpMcpServer.js +5 -2
- package/src/WpMcpServer.js.map +1 -1
- package/src/__tests__/McpProfileFixtures.d.ts +20 -0
- package/src/__tests__/McpProfileFixtures.js +100 -0
- package/src/__tests__/McpProfileFixtures.js.map +1 -0
- package/src/__tests__/McpRemoteFixtures.js +81 -80
- package/src/__tests__/McpRemoteFixtures.js.map +1 -1
- package/src/__tests__/WpMcpServerTestFixtures.js +42 -41
- package/src/__tests__/WpMcpServerTestFixtures.js.map +1 -1
package/README.md
CHANGED
|
@@ -395,3 +395,49 @@ That endpoint's URL is `WpMcpServerConfig.resourceMetadataUrl` — RFC 9728 §3.
|
|
|
395
395
|
value of `resource_metadata` in the 401 `WWW-Authenticate` challenge. RFC 9728 §5.1 defines that
|
|
396
396
|
parameter as the URL of the metadata DOCUMENT, never the resource identifier itself. The framework
|
|
397
397
|
mounts no `.well-known` route; the app publishes the document there.
|
|
398
|
+
|
|
399
|
+
## Tool profiles and independent endpoints
|
|
400
|
+
|
|
401
|
+
Profile membership partitions tool exposure; account roles still belong exclusively to
|
|
402
|
+
`@WpAuthorization`. Selecting an admin profile never grants admin authority. Import `Mcp` and
|
|
403
|
+
`WpMcpTool` from the browser-safe `@webpieces/core-util` package:
|
|
404
|
+
|
|
405
|
+
```ts
|
|
406
|
+
@WpMcpTool('learner_get_passages', 'Get your passages') // only Mcp.DEFAULT
|
|
407
|
+
@WpMcpTool('admin_list_users', 'List users', { profiles: ['admin'] }) // only admin
|
|
408
|
+
@WpMcpTool('shared_tool', 'Shared tool', { profiles: [Mcp.DEFAULT, 'admin'] })
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
Each declaration also needs the usual endpoint, DTO, authentication, and authorization declarations.
|
|
412
|
+
Two arguments, an omitted `profiles` property, and explicit `[Mcp.DEFAULT]` all select only the base
|
|
413
|
+
group. An explicit list replaces that membership; it never implicitly adds the base group.
|
|
414
|
+
Identifiers match `[a-z][a-z0-9-]{0,63}` (1–64 lowercase ASCII letters, digits or hyphens, starting
|
|
415
|
+
with a letter). `Mcp.DEFAULT` reserves `default` for the base group. Empty lists, duplicates, unknown
|
|
416
|
+
option keys, whitespace, paths, and invalid identifiers are rejected. Lists are snapshotted and sorted.
|
|
417
|
+
|
|
418
|
+
Configure one independent `WpMcpServer` per mount with the same bindings, complete catalogs, and
|
|
419
|
+
receiving router's `AuthorizationService`. Start from the required fluent configuration shown above:
|
|
420
|
+
|
|
421
|
+
```ts
|
|
422
|
+
baseConfig.setResource('https://api.example.com/mcp').setToolProfiles([Mcp.DEFAULT]);
|
|
423
|
+
adminConfig.setResource('https://api.example.com/mcp/admin').setToolProfiles(['admin']);
|
|
424
|
+
bothConfig.setResource('https://api.example.com/mcp/both').setToolProfiles([Mcp.DEFAULT, 'admin']);
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
Bind each instance with `McpBindOptions.endpointPath` equal to its resource path: `/mcp`,
|
|
428
|
+
`/mcp/admin`, or `/mcp/both`. Omitting `setToolProfiles` selects `[Mcp.DEFAULT]`.
|
|
429
|
+
A combined selection is the union, with shared tools listed once in stable name order. Further groups
|
|
430
|
+
such as `course-authoring` work identically. No caller-controlled profile route or hint exists.
|
|
431
|
+
Configure instances before binding; the selected groups are fixed when bound.
|
|
432
|
+
|
|
433
|
+
Publish each instance's `protectedResourceMetadata()` at its configuration's `resourceMetadataUrl`,
|
|
434
|
+
e.g. `/.well-known/oauth-protected-resource/mcp/admin`. Token authorities must verify the fixed exact
|
|
435
|
+
resource, trusted issuer, expiry, scopes, and fresh account state. A `/mcp` token is rejected on
|
|
436
|
+
`/mcp/admin`. Never derive audiences from Host headers. OAuth consent, grants, and connector setup
|
|
437
|
+
remain application-owned; framework tests do not establish Claude.ai's simultaneous credential behavior.
|
|
438
|
+
|
|
439
|
+
All supplied contracts/catalogs are validated before profile projection, including excluded entries.
|
|
440
|
+
Discovery, schema lookup, and execution share the same profile-and-authorization view. Membership
|
|
441
|
+
is catalog metadata, never a nonstandard MCP Tool field. Catalog entries now require `profiles`;
|
|
442
|
+
artifacts from before this change are rejected with a cure to rebuild the contract library's
|
|
443
|
+
`openapi-generate` target. Regenerate catalogs when upgrading; there is no schema reflection fallback.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpieces/mcp-server",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.888",
|
|
4
4
|
"description": "Secure MCP tool generation from annotated Webpieces API contracts",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -24,15 +24,15 @@
|
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"@modelcontextprotocol/node": "2.0.0",
|
|
26
26
|
"@modelcontextprotocol/server": "2.0.0",
|
|
27
|
-
"@webpieces/core-context": "0.4.
|
|
28
|
-
"@webpieces/core-util": "0.4.
|
|
29
|
-
"@webpieces/http-routing": "0.4.
|
|
30
|
-
"@webpieces/http-server": "0.4.
|
|
27
|
+
"@webpieces/core-context": "0.4.888",
|
|
28
|
+
"@webpieces/core-util": "0.4.888",
|
|
29
|
+
"@webpieces/http-routing": "0.4.888",
|
|
30
|
+
"@webpieces/http-server": "0.4.888",
|
|
31
31
|
"express": "5.1.0",
|
|
32
32
|
"reflect-metadata": "0.2.2",
|
|
33
33
|
"tslib": "2.8.1"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
|
-
"@webpieces/http-client-node": "0.4.
|
|
36
|
+
"@webpieces/http-client-node": "0.4.888"
|
|
37
37
|
}
|
|
38
38
|
}
|
package/src/McpAuth.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { McpToolProfiles } from '@webpieces/core-util';
|
|
1
2
|
import { AuthenticatedCaller, AuthorizationService } from '@webpieces/http-routing';
|
|
2
3
|
export declare const MAX_MCP_ACCESS_TOKEN_LIFETIME_SECONDS: number;
|
|
3
4
|
export declare const MAX_MCP_ACCOUNT_VALIDATION_AGE_SECONDS: number;
|
|
@@ -82,6 +83,7 @@ export declare class McpProtectedResourceMetadata {
|
|
|
82
83
|
* ```
|
|
83
84
|
*/
|
|
84
85
|
export declare class WpMcpServerConfig<TGrant> {
|
|
86
|
+
private toolProfilesValue;
|
|
85
87
|
private nameValue?;
|
|
86
88
|
private versionValue?;
|
|
87
89
|
private resourceValue?;
|
|
@@ -90,6 +92,9 @@ export declare class WpMcpServerConfig<TGrant> {
|
|
|
90
92
|
private authorizationServersValue?;
|
|
91
93
|
private requiredScopesValue?;
|
|
92
94
|
private maxAccountValidationAgeSecondsValue;
|
|
95
|
+
/** OPTIONAL. Union of groups exposed by this instance; defaults to [Mcp.DEFAULT]. */
|
|
96
|
+
setToolProfiles(profiles: McpToolProfiles): this;
|
|
97
|
+
get toolProfiles(): readonly string[];
|
|
93
98
|
/** REQUIRED. The MCP server name reported by `initialize`. */
|
|
94
99
|
setName(name: string): this;
|
|
95
100
|
/** REQUIRED. The MCP server version; the registry revision is appended to it at bind time. */
|
package/src/McpAuth.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.WpMcpServerConfig = exports.McpProtectedResourceMetadata = exports.VerifiedMcpCredential = exports.MintedMcpAccessToken = exports.MAX_MCP_ACCOUNT_VALIDATION_AGE_SECONDS = exports.MAX_MCP_ACCESS_TOKEN_LIFETIME_SECONDS = void 0;
|
|
4
|
+
const core_util_1 = require("@webpieces/core-util");
|
|
4
5
|
exports.MAX_MCP_ACCESS_TOKEN_LIFETIME_SECONDS = 30 * 24 * 60 * 60;
|
|
5
6
|
exports.MAX_MCP_ACCOUNT_VALIDATION_AGE_SECONDS = 60 * 60;
|
|
6
7
|
/** Framework-normalized access-token result. The token representation remains application-owned. */
|
|
@@ -108,6 +109,7 @@ exports.McpProtectedResourceMetadata = McpProtectedResourceMetadata;
|
|
|
108
109
|
* ```
|
|
109
110
|
*/
|
|
110
111
|
class WpMcpServerConfig {
|
|
112
|
+
toolProfilesValue = Object.freeze([core_util_1.Mcp.DEFAULT]);
|
|
111
113
|
nameValue;
|
|
112
114
|
versionValue;
|
|
113
115
|
resourceValue;
|
|
@@ -116,6 +118,14 @@ class WpMcpServerConfig {
|
|
|
116
118
|
authorizationServersValue;
|
|
117
119
|
requiredScopesValue;
|
|
118
120
|
maxAccountValidationAgeSecondsValue = exports.MAX_MCP_ACCOUNT_VALIDATION_AGE_SECONDS;
|
|
121
|
+
/** OPTIONAL. Union of groups exposed by this instance; defaults to [Mcp.DEFAULT]. */
|
|
122
|
+
setToolProfiles(profiles) {
|
|
123
|
+
this.toolProfilesValue = core_util_1.Mcp.profiles(profiles, 'WpMcpServerConfig.setToolProfiles');
|
|
124
|
+
return this;
|
|
125
|
+
}
|
|
126
|
+
get toolProfiles() {
|
|
127
|
+
return this.toolProfilesValue;
|
|
128
|
+
}
|
|
119
129
|
/** REQUIRED. The MCP server name reported by `initialize`. */
|
|
120
130
|
setName(name) {
|
|
121
131
|
this.nameValue = this.requireText(name, 'setName');
|
package/src/McpAuth.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"McpAuth.js","sourceRoot":"","sources":["../../../../../packages/http/mcp-server/src/McpAuth.ts"],"names":[],"mappings":"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","sourcesContent":["import { AuthenticatedCaller, AuthorizationService } from '@webpieces/http-routing';\n\nexport const MAX_MCP_ACCESS_TOKEN_LIFETIME_SECONDS = 30 * 24 * 60 * 60;\nexport const MAX_MCP_ACCOUNT_VALIDATION_AGE_SECONDS = 60 * 60;\n\n/** Framework-normalized access-token result. The token representation remains application-owned. */\nexport class MintedMcpAccessToken {\n constructor(\n public readonly token: string,\n public readonly resource: string,\n public readonly issuedAtEpochSeconds: number,\n public readonly expiresAtEpochSeconds: number,\n ) {\n if (token.trim() === '' || resource.trim() === '') {\n throw new Error('Minted MCP access token and resource must be non-empty.');\n }\n if (!Number.isFinite(issuedAtEpochSeconds) || !Number.isFinite(expiresAtEpochSeconds)) {\n throw new Error(\n 'Minted MCP access token timestamps must be finite epoch-second values.',\n );\n }\n if (expiresAtEpochSeconds <= issuedAtEpochSeconds) {\n throw new Error('MCP access token expiry must be after issuance.');\n }\n if (expiresAtEpochSeconds - issuedAtEpochSeconds > MAX_MCP_ACCESS_TOKEN_LIFETIME_SECONDS) {\n throw new Error('MCP access token lifetime must not exceed 30 days.');\n }\n }\n}\n\n/**\n * Fresh, authoritative security facts and an explicit application-owned caller mapping resolved at the MCP resource boundary. Implementations must\n * verify issuer, exact resource/audience, expiry, token type/version, key/algorithm policy, and\n * current account state. Opaque access tokens are fully supported.\n */\nexport class VerifiedMcpCredential {\n constructor(\n public readonly subject: string,\n public readonly issuer: string,\n public readonly resource: string,\n public readonly issuedAtEpochSeconds: number,\n public readonly expiresAtEpochSeconds: number,\n public readonly scopes: readonly string[],\n /** When current enabled/revoked state and roles were read from the authoritative source. */\n public readonly accountValidatedAtEpochSeconds: number,\n /** Application mapping of freshly verified account facts into canonical trusted context. */\n public readonly caller: AuthenticatedCaller,\n ) {}\n\n /** The canonical application's roles; there is no independent listing-role snapshot. */\n get principalRoles(): readonly string[] {\n return this.caller.roles;\n }\n}\n\n/**\n * Application-owned paired mint/verify authority for resource-bound MCP access tokens.\n * `verifyAccessToken` must throw `ApiUnauthorizedError` for a token it rejects (the bind boundary\n * answers 401 + `WWW-Authenticate`); any other throw is treated as an implementation failure (500).\n */\nexport interface McpAccessTokenAuthority<TGrant> {\n mintAccessToken(grant: TGrant): Promise<MintedMcpAccessToken>;\n verifyAccessToken(\n accessToken: string,\n expectedResource: string,\n ): Promise<VerifiedMcpCredential>;\n}\n\n/** RFC 9728-style metadata exposed by the MCP protected resource. */\nexport class McpProtectedResourceMetadata {\n readonly resource: string;\n readonly authorization_servers: readonly string[];\n readonly bearer_methods_supported = ['header'];\n readonly scopes_supported?: readonly string[];\n\n constructor(\n resource: string,\n authorizationServers: readonly string[],\n scopesSupported?: readonly string[],\n ) {\n this.resource = resource;\n this.authorization_servers = authorizationServers;\n this.scopes_supported = scopesSupported;\n }\n}\n\n/**\n * Node MCP server configuration, built with fluent setters: every name sits next to its own value, so\n * no two settings can be swapped, and a future setting is an additive setter rather than a breaking\n * signature change. The access-token authority verifies MCP credentials; the authorization service\n * is shared with receiving endpoints and document projection.\n *\n * SINGLE CANONICAL ORIGIN: one server serves exactly one protected resource. {@link setResource} is\n * fixed for the lifetime of the process, is the audience of every access token, and is never derived\n * from the request `Host` — see that setter for why a host-derived audience is a confused deputy.\n *\n * Each setter validates its own value immediately and names itself in the failure. Setters cannot\n * make a field impossible to forget the way a positional constructor could, so\n * {@link WpMcpServerConfig.validate} runs from `WpMcpServer.bind(...)` and fails at startup listing\n * EVERY missing required setter at once. A misconfiguration is therefore a boot failure naming the\n * setter, instead of a first-request 401 + `WWW-Authenticate` — which is the OAuth discovery signal,\n * so the client's correct response is to re-authenticate and fail again, forever.\n *\n * ```ts\n * const config = new WpMcpServerConfig<MyGrant>()\n * .setName('my-server')\n * .setVersion('1.0.0')\n * .setResource('https://api.example.com/mcp')\n * .setAccessTokenAuthority(authority)\n * .setAuthorizationService(router.authorizationService())\n * .setAuthorizationServers(['https://login.example.com'])\n * .setRequiredScopes(['tools']);\n *\n * // the tools/call error translator is a PROCESS-GLOBAL, not a member of this config:\n * McpRegistry.setErrorTranslator(new MyMcpErrorTranslator());\n * ```\n */\nexport class WpMcpServerConfig<TGrant> {\n private nameValue?: string;\n private versionValue?: string;\n private resourceValue?: string;\n private accessTokenAuthorityValue?: McpAccessTokenAuthority<TGrant>;\n private authorizationServiceValue?: AuthorizationService;\n private authorizationServersValue?: readonly string[];\n private requiredScopesValue?: readonly string[];\n private maxAccountValidationAgeSecondsValue = MAX_MCP_ACCOUNT_VALIDATION_AGE_SECONDS;\n\n /** REQUIRED. The MCP server name reported by `initialize`. */\n setName(name: string): this {\n this.nameValue = this.requireText(name, 'setName');\n return this;\n }\n\n /** REQUIRED. The MCP server version; the registry revision is appended to it at bind time. */\n setVersion(version: string): this {\n this.versionValue = this.requireText(version, 'setVersion');\n return this;\n }\n\n /**\n * REQUIRED. The ONE canonical protected-resource URI, which must be an absolute URL: it is the\n * exact audience every access token is checked against, and {@link resourceMetadataUrl} is\n * derived from it for the 401 challenge, so a non-URL here is always a mistake — including a\n * name or version landed on it.\n *\n * It is fixed for the lifetime of the process and must NEVER be derived from the request `Host`\n * header: the boundary compares a token's audience against this value, so a host-derived\n * resource compares two values both taken from caller-controlled input — the confused-deputy\n * shape this check exists to prevent. Its path must equal `McpBindOptions.endpointPath`, which\n * `WpMcpServer.bind(...)` verifies. A second hostname is a second deployment, not a second\n * audience.\n */\n setResource(resource: string): this {\n this.resourceValue = this.requireUrl(resource, 'setResource');\n return this;\n }\n\n /** REQUIRED. Application-owned paired mint/verify authority for MCP access tokens. */\n setAccessTokenAuthority(authority: McpAccessTokenAuthority<TGrant>): this {\n this.accessTokenAuthorityValue = this.requirePresent(authority, 'setAccessTokenAuthority');\n return this;\n }\n\n /** REQUIRED. The common authorization service for tool projection and invocation. */\n setAuthorizationService(authority: AuthorizationService): this {\n this.authorizationServiceValue = this.requirePresent(authority, 'setAuthorizationService');\n return this;\n }\n\n /**\n * REQUIRED. The trusted token issuers. Every entry must be an absolute URL, because each is\n * compared against a token's `issuer` claim and published as an `authorization_servers` entry —\n * which is what makes a scope list landed here fail at boot rather than on the first request.\n */\n setAuthorizationServers(authorizationServers: readonly string[]): this {\n const servers = this.requireArray(authorizationServers, 'setAuthorizationServers');\n if (servers.length === 0) {\n throw new Error(\n 'WpMcpServerConfig.setAuthorizationServers(...) requires at least one issuer URL.',\n );\n }\n for (const server of servers) this.requireUrl(server, 'setAuthorizationServers');\n this.authorizationServersValue = servers;\n return this;\n }\n\n /**\n * REQUIRED, and empty only by saying so explicitly. Scopes every access token must carry before\n * any tool is dispatched.\n */\n setRequiredScopes(requiredScopes: readonly string[]): this {\n const scopes = this.requireArray(requiredScopes, 'setRequiredScopes');\n for (const scope of scopes) this.requireText(scope, 'setRequiredScopes');\n this.requiredScopesValue = scopes;\n return this;\n }\n\n /** OPTIONAL ceiling, 1 second to 1 hour. Defaults to 1 hour. */\n setMaxAccountValidationAgeSeconds(seconds: number): this {\n this.maxAccountValidationAgeSecondsValue = this.requireSeconds(\n seconds,\n MAX_MCP_ACCOUNT_VALIDATION_AGE_SECONDS,\n 'setMaxAccountValidationAgeSeconds',\n );\n return this;\n }\n\n /**\n * Called by `WpMcpServer.bind(...)`. Throws naming EVERY missing required setter at once, so a\n * half-built config is repaired in one pass rather than one boot failure at a time.\n */\n validate(): void {\n const missing: string[] = [];\n if (this.nameValue === undefined) missing.push('setName(...)');\n if (this.versionValue === undefined) missing.push('setVersion(...)');\n if (this.resourceValue === undefined) missing.push('setResource(...)');\n if (this.accessTokenAuthorityValue === undefined) {\n missing.push('setAccessTokenAuthority(...)');\n }\n if (this.authorizationServiceValue === undefined) {\n missing.push('setAuthorizationService(...)');\n }\n if (this.authorizationServersValue === undefined) {\n missing.push('setAuthorizationServers(...)');\n }\n if (this.requiredScopesValue === undefined) missing.push('setRequiredScopes(...)');\n if (missing.length > 0) {\n throw new Error(`WpMcpServerConfig is missing ${missing.join(', ')}`);\n }\n }\n\n get name(): string {\n return this.read(this.nameValue, 'setName');\n }\n\n get version(): string {\n return this.read(this.versionValue, 'setVersion');\n }\n\n get resource(): string {\n return this.read(this.resourceValue, 'setResource');\n }\n\n get accessTokenAuthority(): McpAccessTokenAuthority<TGrant> {\n return this.read(this.accessTokenAuthorityValue, 'setAccessTokenAuthority');\n }\n\n get authorizationService(): AuthorizationService {\n return this.read(this.authorizationServiceValue, 'setAuthorizationService');\n }\n\n get authorizationServers(): readonly string[] {\n return this.read(this.authorizationServersValue, 'setAuthorizationServers');\n }\n\n get requiredScopes(): readonly string[] {\n return this.read(this.requiredScopesValue, 'setRequiredScopes');\n }\n\n get maxAccountValidationAgeSeconds(): number {\n return this.maxAccountValidationAgeSecondsValue;\n }\n\n /**\n * RFC 9728 §3.1: the URL of this resource's protected-resource METADATA DOCUMENT, derived from\n * {@link resource} by inserting `/.well-known/oauth-protected-resource` between origin and path\n * (`https://host/mcp` → `https://host/.well-known/oauth-protected-resource/mcp`).\n *\n * This — never the resource identifier itself — is what RFC 9728 §5.1 defines `resource_metadata`\n * to be, so it is the value webpieces puts in the 401 `WWW-Authenticate` challenge. webpieces\n * mounts no `.well-known` route; publish {@link protectedResourceMetadata} at this URL.\n */\n get resourceMetadataUrl(): string {\n const url = new URL(this.resource);\n const path = url.pathname === '/' ? '' : url.pathname;\n return `${url.origin}/.well-known/oauth-protected-resource${path}`;\n }\n\n protectedResourceMetadata(): McpProtectedResourceMetadata {\n return new McpProtectedResourceMetadata(\n this.resource,\n this.authorizationServers,\n this.requiredScopes,\n );\n }\n\n private requireText(value: string, setter: string): string {\n if (typeof value !== 'string' || value.trim() === '') {\n throw new Error(`WpMcpServerConfig.${setter}(...) requires a non-empty string.`);\n }\n return value;\n }\n\n private requireUrl(value: string, setter: string): string {\n this.requireText(value, setter);\n if (!URL.canParse(value)) {\n throw new Error(\n `WpMcpServerConfig.${setter}(...) requires an absolute URL, got '${value}'.`,\n );\n }\n return value;\n }\n\n private requireArray(value: readonly string[], setter: string): readonly string[] {\n if (!Array.isArray(value)) {\n throw new Error(`WpMcpServerConfig.${setter}(...) requires an array of strings.`);\n }\n return value;\n }\n\n private requireSeconds(value: number, ceiling: number, setter: string): number {\n if (!Number.isFinite(value) || value <= 0 || value > ceiling) {\n throw new Error(\n `WpMcpServerConfig.${setter}(...) requires 1..${ceiling} seconds, got ${String(value)}.`,\n );\n }\n return value;\n }\n\n private requirePresent<T>(value: T, setter: string): T {\n if (value === undefined || value === null) {\n throw new Error(`WpMcpServerConfig.${setter}(...) requires a value.`);\n }\n return value;\n }\n\n private read<T>(value: T | undefined, setter: string): T {\n if (value === undefined) {\n throw new Error(`WpMcpServerConfig is missing ${setter}(...)`);\n }\n return value;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"McpAuth.js","sourceRoot":"","sources":["../../../../../packages/http/mcp-server/src/McpAuth.ts"],"names":[],"mappings":"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","sourcesContent":["import { Mcp, McpToolProfiles } from '@webpieces/core-util';\nimport { AuthenticatedCaller, AuthorizationService } from '@webpieces/http-routing';\n\nexport const MAX_MCP_ACCESS_TOKEN_LIFETIME_SECONDS = 30 * 24 * 60 * 60;\nexport const MAX_MCP_ACCOUNT_VALIDATION_AGE_SECONDS = 60 * 60;\n\n/** Framework-normalized access-token result. The token representation remains application-owned. */\nexport class MintedMcpAccessToken {\n constructor(\n public readonly token: string,\n public readonly resource: string,\n public readonly issuedAtEpochSeconds: number,\n public readonly expiresAtEpochSeconds: number,\n ) {\n if (token.trim() === '' || resource.trim() === '') {\n throw new Error('Minted MCP access token and resource must be non-empty.');\n }\n if (!Number.isFinite(issuedAtEpochSeconds) || !Number.isFinite(expiresAtEpochSeconds)) {\n throw new Error(\n 'Minted MCP access token timestamps must be finite epoch-second values.',\n );\n }\n if (expiresAtEpochSeconds <= issuedAtEpochSeconds) {\n throw new Error('MCP access token expiry must be after issuance.');\n }\n if (expiresAtEpochSeconds - issuedAtEpochSeconds > MAX_MCP_ACCESS_TOKEN_LIFETIME_SECONDS) {\n throw new Error('MCP access token lifetime must not exceed 30 days.');\n }\n }\n}\n\n/**\n * Fresh, authoritative security facts and an explicit application-owned caller mapping resolved at the MCP resource boundary. Implementations must\n * verify issuer, exact resource/audience, expiry, token type/version, key/algorithm policy, and\n * current account state. Opaque access tokens are fully supported.\n */\nexport class VerifiedMcpCredential {\n constructor(\n public readonly subject: string,\n public readonly issuer: string,\n public readonly resource: string,\n public readonly issuedAtEpochSeconds: number,\n public readonly expiresAtEpochSeconds: number,\n public readonly scopes: readonly string[],\n /** When current enabled/revoked state and roles were read from the authoritative source. */\n public readonly accountValidatedAtEpochSeconds: number,\n /** Application mapping of freshly verified account facts into canonical trusted context. */\n public readonly caller: AuthenticatedCaller,\n ) {}\n\n /** The canonical application's roles; there is no independent listing-role snapshot. */\n get principalRoles(): readonly string[] {\n return this.caller.roles;\n }\n}\n\n/**\n * Application-owned paired mint/verify authority for resource-bound MCP access tokens.\n * `verifyAccessToken` must throw `ApiUnauthorizedError` for a token it rejects (the bind boundary\n * answers 401 + `WWW-Authenticate`); any other throw is treated as an implementation failure (500).\n */\nexport interface McpAccessTokenAuthority<TGrant> {\n mintAccessToken(grant: TGrant): Promise<MintedMcpAccessToken>;\n verifyAccessToken(\n accessToken: string,\n expectedResource: string,\n ): Promise<VerifiedMcpCredential>;\n}\n\n/** RFC 9728-style metadata exposed by the MCP protected resource. */\nexport class McpProtectedResourceMetadata {\n readonly resource: string;\n readonly authorization_servers: readonly string[];\n readonly bearer_methods_supported = ['header'];\n readonly scopes_supported?: readonly string[];\n\n constructor(\n resource: string,\n authorizationServers: readonly string[],\n scopesSupported?: readonly string[],\n ) {\n this.resource = resource;\n this.authorization_servers = authorizationServers;\n this.scopes_supported = scopesSupported;\n }\n}\n\n/**\n * Node MCP server configuration, built with fluent setters: every name sits next to its own value, so\n * no two settings can be swapped, and a future setting is an additive setter rather than a breaking\n * signature change. The access-token authority verifies MCP credentials; the authorization service\n * is shared with receiving endpoints and document projection.\n *\n * SINGLE CANONICAL ORIGIN: one server serves exactly one protected resource. {@link setResource} is\n * fixed for the lifetime of the process, is the audience of every access token, and is never derived\n * from the request `Host` — see that setter for why a host-derived audience is a confused deputy.\n *\n * Each setter validates its own value immediately and names itself in the failure. Setters cannot\n * make a field impossible to forget the way a positional constructor could, so\n * {@link WpMcpServerConfig.validate} runs from `WpMcpServer.bind(...)` and fails at startup listing\n * EVERY missing required setter at once. A misconfiguration is therefore a boot failure naming the\n * setter, instead of a first-request 401 + `WWW-Authenticate` — which is the OAuth discovery signal,\n * so the client's correct response is to re-authenticate and fail again, forever.\n *\n * ```ts\n * const config = new WpMcpServerConfig<MyGrant>()\n * .setName('my-server')\n * .setVersion('1.0.0')\n * .setResource('https://api.example.com/mcp')\n * .setAccessTokenAuthority(authority)\n * .setAuthorizationService(router.authorizationService())\n * .setAuthorizationServers(['https://login.example.com'])\n * .setRequiredScopes(['tools']);\n *\n * // the tools/call error translator is a PROCESS-GLOBAL, not a member of this config:\n * McpRegistry.setErrorTranslator(new MyMcpErrorTranslator());\n * ```\n */\nexport class WpMcpServerConfig<TGrant> {\n private toolProfilesValue: readonly string[] = Object.freeze([Mcp.DEFAULT]);\n private nameValue?: string;\n private versionValue?: string;\n private resourceValue?: string;\n private accessTokenAuthorityValue?: McpAccessTokenAuthority<TGrant>;\n private authorizationServiceValue?: AuthorizationService;\n private authorizationServersValue?: readonly string[];\n private requiredScopesValue?: readonly string[];\n private maxAccountValidationAgeSecondsValue = MAX_MCP_ACCOUNT_VALIDATION_AGE_SECONDS;\n\n /** OPTIONAL. Union of groups exposed by this instance; defaults to [Mcp.DEFAULT]. */\n setToolProfiles(profiles: McpToolProfiles): this {\n this.toolProfilesValue = Mcp.profiles(profiles, 'WpMcpServerConfig.setToolProfiles');\n return this;\n }\n\n get toolProfiles(): readonly string[] {\n return this.toolProfilesValue;\n }\n\n /** REQUIRED. The MCP server name reported by `initialize`. */\n setName(name: string): this {\n this.nameValue = this.requireText(name, 'setName');\n return this;\n }\n\n /** REQUIRED. The MCP server version; the registry revision is appended to it at bind time. */\n setVersion(version: string): this {\n this.versionValue = this.requireText(version, 'setVersion');\n return this;\n }\n\n /**\n * REQUIRED. The ONE canonical protected-resource URI, which must be an absolute URL: it is the\n * exact audience every access token is checked against, and {@link resourceMetadataUrl} is\n * derived from it for the 401 challenge, so a non-URL here is always a mistake — including a\n * name or version landed on it.\n *\n * It is fixed for the lifetime of the process and must NEVER be derived from the request `Host`\n * header: the boundary compares a token's audience against this value, so a host-derived\n * resource compares two values both taken from caller-controlled input — the confused-deputy\n * shape this check exists to prevent. Its path must equal `McpBindOptions.endpointPath`, which\n * `WpMcpServer.bind(...)` verifies. A second hostname is a second deployment, not a second\n * audience.\n */\n setResource(resource: string): this {\n this.resourceValue = this.requireUrl(resource, 'setResource');\n return this;\n }\n\n /** REQUIRED. Application-owned paired mint/verify authority for MCP access tokens. */\n setAccessTokenAuthority(authority: McpAccessTokenAuthority<TGrant>): this {\n this.accessTokenAuthorityValue = this.requirePresent(authority, 'setAccessTokenAuthority');\n return this;\n }\n\n /** REQUIRED. The common authorization service for tool projection and invocation. */\n setAuthorizationService(authority: AuthorizationService): this {\n this.authorizationServiceValue = this.requirePresent(authority, 'setAuthorizationService');\n return this;\n }\n\n /**\n * REQUIRED. The trusted token issuers. Every entry must be an absolute URL, because each is\n * compared against a token's `issuer` claim and published as an `authorization_servers` entry —\n * which is what makes a scope list landed here fail at boot rather than on the first request.\n */\n setAuthorizationServers(authorizationServers: readonly string[]): this {\n const servers = this.requireArray(authorizationServers, 'setAuthorizationServers');\n if (servers.length === 0) {\n throw new Error(\n 'WpMcpServerConfig.setAuthorizationServers(...) requires at least one issuer URL.',\n );\n }\n for (const server of servers) this.requireUrl(server, 'setAuthorizationServers');\n this.authorizationServersValue = servers;\n return this;\n }\n\n /**\n * REQUIRED, and empty only by saying so explicitly. Scopes every access token must carry before\n * any tool is dispatched.\n */\n setRequiredScopes(requiredScopes: readonly string[]): this {\n const scopes = this.requireArray(requiredScopes, 'setRequiredScopes');\n for (const scope of scopes) this.requireText(scope, 'setRequiredScopes');\n this.requiredScopesValue = scopes;\n return this;\n }\n\n /** OPTIONAL ceiling, 1 second to 1 hour. Defaults to 1 hour. */\n setMaxAccountValidationAgeSeconds(seconds: number): this {\n this.maxAccountValidationAgeSecondsValue = this.requireSeconds(\n seconds,\n MAX_MCP_ACCOUNT_VALIDATION_AGE_SECONDS,\n 'setMaxAccountValidationAgeSeconds',\n );\n return this;\n }\n\n /**\n * Called by `WpMcpServer.bind(...)`. Throws naming EVERY missing required setter at once, so a\n * half-built config is repaired in one pass rather than one boot failure at a time.\n */\n validate(): void {\n const missing: string[] = [];\n if (this.nameValue === undefined) missing.push('setName(...)');\n if (this.versionValue === undefined) missing.push('setVersion(...)');\n if (this.resourceValue === undefined) missing.push('setResource(...)');\n if (this.accessTokenAuthorityValue === undefined) {\n missing.push('setAccessTokenAuthority(...)');\n }\n if (this.authorizationServiceValue === undefined) {\n missing.push('setAuthorizationService(...)');\n }\n if (this.authorizationServersValue === undefined) {\n missing.push('setAuthorizationServers(...)');\n }\n if (this.requiredScopesValue === undefined) missing.push('setRequiredScopes(...)');\n if (missing.length > 0) {\n throw new Error(`WpMcpServerConfig is missing ${missing.join(', ')}`);\n }\n }\n\n get name(): string {\n return this.read(this.nameValue, 'setName');\n }\n\n get version(): string {\n return this.read(this.versionValue, 'setVersion');\n }\n\n get resource(): string {\n return this.read(this.resourceValue, 'setResource');\n }\n\n get accessTokenAuthority(): McpAccessTokenAuthority<TGrant> {\n return this.read(this.accessTokenAuthorityValue, 'setAccessTokenAuthority');\n }\n\n get authorizationService(): AuthorizationService {\n return this.read(this.authorizationServiceValue, 'setAuthorizationService');\n }\n\n get authorizationServers(): readonly string[] {\n return this.read(this.authorizationServersValue, 'setAuthorizationServers');\n }\n\n get requiredScopes(): readonly string[] {\n return this.read(this.requiredScopesValue, 'setRequiredScopes');\n }\n\n get maxAccountValidationAgeSeconds(): number {\n return this.maxAccountValidationAgeSecondsValue;\n }\n\n /**\n * RFC 9728 §3.1: the URL of this resource's protected-resource METADATA DOCUMENT, derived from\n * {@link resource} by inserting `/.well-known/oauth-protected-resource` between origin and path\n * (`https://host/mcp` → `https://host/.well-known/oauth-protected-resource/mcp`).\n *\n * This — never the resource identifier itself — is what RFC 9728 §5.1 defines `resource_metadata`\n * to be, so it is the value webpieces puts in the 401 `WWW-Authenticate` challenge. webpieces\n * mounts no `.well-known` route; publish {@link protectedResourceMetadata} at this URL.\n */\n get resourceMetadataUrl(): string {\n const url = new URL(this.resource);\n const path = url.pathname === '/' ? '' : url.pathname;\n return `${url.origin}/.well-known/oauth-protected-resource${path}`;\n }\n\n protectedResourceMetadata(): McpProtectedResourceMetadata {\n return new McpProtectedResourceMetadata(\n this.resource,\n this.authorizationServers,\n this.requiredScopes,\n );\n }\n\n private requireText(value: string, setter: string): string {\n if (typeof value !== 'string' || value.trim() === '') {\n throw new Error(`WpMcpServerConfig.${setter}(...) requires a non-empty string.`);\n }\n return value;\n }\n\n private requireUrl(value: string, setter: string): string {\n this.requireText(value, setter);\n if (!URL.canParse(value)) {\n throw new Error(\n `WpMcpServerConfig.${setter}(...) requires an absolute URL, got '${value}'.`,\n );\n }\n return value;\n }\n\n private requireArray(value: readonly string[], setter: string): readonly string[] {\n if (!Array.isArray(value)) {\n throw new Error(`WpMcpServerConfig.${setter}(...) requires an array of strings.`);\n }\n return value;\n }\n\n private requireSeconds(value: number, ceiling: number, setter: string): number {\n if (!Number.isFinite(value) || value <= 0 || value > ceiling) {\n throw new Error(\n `WpMcpServerConfig.${setter}(...) requires 1..${ceiling} seconds, got ${String(value)}.`,\n );\n }\n return value;\n }\n\n private requirePresent<T>(value: T, setter: string): T {\n if (value === undefined || value === null) {\n throw new Error(`WpMcpServerConfig.${setter}(...) requires a value.`);\n }\n return value;\n }\n\n private read<T>(value: T | undefined, setter: string): T {\n if (value === undefined) {\n throw new Error(`WpMcpServerConfig is missing ${setter}(...)`);\n }\n return value;\n }\n}\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AuthenticatedCaller, JwtHook, MintedJwt } from '@webpieces/http-routing';
|
|
2
|
-
import { McpAccessTokenAuthority, MintedMcpAccessToken, VerifiedMcpCredential } from './McpAuth';
|
|
2
|
+
import { McpAccessTokenAuthority, MintedMcpAccessToken, VerifiedMcpCredential, WpMcpServerConfig } from './McpAuth';
|
|
3
3
|
declare class ExampleJwtMintRequest {
|
|
4
4
|
readonly subject: string;
|
|
5
5
|
constructor(subject: string);
|
|
@@ -20,4 +20,8 @@ export declare class McpAuthorityCompileAssertions extends JwtHook<ExampleJwtMin
|
|
|
20
20
|
export declare class McpPrincipalCompileAssertions {
|
|
21
21
|
roles(credential: VerifiedMcpCredential): readonly string[];
|
|
22
22
|
}
|
|
23
|
+
/** Profile-selection arity is checked by tsc, not only by runtime specs. */
|
|
24
|
+
export declare class McpProfilesCompileAssertions {
|
|
25
|
+
empty(config: WpMcpServerConfig<string>): void;
|
|
26
|
+
}
|
|
23
27
|
export {};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.McpPrincipalCompileAssertions = exports.McpAuthorityCompileAssertions = void 0;
|
|
3
|
+
exports.McpProfilesCompileAssertions = exports.McpPrincipalCompileAssertions = exports.McpAuthorityCompileAssertions = void 0;
|
|
4
4
|
const http_routing_1 = require("@webpieces/http-routing");
|
|
5
5
|
const McpAuth_1 = require("./McpAuth");
|
|
6
6
|
class ExampleJwtMintRequest {
|
|
@@ -44,4 +44,12 @@ class McpPrincipalCompileAssertions {
|
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
exports.McpPrincipalCompileAssertions = McpPrincipalCompileAssertions;
|
|
47
|
+
/** Profile-selection arity is checked by tsc, not only by runtime specs. */
|
|
48
|
+
class McpProfilesCompileAssertions {
|
|
49
|
+
empty(config) {
|
|
50
|
+
// @ts-expect-error an explicit server selection must include at least one group
|
|
51
|
+
config.setToolProfiles([]);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
exports.McpProfilesCompileAssertions = McpProfilesCompileAssertions;
|
|
47
55
|
//# sourceMappingURL=McpAuthorityCompileAssertions.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"McpAuthorityCompileAssertions.js","sourceRoot":"","sources":["../../../../../packages/http/mcp-server/src/McpAuthorityCompileAssertions.ts"],"names":[],"mappings":";;;AAAA,0DAAkF;AAClF,
|
|
1
|
+
{"version":3,"file":"McpAuthorityCompileAssertions.js","sourceRoot":"","sources":["../../../../../packages/http/mcp-server/src/McpAuthorityCompileAssertions.ts"],"names":[],"mappings":";;;AAAA,0DAAkF;AAClF,uCAAoH;AAEpH,MAAM,qBAAqB;IACK;IAA5B,YAA4B,OAAe;QAAf,YAAO,GAAP,OAAO,CAAQ;IAAG,CAAC;CAClD;AAED,MAAM,eAAe;IAEG;IACA;IAFpB,YACoB,OAAe,EACf,QAAgB;QADhB,YAAO,GAAP,OAAO,CAAQ;QACf,aAAQ,GAAR,QAAQ,CAAQ;IACjC,CAAC;CACP;AAED,uGAAuG;AACvG,MAAa,6BACT,SAAQ,sBAA8B;IAG7B,KAAK,CAAC,IAAI,CAAC,OAA8B;QAC9C,OAAO,IAAI,wBAAS,CAAC,YAAY,OAAO,CAAC,OAAO,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,GAAG,EAAE,CAAC,CAAC;IAChF,CAAC;IAEQ,KAAK,CAAC,QAAQ,CAAC,KAAa;QACjC,OAAO,IAAI,kCAAmB,CAAC,KAAK,CAAC,CAAC;IAC1C,CAAC;IAED,KAAK,CAAC,eAAe,CAAC,KAAsB;QACxC,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;QAC1C,OAAO,IAAI,8BAAoB,CAAC,OAAO,KAAK,CAAC,OAAO,EAAE,EAAE,KAAK,CAAC,QAAQ,EAAE,GAAG,EAAE,GAAG,GAAG,EAAE,CAAC,CAAC;IAC3F,CAAC;IAED,KAAK,CAAC,iBAAiB,CACnB,YAAoB,EACpB,gBAAwB;QAExB,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;QAC1C,OAAO,IAAI,+BAAqB,CAC5B,cAAc,EACd,6BAA6B,EAC7B,gBAAgB,EAChB,GAAG,EACH,GAAG,GAAG,EAAE,EACR,CAAC,OAAO,CAAC,EACT,GAAG,EACH,IAAI,kCAAmB,CAAC,cAAc,CAAC,CAC1C,CAAC;IACN,CAAC;CACJ;AAjCD,sEAiCC;AAED,+EAA+E;AAC/E,MAAa,6BAA6B;IACtC,KAAK,CAAC,UAAiC;QACnC,iGAAiG;QACjG,UAAU,CAAC,YAAY,CAAC;QACxB,OAAO,UAAU,CAAC,cAAc,CAAC;IACrC,CAAC;CACJ;AAND,sEAMC;AAED,4EAA4E;AAC5E,MAAa,4BAA4B;IACrC,KAAK,CAAC,MAAiC;QACnC,gFAAgF;QAChF,MAAM,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC;IAC/B,CAAC;CACJ;AALD,oEAKC","sourcesContent":["import { AuthenticatedCaller, JwtHook, MintedJwt } from '@webpieces/http-routing';\nimport { McpAccessTokenAuthority, MintedMcpAccessToken, VerifiedMcpCredential, WpMcpServerConfig } from './McpAuth';\n\nclass ExampleJwtMintRequest {\n constructor(public readonly subject: string) {}\n}\n\nclass ExampleMcpGrant {\n constructor(\n public readonly subject: string,\n public readonly resource: string,\n ) {}\n}\n\n/** Compile assertion: one application authority can own both endpoint JWT and MCP token lifecycles. */\nexport class McpAuthorityCompileAssertions\n extends JwtHook<ExampleJwtMintRequest>\n implements McpAccessTokenAuthority<ExampleMcpGrant>\n{\n override async mint(request: ExampleJwtMintRequest): Promise<MintedJwt> {\n return new MintedJwt(`endpoint:${request.subject}`, Date.now() / 1000 + 60);\n }\n\n override async parseJwt(token: string): Promise<AuthenticatedCaller> {\n return new AuthenticatedCaller(token);\n }\n\n async mintAccessToken(grant: ExampleMcpGrant): Promise<MintedMcpAccessToken> {\n const now = Math.floor(Date.now() / 1000);\n return new MintedMcpAccessToken(`mcp:${grant.subject}`, grant.resource, now, now + 60);\n }\n\n async verifyAccessToken(\n _accessToken: string,\n expectedResource: string,\n ): Promise<VerifiedMcpCredential> {\n const now = Math.floor(Date.now() / 1000);\n return new VerifiedMcpCredential(\n 'example-user',\n 'https://issuer.example.test',\n expectedResource,\n now,\n now + 60,\n ['tools'],\n now,\n new AuthenticatedCaller('example-user'),\n );\n }\n}\n\n/** Compiled hard-cut assertions; Vitest does not type-check specifications. */\nexport class McpPrincipalCompileAssertions {\n roles(credential: VerifiedMcpCredential): readonly string[] {\n // @ts-expect-error listingRoles was replaced by principalRoles derived from the canonical caller\n credential.listingRoles;\n return credential.principalRoles;\n }\n}\n\n/** Profile-selection arity is checked by tsc, not only by runtime specs. */\nexport class McpProfilesCompileAssertions {\n empty(config: WpMcpServerConfig<string>): void {\n // @ts-expect-error an explicit server selection must include at least one group\n config.setToolProfiles([]);\n }\n}\n"]}
|
package/src/McpToolRegistry.d.ts
CHANGED
|
@@ -17,9 +17,10 @@ export declare class RegisteredMcpTool {
|
|
|
17
17
|
readonly binding: McpApiBinding;
|
|
18
18
|
readonly inputSchema: ApiJsonSchema;
|
|
19
19
|
readonly outputSchema: ApiJsonSchema;
|
|
20
|
+
readonly profiles: readonly string[];
|
|
20
21
|
constructor(apiClass: ClassType, methodName: string, name: string,
|
|
21
22
|
/** The human-readable display name; published as BOTH `Tool.title` and `annotations.title` (#1180). */
|
|
22
|
-
title: string, description: string, annotations: WpMcpToolHints, operation: EndpointOperation, authMeta: AuthMeta, authorization: AuthorizationRequirement<unknown>, binding: McpApiBinding, inputSchema: ApiJsonSchema, outputSchema: ApiJsonSchema);
|
|
23
|
+
title: string, description: string, annotations: WpMcpToolHints, operation: EndpointOperation, authMeta: AuthMeta, authorization: AuthorizationRequirement<unknown>, binding: McpApiBinding, inputSchema: ApiJsonSchema, outputSchema: ApiJsonSchema, profiles: readonly string[]);
|
|
23
24
|
}
|
|
24
25
|
/**
|
|
25
26
|
* Fail-fast registry: the CONTRACT says which methods are tools and how they are authorized, and the
|
|
@@ -65,5 +66,5 @@ export declare class AuthorizedMcpTools {
|
|
|
65
66
|
readonly tools: readonly RegisteredMcpTool[];
|
|
66
67
|
constructor(tools: readonly RegisteredMcpTool[]);
|
|
67
68
|
find(name: string): RegisteredMcpTool | undefined;
|
|
68
|
-
static create(registry: McpToolRegistry, policy: AuthorizationService): Promise<AuthorizedMcpTools>;
|
|
69
|
+
static create(registry: McpToolRegistry, policy: AuthorizationService, profiles: readonly string[]): Promise<AuthorizedMcpTools>;
|
|
69
70
|
}
|
package/src/McpToolRegistry.js
CHANGED
|
@@ -17,11 +17,12 @@ class RegisteredMcpTool {
|
|
|
17
17
|
binding;
|
|
18
18
|
inputSchema;
|
|
19
19
|
outputSchema;
|
|
20
|
+
profiles;
|
|
20
21
|
constructor(apiClass, methodName, name,
|
|
21
22
|
/** The human-readable display name; published as BOTH `Tool.title` and `annotations.title` (#1180). */
|
|
22
23
|
title, description, annotations, operation, authMeta,
|
|
23
24
|
// webpieces-disable no-any-unknown -- custom policy is validated by AuthorizationService
|
|
24
|
-
authorization, binding, inputSchema, outputSchema) {
|
|
25
|
+
authorization, binding, inputSchema, outputSchema, profiles) {
|
|
25
26
|
this.apiClass = apiClass;
|
|
26
27
|
this.methodName = methodName;
|
|
27
28
|
this.name = name;
|
|
@@ -34,6 +35,7 @@ class RegisteredMcpTool {
|
|
|
34
35
|
this.binding = binding;
|
|
35
36
|
this.inputSchema = inputSchema;
|
|
36
37
|
this.outputSchema = outputSchema;
|
|
38
|
+
this.profiles = profiles;
|
|
37
39
|
}
|
|
38
40
|
}
|
|
39
41
|
exports.RegisteredMcpTool = RegisteredMcpTool;
|
|
@@ -79,6 +81,11 @@ class McpToolRegistry {
|
|
|
79
81
|
(0, core_util_1.assertApiTypeMatchesMcpTools)(apiClass);
|
|
80
82
|
const declared = (0, core_util_1.getWpMcpTools)(apiClass);
|
|
81
83
|
const catalog = pairing.claim(apiClass, declared.length);
|
|
84
|
+
for (const entry of catalog?.file.tools ?? []) {
|
|
85
|
+
if (!declared.some((metadata) => metadata.name === entry.name)) {
|
|
86
|
+
pairing.problem(`Stale tool '${entry.name}' in ${catalog.file.fileName}; rebuild the api library.`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
82
89
|
for (const metadata of declared) {
|
|
83
90
|
if (names.has(metadata.name)) {
|
|
84
91
|
pairing.problem(`Duplicate @WpMcpTool name '${metadata.name}' (again on ${apiClass.name}). Tool ` +
|
|
@@ -123,7 +130,7 @@ class McpToolRegistry {
|
|
|
123
130
|
const published = this.published(apiClass, metadata, catalog, pairing);
|
|
124
131
|
if (published === undefined)
|
|
125
132
|
return undefined;
|
|
126
|
-
return new RegisteredMcpTool(apiClass, metadata.methodName, metadata.name, published.title, published.description, published.hints, (0, core_util_1.getEndpointOperation)(apiClass, metadata.methodName), authMeta, authorization, binding, published.inputSchema, published.outputSchema);
|
|
133
|
+
return new RegisteredMcpTool(apiClass, metadata.methodName, metadata.name, published.title, published.description, published.hints, (0, core_util_1.getEndpointOperation)(apiClass, metadata.methodName), authMeta, authorization, binding, published.inputSchema, published.outputSchema, published.profiles);
|
|
127
134
|
}
|
|
128
135
|
/** The generated entry for this tool, or a recorded refusal naming what the build DID publish. */
|
|
129
136
|
published(apiClass, metadata, catalog, pairing) {
|
|
@@ -135,6 +142,11 @@ class McpToolRegistry {
|
|
|
135
142
|
'rebuild the api library; a tool the build never saw is a tool whose schema nobody checked.');
|
|
136
143
|
return undefined;
|
|
137
144
|
}
|
|
145
|
+
if (published.methodName !== metadata.methodName ||
|
|
146
|
+
published.profiles.join(',') !== metadata.profiles.join(',')) {
|
|
147
|
+
pairing.problem(`MCP tool '${metadata.name}' has stale method/profile metadata in ${catalog.file.fileName}; rebuild the api library.`);
|
|
148
|
+
return undefined;
|
|
149
|
+
}
|
|
138
150
|
if (published.title !== metadata.title) {
|
|
139
151
|
pairing.problem(`@WpMcpTool ${apiClass.name}.${metadata.methodName} is titled '${metadata.title}', but ` +
|
|
140
152
|
`${catalog.file.fileName} (${catalog.directory}) publishes '${metadata.name}' as ` +
|
|
@@ -219,10 +231,12 @@ class AuthorizedMcpTools {
|
|
|
219
231
|
return this.tools.find((tool) => tool.name === name);
|
|
220
232
|
}
|
|
221
233
|
// webpieces-disable no-function-outside-class -- asynchronous projection factory
|
|
222
|
-
static async create(registry, policy) {
|
|
234
|
+
static async create(registry, policy, profiles) {
|
|
223
235
|
const tools = [];
|
|
224
236
|
const authorization = new McpToolPolicy_1.McpToolPolicy(policy);
|
|
225
|
-
for (const tool of registry.tools) {
|
|
237
|
+
for (const tool of [...registry.tools].sort((a, b) => a.name.localeCompare(b.name))) {
|
|
238
|
+
if (!tool.profiles.some((profile) => profiles.includes(profile)))
|
|
239
|
+
continue;
|
|
226
240
|
if (await authorization.permits(tool.authorization))
|
|
227
241
|
tools.push(tool);
|
|
228
242
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"McpToolRegistry.js","sourceRoot":"","sources":["../../../../../packages/http/mcp-server/src/McpToolRegistry.ts"],"names":[],"mappings":"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","sourcesContent":["import { McpToolPolicy } from './McpToolPolicy';\nimport {\n ApiJsonSchema,\n AuthMeta,\n assertApiTypeMatchesMcpTools,\n getAuthMeta,\n getEndpointKind,\n getEndpointOperation,\n getEndpoints,\n getWpMcpTools,\n getAuthorization,\n AuthorizationRequirement,\n EndpointOperation,\n McpToolCatalogError,\n McpToolCatalogFile,\n McpToolDefinition,\n WpMcpToolHints,\n WpMcpToolMetadata,\n} from '@webpieces/core-util';\nimport { ClassType, AuthorizationService } from '@webpieces/http-routing';\nimport { McpApiBinding } from './McpApiBinding';\nimport { McpToolCatalog } from './McpToolCatalog';\n\n/** Fully resolved contract metadata for one MCP tool. */\nexport class RegisteredMcpTool {\n constructor(\n public readonly apiClass: ClassType,\n public readonly methodName: string,\n public readonly name: string,\n /** The human-readable display name; published as BOTH `Tool.title` and `annotations.title` (#1180). */\n public readonly title: string,\n public readonly description: string,\n public readonly annotations: WpMcpToolHints,\n public readonly operation: EndpointOperation,\n public readonly authMeta: AuthMeta,\n // webpieces-disable no-any-unknown -- custom policy is validated by AuthorizationService\n public readonly authorization: AuthorizationRequirement<unknown>,\n public readonly binding: McpApiBinding,\n public readonly inputSchema: ApiJsonSchema,\n public readonly outputSchema: ApiJsonSchema,\n ) {}\n}\n\n/**\n * Fail-fast registry: the CONTRACT says which methods are tools and how they are authorized, and the\n * build's generated catalog OF THAT CONTRACT says what each tool's documentation and schemas are.\n *\n * ## Why the schemas come from the catalog and not from here\n *\n * They used to be built at boot by `DtoSchemaBuilder` walking reflect-metadata. That made the\n * document a partner reads and the schema a server accepts two independent derivations of one\n * contract, free to disagree; and it could not see a field's declared TYPE at all, so every fact\n * about a field had to be restated in a `@WpDtoField` argument. #983 measured the compiler against\n * it and found the compiler reproduces all of it, so #984 deleted the decorator and this registry\n * now READS the same catalogs the documents were rendered beside.\n *\n * ## One catalog per bound contract, exactly (#1021)\n *\n * `wp-openapi` writes one `mcp-<ContractClass>-tools.json` per contract, and this registry pairs every\n * `McpApiBinding` with the file of its own contract. Three mismatches refuse the boot, all reported at\n * once, each naming every directory the catalogs were read from:\n *\n * - a bound contract with no catalog — its tools were never given a schema by the build;\n * - a catalog whose contract is not bound — the server was handed tools it does not serve;\n * - a tool name declared twice — the protocol namespace is flat.\n *\n * A registered `@WpMcpTool` the catalog does not name is likewise a HARD FAILURE, not a fallback: a\n * tool the build never saw is a tool whose schema nobody checked.\n */\nexport class McpToolRegistry {\n readonly tools: readonly RegisteredMcpTool[];\n\n constructor(bindings: readonly McpApiBinding[], catalogs: readonly McpToolCatalog[], private readonly authorizationService: AuthorizationService) {\n const pairing = new McpCatalogPairing(catalogs);\n const registered: RegisteredMcpTool[] = [];\n const names = new Set<string>();\n for (const binding of bindings) {\n const apiClass = binding.api;\n // MCP membership is declared twice — on @ApiType and on the methods — and this is the\n // one call that makes the two disagree impossible. It existed before #984 and nothing\n // reached it, so a contract could carry tools nobody published to agents.\n assertApiTypeMatchesMcpTools(apiClass);\n const declared = getWpMcpTools(apiClass);\n const catalog = pairing.claim(apiClass, declared.length);\n for (const metadata of declared) {\n if (names.has(metadata.name)) {\n pairing.problem(\n `Duplicate @WpMcpTool name '${metadata.name}' (again on ${apiClass.name}). Tool ` +\n 'names are the protocol identity and must be globally unique — rename one.',\n );\n continue;\n }\n names.add(metadata.name);\n const tool = this.resolve(apiClass, metadata, binding, catalog, pairing);\n if (tool !== undefined) registered.push(tool);\n }\n }\n pairing.assertComplete();\n this.tools = registered;\n }\n\n find(name: string): RegisteredMcpTool | undefined {\n return this.tools.find((tool: RegisteredMcpTool) => tool.name === name);\n }\n\n /**\n * The contract checks THROW at once — they are about the contract source, not the build. A tool\n * missing from its catalog is RECORDED on `pairing`, so one boot names every catalog mismatch.\n */\n private resolve(\n apiClass: ClassType,\n metadata: WpMcpToolMetadata,\n binding: McpApiBinding,\n catalog: McpToolCatalog | undefined,\n pairing: McpCatalogPairing,\n ): RegisteredMcpTool | undefined {\n const endpoints = getEndpoints(apiClass) ?? {};\n if (!endpoints[metadata.methodName]) {\n throw new Error(\n `@WpMcpTool ${apiClass.name}.${metadata.methodName} must also be an @Endpoint.`,\n );\n }\n if (getEndpointKind(apiClass, metadata.methodName) !== 'rpc') {\n throw new Error(\n `@WpMcpTool ${apiClass.name}.${metadata.methodName} must be an RPC endpoint.`,\n );\n }\n const authMeta = getAuthMeta(apiClass, metadata.methodName);\n if (!authMeta)\n throw new Error(`@WpMcpTool ${apiClass.name}.${metadata.methodName} has no HTTP auth.`);\n const authorization = getAuthorization(apiClass, metadata.methodName);\n if (!authorization) {\n throw new Error(\n `@WpMcpTool ${apiClass.name}.${metadata.methodName} must declare @WpAuthorization(...).`,\n );\n }\n this.authorizationService.validate(authorization);\n binding.validateMethod(metadata.methodName, this.authorizationService);\n if (catalog === undefined) return undefined;\n const published = this.published(apiClass, metadata, catalog, pairing);\n if (published === undefined) return undefined;\n return new RegisteredMcpTool(\n apiClass,\n metadata.methodName,\n metadata.name,\n published.title,\n published.description,\n published.hints,\n getEndpointOperation(apiClass, metadata.methodName),\n authMeta,\n authorization,\n binding,\n published.inputSchema,\n published.outputSchema,\n );\n }\n\n /** The generated entry for this tool, or a recorded refusal naming what the build DID publish. */\n private published(\n apiClass: ClassType,\n metadata: WpMcpToolMetadata,\n catalog: McpToolCatalog,\n pairing: McpCatalogPairing,\n ): McpToolDefinition | undefined {\n const published = catalog.find(metadata.name);\n if (published === undefined) {\n pairing.problem(\n `@WpMcpTool ${apiClass.name}.${metadata.methodName} publishes '${metadata.name}', which ` +\n `${catalog.file.fileName} (${catalog.directory}) does not contain. It has: ` +\n `${catalog.names().join(', ') || '(nothing)'}. The catalog is older than the contract — ` +\n 'rebuild the api library; a tool the build never saw is a tool whose schema nobody checked.',\n );\n return undefined;\n }\n if (published.title !== metadata.title) {\n pairing.problem(\n `@WpMcpTool ${apiClass.name}.${metadata.methodName} is titled '${metadata.title}', but ` +\n `${catalog.file.fileName} (${catalog.directory}) publishes '${metadata.name}' as ` +\n `'${published.title}'. The catalog is older than the contract — rebuild the api library.`,\n );\n return undefined;\n }\n return published;\n }\n}\n\n/**\n * Pairs each bound contract with the catalog of exactly that contract, and collects every mismatch so\n * the boot refusal lists them all at once instead of one per restart.\n */\nclass McpCatalogPairing {\n private readonly byContract = new Map<string, McpToolCatalog>();\n private readonly claimed = new Set<string>();\n private readonly problems: string[] = [];\n\n constructor(private readonly catalogs: readonly McpToolCatalog[]) {\n for (const catalog of catalogs) {\n const earlier = this.byContract.get(catalog.contractName);\n if (earlier !== undefined) {\n this.problem(\n `Two MCP tool catalogs for ${catalog.contractName}: ${earlier.directory} and ` +\n `${catalog.directory}. Name each api library in fromPackages(...) once, and bind ` +\n 'each contract from exactly one of them.',\n );\n continue;\n }\n this.byContract.set(catalog.contractName, catalog);\n }\n }\n\n /** The catalog of `apiClass`, recording a problem when there is none. */\n claim(apiClass: ClassType, declaredTools: number): McpToolCatalog | undefined {\n const catalog = this.byContract.get(apiClass.name);\n this.claimed.add(apiClass.name);\n if (catalog !== undefined) return catalog;\n this.problem(\n declaredTools === 0\n ? `The server binds ${apiClass.name}, which declares no @WpMcpTool method, so the build ` +\n `wrote no ${McpToolCatalogFile.fileNameFor(apiClass.name)} for it. Delete its ` +\n 'McpApiBinding — it publishes nothing.'\n : `The server binds ${apiClass.name}, and no ${McpToolCatalogFile.fileNameFor(apiClass.name)} ` +\n 'was handed to it. Add the api library that declares it to McpToolCatalog.fromPackages([...]) ' +\n 'and rebuild that library (its openapi-generate target writes the file).',\n );\n return undefined;\n }\n\n problem(text: string): void {\n this.problems.push(text);\n }\n\n /** Refuse the boot on any mismatch, naming every problem and every directory searched. */\n assertComplete(): void {\n for (const catalog of this.byContract.values()) {\n if (this.claimed.has(catalog.contractName)) continue;\n this.problem(\n `${catalog.file.fileName} (${catalog.directory}) is the catalog of ${catalog.contractName}, ` +\n `which the server does not bind. Bind it with McpApiBinding.local(${catalog.contractName}, ...) ` +\n 'or .remote(...), or leave it out of toolCatalogs, e.g. ' +\n `catalogs.filter((catalog: McpToolCatalog) => catalog.contractName !== '${catalog.contractName}').`,\n );\n }\n if (this.problems.length === 0) return;\n const directories = [\n ...new Set(this.catalogs.map((catalog: McpToolCatalog) => catalog.directory)),\n ];\n throw new McpToolCatalogError(\n 'The MCP tool catalogs do not match the bound contracts:\\n' +\n this.problems.map((text: string) => ` - ${text}`).join('\\n') +\n '\\nDirectories searched for mcp-<ContractClass>-tools.json:\\n' +\n (directories.length === 0\n ? ' (none — toolCatalogs is empty)'\n : directories.map((dir: string) => ` ${dir}`).join('\\n')) +\n '\\n',\n 'Each bound contract needs exactly the one catalog its api library build writes, and nothing else.',\n );\n }\n}\n\n/** Request-specific authorized projection, shared by documentation, schema lookup and name lookup. */\nexport class AuthorizedMcpTools {\n constructor(public readonly tools: readonly RegisteredMcpTool[]) {}\n\n find(name: string): RegisteredMcpTool | undefined {\n return this.tools.find((tool: RegisteredMcpTool) => tool.name === name);\n }\n\n // webpieces-disable no-function-outside-class -- asynchronous projection factory\n static async create(\n registry: McpToolRegistry,\n policy: AuthorizationService,\n ): Promise<AuthorizedMcpTools> {\n const tools: RegisteredMcpTool[] = [];\n const authorization = new McpToolPolicy(policy);\n for (const tool of registry.tools) {\n if (await authorization.permits(tool.authorization)) tools.push(tool);\n }\n return new AuthorizedMcpTools(tools);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"McpToolRegistry.js","sourceRoot":"","sources":["../../../../../packages/http/mcp-server/src/McpToolRegistry.ts"],"names":[],"mappings":"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","sourcesContent":["import { McpToolPolicy } from './McpToolPolicy';\nimport {\n ApiJsonSchema,\n AuthMeta,\n assertApiTypeMatchesMcpTools,\n getAuthMeta,\n getEndpointKind,\n getEndpointOperation,\n getEndpoints,\n getWpMcpTools,\n getAuthorization,\n AuthorizationRequirement,\n EndpointOperation,\n McpToolCatalogError,\n McpToolCatalogFile,\n McpToolDefinition,\n WpMcpToolHints,\n WpMcpToolMetadata,\n} from '@webpieces/core-util';\nimport { ClassType, AuthorizationService } from '@webpieces/http-routing';\nimport { McpApiBinding } from './McpApiBinding';\nimport { McpToolCatalog } from './McpToolCatalog';\n\n/** Fully resolved contract metadata for one MCP tool. */\nexport class RegisteredMcpTool {\n constructor(\n public readonly apiClass: ClassType,\n public readonly methodName: string,\n public readonly name: string,\n /** The human-readable display name; published as BOTH `Tool.title` and `annotations.title` (#1180). */\n public readonly title: string,\n public readonly description: string,\n public readonly annotations: WpMcpToolHints,\n public readonly operation: EndpointOperation,\n public readonly authMeta: AuthMeta,\n // webpieces-disable no-any-unknown -- custom policy is validated by AuthorizationService\n public readonly authorization: AuthorizationRequirement<unknown>,\n public readonly binding: McpApiBinding,\n public readonly inputSchema: ApiJsonSchema,\n public readonly outputSchema: ApiJsonSchema,\n public readonly profiles: readonly string[],\n ) {}\n}\n\n/**\n * Fail-fast registry: the CONTRACT says which methods are tools and how they are authorized, and the\n * build's generated catalog OF THAT CONTRACT says what each tool's documentation and schemas are.\n *\n * ## Why the schemas come from the catalog and not from here\n *\n * They used to be built at boot by `DtoSchemaBuilder` walking reflect-metadata. That made the\n * document a partner reads and the schema a server accepts two independent derivations of one\n * contract, free to disagree; and it could not see a field's declared TYPE at all, so every fact\n * about a field had to be restated in a `@WpDtoField` argument. #983 measured the compiler against\n * it and found the compiler reproduces all of it, so #984 deleted the decorator and this registry\n * now READS the same catalogs the documents were rendered beside.\n *\n * ## One catalog per bound contract, exactly (#1021)\n *\n * `wp-openapi` writes one `mcp-<ContractClass>-tools.json` per contract, and this registry pairs every\n * `McpApiBinding` with the file of its own contract. Three mismatches refuse the boot, all reported at\n * once, each naming every directory the catalogs were read from:\n *\n * - a bound contract with no catalog — its tools were never given a schema by the build;\n * - a catalog whose contract is not bound — the server was handed tools it does not serve;\n * - a tool name declared twice — the protocol namespace is flat.\n *\n * A registered `@WpMcpTool` the catalog does not name is likewise a HARD FAILURE, not a fallback: a\n * tool the build never saw is a tool whose schema nobody checked.\n */\nexport class McpToolRegistry {\n readonly tools: readonly RegisteredMcpTool[];\n\n constructor(\n bindings: readonly McpApiBinding[],\n catalogs: readonly McpToolCatalog[],\n private readonly authorizationService: AuthorizationService,\n ) {\n const pairing = new McpCatalogPairing(catalogs);\n const registered: RegisteredMcpTool[] = [];\n const names = new Set<string>();\n for (const binding of bindings) {\n const apiClass = binding.api;\n // MCP membership is declared twice — on @ApiType and on the methods — and this is the\n // one call that makes the two disagree impossible. It existed before #984 and nothing\n // reached it, so a contract could carry tools nobody published to agents.\n assertApiTypeMatchesMcpTools(apiClass);\n const declared = getWpMcpTools(apiClass);\n const catalog = pairing.claim(apiClass, declared.length);\n for (const entry of catalog?.file.tools ?? []) {\n if (!declared.some((metadata: WpMcpToolMetadata) => metadata.name === entry.name)) {\n pairing.problem(\n `Stale tool '${entry.name}' in ${catalog!.file.fileName}; rebuild the api library.`,\n );\n }\n }\n for (const metadata of declared) {\n if (names.has(metadata.name)) {\n pairing.problem(\n `Duplicate @WpMcpTool name '${metadata.name}' (again on ${apiClass.name}). Tool ` +\n 'names are the protocol identity and must be globally unique — rename one.',\n );\n continue;\n }\n names.add(metadata.name);\n const tool = this.resolve(apiClass, metadata, binding, catalog, pairing);\n if (tool !== undefined) registered.push(tool);\n }\n }\n pairing.assertComplete();\n this.tools = registered;\n }\n\n find(name: string): RegisteredMcpTool | undefined {\n return this.tools.find((tool: RegisteredMcpTool) => tool.name === name);\n }\n\n /**\n * The contract checks THROW at once — they are about the contract source, not the build. A tool\n * missing from its catalog is RECORDED on `pairing`, so one boot names every catalog mismatch.\n */\n private resolve(\n apiClass: ClassType,\n metadata: WpMcpToolMetadata,\n binding: McpApiBinding,\n catalog: McpToolCatalog | undefined,\n pairing: McpCatalogPairing,\n ): RegisteredMcpTool | undefined {\n const endpoints = getEndpoints(apiClass) ?? {};\n if (!endpoints[metadata.methodName]) {\n throw new Error(\n `@WpMcpTool ${apiClass.name}.${metadata.methodName} must also be an @Endpoint.`,\n );\n }\n if (getEndpointKind(apiClass, metadata.methodName) !== 'rpc') {\n throw new Error(\n `@WpMcpTool ${apiClass.name}.${metadata.methodName} must be an RPC endpoint.`,\n );\n }\n const authMeta = getAuthMeta(apiClass, metadata.methodName);\n if (!authMeta)\n throw new Error(`@WpMcpTool ${apiClass.name}.${metadata.methodName} has no HTTP auth.`);\n const authorization = getAuthorization(apiClass, metadata.methodName);\n if (!authorization) {\n throw new Error(\n `@WpMcpTool ${apiClass.name}.${metadata.methodName} must declare @WpAuthorization(...).`,\n );\n }\n this.authorizationService.validate(authorization);\n binding.validateMethod(metadata.methodName, this.authorizationService);\n if (catalog === undefined) return undefined;\n const published = this.published(apiClass, metadata, catalog, pairing);\n if (published === undefined) return undefined;\n return new RegisteredMcpTool(\n apiClass,\n metadata.methodName,\n metadata.name,\n published.title,\n published.description,\n published.hints,\n getEndpointOperation(apiClass, metadata.methodName),\n authMeta,\n authorization,\n binding,\n published.inputSchema,\n published.outputSchema,\n published.profiles,\n );\n }\n\n /** The generated entry for this tool, or a recorded refusal naming what the build DID publish. */\n private published(\n apiClass: ClassType,\n metadata: WpMcpToolMetadata,\n catalog: McpToolCatalog,\n pairing: McpCatalogPairing,\n ): McpToolDefinition | undefined {\n const published = catalog.find(metadata.name);\n if (published === undefined) {\n pairing.problem(\n `@WpMcpTool ${apiClass.name}.${metadata.methodName} publishes '${metadata.name}', which ` +\n `${catalog.file.fileName} (${catalog.directory}) does not contain. It has: ` +\n `${catalog.names().join(', ') || '(nothing)'}. The catalog is older than the contract — ` +\n 'rebuild the api library; a tool the build never saw is a tool whose schema nobody checked.',\n );\n return undefined;\n }\n if (\n published.methodName !== metadata.methodName ||\n published.profiles.join(',') !== metadata.profiles.join(',')\n ) {\n pairing.problem(\n `MCP tool '${metadata.name}' has stale method/profile metadata in ${catalog.file.fileName}; rebuild the api library.`,\n );\n return undefined;\n }\n if (published.title !== metadata.title) {\n pairing.problem(\n `@WpMcpTool ${apiClass.name}.${metadata.methodName} is titled '${metadata.title}', but ` +\n `${catalog.file.fileName} (${catalog.directory}) publishes '${metadata.name}' as ` +\n `'${published.title}'. The catalog is older than the contract — rebuild the api library.`,\n );\n return undefined;\n }\n return published;\n }\n}\n\n/**\n * Pairs each bound contract with the catalog of exactly that contract, and collects every mismatch so\n * the boot refusal lists them all at once instead of one per restart.\n */\nclass McpCatalogPairing {\n private readonly byContract = new Map<string, McpToolCatalog>();\n private readonly claimed = new Set<string>();\n private readonly problems: string[] = [];\n\n constructor(private readonly catalogs: readonly McpToolCatalog[]) {\n for (const catalog of catalogs) {\n const earlier = this.byContract.get(catalog.contractName);\n if (earlier !== undefined) {\n this.problem(\n `Two MCP tool catalogs for ${catalog.contractName}: ${earlier.directory} and ` +\n `${catalog.directory}. Name each api library in fromPackages(...) once, and bind ` +\n 'each contract from exactly one of them.',\n );\n continue;\n }\n this.byContract.set(catalog.contractName, catalog);\n }\n }\n\n /** The catalog of `apiClass`, recording a problem when there is none. */\n claim(apiClass: ClassType, declaredTools: number): McpToolCatalog | undefined {\n const catalog = this.byContract.get(apiClass.name);\n this.claimed.add(apiClass.name);\n if (catalog !== undefined) return catalog;\n this.problem(\n declaredTools === 0\n ? `The server binds ${apiClass.name}, which declares no @WpMcpTool method, so the build ` +\n `wrote no ${McpToolCatalogFile.fileNameFor(apiClass.name)} for it. Delete its ` +\n 'McpApiBinding — it publishes nothing.'\n : `The server binds ${apiClass.name}, and no ${McpToolCatalogFile.fileNameFor(apiClass.name)} ` +\n 'was handed to it. Add the api library that declares it to McpToolCatalog.fromPackages([...]) ' +\n 'and rebuild that library (its openapi-generate target writes the file).',\n );\n return undefined;\n }\n\n problem(text: string): void {\n this.problems.push(text);\n }\n\n /** Refuse the boot on any mismatch, naming every problem and every directory searched. */\n assertComplete(): void {\n for (const catalog of this.byContract.values()) {\n if (this.claimed.has(catalog.contractName)) continue;\n this.problem(\n `${catalog.file.fileName} (${catalog.directory}) is the catalog of ${catalog.contractName}, ` +\n `which the server does not bind. Bind it with McpApiBinding.local(${catalog.contractName}, ...) ` +\n 'or .remote(...), or leave it out of toolCatalogs, e.g. ' +\n `catalogs.filter((catalog: McpToolCatalog) => catalog.contractName !== '${catalog.contractName}').`,\n );\n }\n if (this.problems.length === 0) return;\n const directories = [\n ...new Set(this.catalogs.map((catalog: McpToolCatalog) => catalog.directory)),\n ];\n throw new McpToolCatalogError(\n 'The MCP tool catalogs do not match the bound contracts:\\n' +\n this.problems.map((text: string) => ` - ${text}`).join('\\n') +\n '\\nDirectories searched for mcp-<ContractClass>-tools.json:\\n' +\n (directories.length === 0\n ? ' (none — toolCatalogs is empty)'\n : directories.map((dir: string) => ` ${dir}`).join('\\n')) +\n '\\n',\n 'Each bound contract needs exactly the one catalog its api library build writes, and nothing else.',\n );\n }\n}\n\n/** Request-specific authorized projection, shared by documentation, schema lookup and name lookup. */\nexport class AuthorizedMcpTools {\n constructor(public readonly tools: readonly RegisteredMcpTool[]) {}\n\n find(name: string): RegisteredMcpTool | undefined {\n return this.tools.find((tool: RegisteredMcpTool) => tool.name === name);\n }\n\n // webpieces-disable no-function-outside-class -- asynchronous projection factory\n static async create(\n registry: McpToolRegistry,\n policy: AuthorizationService,\n profiles: readonly string[],\n ): Promise<AuthorizedMcpTools> {\n const tools: RegisteredMcpTool[] = [];\n const authorization = new McpToolPolicy(policy);\n for (const tool of [...registry.tools].sort((a: RegisteredMcpTool, b: RegisteredMcpTool) =>\n a.name.localeCompare(b.name),\n )) {\n if (!tool.profiles.some((profile: string) => profiles.includes(profile))) continue;\n if (await authorization.permits(tool.authorization)) tools.push(tool);\n }\n return new AuthorizedMcpTools(tools);\n }\n}\n"]}
|
package/src/WpMcpServer.d.ts
CHANGED
|
@@ -39,6 +39,7 @@ export declare class WpMcpServer<TGrant> {
|
|
|
39
39
|
/** tools/call reads `McpRegistry`; every other reply stays framework-owned. See McpErrorTranslator. */
|
|
40
40
|
private readonly translator;
|
|
41
41
|
private registry?;
|
|
42
|
+
private toolProfiles;
|
|
42
43
|
private handler?;
|
|
43
44
|
private streamingHandler?;
|
|
44
45
|
private nodeHandler?;
|
package/src/WpMcpServer.js
CHANGED
|
@@ -124,6 +124,7 @@ class WpMcpServer {
|
|
|
124
124
|
/** tools/call reads `McpRegistry`; every other reply stays framework-owned. See McpErrorTranslator. */
|
|
125
125
|
translator;
|
|
126
126
|
registry;
|
|
127
|
+
toolProfiles = [];
|
|
127
128
|
handler;
|
|
128
129
|
streamingHandler;
|
|
129
130
|
nodeHandler;
|
|
@@ -161,6 +162,7 @@ class WpMcpServer {
|
|
|
161
162
|
`'${options.endpointPath}'; the canonical resource URI ` +
|
|
162
163
|
`'${this.config.resource}' and the bound route are the same endpoint.`);
|
|
163
164
|
}
|
|
165
|
+
this.toolProfiles = this.config.toolProfiles;
|
|
164
166
|
this.registry = new McpToolRegistry_1.McpToolRegistry(options.bindings, options.toolCatalogs, this.config.authorizationService);
|
|
165
167
|
this.revision = this.calculateRegistryRevision(this.registry);
|
|
166
168
|
this.handler = this.createHandler(options, 'auto');
|
|
@@ -280,7 +282,7 @@ class WpMcpServer {
|
|
|
280
282
|
this.callerContext.publish(credential.caller);
|
|
281
283
|
core_context_1.RequestContext.putTrusted(core_util_1.WebpiecesCoreHeaders.SURFACE, 'llm');
|
|
282
284
|
this.callerContext.reconcileWireTrust(false);
|
|
283
|
-
const tools = await McpToolRegistry_1.AuthorizedMcpTools.create(this.requireRegistry(), this.config.authorizationService);
|
|
285
|
+
const tools = await McpToolRegistry_1.AuthorizedMcpTools.create(this.requireRegistry(), this.config.authorizationService, this.toolProfiles);
|
|
284
286
|
return new McpPostAuthentication(token, credential, this.disconnectSignal(req), tools);
|
|
285
287
|
}
|
|
286
288
|
disconnectSignal(req) {
|
|
@@ -508,8 +510,9 @@ class WpMcpServer {
|
|
|
508
510
|
tool.inputSchema,
|
|
509
511
|
tool.outputSchema,
|
|
510
512
|
tool.annotations,
|
|
513
|
+
tool.profiles,
|
|
511
514
|
]);
|
|
512
|
-
return (0, node_crypto_1.createHash)('sha256').update(JSON.stringify(surface)).digest('hex').slice(0, 12);
|
|
515
|
+
return (0, node_crypto_1.createHash)('sha256').update(JSON.stringify([this.toolProfiles, surface])).digest('hex').slice(0, 12);
|
|
513
516
|
}
|
|
514
517
|
requireRegistry() {
|
|
515
518
|
if (!this.registry)
|