@webpieces/mcp-server 0.4.989 → 0.4.991

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.
Files changed (48) hide show
  1. package/README.md +199 -42
  2. package/package.json +7 -6
  3. package/src/McpApiDispatcher.d.ts +6 -3
  4. package/src/McpApiDispatcher.js +6 -2
  5. package/src/McpApiDispatcher.js.map +1 -1
  6. package/src/McpAuth.d.ts +115 -56
  7. package/src/McpAuth.js +180 -111
  8. package/src/McpAuth.js.map +1 -1
  9. package/src/McpAuthorityCompileAssertions.d.ts +12 -2
  10. package/src/McpAuthorityCompileAssertions.js +32 -4
  11. package/src/McpAuthorityCompileAssertions.js.map +1 -1
  12. package/src/McpBindOptions.d.ts +5 -20
  13. package/src/McpBindOptions.js +5 -15
  14. package/src/McpBindOptions.js.map +1 -1
  15. package/src/McpClientSet.d.ts +35 -0
  16. package/src/McpClientSet.js +86 -0
  17. package/src/McpClientSet.js.map +1 -0
  18. package/src/McpDoorAuth.d.ts +117 -0
  19. package/src/McpDoorAuth.js +455 -0
  20. package/src/McpDoorAuth.js.map +1 -0
  21. package/src/McpToolCallRendering.d.ts +5 -4
  22. package/src/McpToolCallRendering.js.map +1 -1
  23. package/src/McpToolCatalog.d.ts +1 -1
  24. package/src/McpToolCatalog.js +1 -1
  25. package/src/McpToolCatalog.js.map +1 -1
  26. package/src/McpToolRegistry.js +8 -2
  27. package/src/McpToolRegistry.js.map +1 -1
  28. package/src/McpToolset.d.ts +115 -0
  29. package/src/McpToolset.js +323 -0
  30. package/src/McpToolset.js.map +1 -0
  31. package/src/WpMcpErrorTranslator.d.ts +5 -7
  32. package/src/WpMcpErrorTranslator.js +7 -13
  33. package/src/WpMcpErrorTranslator.js.map +1 -1
  34. package/src/WpMcpInMemory.d.ts +30 -0
  35. package/src/WpMcpInMemory.js +97 -0
  36. package/src/WpMcpInMemory.js.map +1 -0
  37. package/src/WpMcpRemote.d.ts +23 -0
  38. package/src/WpMcpRemote.js +89 -0
  39. package/src/WpMcpRemote.js.map +1 -0
  40. package/src/WpMcpServer.d.ts +45 -76
  41. package/src/WpMcpServer.js +101 -327
  42. package/src/WpMcpServer.js.map +1 -1
  43. package/src/__tests__/McpTransportFixtures.d.ts +107 -0
  44. package/src/__tests__/McpTransportFixtures.js +266 -0
  45. package/src/__tests__/McpTransportFixtures.js.map +1 -0
  46. package/src/index.d.ts +9 -2
  47. package/src/index.js +17 -1
  48. package/src/index.js.map +1 -1
package/src/McpAuth.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
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
- const core_util_1 = require("@webpieces/core-util");
3
+ exports.McpOAuthMode = exports.McpAccessTokenFormat = exports.JWT_PREFIX = exports.DEV_OIDC_TOKEN_PREFIX = exports.GOOGLE_OIDC_ISSUERS = 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
+ exports.mcpOAuth = mcpOAuth;
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;
@@ -83,41 +83,44 @@ class McpProtectedResourceMetadata {
83
83
  }
84
84
  exports.McpProtectedResourceMetadata = McpProtectedResourceMetadata;
85
85
  /**
86
- * Node MCP server configuration, built with fluent setters: every name sits next to its own value, so
87
- * no two settings can be swapped, and a future setting is an additive setter rather than a breaking
88
- * signature change. The access-token authority verifies MCP credentials; the authorization service
89
- * is shared with receiving endpoints and document projection.
86
+ * Node MCP HTTP server configuration, built with fluent setters: every name sits next to its own
87
+ * value, so no two settings can be swapped, and a future setting is an additive setter rather than a
88
+ * breaking signature change.
90
89
  *
91
- * SINGLE CANONICAL ORIGIN: one server serves exactly one protected resource. {@link setResource} is
92
- * fixed for the lifetime of the process, is the audience of every access token, and is never derived
93
- * from the request `Host` — see that setter for why a host-derived audience is a confused deputy.
90
+ * WHICH tools the server serves is not here: that is the `McpToolset` handed to
91
+ * `new WpMcpServer(config, toolset)`. This config says only who the server is and HOW A CALLER GETS
92
+ * IN — {@link setAuth}, an OR-set of the same `@WpAuth` builders an endpoint uses plus `mcpOAuth(...)`.
93
+ *
94
+ * SINGLE CANONICAL ORIGIN: one server serves exactly one resource. {@link setResource} is fixed for
95
+ * the lifetime of the process, is the audience of every `mcpOAuth(...)` access token, and is never
96
+ * derived from the request `Host` — see that setter for why a host-derived audience is a confused
97
+ * deputy.
94
98
  *
95
99
  * Each setter validates its own value immediately and names itself in the failure. Setters cannot
96
100
  * make a field impossible to forget the way a positional constructor could, so
97
101
  * {@link WpMcpServerConfig.validate} runs from `WpMcpServer.bind(...)` and fails at startup listing
98
102
  * EVERY missing required setter at once. A misconfiguration is therefore a boot failure naming the
99
- * setter, instead of a first-request 401 + `WWW-Authenticate` — which is the OAuth discovery signal,
100
- * so the client's correct response is to re-authenticate and fail again, forever.
103
+ * setter, instead of a first-request 401 — which, with `mcpOAuth(...)`, is the OAuth discovery
104
+ * signal, so the client's correct response is to re-authenticate and fail again, forever.
101
105
  *
102
106
  * ```ts
103
- * const config = new WpMcpServerConfig<MyGrant>()
107
+ * const config = new WpMcpServerConfig()
104
108
  * .setName('my-server')
105
109
  * .setTitle('My Product')
106
110
  * .setDescription('Search and manage My Product accounts from any MCP client.')
107
111
  * .setWebsiteUrl('https://www.example.com/my-product')
108
112
  * .setVersion('1.0.0')
109
- * .setResource('https://api.example.com/mcp')
110
- * .setAccessTokenAuthority(authority)
111
- * .setAuthorizationService(router.authorizationService())
112
- * .setAuthorizationServers(['https://login.example.com'])
113
- * .setRequiredScopes(['tools']);
113
+ * .setResource('https://api.example.com/mcp/user')
114
+ * .setAuth([oidcSameProject()]) // internal svc-to-svc MCP
115
+ * .setCredentialVerifiers(router.credentialVerifiers());
116
+ * // .setAuth([oidcSameProject(), jwt(), apiKey('partner', creds),
117
+ * // mcpOAuth(authority, ['https://login.example.com'], ['tools'], McpAccessTokenFormat.opaquePrefix('wpmcp_'))])
114
118
  *
115
119
  * // the tools/call error translator is a PROCESS-GLOBAL, not a member of this config:
116
120
  * McpRegistry.setErrorTranslator(new MyMcpErrorTranslator());
117
121
  * ```
118
122
  */
119
123
  class WpMcpServerConfig {
120
- toolProfilesValue = Object.freeze([core_util_1.Mcp.DEFAULT]);
121
124
  nameValue;
122
125
  titleValue;
123
126
  descriptionValue;
@@ -125,19 +128,8 @@ class WpMcpServerConfig {
125
128
  versionValue;
126
129
  iconsValue;
127
130
  resourceValue;
128
- accessTokenAuthorityValue;
129
- authorizationServiceValue;
130
- authorizationServersValue;
131
- requiredScopesValue;
132
- maxAccountValidationAgeSecondsValue = exports.MAX_MCP_ACCOUNT_VALIDATION_AGE_SECONDS;
133
- /** OPTIONAL. Union of groups exposed by this instance; defaults to [Mcp.DEFAULT]. */
134
- setToolProfiles(profiles) {
135
- this.toolProfilesValue = core_util_1.Mcp.profiles(profiles, 'WpMcpServerConfig.setToolProfiles');
136
- return this;
137
- }
138
- get toolProfiles() {
139
- return this.toolProfilesValue;
140
- }
131
+ authValue;
132
+ credentialVerifiersValue;
141
133
  /** REQUIRED. The MCP server name reported by `initialize`. */
142
134
  setName(name) {
143
135
  this.nameValue = this.requireText(name, 'setName');
@@ -199,9 +191,9 @@ class WpMcpServerConfig {
199
191
  return this.iconsValue;
200
192
  }
201
193
  /**
202
- * REQUIRED. The ONE canonical protected-resource URI, which must be an absolute URL: it is the
203
- * exact audience every access token is checked against, and {@link resourceMetadataUrl} is
204
- * derived from it for the 401 challenge, so a non-URL here is always a mistake — including a
194
+ * REQUIRED. The ONE canonical resource URI, which must be an absolute URL: it is the exact
195
+ * audience every `mcpOAuth(...)` access token is checked against, and {@link resourceMetadataUrl}
196
+ * is derived from it for the 401 challenge, so a non-URL here is always a mistake — including a
205
197
  * name or version landed on it.
206
198
  *
207
199
  * It is fixed for the lifetime of the process and must NEVER be derived from the request `Host`
@@ -215,45 +207,33 @@ class WpMcpServerConfig {
215
207
  this.resourceValue = this.requireUrl(resource, 'setResource');
216
208
  return this;
217
209
  }
218
- /** REQUIRED. Application-owned paired mint/verify authority for MCP access tokens. */
219
- setAccessTokenAuthority(authority) {
220
- this.accessTokenAuthorityValue = this.requirePresent(authority, 'setAccessTokenAuthority');
221
- return this;
222
- }
223
- /** REQUIRED. The common authorization service for tool projection and invocation. */
224
- setAuthorizationService(authority) {
225
- this.authorizationServiceValue = this.requirePresent(authority, 'setAuthorizationService');
226
- return this;
227
- }
228
210
  /**
229
- * REQUIRED. The trusted token issuers. Every entry must be an absolute URL, because each is
230
- * compared against a token's `issuer` claim and published as an `authorization_servers` entry —
231
- * which is what makes a scope list landed here fail at boot rather than on the first request.
211
+ * REQUIRED. HOW a caller gets in: an OR-set of the `@WpAuth` builders endpoints already use —
212
+ * `oidcSameProject()`, `oidcSvcs([...])`, `jwt()`, `apiKey(regime, creds)`, `sharedSecret(key)` —
213
+ * plus `mcpOAuth(...)` for off-the-shelf connectors. Each request is CLASSIFIED from unverified
214
+ * surface facts (which header; a JWT's unverified `iss`) and handed to exactly ONE verifier, whose
215
+ * rejection is the answer — it never falls through to another mode.
216
+ *
217
+ * `vendorAuth(...)` and `@WpAuthPublic` identify no caller to authorize tools against, so they are
218
+ * refused at bind; so is a set whose modes could both claim one request.
232
219
  */
233
- setAuthorizationServers(authorizationServers) {
234
- const servers = this.requireArray(authorizationServers, 'setAuthorizationServers');
235
- if (servers.length === 0) {
236
- throw new Error('WpMcpServerConfig.setAuthorizationServers(...) requires at least one issuer URL.');
220
+ setAuth(auth) {
221
+ if (!Array.isArray(auth) || auth.length === 0) {
222
+ throw new Error('WpMcpServerConfig.setAuth(...) requires at least one auth mode.');
237
223
  }
238
- for (const server of servers)
239
- this.requireUrl(server, 'setAuthorizationServers');
240
- this.authorizationServersValue = servers;
224
+ this.authValue = Object.freeze([...auth]);
241
225
  return this;
242
226
  }
243
227
  /**
244
- * REQUIRED, and empty only by saying so explicitly. Scopes every access token must carry before
245
- * any tool is dispatched.
228
+ * REQUIRED when {@link setAuth} holds any `@WpAuth` mode (everything but `mcpOAuth(...)`): the
229
+ * verifiers this app bound, from `router.credentialVerifiers()` — the SAME JwtHook, OidcHook /
230
+ * DefaultOidcVerifier, AuthConfig and ApiKeyHook the endpoint `AuthFilter` uses.
246
231
  */
247
- setRequiredScopes(requiredScopes) {
248
- const scopes = this.requireArray(requiredScopes, 'setRequiredScopes');
249
- for (const scope of scopes)
250
- this.requireText(scope, 'setRequiredScopes');
251
- this.requiredScopesValue = scopes;
252
- return this;
253
- }
254
- /** OPTIONAL ceiling, 1 second to 1 hour. Defaults to 1 hour. */
255
- setMaxAccountValidationAgeSeconds(seconds) {
256
- this.maxAccountValidationAgeSecondsValue = this.requireSeconds(seconds, exports.MAX_MCP_ACCOUNT_VALIDATION_AGE_SECONDS, 'setMaxAccountValidationAgeSeconds');
232
+ setCredentialVerifiers(verifiers) {
233
+ if (!verifiers || typeof verifiers.verifyOidc !== 'function') {
234
+ throw new Error('WpMcpServerConfig.setCredentialVerifiers(...) requires router.credentialVerifiers().');
235
+ }
236
+ this.credentialVerifiersValue = verifiers;
257
237
  return this;
258
238
  }
259
239
  /**
@@ -274,17 +254,12 @@ class WpMcpServerConfig {
274
254
  missing.push('setVersion(...)');
275
255
  if (this.resourceValue === undefined)
276
256
  missing.push('setResource(...)');
277
- if (this.accessTokenAuthorityValue === undefined) {
278
- missing.push('setAccessTokenAuthority(...)');
279
- }
280
- if (this.authorizationServiceValue === undefined) {
281
- missing.push('setAuthorizationService(...)');
257
+ if (this.authValue === undefined)
258
+ missing.push('setAuth([...])');
259
+ const needsVerifiers = (this.authValue ?? []).some((mode) => mode.kind !== 'mcp-oauth');
260
+ if (needsVerifiers && this.credentialVerifiersValue === undefined) {
261
+ missing.push('setCredentialVerifiers(router.credentialVerifiers())');
282
262
  }
283
- if (this.authorizationServersValue === undefined) {
284
- missing.push('setAuthorizationServers(...)');
285
- }
286
- if (this.requiredScopesValue === undefined)
287
- missing.push('setRequiredScopes(...)');
288
263
  if (missing.length > 0) {
289
264
  throw new Error(`WpMcpServerConfig is missing ${missing.join(', ')}`);
290
265
  }
@@ -313,20 +288,12 @@ class WpMcpServerConfig {
313
288
  get resource() {
314
289
  return this.read(this.resourceValue, 'setResource');
315
290
  }
316
- get accessTokenAuthority() {
317
- return this.read(this.accessTokenAuthorityValue, 'setAccessTokenAuthority');
318
- }
319
- get authorizationService() {
320
- return this.read(this.authorizationServiceValue, 'setAuthorizationService');
321
- }
322
- get authorizationServers() {
323
- return this.read(this.authorizationServersValue, 'setAuthorizationServers');
324
- }
325
- get requiredScopes() {
326
- return this.read(this.requiredScopesValue, 'setRequiredScopes');
291
+ get auth() {
292
+ return this.read(this.authValue, 'setAuth');
327
293
  }
328
- get maxAccountValidationAgeSeconds() {
329
- return this.maxAccountValidationAgeSecondsValue;
294
+ /** The bound verifiers, or `undefined` for an `mcpOAuth(...)`-only server, which needs none. */
295
+ get credentialVerifiers() {
296
+ return this.credentialVerifiersValue;
330
297
  }
331
298
  /**
332
299
  * RFC 9728 §3.1: the URL of this resource's protected-resource METADATA DOCUMENT, derived from
@@ -334,17 +301,15 @@ class WpMcpServerConfig {
334
301
  * (`https://host/mcp` → `https://host/.well-known/oauth-protected-resource/mcp`).
335
302
  *
336
303
  * This — never the resource identifier itself — is what RFC 9728 §5.1 defines `resource_metadata`
337
- * to be, so it is the value webpieces puts in the 401 `WWW-Authenticate` challenge. webpieces
338
- * mounts no `.well-known` route; publish {@link protectedResourceMetadata} at this URL.
304
+ * to be, so it is the value webpieces puts in the `mcpOAuth(...)` 401 `WWW-Authenticate`
305
+ * challenge. webpieces mounts no `.well-known` route; publish
306
+ * `WpMcpServer.protectedResourceMetadata()` at this URL.
339
307
  */
340
308
  get resourceMetadataUrl() {
341
309
  const url = new URL(this.resource);
342
310
  const path = url.pathname === '/' ? '' : url.pathname;
343
311
  return `${url.origin}/.well-known/oauth-protected-resource${path}`;
344
312
  }
345
- protectedResourceMetadata() {
346
- return new McpProtectedResourceMetadata(this.resource, this.authorizationServers, this.requiredScopes);
347
- }
348
313
  /** Copies caller-owned descriptors and nested size arrays before freezing the snapshot. */
349
314
  snapshotIcons(icons) {
350
315
  if (!Array.isArray(icons)) {
@@ -376,30 +341,134 @@ class WpMcpServerConfig {
376
341
  }
377
342
  return value;
378
343
  }
379
- requireArray(value, setter) {
380
- if (!Array.isArray(value)) {
381
- throw new Error(`WpMcpServerConfig.${setter}(...) requires an array of strings.`);
344
+ read(value, setter) {
345
+ if (value === undefined) {
346
+ throw new Error(`WpMcpServerConfig is missing ${setter}(...)`);
382
347
  }
383
348
  return value;
384
349
  }
385
- requireSeconds(value, ceiling, setter) {
386
- if (!Number.isFinite(value) || value <= 0 || value > ceiling) {
387
- throw new Error(`WpMcpServerConfig.${setter}(...) requires 1..${ceiling} seconds, got ${String(value)}.`);
350
+ }
351
+ exports.WpMcpServerConfig = WpMcpServerConfig;
352
+ /** Google's OIDC issuers. An `mcpOAuth(...)` issuer may never be one: it would claim Google tokens. */
353
+ exports.GOOGLE_OIDC_ISSUERS = Object.freeze([
354
+ 'https://accounts.google.com',
355
+ 'accounts.google.com',
356
+ ]);
357
+ /** The unsigned off-GCP OIDC token prefix `@webpieces/gcp-identity` mints and verifies. */
358
+ exports.DEV_OIDC_TOKEN_PREFIX = 'dev-oidc.';
359
+ /** Every JWT starts with the base64url of `{"`. An opaque prefix starting so would claim JWTs. */
360
+ exports.JWT_PREFIX = 'eyJ';
361
+ /**
362
+ * How an `mcpOAuth(...)` access token LOOKS, decided by the authority that mints it. The MCP door
363
+ * classifies every request from unverified surface facts before handing it to exactly ONE verifier, so
364
+ * the OAuth mode must say which bearers are its kind without any crypto or I/O:
365
+ *
366
+ * - {@link opaquePrefix} — the authority mints opaque tokens that all start with `prefix`
367
+ * (e.g. `'wpmcp_'`). Disjoint from every JWT and from off-GCP OIDC tokens by construction.
368
+ * - {@link jwtIssuer} — the authority mints JWTs whose `iss` is one of the mode's
369
+ * `authorizationServers`. A JWT with any other issuer is not this mode's.
370
+ * - {@link everyBearer} — every `Authorization: Bearer` credential is this mode's. Legal ONLY when no
371
+ * other bearer mode (`oidcSameProject()`, `oidcSvcs(...)`, `jwt()`, a bearer `apiKey(...)`) is in the
372
+ * same `setAuth([...])`; startup refuses the combination, because two modes claiming one token is
373
+ * exactly the overlap classify-then-verify forbids.
374
+ */
375
+ class McpAccessTokenFormat {
376
+ kind;
377
+ prefix;
378
+ constructor(kind, prefix) {
379
+ this.kind = kind;
380
+ this.prefix = prefix;
381
+ }
382
+ // webpieces-disable no-function-outside-class -- explicit public factory of a closed set of formats
383
+ static opaquePrefix(prefix) {
384
+ if (typeof prefix !== 'string' || prefix.trim() === '' || /\s/.test(prefix)) {
385
+ throw new Error('McpAccessTokenFormat.opaquePrefix(...) requires a nonblank prefix with no whitespace.');
388
386
  }
389
- return value;
390
- }
391
- requirePresent(value, setter) {
392
- if (value === undefined || value === null) {
393
- throw new Error(`WpMcpServerConfig.${setter}(...) requires a value.`);
387
+ for (const reserved of [exports.JWT_PREFIX, exports.DEV_OIDC_TOKEN_PREFIX]) {
388
+ if (reserved.startsWith(prefix) || prefix.startsWith(reserved)) {
389
+ throw new Error(`McpAccessTokenFormat.opaquePrefix('${prefix}') overlaps '${reserved}', which ` +
390
+ (reserved === exports.JWT_PREFIX
391
+ ? 'every JWT starts with'
392
+ : 'every off-GCP OIDC token starts with') +
393
+ "; pick a prefix the authority alone mints, e.g. 'wpmcp_'.");
394
+ }
394
395
  }
395
- return value;
396
+ return new McpAccessTokenFormat('opaque-prefix', prefix);
396
397
  }
397
- read(value, setter) {
398
- if (value === undefined) {
399
- throw new Error(`WpMcpServerConfig is missing ${setter}(...)`);
398
+ // webpieces-disable no-function-outside-class -- explicit public factory of a closed set of formats
399
+ static jwtIssuer() {
400
+ return new McpAccessTokenFormat('jwt-issuer', '');
401
+ }
402
+ // webpieces-disable no-function-outside-class -- explicit public factory of a closed set of formats
403
+ static everyBearer() {
404
+ return new McpAccessTokenFormat('every-bearer', '');
405
+ }
406
+ }
407
+ exports.McpAccessTokenFormat = McpAccessTokenFormat;
408
+ /**
409
+ * The MCP OAuth mode: today's connector behavior (Claude / ChatGPT) as ONE member of a server's
410
+ * `setAuth([...])` set. It is the only mode that publishes RFC 9728 protected-resource metadata and
411
+ * answers a request carrying no credential with the `401 WWW-Authenticate` discovery challenge, and
412
+ * its access tokens are audience-bound to the server's canonical resource. Build it with
413
+ * {@link mcpOAuth}.
414
+ */
415
+ class McpOAuthMode {
416
+ authority;
417
+ authorizationServers;
418
+ requiredScopes;
419
+ tokenFormat;
420
+ maxAccountValidationAgeSeconds;
421
+ kind = 'mcp-oauth';
422
+ constructor(authority, authorizationServers, requiredScopes, tokenFormat, maxAccountValidationAgeSeconds) {
423
+ this.authority = authority;
424
+ this.authorizationServers = authorizationServers;
425
+ this.requiredScopes = requiredScopes;
426
+ this.tokenFormat = tokenFormat;
427
+ this.maxAccountValidationAgeSeconds = maxAccountValidationAgeSeconds;
428
+ if (!authority || typeof authority.verifyAccessToken !== 'function') {
429
+ throw new Error('mcpOAuth(...) requires an McpAccessTokenAuthority.');
400
430
  }
401
- return value;
431
+ if (!Array.isArray(authorizationServers) || authorizationServers.length === 0) {
432
+ throw new Error('mcpOAuth(...) requires at least one authorization server issuer URL.');
433
+ }
434
+ for (const server of authorizationServers) {
435
+ if (typeof server !== 'string' || !URL.canParse(server)) {
436
+ throw new Error(`mcpOAuth(...) authorization servers must be absolute URLs, got '${String(server)}'.`);
437
+ }
438
+ }
439
+ if (!Array.isArray(requiredScopes) ||
440
+ !requiredScopes.every((scope) => typeof scope === 'string' && scope.trim() !== '')) {
441
+ throw new Error('mcpOAuth(...) requires an array of nonblank required scopes (empty only by saying so: []).');
442
+ }
443
+ if (!(tokenFormat instanceof McpAccessTokenFormat)) {
444
+ throw new Error('mcpOAuth(...) requires an McpAccessTokenFormat describing the tokens the authority mints.');
445
+ }
446
+ if (!Number.isFinite(maxAccountValidationAgeSeconds) ||
447
+ maxAccountValidationAgeSeconds <= 0 ||
448
+ maxAccountValidationAgeSeconds > exports.MAX_MCP_ACCOUNT_VALIDATION_AGE_SECONDS) {
449
+ throw new Error(`mcpOAuth(...) maxAccountValidationAgeSeconds must be 1..${exports.MAX_MCP_ACCOUNT_VALIDATION_AGE_SECONDS}, ` +
450
+ `got ${String(maxAccountValidationAgeSeconds)}.`);
451
+ }
452
+ Object.freeze(this);
453
+ }
454
+ /** RFC 9728 metadata for `resource`, published by the app at the server's `resourceMetadataUrl`. */
455
+ protectedResourceMetadata(resource) {
456
+ return new McpProtectedResourceMetadata(resource, this.authorizationServers, this.requiredScopes);
402
457
  }
403
458
  }
404
- exports.WpMcpServerConfig = WpMcpServerConfig;
459
+ exports.McpOAuthMode = McpOAuthMode;
460
+ /**
461
+ * The MCP OAuth door mode, for off-the-shelf connectors. Everything OAuth-only lives here and is
462
+ * required only when this mode is in the server's `setAuth([...])`:
463
+ *
464
+ * @param authority the application-owned paired mint/verify authority
465
+ * @param authorizationServers the trusted token issuers, published as `authorization_servers`
466
+ * @param requiredScopes scopes every access token must carry before any tool is dispatched
467
+ * @param tokenFormat how the authority's tokens look, so the door can classify them
468
+ * @param maxAccountValidationAgeSeconds how fresh the account facts must be, 1 second to 1 hour
469
+ */
470
+ // webpieces-disable no-function-outside-class -- canonical credential descriptor factory, the twin of jwt()/oidcSameProject()
471
+ function mcpOAuth(authority, authorizationServers, requiredScopes, tokenFormat, maxAccountValidationAgeSeconds = exports.MAX_MCP_ACCOUNT_VALIDATION_AGE_SECONDS) {
472
+ return new McpOAuthMode(authority, authorizationServers, requiredScopes, tokenFormat, maxAccountValidationAgeSeconds);
473
+ }
405
474
  //# sourceMappingURL=McpAuth.js.map
@@ -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/** `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"]}
1
+ {"version":3,"file":"McpAuth.js","sourceRoot":"","sources":["../../../../../packages/http/mcp-server/src/McpAuth.ts"],"names":[],"mappings":"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","sourcesContent":["import { AuthMethod } from '@webpieces/core-util';\nimport { AuthenticatedCaller, CredentialVerifiers } 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, handed to\n * `mcpOAuth(...)`. `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 * One member of a server's `setAuth([...])` set: an `@WpAuth` builder (`oidcSameProject()`,\n * `oidcSvcs([...])`, `jwt()`, `apiKey(...)`, `sharedSecret(...)`) or `mcpOAuth(...)`.\n */\nexport type McpHttpAuth = AuthMethod | McpOAuthMode;\n\n/** A server's `setAuth([...])` set: at least one mode. */\nexport type McpHttpAuths = readonly [McpHttpAuth, ...McpHttpAuth[]];\n\n/**\n * Node MCP HTTP server configuration, built with fluent setters: every name sits next to its own\n * value, so no two settings can be swapped, and a future setting is an additive setter rather than a\n * breaking signature change.\n *\n * WHICH tools the server serves is not here: that is the `McpToolset` handed to\n * `new WpMcpServer(config, toolset)`. This config says only who the server is and HOW A CALLER GETS\n * IN — {@link setAuth}, an OR-set of the same `@WpAuth` builders an endpoint uses plus `mcpOAuth(...)`.\n *\n * SINGLE CANONICAL ORIGIN: one server serves exactly one resource. {@link setResource} is fixed for\n * the lifetime of the process, is the audience of every `mcpOAuth(...)` access token, and is never\n * derived from the request `Host` — see that setter for why a host-derived audience is a confused\n * 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 — which, with `mcpOAuth(...)`, is the OAuth discovery\n * signal, so the client's correct response is to re-authenticate and fail again, forever.\n *\n * ```ts\n * const config = new WpMcpServerConfig()\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/user')\n * .setAuth([oidcSameProject()]) // internal svc-to-svc MCP\n * .setCredentialVerifiers(router.credentialVerifiers());\n * // .setAuth([oidcSameProject(), jwt(), apiKey('partner', creds),\n * // mcpOAuth(authority, ['https://login.example.com'], ['tools'], McpAccessTokenFormat.opaquePrefix('wpmcp_'))])\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 {\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 authValue?: McpHttpAuths;\n private credentialVerifiersValue?: CredentialVerifiers;\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 resource URI, which must be an absolute URL: it is the exact\n * audience every `mcpOAuth(...)` access token is checked against, and {@link resourceMetadataUrl}\n * is 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 /**\n * REQUIRED. HOW a caller gets in: an OR-set of the `@WpAuth` builders endpoints already use —\n * `oidcSameProject()`, `oidcSvcs([...])`, `jwt()`, `apiKey(regime, creds)`, `sharedSecret(key)` —\n * plus `mcpOAuth(...)` for off-the-shelf connectors. Each request is CLASSIFIED from unverified\n * surface facts (which header; a JWT's unverified `iss`) and handed to exactly ONE verifier, whose\n * rejection is the answer — it never falls through to another mode.\n *\n * `vendorAuth(...)` and `@WpAuthPublic` identify no caller to authorize tools against, so they are\n * refused at bind; so is a set whose modes could both claim one request.\n */\n setAuth(auth: McpHttpAuths): this {\n if (!Array.isArray(auth) || auth.length === 0) {\n throw new Error('WpMcpServerConfig.setAuth(...) requires at least one auth mode.');\n }\n this.authValue = Object.freeze([...auth]) as McpHttpAuths;\n return this;\n }\n\n /**\n * REQUIRED when {@link setAuth} holds any `@WpAuth` mode (everything but `mcpOAuth(...)`): the\n * verifiers this app bound, from `router.credentialVerifiers()` — the SAME JwtHook, OidcHook /\n * DefaultOidcVerifier, AuthConfig and ApiKeyHook the endpoint `AuthFilter` uses.\n */\n setCredentialVerifiers(verifiers: CredentialVerifiers): this {\n if (!verifiers || typeof verifiers.verifyOidc !== 'function') {\n throw new Error(\n 'WpMcpServerConfig.setCredentialVerifiers(...) requires router.credentialVerifiers().',\n );\n }\n this.credentialVerifiersValue = verifiers;\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.authValue === undefined) missing.push('setAuth([...])');\n const needsVerifiers = (this.authValue ?? []).some(\n (mode: McpHttpAuth) => mode.kind !== 'mcp-oauth',\n );\n if (needsVerifiers && this.credentialVerifiersValue === undefined) {\n missing.push('setCredentialVerifiers(router.credentialVerifiers())');\n }\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 auth(): McpHttpAuths {\n return this.read(this.authValue, 'setAuth');\n }\n\n /** The bound verifiers, or `undefined` for an `mcpOAuth(...)`-only server, which needs none. */\n get credentialVerifiers(): CredentialVerifiers | undefined {\n return this.credentialVerifiersValue;\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 `mcpOAuth(...)` 401 `WWW-Authenticate`\n * challenge. webpieces mounts no `.well-known` route; publish\n * `WpMcpServer.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 /** 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 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\n/** Google's OIDC issuers. An `mcpOAuth(...)` issuer may never be one: it would claim Google tokens. */\nexport const GOOGLE_OIDC_ISSUERS: readonly string[] = Object.freeze([\n 'https://accounts.google.com',\n 'accounts.google.com',\n]);\n\n/** The unsigned off-GCP OIDC token prefix `@webpieces/gcp-identity` mints and verifies. */\nexport const DEV_OIDC_TOKEN_PREFIX = 'dev-oidc.';\n\n/** Every JWT starts with the base64url of `{\"`. An opaque prefix starting so would claim JWTs. */\nexport const JWT_PREFIX = 'eyJ';\n\nexport type McpAccessTokenFormatKind = 'opaque-prefix' | 'jwt-issuer' | 'every-bearer';\n\n/** The trusted token issuers of an `mcpOAuth(...)` mode: at least one absolute URL. */\nexport type McpAuthorizationServers = readonly [string, ...string[]];\n\n/**\n * How an `mcpOAuth(...)` access token LOOKS, decided by the authority that mints it. The MCP door\n * classifies every request from unverified surface facts before handing it to exactly ONE verifier, so\n * the OAuth mode must say which bearers are its kind without any crypto or I/O:\n *\n * - {@link opaquePrefix} — the authority mints opaque tokens that all start with `prefix`\n * (e.g. `'wpmcp_'`). Disjoint from every JWT and from off-GCP OIDC tokens by construction.\n * - {@link jwtIssuer} — the authority mints JWTs whose `iss` is one of the mode's\n * `authorizationServers`. A JWT with any other issuer is not this mode's.\n * - {@link everyBearer} — every `Authorization: Bearer` credential is this mode's. Legal ONLY when no\n * other bearer mode (`oidcSameProject()`, `oidcSvcs(...)`, `jwt()`, a bearer `apiKey(...)`) is in the\n * same `setAuth([...])`; startup refuses the combination, because two modes claiming one token is\n * exactly the overlap classify-then-verify forbids.\n */\nexport class McpAccessTokenFormat {\n private constructor(\n public readonly kind: McpAccessTokenFormatKind,\n public readonly prefix: string,\n ) {}\n\n // webpieces-disable no-function-outside-class -- explicit public factory of a closed set of formats\n static opaquePrefix(prefix: string): McpAccessTokenFormat {\n if (typeof prefix !== 'string' || prefix.trim() === '' || /\\s/.test(prefix)) {\n throw new Error(\n 'McpAccessTokenFormat.opaquePrefix(...) requires a nonblank prefix with no whitespace.',\n );\n }\n for (const reserved of [JWT_PREFIX, DEV_OIDC_TOKEN_PREFIX]) {\n if (reserved.startsWith(prefix) || prefix.startsWith(reserved)) {\n throw new Error(\n `McpAccessTokenFormat.opaquePrefix('${prefix}') overlaps '${reserved}', which ` +\n (reserved === JWT_PREFIX\n ? 'every JWT starts with'\n : 'every off-GCP OIDC token starts with') +\n \"; pick a prefix the authority alone mints, e.g. 'wpmcp_'.\",\n );\n }\n }\n return new McpAccessTokenFormat('opaque-prefix', prefix);\n }\n\n // webpieces-disable no-function-outside-class -- explicit public factory of a closed set of formats\n static jwtIssuer(): McpAccessTokenFormat {\n return new McpAccessTokenFormat('jwt-issuer', '');\n }\n\n // webpieces-disable no-function-outside-class -- explicit public factory of a closed set of formats\n static everyBearer(): McpAccessTokenFormat {\n return new McpAccessTokenFormat('every-bearer', '');\n }\n}\n\n/**\n * The MCP OAuth mode: today's connector behavior (Claude / ChatGPT) as ONE member of a server's\n * `setAuth([...])` set. It is the only mode that publishes RFC 9728 protected-resource metadata and\n * answers a request carrying no credential with the `401 WWW-Authenticate` discovery challenge, and\n * its access tokens are audience-bound to the server's canonical resource. Build it with\n * {@link mcpOAuth}.\n */\nexport class McpOAuthMode {\n readonly kind = 'mcp-oauth';\n\n constructor(\n public readonly authority: McpAccessTokenAuthority<never>,\n public readonly authorizationServers: McpAuthorizationServers,\n public readonly requiredScopes: readonly string[],\n public readonly tokenFormat: McpAccessTokenFormat,\n public readonly maxAccountValidationAgeSeconds: number,\n ) {\n if (!authority || typeof authority.verifyAccessToken !== 'function') {\n throw new Error('mcpOAuth(...) requires an McpAccessTokenAuthority.');\n }\n if (!Array.isArray(authorizationServers) || authorizationServers.length === 0) {\n throw new Error('mcpOAuth(...) requires at least one authorization server issuer URL.');\n }\n for (const server of authorizationServers) {\n if (typeof server !== 'string' || !URL.canParse(server)) {\n throw new Error(\n `mcpOAuth(...) authorization servers must be absolute URLs, got '${String(server)}'.`,\n );\n }\n }\n if (\n !Array.isArray(requiredScopes) ||\n !requiredScopes.every(\n (scope: string) => typeof scope === 'string' && scope.trim() !== '',\n )\n ) {\n throw new Error(\n 'mcpOAuth(...) requires an array of nonblank required scopes (empty only by saying so: []).',\n );\n }\n if (!(tokenFormat instanceof McpAccessTokenFormat)) {\n throw new Error(\n 'mcpOAuth(...) requires an McpAccessTokenFormat describing the tokens the authority mints.',\n );\n }\n if (\n !Number.isFinite(maxAccountValidationAgeSeconds) ||\n maxAccountValidationAgeSeconds <= 0 ||\n maxAccountValidationAgeSeconds > MAX_MCP_ACCOUNT_VALIDATION_AGE_SECONDS\n ) {\n throw new Error(\n `mcpOAuth(...) maxAccountValidationAgeSeconds must be 1..${MAX_MCP_ACCOUNT_VALIDATION_AGE_SECONDS}, ` +\n `got ${String(maxAccountValidationAgeSeconds)}.`,\n );\n }\n Object.freeze(this);\n }\n\n /** RFC 9728 metadata for `resource`, published by the app at the server's `resourceMetadataUrl`. */\n protectedResourceMetadata(resource: string): McpProtectedResourceMetadata {\n return new McpProtectedResourceMetadata(\n resource,\n this.authorizationServers,\n this.requiredScopes,\n );\n }\n}\n\n/**\n * The MCP OAuth door mode, for off-the-shelf connectors. Everything OAuth-only lives here and is\n * required only when this mode is in the server's `setAuth([...])`:\n *\n * @param authority the application-owned paired mint/verify authority\n * @param authorizationServers the trusted token issuers, published as `authorization_servers`\n * @param requiredScopes scopes every access token must carry before any tool is dispatched\n * @param tokenFormat how the authority's tokens look, so the door can classify them\n * @param maxAccountValidationAgeSeconds how fresh the account facts must be, 1 second to 1 hour\n */\n// webpieces-disable no-function-outside-class -- canonical credential descriptor factory, the twin of jwt()/oidcSameProject()\nexport function mcpOAuth<TGrant>(\n authority: McpAccessTokenAuthority<TGrant>,\n authorizationServers: McpAuthorizationServers,\n requiredScopes: readonly string[],\n tokenFormat: McpAccessTokenFormat,\n maxAccountValidationAgeSeconds: number = MAX_MCP_ACCOUNT_VALIDATION_AGE_SECONDS,\n): McpOAuthMode {\n return new McpOAuthMode(\n authority as McpAccessTokenAuthority<never>,\n authorizationServers,\n requiredScopes,\n tokenFormat,\n maxAccountValidationAgeSeconds,\n );\n}\n"]}
@@ -1,5 +1,6 @@
1
- import { AuthenticatedCaller, JwtHook, MintedJwt } from '@webpieces/http-routing';
1
+ import { AuthenticatedCaller, AuthorizationService, JwtHook, MintedJwt } from '@webpieces/http-routing';
2
2
  import { McpAccessTokenAuthority, MintedMcpAccessToken, VerifiedMcpCredential, WpMcpServerConfig } from './McpAuth';
3
+ import { McpApiBinding } from './McpApiBinding';
3
4
  declare class ExampleJwtMintRequest {
4
5
  readonly subject: string;
5
6
  constructor(subject: string);
@@ -22,6 +23,15 @@ export declare class McpPrincipalCompileAssertions {
22
23
  }
23
24
  /** Profile-selection arity is checked by tsc, not only by runtime specs. */
24
25
  export declare class McpProfilesCompileAssertions {
25
- empty(config: WpMcpServerConfig<string>): void;
26
+ empty(bindings: readonly McpApiBinding[], policy: AuthorizationService): void;
27
+ }
28
+ /**
29
+ * The OAuth-only setters moved into `mcpOAuth(...)` (#1416) and the toolset owns WHICH tools: none of
30
+ * the old spellings compiles, so an agent upgrading is told by tsc, not by a deprecation it never sees.
31
+ */
32
+ export declare class McpServerConfigCompileAssertions {
33
+ removedOAuthSetters(config: WpMcpServerConfig, authority: McpAccessTokenAuthority<string>): void;
34
+ noPublicDoor(config: WpMcpServerConfig): void;
35
+ removedToolsetSetters(config: WpMcpServerConfig, policy: AuthorizationService): void;
26
36
  }
27
37
  export {};