@webpieces/mcp-server 0.4.934 → 0.4.936

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 CHANGED
@@ -24,31 +24,42 @@ interface FindOrderResponse {
24
24
  @ApiPath('/orders')
25
25
  @ApiType(SVC_TO_SVC, MCP)
26
26
  abstract class OrdersApi {
27
- /** Find one order owned by the signed-in user. */
27
+ /**
28
+ * Find one order owned by the signed-in user.
29
+ *
30
+ * @summary Find orders
31
+ * @returns The order, with its current state.
32
+ */
28
33
  @WpAuth([jwt()])
29
34
  @WpAuthorization({ authType: AuthorizationType.ALL_USERS })
30
35
  @Endpoint(POST, '/find', READ, RPC)
31
- @WpMcpTool('orders_find', 'Find orders')
36
+ @WpMcpTool('orders_find')
32
37
  find(request: FindOrderRequest): Promise<FindOrderResponse> {
33
38
  throw new Error('contract only');
34
39
  }
35
40
  }
36
41
  ```
37
42
 
38
- ## Everything but the tool NAME and TITLE comes from the source
43
+ ## Everything but the tool NAME comes from the source
39
44
 
40
- `@WpMcpTool(name, title)` takes two REQUIRED arguments, because they are the only facts the source
41
- cannot state: the stable protocol name, and the human-readable title clients and the Claude Connectors
42
- Directory display. `tools/list` publishes the title as BOTH the top-level `Tool.title` and
43
- `annotations.title` (#1180). An empty or whitespace title throws at decoration time, the one-argument
44
- spelling does not compile, and a catalog entry with no title is refused when it is loaded — so a catalog
45
- generated before titles existed fails the boot instead of publishing untitled tools. Write the title a
46
- person would read ("Find orders"); never derive it from the name. Everything else is read off the
47
- contract by `@webpieces/api-doc-model` at BUILD time:
45
+ `@WpMcpTool(name)` takes ONE argument (plus the optional profiles object), because the stable protocol
46
+ name is the only fact the source cannot state. It must be 1-64 characters of `[A-Za-z0-9_-]` — the
47
+ Claude Connectors Directory refuses a server with a longer name — and `McpToolName` enforces that rule
48
+ at decoration time, in `wp-openapi` (every offender named in one run), in `api-rules-for-mcp`, and when a
49
+ catalog is loaded.
50
+
51
+ The TITLE is the method's `@summary` JSDoc tag (#1296): one line, at most 100 characters, REQUIRED on
52
+ every published operation. It is the same line the OpenAPI documents publish as the operation's
53
+ `summary`, so a partner's docs page and an agent's tool list name the endpoint identically, and
54
+ `tools/list` publishes it as BOTH the top-level `Tool.title` and `annotations.title` (#1180). The title
55
+ argument the decorator used to take is gone — passing one is a compile error — and a catalog entry
56
+ with no title is refused when it is loaded, so a stale catalog fails the boot instead of publishing
57
+ untitled tools. Write the title a person would read ("Find orders"); never derive it from the name.
58
+ Everything else is read off the contract by `@webpieces/api-doc-model` at BUILD time:
48
59
 
49
60
  | fact | source |
50
61
  |---|---|
51
- | `title` / `annotations.title` | `@WpMcpTool`'s second argument |
62
+ | `title` / `annotations.title` | the method's `@summary` JSDoc tag — the OpenAPI operation's `summary` |
52
63
  | `description` | the method's JSDoc body, or its `@mcp` tag — the same words the partner reads in OpenAPI |
53
64
  | `inputSchema` / `outputSchema` | the declared request and response types, with each field's JSDoc as its description; `inputSchema.description` is the request DTO's JSDoc and `outputSchema.description` is the method's `@returns` — the sentence OpenAPI publishes as the success response's description (#1292) |
54
65
  | `readOnlyHint` / `destructiveHint` / `idempotentHint` | `@Endpoint`'s `operation` — `READ`, `WRITE_IDEMPOTENT` or `WRITE` |
@@ -117,6 +128,9 @@ change. Expect to supply the type arguments explicitly:
117
128
  ```ts
118
129
  const config = new WpMcpServerConfig<MyGrant>()
119
130
  .setName('my-server')
131
+ .setTitle('My Product')
132
+ .setDescription('Search and manage My Product accounts from any MCP client.')
133
+ .setWebsiteUrl('https://www.example.com/my-product')
120
134
  .setVersion('1.0.0')
121
135
  .setResource('https://api.example.com/mcp')
122
136
  .setAccessTokenAuthority(authority)
@@ -131,9 +145,26 @@ McpRegistry.setErrorTranslator(new MyMcpErrorTranslator());
131
145
 
132
146
  Each setter validates its own value immediately and names itself in the failure: `setResource(...)`
133
147
  and every entry of `setAuthorizationServers(...)` must be an absolute URL, because they are compared
134
- against a token's audience and issuer. `setMaxAccountValidationAgeSeconds` and
135
- `setMaxEndpointJwtLifetimeSeconds` default to one hour and are capped there.
136
- `setErrorTranslator(...)` is the only optional non-ceiling setting; everything else is required and
148
+ against a token's audience and issuer. `setMaxAccountValidationAgeSeconds` defaults to one hour and is capped there.
149
+
150
+ `setTitle`, `setDescription` and `setWebsiteUrl` are REQUIRED and are sent on `initialize`'s
151
+ `serverInfo` (and on every modern-era result's `io.modelcontextprotocol/serverInfo` meta) as
152
+ `title`, `description` and `websiteUrl`:
153
+
154
+ | setter | `serverInfo` field | validation |
155
+ |---|---|---|
156
+ | `setTitle(title)` | `title` (MCP 2025-06-18) | one line, at most 100 characters |
157
+ | `setDescription(description)` | `description` (MCP 2025-11-25) | non-empty, at most 2000 characters |
158
+ | `setWebsiteUrl(url)` | `websiteUrl` (MCP 2025-11-25) | absolute `https://` URL |
159
+
160
+ The limits are the Claude Connectors Directory's listing Name and Description limits. The app sets all
161
+ three because the server cannot derive them: it does not read the OpenAPI manifest at runtime, and one
162
+ server may bind contracts from several api libraries. `websiteUrl` is a page a human reads (the product
163
+ homepage or docs site), so it is never derived from `setResource` — that is the MCP endpoint, not a
164
+ website. There are no defaults; a missing one fails `bind(...)` naming the setter, like `setName`.
165
+
166
+ `setIcons(...)` and `setToolProfiles(...)` are the only optional non-ceiling settings (the error
167
+ translator is process-global, on `McpRegistry`); everything else is required and
137
168
  `WpMcpServer.bind(...)` fails at startup listing **every** missing setter
138
169
  (`WpMcpServerConfig is missing setResource(...)`). That is deliberate: a misconfigured audience or
139
170
  issuer otherwise surfaces as a first-request `401 + WWW-Authenticate`, which is the OAuth discovery
@@ -413,13 +444,14 @@ Profile membership partitions tool exposure; account roles still belong exclusiv
413
444
  `WpMcpTool` from the browser-safe `@webpieces/core-util` package:
414
445
 
415
446
  ```ts
416
- @WpMcpTool('learner_get_passages', 'Get your passages') // only Mcp.DEFAULT
417
- @WpMcpTool('admin_list_users', 'List users', { profiles: ['admin'] }) // only admin
418
- @WpMcpTool('shared_tool', 'Shared tool', { profiles: [Mcp.DEFAULT, 'admin'] })
447
+ @WpMcpTool('learner_get_passages') // only Mcp.DEFAULT
448
+ @WpMcpTool('admin_list_users', { profiles: ['admin'] }) // only admin
449
+ @WpMcpTool('shared_tool', { profiles: [Mcp.DEFAULT, 'admin'] })
419
450
  ```
420
451
 
421
- Each declaration also needs the usual endpoint, DTO, authentication, and authorization declarations.
422
- Two arguments, an omitted `profiles` property, and explicit `[Mcp.DEFAULT]` all select only the base
452
+ Each declaration also needs the usual endpoint, DTO, authentication, and authorization declarations,
453
+ and a `@summary` line in the method's JSDoc for its title.
454
+ One argument, an omitted `profiles` property, and explicit `[Mcp.DEFAULT]` all select only the base
423
455
  group. An explicit list replaces that membership; it never implicitly adds the base group.
424
456
  Identifiers match `[a-z][a-z0-9-]{0,63}` (1–64 lowercase ASCII letters, digits or hyphens, starting
425
457
  with a letter). `Mcp.DEFAULT` reserves `default` for the base group. Empty lists, duplicates, unknown
@@ -460,6 +492,9 @@ import { McpIcon, WpMcpServerConfig } from '@webpieces/mcp-server';
460
492
 
461
493
  const config = new WpMcpServerConfig<MyGrant>()
462
494
  .setName('my-product')
495
+ .setTitle('My Product')
496
+ .setDescription('Search and manage My Product accounts from any MCP client.')
497
+ .setWebsiteUrl('https://www.example.com/my-product')
463
498
  .setVersion('1.0.0')
464
499
  .setResource('https://api.example.com/mcp')
465
500
  .setIcons([
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpieces/mcp-server",
3
- "version": "0.4.934",
3
+ "version": "0.4.936",
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.934",
28
- "@webpieces/core-util": "0.4.934",
29
- "@webpieces/http-routing": "0.4.934",
30
- "@webpieces/http-server": "0.4.934",
27
+ "@webpieces/core-context": "0.4.936",
28
+ "@webpieces/core-util": "0.4.936",
29
+ "@webpieces/http-routing": "0.4.936",
30
+ "@webpieces/http-server": "0.4.936",
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.934"
36
+ "@webpieces/http-client-node": "0.4.936"
37
37
  }
38
38
  }
package/src/McpAuth.d.ts CHANGED
@@ -4,6 +4,10 @@ import { Icon } from '@modelcontextprotocol/server';
4
4
  import { McpIcon } from './McpIcon';
5
5
  export declare const MAX_MCP_ACCESS_TOKEN_LIFETIME_SECONDS: number;
6
6
  export declare const MAX_MCP_ACCOUNT_VALIDATION_AGE_SECONDS: number;
7
+ /** `setTitle` ceiling: the Claude Connectors Directory's listing Name limit. */
8
+ export declare const MAX_MCP_SERVER_TITLE_LENGTH = 100;
9
+ /** `setDescription` ceiling: the Claude Connectors Directory's listing Description limit. */
10
+ export declare const MAX_MCP_SERVER_DESCRIPTION_LENGTH = 2000;
7
11
  /** Framework-normalized access-token result. The token representation remains application-owned. */
8
12
  export declare class MintedMcpAccessToken {
9
13
  readonly token: string;
@@ -73,6 +77,9 @@ export declare class McpProtectedResourceMetadata {
73
77
  * ```ts
74
78
  * const config = new WpMcpServerConfig<MyGrant>()
75
79
  * .setName('my-server')
80
+ * .setTitle('My Product')
81
+ * .setDescription('Search and manage My Product accounts from any MCP client.')
82
+ * .setWebsiteUrl('https://www.example.com/my-product')
76
83
  * .setVersion('1.0.0')
77
84
  * .setResource('https://api.example.com/mcp')
78
85
  * .setAccessTokenAuthority(authority)
@@ -87,6 +94,9 @@ export declare class McpProtectedResourceMetadata {
87
94
  export declare class WpMcpServerConfig<TGrant> {
88
95
  private toolProfilesValue;
89
96
  private nameValue?;
97
+ private titleValue?;
98
+ private descriptionValue?;
99
+ private websiteUrlValue?;
90
100
  private versionValue?;
91
101
  private iconsValue?;
92
102
  private resourceValue?;
@@ -100,6 +110,29 @@ export declare class WpMcpServerConfig<TGrant> {
100
110
  get toolProfiles(): readonly string[];
101
111
  /** REQUIRED. The MCP server name reported by `initialize`. */
102
112
  setName(name: string): this;
113
+ /**
114
+ * REQUIRED. The human-readable display name sent as `serverInfo.title` (MCP 2025-06-18), e.g.
115
+ * `'Lang Learning'` where {@link setName} is the programmatic `'lang-learning'`. One line, at most
116
+ * 100 characters — the Claude Connectors Directory's listing Name limit.
117
+ *
118
+ * The app sets it because the server cannot derive it: it does not read the OpenAPI manifest at
119
+ * runtime, and one MCP server may bind contracts from several api libraries.
120
+ */
121
+ setTitle(title: string): this;
122
+ /**
123
+ * REQUIRED. What this server does, sent as `serverInfo.description` (MCP 2025-11-25). At most
124
+ * 2000 characters — the Claude Connectors Directory's listing Description limit.
125
+ */
126
+ setDescription(description: string): this;
127
+ /**
128
+ * REQUIRED. An absolute `https://` URL of a page a HUMAN reads about this server — the product
129
+ * homepage or its docs site — sent as `serverInfo.websiteUrl` (MCP 2025-11-25).
130
+ *
131
+ * Deliberately NOT derived from {@link setResource}: that is the MCP endpoint, an API host, not a
132
+ * website, and the resource is never derived from request input either. No default, because the
133
+ * page a user is sent to is the app's decision.
134
+ */
135
+ setWebsiteUrl(websiteUrl: string): this;
103
136
  /** REQUIRED. The MCP server version; the registry revision is appended to it at bind time. */
104
137
  setVersion(version: string): this;
105
138
  /** OPTIONAL. Standard MCP icons; copied and frozen, with no network access. */
@@ -143,6 +176,9 @@ export declare class WpMcpServerConfig<TGrant> {
143
176
  */
144
177
  validate(): void;
145
178
  get name(): string;
179
+ get title(): string;
180
+ get description(): string;
181
+ get websiteUrl(): string;
146
182
  get version(): string;
147
183
  get resource(): string;
148
184
  get accessTokenAuthority(): McpAccessTokenAuthority<TGrant>;
@@ -165,6 +201,7 @@ export declare class WpMcpServerConfig<TGrant> {
165
201
  private snapshotIcons;
166
202
  private requireText;
167
203
  private requireUrl;
204
+ private requireMaxLength;
168
205
  private requireArray;
169
206
  private requireSeconds;
170
207
  private requirePresent;
package/src/McpAuth.js CHANGED
@@ -1,10 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
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;
3
+ exports.WpMcpServerConfig = exports.McpProtectedResourceMetadata = exports.VerifiedMcpCredential = exports.MintedMcpAccessToken = exports.MAX_MCP_SERVER_DESCRIPTION_LENGTH = exports.MAX_MCP_SERVER_TITLE_LENGTH = exports.MAX_MCP_ACCOUNT_VALIDATION_AGE_SECONDS = exports.MAX_MCP_ACCESS_TOKEN_LIFETIME_SECONDS = void 0;
4
4
  const core_util_1 = require("@webpieces/core-util");
5
5
  const McpIcon_1 = require("./McpIcon");
6
6
  exports.MAX_MCP_ACCESS_TOKEN_LIFETIME_SECONDS = 30 * 24 * 60 * 60;
7
7
  exports.MAX_MCP_ACCOUNT_VALIDATION_AGE_SECONDS = 60 * 60;
8
+ /** `setTitle` ceiling: the Claude Connectors Directory's listing Name limit. */
9
+ exports.MAX_MCP_SERVER_TITLE_LENGTH = 100;
10
+ /** `setDescription` ceiling: the Claude Connectors Directory's listing Description limit. */
11
+ exports.MAX_MCP_SERVER_DESCRIPTION_LENGTH = 2000;
8
12
  /** Framework-normalized access-token result. The token representation remains application-owned. */
9
13
  class MintedMcpAccessToken {
10
14
  token;
@@ -98,6 +102,9 @@ exports.McpProtectedResourceMetadata = McpProtectedResourceMetadata;
98
102
  * ```ts
99
103
  * const config = new WpMcpServerConfig<MyGrant>()
100
104
  * .setName('my-server')
105
+ * .setTitle('My Product')
106
+ * .setDescription('Search and manage My Product accounts from any MCP client.')
107
+ * .setWebsiteUrl('https://www.example.com/my-product')
101
108
  * .setVersion('1.0.0')
102
109
  * .setResource('https://api.example.com/mcp')
103
110
  * .setAccessTokenAuthority(authority)
@@ -112,6 +119,9 @@ exports.McpProtectedResourceMetadata = McpProtectedResourceMetadata;
112
119
  class WpMcpServerConfig {
113
120
  toolProfilesValue = Object.freeze([core_util_1.Mcp.DEFAULT]);
114
121
  nameValue;
122
+ titleValue;
123
+ descriptionValue;
124
+ websiteUrlValue;
115
125
  versionValue;
116
126
  iconsValue;
117
127
  resourceValue;
@@ -133,6 +143,47 @@ class WpMcpServerConfig {
133
143
  this.nameValue = this.requireText(name, 'setName');
134
144
  return this;
135
145
  }
146
+ /**
147
+ * REQUIRED. The human-readable display name sent as `serverInfo.title` (MCP 2025-06-18), e.g.
148
+ * `'Lang Learning'` where {@link setName} is the programmatic `'lang-learning'`. One line, at most
149
+ * 100 characters — the Claude Connectors Directory's listing Name limit.
150
+ *
151
+ * The app sets it because the server cannot derive it: it does not read the OpenAPI manifest at
152
+ * runtime, and one MCP server may bind contracts from several api libraries.
153
+ */
154
+ setTitle(title) {
155
+ const value = this.requireText(title, 'setTitle');
156
+ if (/[\r\n]/.test(value)) {
157
+ throw new Error('WpMcpServerConfig.setTitle(...) requires a single line of text.');
158
+ }
159
+ this.titleValue = this.requireMaxLength(value, exports.MAX_MCP_SERVER_TITLE_LENGTH, 'setTitle');
160
+ return this;
161
+ }
162
+ /**
163
+ * REQUIRED. What this server does, sent as `serverInfo.description` (MCP 2025-11-25). At most
164
+ * 2000 characters — the Claude Connectors Directory's listing Description limit.
165
+ */
166
+ setDescription(description) {
167
+ const value = this.requireText(description, 'setDescription');
168
+ this.descriptionValue = this.requireMaxLength(value, exports.MAX_MCP_SERVER_DESCRIPTION_LENGTH, 'setDescription');
169
+ return this;
170
+ }
171
+ /**
172
+ * REQUIRED. An absolute `https://` URL of a page a HUMAN reads about this server — the product
173
+ * homepage or its docs site — sent as `serverInfo.websiteUrl` (MCP 2025-11-25).
174
+ *
175
+ * Deliberately NOT derived from {@link setResource}: that is the MCP endpoint, an API host, not a
176
+ * website, and the resource is never derived from request input either. No default, because the
177
+ * page a user is sent to is the app's decision.
178
+ */
179
+ setWebsiteUrl(websiteUrl) {
180
+ const value = this.requireUrl(websiteUrl, 'setWebsiteUrl');
181
+ if (new URL(value).protocol !== 'https:') {
182
+ throw new Error(`WpMcpServerConfig.setWebsiteUrl(...) requires an https:// URL, got '${value}'.`);
183
+ }
184
+ this.websiteUrlValue = value;
185
+ return this;
186
+ }
136
187
  /** REQUIRED. The MCP server version; the registry revision is appended to it at bind time. */
137
188
  setVersion(version) {
138
189
  this.versionValue = this.requireText(version, 'setVersion');
@@ -213,6 +264,12 @@ class WpMcpServerConfig {
213
264
  const missing = [];
214
265
  if (this.nameValue === undefined)
215
266
  missing.push('setName(...)');
267
+ if (this.titleValue === undefined)
268
+ missing.push('setTitle(...)');
269
+ if (this.descriptionValue === undefined)
270
+ missing.push('setDescription(...)');
271
+ if (this.websiteUrlValue === undefined)
272
+ missing.push('setWebsiteUrl(...)');
216
273
  if (this.versionValue === undefined)
217
274
  missing.push('setVersion(...)');
218
275
  if (this.resourceValue === undefined)
@@ -241,6 +298,15 @@ class WpMcpServerConfig {
241
298
  get name() {
242
299
  return this.read(this.nameValue, 'setName');
243
300
  }
301
+ get title() {
302
+ return this.read(this.titleValue, 'setTitle');
303
+ }
304
+ get description() {
305
+ return this.read(this.descriptionValue, 'setDescription');
306
+ }
307
+ get websiteUrl() {
308
+ return this.read(this.websiteUrlValue, 'setWebsiteUrl');
309
+ }
244
310
  get version() {
245
311
  return this.read(this.versionValue, 'setVersion');
246
312
  }
@@ -304,6 +370,12 @@ class WpMcpServerConfig {
304
370
  }
305
371
  return value;
306
372
  }
373
+ requireMaxLength(value, max, setter) {
374
+ if (value.length > max) {
375
+ throw new Error(`WpMcpServerConfig.${setter}(...) allows at most ${max} characters, got ${value.length}.`);
376
+ }
377
+ return value;
378
+ }
307
379
  requireArray(value, setter) {
308
380
  if (!Array.isArray(value)) {
309
381
  throw new Error(`WpMcpServerConfig.${setter}(...) requires an array of strings.`);
@@ -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 { Mcp, McpToolProfiles } from '@webpieces/core-util';\nimport { AuthenticatedCaller, AuthorizationService } from '@webpieces/http-routing';\nimport { Icon } from '@modelcontextprotocol/server';\nimport { McpIcon } from './McpIcon';\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 iconsValue?: readonly McpIcon[];\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 /** OPTIONAL. Standard MCP icons; copied and frozen, with no network access. */\n setIcons(icons: readonly Icon[]): this {\n this.iconsValue = this.snapshotIcons(icons);\n return this;\n }\n\n /** Immutable snapshot; omitted icons remain absent from SDK implementation metadata. */\n get icons(): readonly McpIcon[] | undefined {\n return this.iconsValue;\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 for (const icon of this.iconsValue ?? []) {\n const url = new URL(icon.src);\n if (url.protocol === 'https:' && url.origin !== new URL(this.resource).origin) {\n throw new Error(\n 'WpMcpServerConfig.setIcons(...) requires HTTPS icons on the resource origin.',\n );\n }\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 /** Copies caller-owned descriptors and nested size arrays before freezing the snapshot. */\n private snapshotIcons(icons: readonly Icon[]): readonly McpIcon[] {\n if (!Array.isArray(icons)) {\n throw new Error(\n 'WpMcpServerConfig.setIcons(...) requires an array of icon descriptors.',\n );\n }\n return Object.freeze(\n icons.map((icon: Icon) => {\n if (!icon || typeof icon !== 'object') {\n throw new Error('WpMcpServerConfig.setIcons(...) requires icon descriptors.');\n }\n return new McpIcon(icon.src, icon.mimeType, icon.sizes, icon.theme);\n }),\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":";;;AAAA,oDAA4D;AAG5D,uCAAoC;AAEvB,QAAA,qCAAqC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;AAC1D,QAAA,sCAAsC,GAAG,EAAE,GAAG,EAAE,CAAC;AAC9D,gFAAgF;AACnE,QAAA,2BAA2B,GAAG,GAAG,CAAC;AAC/C,6FAA6F;AAChF,QAAA,iCAAiC,GAAG,IAAI,CAAC;AAEtD,oGAAoG;AACpG,MAAa,oBAAoB;IAET;IACA;IACA;IACA;IAJpB,YACoB,KAAa,EACb,QAAgB,EAChB,oBAA4B,EAC5B,qBAA6B;QAH7B,UAAK,GAAL,KAAK,CAAQ;QACb,aAAQ,GAAR,QAAQ,CAAQ;QAChB,yBAAoB,GAApB,oBAAoB,CAAQ;QAC5B,0BAAqB,GAArB,qBAAqB,CAAQ;QAE7C,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,QAAQ,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;YAChD,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAC;QAC/E,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,oBAAoB,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,qBAAqB,CAAC,EAAE,CAAC;YACpF,MAAM,IAAI,KAAK,CACX,wEAAwE,CAC3E,CAAC;QACN,CAAC;QACD,IAAI,qBAAqB,IAAI,oBAAoB,EAAE,CAAC;YAChD,MAAM,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAC;QACvE,CAAC;QACD,IAAI,qBAAqB,GAAG,oBAAoB,GAAG,6CAAqC,EAAE,CAAC;YACvF,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAC;QAC1E,CAAC;IACL,CAAC;CACJ;AAtBD,oDAsBC;AAED;;;;GAIG;AACH,MAAa,qBAAqB;IAEV;IACA;IACA;IACA;IACA;IACA;IAEA;IAEA;IAVpB,YACoB,OAAe,EACf,MAAc,EACd,QAAgB,EAChB,oBAA4B,EAC5B,qBAA6B,EAC7B,MAAyB;IACzC,4FAA4F;IAC5E,8BAAsC;IACtD,4FAA4F;IAC5E,MAA2B;QAT3B,YAAO,GAAP,OAAO,CAAQ;QACf,WAAM,GAAN,MAAM,CAAQ;QACd,aAAQ,GAAR,QAAQ,CAAQ;QAChB,yBAAoB,GAApB,oBAAoB,CAAQ;QAC5B,0BAAqB,GAArB,qBAAqB,CAAQ;QAC7B,WAAM,GAAN,MAAM,CAAmB;QAEzB,mCAA8B,GAA9B,8BAA8B,CAAQ;QAEtC,WAAM,GAAN,MAAM,CAAqB;IAC5C,CAAC;IAEJ,wFAAwF;IACxF,IAAI,cAAc;QACd,OAAO,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC;IAC7B,CAAC;CACJ;AAlBD,sDAkBC;AAeD,qEAAqE;AACrE,MAAa,4BAA4B;IAC5B,QAAQ,CAAS;IACjB,qBAAqB,CAAoB;IACzC,wBAAwB,GAAG,CAAC,QAAQ,CAAC,CAAC;IACtC,gBAAgB,CAAqB;IAE9C,YACI,QAAgB,EAChB,oBAAuC,EACvC,eAAmC;QAEnC,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,qBAAqB,GAAG,oBAAoB,CAAC;QAClD,IAAI,CAAC,gBAAgB,GAAG,eAAe,CAAC;IAC5C,CAAC;CACJ;AAfD,oEAeC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,MAAa,iBAAiB;IAClB,iBAAiB,GAAsB,MAAM,CAAC,MAAM,CAAC,CAAC,eAAG,CAAC,OAAO,CAAC,CAAC,CAAC;IACpE,SAAS,CAAU;IACnB,UAAU,CAAU;IACpB,gBAAgB,CAAU;IAC1B,eAAe,CAAU;IACzB,YAAY,CAAU;IACtB,UAAU,CAAsB;IAChC,aAAa,CAAU;IACvB,yBAAyB,CAAmC;IAC5D,yBAAyB,CAAwB;IACjD,yBAAyB,CAAqB;IAC9C,mBAAmB,CAAqB;IACxC,mCAAmC,GAAG,8CAAsC,CAAC;IAErF,qFAAqF;IACrF,eAAe,CAAC,QAAyB;QACrC,IAAI,CAAC,iBAAiB,GAAG,eAAG,CAAC,QAAQ,CAAC,QAAQ,EAAE,mCAAmC,CAAC,CAAC;QACrF,OAAO,IAAI,CAAC;IAChB,CAAC;IAED,IAAI,YAAY;QACZ,OAAO,IAAI,CAAC,iBAAiB,CAAC;IAClC,CAAC;IAED,8DAA8D;IAC9D,OAAO,CAAC,IAAY;QAChB,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;QACnD,OAAO,IAAI,CAAC;IAChB,CAAC;IAED;;;;;;;OAOG;IACH,QAAQ,CAAC,KAAa;QAClB,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC;QAClD,IAAI,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YACvB,MAAM,IAAI,KAAK,CAAC,iEAAiE,CAAC,CAAC;QACvF,CAAC;QACD,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,gBAAgB,CAAC,KAAK,EAAE,mCAA2B,EAAE,UAAU,CAAC,CAAC;QACxF,OAAO,IAAI,CAAC;IAChB,CAAC;IAED;;;OAGG;IACH,cAAc,CAAC,WAAmB;QAC9B,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,CAAC,WAAW,EAAE,gBAAgB,CAAC,CAAC;QAC9D,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC,gBAAgB,CACzC,KAAK,EACL,yCAAiC,EACjC,gBAAgB,CACnB,CAAC;QACF,OAAO,IAAI,CAAC;IAChB,CAAC;IAED;;;;;;;OAOG;IACH,aAAa,CAAC,UAAkB;QAC5B,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC;QAC3D,IAAI,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;YACvC,MAAM,IAAI,KAAK,CACX,uEAAuE,KAAK,IAAI,CACnF,CAAC;QACN,CAAC;QACD,IAAI,CAAC,eAAe,GAAG,KAAK,CAAC;QAC7B,OAAO,IAAI,CAAC;IAChB,CAAC;IAED,8FAA8F;IAC9F,UAAU,CAAC,OAAe;QACtB,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;QAC5D,OAAO,IAAI,CAAC;IAChB,CAAC;IAED,+EAA+E;IAC/E,QAAQ,CAAC,KAAsB;QAC3B,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;QAC5C,OAAO,IAAI,CAAC;IAChB,CAAC;IAED,wFAAwF;IACxF,IAAI,KAAK;QACL,OAAO,IAAI,CAAC,UAAU,CAAC;IAC3B,CAAC;IAED;;;;;;;;;;;;OAYG;IACH,WAAW,CAAC,QAAgB;QACxB,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,UAAU,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC;QAC9D,OAAO,IAAI,CAAC;IAChB,CAAC;IAED,sFAAsF;IACtF,uBAAuB,CAAC,SAA0C;QAC9D,IAAI,CAAC,yBAAyB,GAAG,IAAI,CAAC,cAAc,CAAC,SAAS,EAAE,yBAAyB,CAAC,CAAC;QAC3F,OAAO,IAAI,CAAC;IAChB,CAAC;IAED,qFAAqF;IACrF,uBAAuB,CAAC,SAA+B;QACnD,IAAI,CAAC,yBAAyB,GAAG,IAAI,CAAC,cAAc,CAAC,SAAS,EAAE,yBAAyB,CAAC,CAAC;QAC3F,OAAO,IAAI,CAAC;IAChB,CAAC;IAED;;;;OAIG;IACH,uBAAuB,CAAC,oBAAuC;QAC3D,MAAM,OAAO,GAAG,IAAI,CAAC,YAAY,CAAC,oBAAoB,EAAE,yBAAyB,CAAC,CAAC;QACnF,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvB,MAAM,IAAI,KAAK,CACX,kFAAkF,CACrF,CAAC;QACN,CAAC;QACD,KAAK,MAAM,MAAM,IAAI,OAAO;YAAE,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,yBAAyB,CAAC,CAAC;QACjF,IAAI,CAAC,yBAAyB,GAAG,OAAO,CAAC;QACzC,OAAO,IAAI,CAAC;IAChB,CAAC;IAED;;;OAGG;IACH,iBAAiB,CAAC,cAAiC;QAC/C,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,cAAc,EAAE,mBAAmB,CAAC,CAAC;QACtE,KAAK,MAAM,KAAK,IAAI,MAAM;YAAE,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,mBAAmB,CAAC,CAAC;QACzE,IAAI,CAAC,mBAAmB,GAAG,MAAM,CAAC;QAClC,OAAO,IAAI,CAAC;IAChB,CAAC;IAED,gEAAgE;IAChE,iCAAiC,CAAC,OAAe;QAC7C,IAAI,CAAC,mCAAmC,GAAG,IAAI,CAAC,cAAc,CAC1D,OAAO,EACP,8CAAsC,EACtC,mCAAmC,CACtC,CAAC;QACF,OAAO,IAAI,CAAC;IAChB,CAAC;IAED;;;OAGG;IACH,QAAQ;QACJ,MAAM,OAAO,GAAa,EAAE,CAAC;QAC7B,IAAI,IAAI,CAAC,SAAS,KAAK,SAAS;YAAE,OAAO,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QAC/D,IAAI,IAAI,CAAC,UAAU,KAAK,SAAS;YAAE,OAAO,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;QACjE,IAAI,IAAI,CAAC,gBAAgB,KAAK,SAAS;YAAE,OAAO,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;QAC7E,IAAI,IAAI,CAAC,eAAe,KAAK,SAAS;YAAE,OAAO,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;QAC3E,IAAI,IAAI,CAAC,YAAY,KAAK,SAAS;YAAE,OAAO,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;QACrE,IAAI,IAAI,CAAC,aAAa,KAAK,SAAS;YAAE,OAAO,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;QACvE,IAAI,IAAI,CAAC,yBAAyB,KAAK,SAAS,EAAE,CAAC;YAC/C,OAAO,CAAC,IAAI,CAAC,8BAA8B,CAAC,CAAC;QACjD,CAAC;QACD,IAAI,IAAI,CAAC,yBAAyB,KAAK,SAAS,EAAE,CAAC;YAC/C,OAAO,CAAC,IAAI,CAAC,8BAA8B,CAAC,CAAC;QACjD,CAAC;QACD,IAAI,IAAI,CAAC,yBAAyB,KAAK,SAAS,EAAE,CAAC;YAC/C,OAAO,CAAC,IAAI,CAAC,8BAA8B,CAAC,CAAC;QACjD,CAAC;QACD,IAAI,IAAI,CAAC,mBAAmB,KAAK,SAAS;YAAE,OAAO,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAC;QACnF,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACrB,MAAM,IAAI,KAAK,CAAC,gCAAgC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC1E,CAAC;QACD,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,UAAU,IAAI,EAAE,EAAE,CAAC;YACvC,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAC9B,IAAI,GAAG,CAAC,QAAQ,KAAK,QAAQ,IAAI,GAAG,CAAC,MAAM,KAAK,IAAI,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,MAAM,EAAE,CAAC;gBAC5E,MAAM,IAAI,KAAK,CACX,8EAA8E,CACjF,CAAC;YACN,CAAC;QACL,CAAC;IACL,CAAC;IAED,IAAI,IAAI;QACJ,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;IAChD,CAAC;IAED,IAAI,KAAK;QACL,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;IAClD,CAAC;IAED,IAAI,WAAW;QACX,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,gBAAgB,CAAC,CAAC;IAC9D,CAAC;IAED,IAAI,UAAU;QACV,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,eAAe,CAAC,CAAC;IAC5D,CAAC;IAED,IAAI,OAAO;QACP,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,YAAY,CAAC,CAAC;IACtD,CAAC;IAED,IAAI,QAAQ;QACR,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,aAAa,CAAC,CAAC;IACxD,CAAC;IAED,IAAI,oBAAoB;QACpB,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,yBAAyB,EAAE,yBAAyB,CAAC,CAAC;IAChF,CAAC;IAED,IAAI,oBAAoB;QACpB,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,yBAAyB,EAAE,yBAAyB,CAAC,CAAC;IAChF,CAAC;IAED,IAAI,oBAAoB;QACpB,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,yBAAyB,EAAE,yBAAyB,CAAC,CAAC;IAChF,CAAC;IAED,IAAI,cAAc;QACd,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,mBAAmB,EAAE,mBAAmB,CAAC,CAAC;IACpE,CAAC;IAED,IAAI,8BAA8B;QAC9B,OAAO,IAAI,CAAC,mCAAmC,CAAC;IACpD,CAAC;IAED;;;;;;;;OAQG;IACH,IAAI,mBAAmB;QACnB,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACnC,MAAM,IAAI,GAAG,GAAG,CAAC,QAAQ,KAAK,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC;QACtD,OAAO,GAAG,GAAG,CAAC,MAAM,wCAAwC,IAAI,EAAE,CAAC;IACvE,CAAC;IAED,yBAAyB;QACrB,OAAO,IAAI,4BAA4B,CACnC,IAAI,CAAC,QAAQ,EACb,IAAI,CAAC,oBAAoB,EACzB,IAAI,CAAC,cAAc,CACtB,CAAC;IACN,CAAC;IAED,2FAA2F;IACnF,aAAa,CAAC,KAAsB;QACxC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YACxB,MAAM,IAAI,KAAK,CACX,wEAAwE,CAC3E,CAAC;QACN,CAAC;QACD,OAAO,MAAM,CAAC,MAAM,CAChB,KAAK,CAAC,GAAG,CAAC,CAAC,IAAU,EAAE,EAAE;YACrB,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACpC,MAAM,IAAI,KAAK,CAAC,4DAA4D,CAAC,CAAC;YAClF,CAAC;YACD,OAAO,IAAI,iBAAO,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QACxE,CAAC,CAAC,CACL,CAAC;IACN,CAAC;IAEO,WAAW,CAAC,KAAa,EAAE,MAAc;QAC7C,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;YACnD,MAAM,IAAI,KAAK,CAAC,qBAAqB,MAAM,oCAAoC,CAAC,CAAC;QACrF,CAAC;QACD,OAAO,KAAK,CAAC;IACjB,CAAC;IAEO,UAAU,CAAC,KAAa,EAAE,MAAc;QAC5C,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QAChC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;YACvB,MAAM,IAAI,KAAK,CACX,qBAAqB,MAAM,wCAAwC,KAAK,IAAI,CAC/E,CAAC;QACN,CAAC;QACD,OAAO,KAAK,CAAC;IACjB,CAAC;IAEO,gBAAgB,CAAC,KAAa,EAAE,GAAW,EAAE,MAAc;QAC/D,IAAI,KAAK,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC;YACrB,MAAM,IAAI,KAAK,CACX,qBAAqB,MAAM,wBAAwB,GAAG,oBAAoB,KAAK,CAAC,MAAM,GAAG,CAC5F,CAAC;QACN,CAAC;QACD,OAAO,KAAK,CAAC;IACjB,CAAC;IAEO,YAAY,CAAC,KAAwB,EAAE,MAAc;QACzD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YACxB,MAAM,IAAI,KAAK,CAAC,qBAAqB,MAAM,qCAAqC,CAAC,CAAC;QACtF,CAAC;QACD,OAAO,KAAK,CAAC;IACjB,CAAC;IAEO,cAAc,CAAC,KAAa,EAAE,OAAe,EAAE,MAAc;QACjE,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,KAAK,GAAG,OAAO,EAAE,CAAC;YAC3D,MAAM,IAAI,KAAK,CACX,qBAAqB,MAAM,qBAAqB,OAAO,iBAAiB,MAAM,CAAC,KAAK,CAAC,GAAG,CAC3F,CAAC;QACN,CAAC;QACD,OAAO,KAAK,CAAC;IACjB,CAAC;IAEO,cAAc,CAAI,KAAQ,EAAE,MAAc;QAC9C,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;YACxC,MAAM,IAAI,KAAK,CAAC,qBAAqB,MAAM,yBAAyB,CAAC,CAAC;QAC1E,CAAC;QACD,OAAO,KAAK,CAAC;IACjB,CAAC;IAEO,IAAI,CAAI,KAAoB,EAAE,MAAc;QAChD,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACtB,MAAM,IAAI,KAAK,CAAC,gCAAgC,MAAM,OAAO,CAAC,CAAC;QACnE,CAAC;QACD,OAAO,KAAK,CAAC;IACjB,CAAC;CACJ;AApVD,8CAoVC","sourcesContent":["import { Mcp, McpToolProfiles } from '@webpieces/core-util';\nimport { AuthenticatedCaller, AuthorizationService } from '@webpieces/http-routing';\nimport { Icon } from '@modelcontextprotocol/server';\nimport { McpIcon } from './McpIcon';\n\nexport const MAX_MCP_ACCESS_TOKEN_LIFETIME_SECONDS = 30 * 24 * 60 * 60;\nexport const MAX_MCP_ACCOUNT_VALIDATION_AGE_SECONDS = 60 * 60;\n/** `setTitle` ceiling: the Claude Connectors Directory's listing Name limit. */\nexport const MAX_MCP_SERVER_TITLE_LENGTH = 100;\n/** `setDescription` ceiling: the Claude Connectors Directory's listing Description limit. */\nexport const MAX_MCP_SERVER_DESCRIPTION_LENGTH = 2000;\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 * .setTitle('My Product')\n * .setDescription('Search and manage My Product accounts from any MCP client.')\n * .setWebsiteUrl('https://www.example.com/my-product')\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 titleValue?: string;\n private descriptionValue?: string;\n private websiteUrlValue?: string;\n private versionValue?: string;\n private iconsValue?: readonly McpIcon[];\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 /**\n * REQUIRED. The human-readable display name sent as `serverInfo.title` (MCP 2025-06-18), e.g.\n * `'Lang Learning'` where {@link setName} is the programmatic `'lang-learning'`. One line, at most\n * 100 characters — the Claude Connectors Directory's listing Name limit.\n *\n * The app sets it because the server cannot derive it: it does not read the OpenAPI manifest at\n * runtime, and one MCP server may bind contracts from several api libraries.\n */\n setTitle(title: string): this {\n const value = this.requireText(title, 'setTitle');\n if (/[\\r\\n]/.test(value)) {\n throw new Error('WpMcpServerConfig.setTitle(...) requires a single line of text.');\n }\n this.titleValue = this.requireMaxLength(value, MAX_MCP_SERVER_TITLE_LENGTH, 'setTitle');\n return this;\n }\n\n /**\n * REQUIRED. What this server does, sent as `serverInfo.description` (MCP 2025-11-25). At most\n * 2000 characters — the Claude Connectors Directory's listing Description limit.\n */\n setDescription(description: string): this {\n const value = this.requireText(description, 'setDescription');\n this.descriptionValue = this.requireMaxLength(\n value,\n MAX_MCP_SERVER_DESCRIPTION_LENGTH,\n 'setDescription',\n );\n return this;\n }\n\n /**\n * REQUIRED. An absolute `https://` URL of a page a HUMAN reads about this server — the product\n * homepage or its docs site — sent as `serverInfo.websiteUrl` (MCP 2025-11-25).\n *\n * Deliberately NOT derived from {@link setResource}: that is the MCP endpoint, an API host, not a\n * website, and the resource is never derived from request input either. No default, because the\n * page a user is sent to is the app's decision.\n */\n setWebsiteUrl(websiteUrl: string): this {\n const value = this.requireUrl(websiteUrl, 'setWebsiteUrl');\n if (new URL(value).protocol !== 'https:') {\n throw new Error(\n `WpMcpServerConfig.setWebsiteUrl(...) requires an https:// URL, got '${value}'.`,\n );\n }\n this.websiteUrlValue = value;\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 /** OPTIONAL. Standard MCP icons; copied and frozen, with no network access. */\n setIcons(icons: readonly Icon[]): this {\n this.iconsValue = this.snapshotIcons(icons);\n return this;\n }\n\n /** Immutable snapshot; omitted icons remain absent from SDK implementation metadata. */\n get icons(): readonly McpIcon[] | undefined {\n return this.iconsValue;\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.titleValue === undefined) missing.push('setTitle(...)');\n if (this.descriptionValue === undefined) missing.push('setDescription(...)');\n if (this.websiteUrlValue === undefined) missing.push('setWebsiteUrl(...)');\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 for (const icon of this.iconsValue ?? []) {\n const url = new URL(icon.src);\n if (url.protocol === 'https:' && url.origin !== new URL(this.resource).origin) {\n throw new Error(\n 'WpMcpServerConfig.setIcons(...) requires HTTPS icons on the resource origin.',\n );\n }\n }\n }\n\n get name(): string {\n return this.read(this.nameValue, 'setName');\n }\n\n get title(): string {\n return this.read(this.titleValue, 'setTitle');\n }\n\n get description(): string {\n return this.read(this.descriptionValue, 'setDescription');\n }\n\n get websiteUrl(): string {\n return this.read(this.websiteUrlValue, 'setWebsiteUrl');\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 /** Copies caller-owned descriptors and nested size arrays before freezing the snapshot. */\n private snapshotIcons(icons: readonly Icon[]): readonly McpIcon[] {\n if (!Array.isArray(icons)) {\n throw new Error(\n 'WpMcpServerConfig.setIcons(...) requires an array of icon descriptors.',\n );\n }\n return Object.freeze(\n icons.map((icon: Icon) => {\n if (!icon || typeof icon !== 'object') {\n throw new Error('WpMcpServerConfig.setIcons(...) requires icon descriptors.');\n }\n return new McpIcon(icon.src, icon.mimeType, icon.sizes, icon.theme);\n }),\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 requireMaxLength(value: string, max: number, setter: string): string {\n if (value.length > max) {\n throw new Error(\n `WpMcpServerConfig.${setter}(...) allows at most ${max} characters, got ${value.length}.`,\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"]}
@@ -7,7 +7,11 @@ export declare class RegisteredMcpTool {
7
7
  readonly apiClass: ClassType;
8
8
  readonly methodName: string;
9
9
  readonly name: string;
10
- /** The human-readable display name; published as BOTH `Tool.title` and `annotations.title` (#1180). */
10
+ /**
11
+ * The human-readable display name, read from the CATALOG — which the generator wrote from the
12
+ * method's `@summary` JSDoc tag (#1296). Published as BOTH `Tool.title` and `annotations.title`.
13
+ * The contract carries no title at runtime, so there is nothing to compare it with: one source.
14
+ */
11
15
  readonly title: string;
12
16
  readonly description: string;
13
17
  readonly annotations: WpMcpToolHints;
@@ -19,12 +23,18 @@ export declare class RegisteredMcpTool {
19
23
  readonly outputSchema: ApiJsonSchema;
20
24
  readonly profiles: readonly string[];
21
25
  constructor(apiClass: ClassType, methodName: string, name: string,
22
- /** The human-readable display name; published as BOTH `Tool.title` and `annotations.title` (#1180). */
26
+ /**
27
+ * The human-readable display name, read from the CATALOG — which the generator wrote from the
28
+ * method's `@summary` JSDoc tag (#1296). Published as BOTH `Tool.title` and `annotations.title`.
29
+ * The contract carries no title at runtime, so there is nothing to compare it with: one source.
30
+ */
23
31
  title: string, description: string, annotations: WpMcpToolHints, operation: EndpointOperation, authMeta: AuthMeta, authorization: AuthorizationRequirement<unknown>, binding: McpApiBinding, inputSchema: ApiJsonSchema, outputSchema: ApiJsonSchema, profiles: readonly string[]);
24
32
  }
25
33
  /**
26
34
  * Fail-fast registry: the CONTRACT says which methods are tools and how they are authorized, and the
27
- * build's generated catalog OF THAT CONTRACT says what each tool's documentation and schemas are.
35
+ * build's generated catalog OF THAT CONTRACT says what each tool's title, documentation and schemas
36
+ * are. The title and the description are JSDoc (`@summary` and the body), which only the compiler can
37
+ * read, so the catalog is their one runtime source (#1296).
28
38
  *
29
39
  * ## Why the schemas come from the catalog and not from here
30
40
  *
@@ -19,7 +19,11 @@ class RegisteredMcpTool {
19
19
  outputSchema;
20
20
  profiles;
21
21
  constructor(apiClass, methodName, name,
22
- /** The human-readable display name; published as BOTH `Tool.title` and `annotations.title` (#1180). */
22
+ /**
23
+ * The human-readable display name, read from the CATALOG — which the generator wrote from the
24
+ * method's `@summary` JSDoc tag (#1296). Published as BOTH `Tool.title` and `annotations.title`.
25
+ * The contract carries no title at runtime, so there is nothing to compare it with: one source.
26
+ */
23
27
  title, description, annotations, operation, authMeta,
24
28
  // webpieces-disable no-any-unknown -- custom policy is validated by AuthorizationService
25
29
  authorization, binding, inputSchema, outputSchema, profiles) {
@@ -41,7 +45,9 @@ class RegisteredMcpTool {
41
45
  exports.RegisteredMcpTool = RegisteredMcpTool;
42
46
  /**
43
47
  * Fail-fast registry: the CONTRACT says which methods are tools and how they are authorized, and the
44
- * build's generated catalog OF THAT CONTRACT says what each tool's documentation and schemas are.
48
+ * build's generated catalog OF THAT CONTRACT says what each tool's title, documentation and schemas
49
+ * are. The title and the description are JSDoc (`@summary` and the body), which only the compiler can
50
+ * read, so the catalog is their one runtime source (#1296).
45
51
  *
46
52
  * ## Why the schemas come from the catalog and not from here
47
53
  *
@@ -147,12 +153,6 @@ class McpToolRegistry {
147
153
  pairing.problem(`MCP tool '${metadata.name}' has stale method/profile metadata in ${catalog.file.fileName}; rebuild the api library.`);
148
154
  return undefined;
149
155
  }
150
- if (published.title !== metadata.title) {
151
- pairing.problem(`@WpMcpTool ${apiClass.name}.${metadata.methodName} is titled '${metadata.title}', but ` +
152
- `${catalog.file.fileName} (${catalog.directory}) publishes '${metadata.name}' as ` +
153
- `'${published.title}'. The catalog is older than the contract — rebuild the api library.`);
154
- return undefined;
155
- }
156
156
  return published;
157
157
  }
158
158
  }