@webpieces/mcp-server 0.4.990 → 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
@@ -0,0 +1,455 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.McpDoorAuthenticator = exports.McpOAuthDoorMode = exports.McpDoorPass = exports.McpDoorRequest = exports.JwtPeek = void 0;
4
+ const core_context_1 = require("@webpieces/core-context");
5
+ const core_util_1 = require("@webpieces/core-util");
6
+ const http_routing_1 = require("@webpieces/http-routing");
7
+ const McpAuth_1 = require("./McpAuth");
8
+ const log = core_util_1.LogManager.getLogger('McpDoorAuth');
9
+ const BEARER_SCHEME = 'Bearer ';
10
+ const SHARED_SECRET_SCHEME = 'Webpieces ';
11
+ /**
12
+ * JwtPeek: base64url-decode a JWT's PAYLOAD only. NEVER trusted — the result is used solely to pick
13
+ * which ONE verifier sees the request, and that verifier fully verifies, so a token that merely
14
+ * claims an issuer fails its verification and is a 401. It never falls through to another mode.
15
+ */
16
+ class JwtPeek {
17
+ /** The unverified payload claims, or `undefined` when `token` is not a well-formed JWT. */
18
+ // webpieces-disable no-function-outside-class -- pure decoder used by classification only
19
+ static payloadOf(token) {
20
+ const parts = token.split('.');
21
+ if (parts.length !== 3 || parts.some((part) => part === ''))
22
+ return undefined;
23
+ const header = JwtPeek.decodeRecord(parts[0]);
24
+ return header === undefined ? undefined : JwtPeek.decodeRecord(parts[1]);
25
+ }
26
+ /** The unverified `iss` claim, or `undefined`. */
27
+ // webpieces-disable no-function-outside-class -- pure decoder used by classification only
28
+ static issuerOf(token) {
29
+ const iss = JwtPeek.payloadOf(token)?.['iss'];
30
+ return typeof iss === 'string' ? iss : undefined;
31
+ }
32
+ // webpieces-disable no-function-outside-class -- pure decoder used by classification only
33
+ static decodeRecord(segment) {
34
+ const text = Buffer.from(segment, 'base64url').toString('utf8');
35
+ if (!text.startsWith('{'))
36
+ return undefined;
37
+ // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- a malformed segment is simply "not a JWT" for classification; the chosen verifier decides the rest
38
+ try {
39
+ const value = JSON.parse(text);
40
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
41
+ ? value
42
+ : undefined;
43
+ }
44
+ catch (err) {
45
+ const error = (0, core_util_1.toError)(err);
46
+ log.debug('Bearer segment is not JSON; classifying it as not a JWT', error);
47
+ return undefined;
48
+ }
49
+ }
50
+ }
51
+ exports.JwtPeek = JwtPeek;
52
+ /** The inbound facts one door mode classifies and verifies: the transport-neutral request. */
53
+ class McpDoorRequest {
54
+ http;
55
+ constructor(http) {
56
+ this.http = http;
57
+ }
58
+ header(name) {
59
+ return this.http.getHeader(name.toLowerCase());
60
+ }
61
+ /** The `Authorization: Bearer <token>` value, or `undefined`. */
62
+ bearer() {
63
+ const value = this.header('authorization');
64
+ if (!value || !value.startsWith(BEARER_SCHEME))
65
+ return undefined;
66
+ const token = value.substring(BEARER_SCHEME.length);
67
+ return token === '' || /\s/.test(token) ? undefined : token;
68
+ }
69
+ /** The `Authorization: Webpieces <secret>` value, or `undefined`. */
70
+ sharedSecret() {
71
+ const value = this.header('authorization');
72
+ if (!value || !value.startsWith(SHARED_SECRET_SCHEME))
73
+ return undefined;
74
+ return value.substring(SHARED_SECRET_SCHEME.length);
75
+ }
76
+ }
77
+ exports.McpDoorRequest = McpDoorRequest;
78
+ /**
79
+ * What a successful authentication established. The caller itself is already PUBLISHED into
80
+ * `RequestContext` by the mode; these are the facts the door needs afterwards.
81
+ */
82
+ class McpDoorPass {
83
+ mode;
84
+ callerVerified;
85
+ token;
86
+ subject;
87
+ scopes;
88
+ expiresAtEpochSeconds;
89
+ constructor(
90
+ /** Which mode authenticated, e.g. `jwt()`, `mcpOAuth(...)`. */
91
+ mode,
92
+ /** True for an internal hop (OIDC, shared secret) whose forwarded trusted context is believed. */
93
+ callerVerified,
94
+ /** The raw credential, for the SDK's `AuthInfo.token`. Never forwarded downstream. */
95
+ token,
96
+ /** The verified subject (user id or machine identity). */
97
+ subject, scopes, expiresAtEpochSeconds) {
98
+ this.mode = mode;
99
+ this.callerVerified = callerVerified;
100
+ this.token = token;
101
+ this.subject = subject;
102
+ this.scopes = scopes;
103
+ this.expiresAtEpochSeconds = expiresAtEpochSeconds;
104
+ }
105
+ }
106
+ exports.McpDoorPass = McpDoorPass;
107
+ /** `oidcSameProject()` / `oidcSvcs([...])` — ONE bearer credential, verified once against both. */
108
+ class OidcDoorMode {
109
+ methods;
110
+ verifiers;
111
+ name;
112
+ bearerFamily = true;
113
+ constructor(methods, verifiers) {
114
+ this.methods = methods;
115
+ this.verifiers = verifiers;
116
+ this.name = methods
117
+ .map((method) => method.callerScope === 'same-project' ? 'oidcSameProject()' : 'oidcSvcs([...])')
118
+ .join(' + ');
119
+ }
120
+ claims(req) {
121
+ const token = req.bearer();
122
+ if (token === undefined)
123
+ return false;
124
+ if (token.startsWith(McpAuth_1.DEV_OIDC_TOKEN_PREFIX))
125
+ return true;
126
+ const issuer = JwtPeek.issuerOf(token);
127
+ return issuer !== undefined && McpAuth_1.GOOGLE_OIDC_ISSUERS.includes(issuer);
128
+ }
129
+ async authenticate(req) {
130
+ const token = req.bearer();
131
+ const identity = await this.verifiers.verifyOidc(token, this.methods);
132
+ core_context_1.RequestContext.putTrusted(http_routing_1.VERIFIED_MACHINE_CALLER, new http_routing_1.VerifiedMachineCaller(identity.mechanism, identity.identity));
133
+ return new McpDoorPass(this.name, true, token, identity.identity, []);
134
+ }
135
+ }
136
+ /** `sharedSecret(key)` — the `Authorization: Webpieces <secret>` scheme, disjoint from every bearer. */
137
+ class SharedSecretDoorMode {
138
+ secretKey;
139
+ verifiers;
140
+ name;
141
+ bearerFamily = false;
142
+ constructor(secretKey, verifiers) {
143
+ this.secretKey = secretKey;
144
+ this.verifiers = verifiers;
145
+ this.name = `sharedSecret('${secretKey}')`;
146
+ }
147
+ claims(req) {
148
+ return req.sharedSecret() !== undefined;
149
+ }
150
+ async authenticate(req) {
151
+ const secret = req.sharedSecret();
152
+ this.verifiers.verifySharedSecret(secret, this.secretKey);
153
+ core_context_1.RequestContext.putTrusted(http_routing_1.VERIFIED_MACHINE_CALLER, new http_routing_1.VerifiedMachineCaller('shared-secret', this.secretKey));
154
+ return new McpDoorPass(this.name, true, secret, `shared-secret:${this.secretKey}`, []);
155
+ }
156
+ }
157
+ /**
158
+ * `apiKey(regime, credentials)` — claimed by its OWN declared headers. A bearer credential in the
159
+ * regime also claims `Authorization: Bearer`, which is why startup refuses it beside any other bearer
160
+ * mode.
161
+ */
162
+ class ApiKeyDoorMode {
163
+ regime;
164
+ verifiers;
165
+ name;
166
+ bearerFamily;
167
+ headers;
168
+ callerContext = new http_routing_1.AuthenticatedCallerContext();
169
+ constructor(regime, credentials, verifiers) {
170
+ this.regime = regime;
171
+ this.verifiers = verifiers;
172
+ this.name = `apiKey('${regime}', ...)`;
173
+ const headers = [];
174
+ let bearer = false;
175
+ for (const credential of credentials) {
176
+ if (credential.in === 'bearer') {
177
+ bearer = true;
178
+ continue;
179
+ }
180
+ const header = (credential.key.httpHeader ?? '').toLowerCase();
181
+ if (header === 'authorization') {
182
+ throw new Error(`${this.name} declares its header credential on 'authorization', which every other ` +
183
+ "mode's credential rides; declare it { in: 'bearer' } or on its own header.");
184
+ }
185
+ headers.push(header);
186
+ }
187
+ this.headers = headers;
188
+ this.bearerFamily = bearer;
189
+ }
190
+ claims(req) {
191
+ if (this.headers.some((header) => req.header(header) !== undefined))
192
+ return true;
193
+ return this.bearerFamily && req.bearer() !== undefined;
194
+ }
195
+ async authenticate(req) {
196
+ const caller = await this.verifiers.verifyApiKey(this.regime, req.http);
197
+ this.callerContext.publish(caller);
198
+ return new McpDoorPass(this.name, false, '', caller.userId ?? caller.machine?.identity ?? this.regime, []);
199
+ }
200
+ }
201
+ /**
202
+ * `jwt()` — the app's own user token. It claims the REMAINDER of the bearer space: every
203
+ * `Authorization: Bearer` credential that no other configured bearer mode claims. That makes it
204
+ * disjoint from them by construction, with no issuer to configure.
205
+ */
206
+ class JwtDoorMode {
207
+ verifiers;
208
+ name = 'jwt()';
209
+ bearerFamily = true;
210
+ callerContext = new http_routing_1.AuthenticatedCallerContext();
211
+ others = [];
212
+ constructor(verifiers) {
213
+ this.verifiers = verifiers;
214
+ }
215
+ excluding(others) {
216
+ this.others = others.filter((mode) => mode !== this && mode.bearerFamily);
217
+ }
218
+ claims(req) {
219
+ return (req.bearer() !== undefined && !this.others.some((mode) => mode.claims(req)));
220
+ }
221
+ async authenticate(req) {
222
+ const token = req.bearer();
223
+ const caller = await this.verifiers.verifyJwt(token);
224
+ this.callerContext.publish(caller);
225
+ return new McpDoorPass(this.name, false, token, caller.userId ?? caller.machine?.identity ?? 'jwt', []);
226
+ }
227
+ }
228
+ /** `mcpOAuth(...)` — an audience-bound access token minted by the application's authority. */
229
+ class McpOAuthDoorMode {
230
+ mode;
231
+ resource;
232
+ name = 'mcpOAuth(...)';
233
+ bearerFamily = true;
234
+ callerContext = new http_routing_1.AuthenticatedCallerContext();
235
+ constructor(mode, resource) {
236
+ this.mode = mode;
237
+ this.resource = resource;
238
+ }
239
+ claims(req) {
240
+ const token = req.bearer();
241
+ if (token === undefined)
242
+ return false;
243
+ const format = this.mode.tokenFormat;
244
+ switch (format.kind) {
245
+ case 'every-bearer':
246
+ return true;
247
+ case 'opaque-prefix':
248
+ return token.startsWith(format.prefix);
249
+ case 'jwt-issuer': {
250
+ const issuer = JwtPeek.issuerOf(token);
251
+ return issuer !== undefined && this.mode.authorizationServers.includes(issuer);
252
+ }
253
+ }
254
+ }
255
+ async authenticate(req) {
256
+ const token = req.bearer();
257
+ const credential = await this.mode.authority.verifyAccessToken(token, this.resource);
258
+ this.validateCredential(credential);
259
+ this.callerContext.publish(credential.caller);
260
+ return new McpDoorPass(this.name, false, token, credential.subject, credential.scopes, credential.expiresAtEpochSeconds);
261
+ }
262
+ validateCredential(credential) {
263
+ const now = Math.floor(Date.now() / 1000);
264
+ const reject = (reason) => {
265
+ throw new core_util_1.ApiUnauthorizedError(`MCP access token rejected: ${reason}`);
266
+ };
267
+ if (credential.subject.trim() === '')
268
+ reject('no subject');
269
+ const stamps = [
270
+ credential.issuedAtEpochSeconds,
271
+ credential.expiresAtEpochSeconds,
272
+ credential.accountValidatedAtEpochSeconds,
273
+ ];
274
+ if (!stamps.every(Number.isFinite))
275
+ reject('security timestamps must be finite');
276
+ if (credential.resource !== this.resource)
277
+ reject('resource mismatch');
278
+ if (!this.mode.authorizationServers.includes(credential.issuer)) {
279
+ reject('issuer is not trusted');
280
+ }
281
+ if (credential.issuedAtEpochSeconds > now || credential.expiresAtEpochSeconds <= now) {
282
+ reject('outside its valid time window');
283
+ }
284
+ const lifetime = credential.expiresAtEpochSeconds - credential.issuedAtEpochSeconds;
285
+ if (lifetime > McpAuth_1.MAX_MCP_ACCESS_TOKEN_LIFETIME_SECONDS)
286
+ reject('lifetime exceeds 30 days');
287
+ for (const scope of this.mode.requiredScopes) {
288
+ if (!credential.scopes.includes(scope))
289
+ reject(`missing required scope '${scope}'`);
290
+ }
291
+ const validatedAge = now - credential.accountValidatedAtEpochSeconds;
292
+ if (credential.accountValidatedAtEpochSeconds > now ||
293
+ validatedAge > this.mode.maxAccountValidationAgeSeconds) {
294
+ reject('account authorization state is not fresh enough for dispatch');
295
+ }
296
+ }
297
+ }
298
+ exports.McpOAuthDoorMode = McpOAuthDoorMode;
299
+ /**
300
+ * CLASSIFY, then verify with exactly ONE verifier.
301
+ *
302
+ * Credentials are self-describing, so one path serves every mode. The request is classified from
303
+ * UNVERIFIED surface facts — which header; a JWT's unverified `iss` — then handed to exactly ONE mode.
304
+ * Classifying on unverified data is safe because the chosen verifier fully verifies: a token that
305
+ * merely claims `iss: accounts.google.com` fails Google's verification and the request is a 401. It
306
+ * never falls through to another mode.
307
+ *
308
+ * - No claimant: only an `mcpOAuth(...)` server answers with the discovery challenge; others a plain 401.
309
+ * - Two claimants (an api-key header AND a bearer): ambiguous, refused — never "pick the one that works".
310
+ * - One claimant: its rejection IS the answer.
311
+ *
312
+ * The set is checked at construction (bind time): `vendorAuth` / public are refused, every mode's
313
+ * verifier must be bound, and no two modes may be able to claim one request.
314
+ */
315
+ class McpDoorAuthenticator {
316
+ modes;
317
+ oauth;
318
+ constructor(modes, oauth) {
319
+ this.modes = modes;
320
+ this.oauth = oauth;
321
+ }
322
+ // webpieces-disable no-function-outside-class -- validating factory; the constructor stays private so an unchecked set cannot exist
323
+ static create(auth, resource, verifiers) {
324
+ const oauthModes = auth.filter((mode) => mode instanceof McpAuth_1.McpOAuthMode);
325
+ if (oauthModes.length > 1) {
326
+ throw new Error('WpMcpServerConfig.setAuth([...]) accepts at most one mcpOAuth(...) mode.');
327
+ }
328
+ McpDoorAuthenticator.refuseNonCallers(auth);
329
+ const methods = auth.filter((mode) => !(mode instanceof McpAuth_1.McpOAuthMode));
330
+ if (methods.length > 0) {
331
+ // The same declaration rules @WpAuth applies: no duplicate kinds (one oidc per scope), valid shapes.
332
+ new core_util_1.AuthMeta([methods[0], ...methods.slice(1)], undefined);
333
+ }
334
+ const modes = [];
335
+ const oauth = oauthModes[0] === undefined ? undefined : new McpOAuthDoorMode(oauthModes[0], resource);
336
+ if (methods.length > 0) {
337
+ if (!verifiers) {
338
+ throw new Error('WpMcpServerConfig.setAuth([...]) holds @WpAuth modes; add setCredentialVerifiers(router.credentialVerifiers()).');
339
+ }
340
+ McpDoorAuthenticator.refuseUnverifiable(methods, verifiers);
341
+ modes.push(...McpDoorAuthenticator.doorModes(methods, verifiers));
342
+ }
343
+ if (oauth)
344
+ modes.push(oauth);
345
+ McpDoorAuthenticator.refuseOverlaps(modes, oauth);
346
+ for (const mode of modes)
347
+ if (mode instanceof JwtDoorMode)
348
+ mode.excluding(modes);
349
+ return new McpDoorAuthenticator(modes, oauth);
350
+ }
351
+ /**
352
+ * Whether a 401 for this request carries the `mcpOAuth(...)` discovery challenge: only when the
353
+ * server has that mode AND the request either carried no credential at all or was classified as an
354
+ * OAuth access token. A rejected `jwt()` / OIDC / api-key credential gets a plain 401, with no hint
355
+ * of which mode came closest.
356
+ */
357
+ challengeApplies(req) {
358
+ if (!this.oauth)
359
+ return false;
360
+ const claimants = this.modes.filter((mode) => mode.claims(req));
361
+ return claimants.length === 0 || (claimants.length === 1 && claimants[0] === this.oauth);
362
+ }
363
+ async authenticate(req) {
364
+ const claimants = this.modes.filter((mode) => mode.claims(req));
365
+ if (claimants.length === 0) {
366
+ throw new core_util_1.ApiUnauthorizedError('MCP request has no credential this server accepts.');
367
+ }
368
+ if (claimants.length > 1) {
369
+ throw new core_util_1.ApiUnauthorizedError('MCP request carries more than one credential; send exactly one.');
370
+ }
371
+ return claimants[0].authenticate(req);
372
+ }
373
+ // webpieces-disable no-function-outside-class -- bind-time validation helper of the private constructor
374
+ static refuseNonCallers(auth) {
375
+ // A public mode does not compile here (McpHttpAuth excludes it); one smuggled past the type is
376
+ // refused by the AuthMeta declaration check that follows.
377
+ for (const method of auth) {
378
+ if (method.kind === 'vendorAuth') {
379
+ throw new Error('WpMcpServerConfig.setAuth([...]) refuses vendorAuth(...): ' +
380
+ 'it identifies no caller to authorize tools against. Use oidcSameProject(), oidcSvcs([...]), ' +
381
+ 'jwt(), apiKey(...), sharedSecret(...) or mcpOAuth(...).');
382
+ }
383
+ }
384
+ }
385
+ // webpieces-disable no-function-outside-class -- bind-time validation helper of the private constructor
386
+ static refuseUnverifiable(methods, verifiers) {
387
+ const missing = methods
388
+ .map((method) => verifiers.missingVerifier(method))
389
+ .filter((line) => line !== undefined);
390
+ if (missing.length > 0) {
391
+ throw new Error('WpMcpServerConfig.setAuth([...]) accepts a mode this server cannot verify, so it refuses to start. Add:\n ' +
392
+ missing.join('\n '));
393
+ }
394
+ }
395
+ // webpieces-disable no-function-outside-class -- bind-time construction helper of the private constructor
396
+ static doorModes(methods, verifiers) {
397
+ const modes = [];
398
+ const oidc = methods.filter((method) => method.kind === 'oidc');
399
+ if (oidc[0] !== undefined)
400
+ modes.push(new OidcDoorMode([oidc[0], ...oidc.slice(1)], verifiers));
401
+ for (const method of methods) {
402
+ switch (method.kind) {
403
+ case 'oidc':
404
+ break;
405
+ case 'jwt':
406
+ modes.push(new JwtDoorMode(verifiers));
407
+ break;
408
+ case 'shared-secret':
409
+ modes.push(new SharedSecretDoorMode(method.secretKey, verifiers));
410
+ break;
411
+ case 'apikey':
412
+ modes.push(new ApiKeyDoorMode(method.regime, method.credentials, verifiers));
413
+ break;
414
+ case 'vendorAuth':
415
+ throw new core_util_1.ApiImplementationError('vendorAuth was refused before door modes are built.');
416
+ }
417
+ }
418
+ return modes;
419
+ }
420
+ /**
421
+ * `claims()` sets must be DISJOINT BY CONSTRUCTION. The bearer space is the only shared one, so the
422
+ * refusals are about it:
423
+ * - `mcpOAuth(..., everyBearer())` claims every bearer, so it stands alone in the bearer family;
424
+ * - a bearer `apiKey(...)` claims every bearer too, so it stands alone as well;
425
+ * - `mcpOAuth(..., jwtIssuer())` may not list a Google issuer beside an OIDC mode.
426
+ * `jwt()` claims the remainder, and `McpAccessTokenFormat.opaquePrefix` refuses a prefix that could
427
+ * overlap a JWT or a dev-oidc token, so nothing else can collide.
428
+ */
429
+ // webpieces-disable no-function-outside-class -- bind-time validation helper of the private constructor
430
+ static refuseOverlaps(modes, oauth) {
431
+ const bearerModes = modes.filter((mode) => mode.bearerFamily);
432
+ const greedy = bearerModes.filter((mode) => mode instanceof ApiKeyDoorMode ||
433
+ (mode instanceof McpOAuthDoorMode && mode.mode.tokenFormat.kind === 'every-bearer'));
434
+ for (const mode of greedy) {
435
+ if (bearerModes.length > 1) {
436
+ const others = bearerModes
437
+ .filter((other) => other !== mode)
438
+ .map((other) => other.name);
439
+ throw new Error(`WpMcpServerConfig.setAuth([...]): ${mode.name} claims EVERY bearer credential, so it overlaps ` +
440
+ `${others.join(', ')}. Give it a disjoint shape (mcpOAuth: McpAccessTokenFormat.opaquePrefix(...) ` +
441
+ 'or .jwtIssuer(); apiKey: a header credential) or remove the other bearer modes.');
442
+ }
443
+ }
444
+ const oidc = modes.some((mode) => mode instanceof OidcDoorMode);
445
+ if (oauth && oidc && oauth.mode.tokenFormat.kind === 'jwt-issuer') {
446
+ const google = oauth.mode.authorizationServers.filter((issuer) => McpAuth_1.GOOGLE_OIDC_ISSUERS.includes(issuer));
447
+ if (google.length > 0) {
448
+ throw new Error(`WpMcpServerConfig.setAuth([...]): mcpOAuth(...) trusts the Google issuer ${google.join(', ')}, ` +
449
+ 'so it overlaps the OIDC mode, which claims every Google-issued token.');
450
+ }
451
+ }
452
+ }
453
+ }
454
+ exports.McpDoorAuthenticator = McpDoorAuthenticator;
455
+ //# sourceMappingURL=McpDoorAuth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"McpDoorAuth.js","sourceRoot":"","sources":["../../../../../packages/http/mcp-server/src/McpDoorAuth.ts"],"names":[],"mappings":"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","sourcesContent":["import { HttpRequest, RequestContext } from '@webpieces/core-context';\nimport {\n ApiImplementationError,\n ApiKeyCredential,\n ApiUnauthorizedError,\n AuthMeta,\n AuthMethod,\n DtoValue,\n LogManager,\n OidcAuthMethod,\n OidcAuthMethods,\n toError,\n} from '@webpieces/core-util';\nimport {\n AuthenticatedCallerContext,\n CredentialVerifiers,\n VERIFIED_MACHINE_CALLER,\n VerifiedMachineCaller,\n} from '@webpieces/http-routing';\nimport {\n MAX_MCP_ACCESS_TOKEN_LIFETIME_SECONDS,\n McpHttpAuth,\n VerifiedMcpCredential,\n DEV_OIDC_TOKEN_PREFIX,\n GOOGLE_OIDC_ISSUERS,\n McpOAuthMode,\n} from './McpAuth';\n\nconst log = LogManager.getLogger('McpDoorAuth');\nconst BEARER_SCHEME = 'Bearer ';\nconst SHARED_SECRET_SCHEME = 'Webpieces ';\n\n/**\n * JwtPeek: base64url-decode a JWT's PAYLOAD only. NEVER trusted — the result is used solely to pick\n * which ONE verifier sees the request, and that verifier fully verifies, so a token that merely\n * claims an issuer fails its verification and is a 401. It never falls through to another mode.\n */\nexport class JwtPeek {\n /** The unverified payload claims, or `undefined` when `token` is not a well-formed JWT. */\n // webpieces-disable no-function-outside-class -- pure decoder used by classification only\n static payloadOf(token: string): Record<string, DtoValue> | undefined {\n const parts = token.split('.');\n if (parts.length !== 3 || parts.some((part: string) => part === '')) return undefined;\n const header = JwtPeek.decodeRecord(parts[0]);\n return header === undefined ? undefined : JwtPeek.decodeRecord(parts[1]);\n }\n\n /** The unverified `iss` claim, or `undefined`. */\n // webpieces-disable no-function-outside-class -- pure decoder used by classification only\n static issuerOf(token: string): string | undefined {\n const iss = JwtPeek.payloadOf(token)?.['iss'];\n return typeof iss === 'string' ? iss : undefined;\n }\n\n // webpieces-disable no-function-outside-class -- pure decoder used by classification only\n private static decodeRecord(segment: string): Record<string, DtoValue> | undefined {\n const text = Buffer.from(segment, 'base64url').toString('utf8');\n if (!text.startsWith('{')) return undefined;\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- a malformed segment is simply \"not a JWT\" for classification; the chosen verifier decides the rest\n try {\n const value = JSON.parse(text) as DtoValue;\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n ? (value as Record<string, DtoValue>)\n : undefined;\n } catch (err: unknown) {\n const error = toError(err);\n log.debug('Bearer segment is not JSON; classifying it as not a JWT', error);\n return undefined;\n }\n }\n}\n\n/** The inbound facts one door mode classifies and verifies: the transport-neutral request. */\nexport class McpDoorRequest {\n constructor(public readonly http: HttpRequest) {}\n\n header(name: string): string | undefined {\n return this.http.getHeader(name.toLowerCase());\n }\n\n /** The `Authorization: Bearer <token>` value, or `undefined`. */\n bearer(): string | undefined {\n const value = this.header('authorization');\n if (!value || !value.startsWith(BEARER_SCHEME)) return undefined;\n const token = value.substring(BEARER_SCHEME.length);\n return token === '' || /\\s/.test(token) ? undefined : token;\n }\n\n /** The `Authorization: Webpieces <secret>` value, or `undefined`. */\n sharedSecret(): string | undefined {\n const value = this.header('authorization');\n if (!value || !value.startsWith(SHARED_SECRET_SCHEME)) return undefined;\n return value.substring(SHARED_SECRET_SCHEME.length);\n }\n}\n\n/**\n * What a successful authentication established. The caller itself is already PUBLISHED into\n * `RequestContext` by the mode; these are the facts the door needs afterwards.\n */\nexport class McpDoorPass {\n constructor(\n /** Which mode authenticated, e.g. `jwt()`, `mcpOAuth(...)`. */\n public readonly mode: string,\n /** True for an internal hop (OIDC, shared secret) whose forwarded trusted context is believed. */\n public readonly callerVerified: boolean,\n /** The raw credential, for the SDK's `AuthInfo.token`. Never forwarded downstream. */\n public readonly token: string,\n /** The verified subject (user id or machine identity). */\n public readonly subject: string,\n public readonly scopes: readonly string[],\n public readonly expiresAtEpochSeconds?: number,\n ) {}\n}\n\n/** What a door mode needs to say about a request BEFORE any verification. */\ninterface McpDoorMode {\n readonly name: string;\n /** Claims some `Authorization: Bearer` credentials, so it can overlap another bearer mode. */\n readonly bearerFamily: boolean;\n /** Pure, cheap, NO crypto, NO I/O: does this request carry MY kind of credential? */\n claims(req: McpDoorRequest): boolean;\n /** Full verification; throws `ApiUnauthorizedError` on reject. Publishes the trusted caller. */\n authenticate(req: McpDoorRequest): Promise<McpDoorPass>;\n}\n\n/** `oidcSameProject()` / `oidcSvcs([...])` — ONE bearer credential, verified once against both. */\nclass OidcDoorMode implements McpDoorMode {\n readonly name: string;\n readonly bearerFamily = true;\n\n constructor(\n private readonly methods: OidcAuthMethods,\n private readonly verifiers: CredentialVerifiers,\n ) {\n this.name = methods\n .map((method: OidcAuthMethod) =>\n method.callerScope === 'same-project' ? 'oidcSameProject()' : 'oidcSvcs([...])',\n )\n .join(' + ');\n }\n\n claims(req: McpDoorRequest): boolean {\n const token = req.bearer();\n if (token === undefined) return false;\n if (token.startsWith(DEV_OIDC_TOKEN_PREFIX)) return true;\n const issuer = JwtPeek.issuerOf(token);\n return issuer !== undefined && GOOGLE_OIDC_ISSUERS.includes(issuer);\n }\n\n async authenticate(req: McpDoorRequest): Promise<McpDoorPass> {\n const token = req.bearer()!;\n const identity = await this.verifiers.verifyOidc(token, this.methods);\n RequestContext.putTrusted(\n VERIFIED_MACHINE_CALLER,\n new VerifiedMachineCaller(identity.mechanism, identity.identity),\n );\n return new McpDoorPass(this.name, true, token, identity.identity, []);\n }\n}\n\n/** `sharedSecret(key)` — the `Authorization: Webpieces <secret>` scheme, disjoint from every bearer. */\nclass SharedSecretDoorMode implements McpDoorMode {\n readonly name: string;\n readonly bearerFamily = false;\n\n constructor(\n private readonly secretKey: string,\n private readonly verifiers: CredentialVerifiers,\n ) {\n this.name = `sharedSecret('${secretKey}')`;\n }\n\n claims(req: McpDoorRequest): boolean {\n return req.sharedSecret() !== undefined;\n }\n\n async authenticate(req: McpDoorRequest): Promise<McpDoorPass> {\n const secret = req.sharedSecret()!;\n this.verifiers.verifySharedSecret(secret, this.secretKey);\n RequestContext.putTrusted(\n VERIFIED_MACHINE_CALLER,\n new VerifiedMachineCaller('shared-secret', this.secretKey),\n );\n return new McpDoorPass(this.name, true, secret, `shared-secret:${this.secretKey}`, []);\n }\n}\n\n/**\n * `apiKey(regime, credentials)` — claimed by its OWN declared headers. A bearer credential in the\n * regime also claims `Authorization: Bearer`, which is why startup refuses it beside any other bearer\n * mode.\n */\nclass ApiKeyDoorMode implements McpDoorMode {\n readonly name: string;\n readonly bearerFamily: boolean;\n private readonly headers: readonly string[];\n private readonly callerContext = new AuthenticatedCallerContext();\n\n constructor(\n private readonly regime: string,\n credentials: readonly ApiKeyCredential[],\n private readonly verifiers: CredentialVerifiers,\n ) {\n this.name = `apiKey('${regime}', ...)`;\n const headers: string[] = [];\n let bearer = false;\n for (const credential of credentials) {\n if (credential.in === 'bearer') {\n bearer = true;\n continue;\n }\n const header = (credential.key.httpHeader ?? '').toLowerCase();\n if (header === 'authorization') {\n throw new Error(\n `${this.name} declares its header credential on 'authorization', which every other ` +\n \"mode's credential rides; declare it { in: 'bearer' } or on its own header.\",\n );\n }\n headers.push(header);\n }\n this.headers = headers;\n this.bearerFamily = bearer;\n }\n\n claims(req: McpDoorRequest): boolean {\n if (this.headers.some((header: string) => req.header(header) !== undefined)) return true;\n return this.bearerFamily && req.bearer() !== undefined;\n }\n\n async authenticate(req: McpDoorRequest): Promise<McpDoorPass> {\n const caller = await this.verifiers.verifyApiKey(this.regime, req.http);\n this.callerContext.publish(caller);\n return new McpDoorPass(\n this.name,\n false,\n '',\n caller.userId ?? caller.machine?.identity ?? this.regime,\n [],\n );\n }\n}\n\n/**\n * `jwt()` — the app's own user token. It claims the REMAINDER of the bearer space: every\n * `Authorization: Bearer` credential that no other configured bearer mode claims. That makes it\n * disjoint from them by construction, with no issuer to configure.\n */\nclass JwtDoorMode implements McpDoorMode {\n readonly name = 'jwt()';\n readonly bearerFamily = true;\n private readonly callerContext = new AuthenticatedCallerContext();\n private others: readonly McpDoorMode[] = [];\n\n constructor(private readonly verifiers: CredentialVerifiers) {}\n\n excluding(others: readonly McpDoorMode[]): void {\n this.others = others.filter((mode: McpDoorMode) => mode !== this && mode.bearerFamily);\n }\n\n claims(req: McpDoorRequest): boolean {\n return (\n req.bearer() !== undefined && !this.others.some((mode: McpDoorMode) => mode.claims(req))\n );\n }\n\n async authenticate(req: McpDoorRequest): Promise<McpDoorPass> {\n const token = req.bearer()!;\n const caller = await this.verifiers.verifyJwt(token);\n this.callerContext.publish(caller);\n return new McpDoorPass(\n this.name,\n false,\n token,\n caller.userId ?? caller.machine?.identity ?? 'jwt',\n [],\n );\n }\n}\n\n/** `mcpOAuth(...)` — an audience-bound access token minted by the application's authority. */\nexport class McpOAuthDoorMode implements McpDoorMode {\n readonly name = 'mcpOAuth(...)';\n readonly bearerFamily = true;\n private readonly callerContext = new AuthenticatedCallerContext();\n\n constructor(\n public readonly mode: McpOAuthMode,\n private readonly resource: string,\n ) {}\n\n claims(req: McpDoorRequest): boolean {\n const token = req.bearer();\n if (token === undefined) return false;\n const format = this.mode.tokenFormat;\n switch (format.kind) {\n case 'every-bearer':\n return true;\n case 'opaque-prefix':\n return token.startsWith(format.prefix);\n case 'jwt-issuer': {\n const issuer = JwtPeek.issuerOf(token);\n return issuer !== undefined && this.mode.authorizationServers.includes(issuer);\n }\n }\n }\n\n async authenticate(req: McpDoorRequest): Promise<McpDoorPass> {\n const token = req.bearer()!;\n const credential = await this.mode.authority.verifyAccessToken(token, this.resource);\n this.validateCredential(credential);\n this.callerContext.publish(credential.caller);\n return new McpDoorPass(\n this.name,\n false,\n token,\n credential.subject,\n credential.scopes,\n credential.expiresAtEpochSeconds,\n );\n }\n\n private validateCredential(credential: VerifiedMcpCredential): void {\n const now = Math.floor(Date.now() / 1000);\n const reject = (reason: string): never => {\n throw new ApiUnauthorizedError(`MCP access token rejected: ${reason}`);\n };\n if (credential.subject.trim() === '') reject('no subject');\n const stamps = [\n credential.issuedAtEpochSeconds,\n credential.expiresAtEpochSeconds,\n credential.accountValidatedAtEpochSeconds,\n ];\n if (!stamps.every(Number.isFinite)) reject('security timestamps must be finite');\n if (credential.resource !== this.resource) reject('resource mismatch');\n if (!this.mode.authorizationServers.includes(credential.issuer)) {\n reject('issuer is not trusted');\n }\n if (credential.issuedAtEpochSeconds > now || credential.expiresAtEpochSeconds <= now) {\n reject('outside its valid time window');\n }\n const lifetime = credential.expiresAtEpochSeconds - credential.issuedAtEpochSeconds;\n if (lifetime > MAX_MCP_ACCESS_TOKEN_LIFETIME_SECONDS) reject('lifetime exceeds 30 days');\n for (const scope of this.mode.requiredScopes) {\n if (!credential.scopes.includes(scope)) reject(`missing required scope '${scope}'`);\n }\n const validatedAge = now - credential.accountValidatedAtEpochSeconds;\n if (\n credential.accountValidatedAtEpochSeconds > now ||\n validatedAge > this.mode.maxAccountValidationAgeSeconds\n ) {\n reject('account authorization state is not fresh enough for dispatch');\n }\n }\n}\n\n/**\n * CLASSIFY, then verify with exactly ONE verifier.\n *\n * Credentials are self-describing, so one path serves every mode. The request is classified from\n * UNVERIFIED surface facts — which header; a JWT's unverified `iss` — then handed to exactly ONE mode.\n * Classifying on unverified data is safe because the chosen verifier fully verifies: a token that\n * merely claims `iss: accounts.google.com` fails Google's verification and the request is a 401. It\n * never falls through to another mode.\n *\n * - No claimant: only an `mcpOAuth(...)` server answers with the discovery challenge; others a plain 401.\n * - Two claimants (an api-key header AND a bearer): ambiguous, refused — never \"pick the one that works\".\n * - One claimant: its rejection IS the answer.\n *\n * The set is checked at construction (bind time): `vendorAuth` / public are refused, every mode's\n * verifier must be bound, and no two modes may be able to claim one request.\n */\nexport class McpDoorAuthenticator {\n private constructor(\n private readonly modes: readonly McpDoorMode[],\n public readonly oauth: McpOAuthDoorMode | undefined,\n ) {}\n\n // webpieces-disable no-function-outside-class -- validating factory; the constructor stays private so an unchecked set cannot exist\n static create(\n auth: readonly McpHttpAuth[],\n resource: string,\n verifiers: CredentialVerifiers | undefined,\n ): McpDoorAuthenticator {\n const oauthModes = auth.filter(\n (mode: McpHttpAuth): mode is McpOAuthMode => mode instanceof McpOAuthMode,\n );\n if (oauthModes.length > 1) {\n throw new Error(\n 'WpMcpServerConfig.setAuth([...]) accepts at most one mcpOAuth(...) mode.',\n );\n }\n McpDoorAuthenticator.refuseNonCallers(auth);\n const methods = auth.filter(\n (mode: McpHttpAuth): mode is AuthMethod => !(mode instanceof McpOAuthMode),\n );\n if (methods.length > 0) {\n // The same declaration rules @WpAuth applies: no duplicate kinds (one oidc per scope), valid shapes.\n new AuthMeta([methods[0], ...methods.slice(1)], undefined);\n }\n const modes: McpDoorMode[] = [];\n const oauth =\n oauthModes[0] === undefined ? undefined : new McpOAuthDoorMode(oauthModes[0], resource);\n if (methods.length > 0) {\n if (!verifiers) {\n throw new Error(\n 'WpMcpServerConfig.setAuth([...]) holds @WpAuth modes; add setCredentialVerifiers(router.credentialVerifiers()).',\n );\n }\n McpDoorAuthenticator.refuseUnverifiable(methods, verifiers);\n modes.push(...McpDoorAuthenticator.doorModes(methods, verifiers));\n }\n if (oauth) modes.push(oauth);\n McpDoorAuthenticator.refuseOverlaps(modes, oauth);\n for (const mode of modes) if (mode instanceof JwtDoorMode) mode.excluding(modes);\n return new McpDoorAuthenticator(modes, oauth);\n }\n\n /**\n * Whether a 401 for this request carries the `mcpOAuth(...)` discovery challenge: only when the\n * server has that mode AND the request either carried no credential at all or was classified as an\n * OAuth access token. A rejected `jwt()` / OIDC / api-key credential gets a plain 401, with no hint\n * of which mode came closest.\n */\n challengeApplies(req: McpDoorRequest): boolean {\n if (!this.oauth) return false;\n const claimants = this.modes.filter((mode: McpDoorMode) => mode.claims(req));\n return claimants.length === 0 || (claimants.length === 1 && claimants[0] === this.oauth);\n }\n\n async authenticate(req: McpDoorRequest): Promise<McpDoorPass> {\n const claimants = this.modes.filter((mode: McpDoorMode) => mode.claims(req));\n if (claimants.length === 0) {\n throw new ApiUnauthorizedError('MCP request has no credential this server accepts.');\n }\n if (claimants.length > 1) {\n throw new ApiUnauthorizedError(\n 'MCP request carries more than one credential; send exactly one.',\n );\n }\n return claimants[0].authenticate(req);\n }\n\n // webpieces-disable no-function-outside-class -- bind-time validation helper of the private constructor\n private static refuseNonCallers(auth: readonly McpHttpAuth[]): void {\n // A public mode does not compile here (McpHttpAuth excludes it); one smuggled past the type is\n // refused by the AuthMeta declaration check that follows.\n for (const method of auth) {\n if (method.kind === 'vendorAuth') {\n throw new Error(\n 'WpMcpServerConfig.setAuth([...]) refuses vendorAuth(...): ' +\n 'it identifies no caller to authorize tools against. Use oidcSameProject(), oidcSvcs([...]), ' +\n 'jwt(), apiKey(...), sharedSecret(...) or mcpOAuth(...).',\n );\n }\n }\n }\n\n // webpieces-disable no-function-outside-class -- bind-time validation helper of the private constructor\n private static refuseUnverifiable(\n methods: readonly AuthMethod[],\n verifiers: CredentialVerifiers,\n ): void {\n const missing = methods\n .map((method: AuthMethod) => verifiers.missingVerifier(method))\n .filter((line: string | undefined): line is string => line !== undefined);\n if (missing.length > 0) {\n throw new Error(\n 'WpMcpServerConfig.setAuth([...]) accepts a mode this server cannot verify, so it refuses to start. Add:\\n ' +\n missing.join('\\n '),\n );\n }\n }\n\n // webpieces-disable no-function-outside-class -- bind-time construction helper of the private constructor\n private static doorModes(\n methods: readonly AuthMethod[],\n verifiers: CredentialVerifiers,\n ): McpDoorMode[] {\n const modes: McpDoorMode[] = [];\n const oidc = methods.filter(\n (method: AuthMethod): method is OidcAuthMethod => method.kind === 'oidc',\n );\n if (oidc[0] !== undefined)\n modes.push(new OidcDoorMode([oidc[0], ...oidc.slice(1)], verifiers));\n for (const method of methods) {\n switch (method.kind) {\n case 'oidc':\n break;\n case 'jwt':\n modes.push(new JwtDoorMode(verifiers));\n break;\n case 'shared-secret':\n modes.push(new SharedSecretDoorMode(method.secretKey, verifiers));\n break;\n case 'apikey':\n modes.push(new ApiKeyDoorMode(method.regime, method.credentials, verifiers));\n break;\n case 'vendorAuth':\n throw new ApiImplementationError(\n 'vendorAuth was refused before door modes are built.',\n );\n }\n }\n return modes;\n }\n\n /**\n * `claims()` sets must be DISJOINT BY CONSTRUCTION. The bearer space is the only shared one, so the\n * refusals are about it:\n * - `mcpOAuth(..., everyBearer())` claims every bearer, so it stands alone in the bearer family;\n * - a bearer `apiKey(...)` claims every bearer too, so it stands alone as well;\n * - `mcpOAuth(..., jwtIssuer())` may not list a Google issuer beside an OIDC mode.\n * `jwt()` claims the remainder, and `McpAccessTokenFormat.opaquePrefix` refuses a prefix that could\n * overlap a JWT or a dev-oidc token, so nothing else can collide.\n */\n // webpieces-disable no-function-outside-class -- bind-time validation helper of the private constructor\n private static refuseOverlaps(\n modes: readonly McpDoorMode[],\n oauth: McpOAuthDoorMode | undefined,\n ): void {\n const bearerModes = modes.filter((mode: McpDoorMode) => mode.bearerFamily);\n const greedy = bearerModes.filter(\n (mode: McpDoorMode) =>\n mode instanceof ApiKeyDoorMode ||\n (mode instanceof McpOAuthDoorMode && mode.mode.tokenFormat.kind === 'every-bearer'),\n );\n for (const mode of greedy) {\n if (bearerModes.length > 1) {\n const others = bearerModes\n .filter((other: McpDoorMode) => other !== mode)\n .map((other: McpDoorMode) => other.name);\n throw new Error(\n `WpMcpServerConfig.setAuth([...]): ${mode.name} claims EVERY bearer credential, so it overlaps ` +\n `${others.join(', ')}. Give it a disjoint shape (mcpOAuth: McpAccessTokenFormat.opaquePrefix(...) ` +\n 'or .jwtIssuer(); apiKey: a header credential) or remove the other bearer modes.',\n );\n }\n }\n const oidc = modes.some((mode: McpDoorMode) => mode instanceof OidcDoorMode);\n if (oauth && oidc && oauth.mode.tokenFormat.kind === 'jwt-issuer') {\n const google = oauth.mode.authorizationServers.filter((issuer: string) =>\n GOOGLE_OIDC_ISSUERS.includes(issuer),\n );\n if (google.length > 0) {\n throw new Error(\n `WpMcpServerConfig.setAuth([...]): mcpOAuth(...) trusts the Google issuer ${google.join(', ')}, ` +\n 'so it overlaps the OIDC mode, which claims every Google-issued token.',\n );\n }\n }\n }\n}\n"]}
@@ -83,10 +83,11 @@ export declare class McpHttpErrorDetail {
83
83
  * # There is NO `fromWire`, and that is the protocol, not an oversight
84
84
  *
85
85
  * The HTTP and IPC translators have two halves because webpieces sits on BOTH ends of those wires: it
86
- * writes the response AND, in another process, reads one. **webpieces is never the MCP client.** The
87
- * client is Claude (or another model host); there is no return path for webpieces to translate, so
88
- * the pair is one-directional by nature. Do not "restore the symmetry" by adding a half with nothing
89
- * to call it.
86
+ * writes the response AND, in another process, reads one. An MCP tool result is read by a MODEL, not
87
+ * decoded back into an exception: the model host (Claude, or an agent holding `WpMcpInMemory` /
88
+ * `WpMcpRemote`, which are the SDK's own `Client`) hands the `CallToolResult` to the model as-is. There
89
+ * is no return path for webpieces to translate, so the pair is one-directional by nature. Do not
90
+ * "restore the symmetry" by adding a half with nothing to call it.
90
91
  *
91
92
  * # The pre-SDK boundary does NOT share the HTTP `ErrorTranslator`, despite the identical type
92
93
  *