toolcraft 0.0.314 → 0.0.316
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/composition.json +3 -3
- package/dist/composition.json +3 -3
- package/node_modules/tiny-http-mcp-server/dist/composition.json +1 -1
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/README.md +40 -2
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/auth-store-session-store.d.ts +1 -6
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/auth-store-session-store.js +6 -18
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/client-registration.d.ts +6 -0
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/client-registration.js +148 -0
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/default-oauth-client-provider.js +112 -49
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/token-auth-method.d.ts +2 -0
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/token-auth-method.js +7 -0
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/token-endpoint.d.ts +3 -1
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/token-endpoint.js +19 -9
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/types.d.ts +36 -5
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/index.d.ts +2 -1
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/index.js +1 -0
- package/node_modules/tiny-http-mcp-server/package.json +1 -1
- package/node_modules/tiny-mcp-client/dist/index.d.ts +36 -5
- package/node_modules/tiny-mcp-client/dist/index.js +1312 -1095
- package/node_modules/tiny-stdio-mcp-server/dist/composition.json +1 -1
- package/node_modules/toolcraft-schema/package.json +1 -1
- package/package.json +2 -2
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { normalizeStoredOAuthClient, parseOAuthClientRegistration, registrationMatchesRedirect } from "./client-registration.js";
|
|
1
2
|
import { normalizeOAuthScope } from "./scope.js";
|
|
3
|
+
import { normalizeOAuthTokenEndpointAuthMethod } from "./token-auth-method.js";
|
|
2
4
|
import { isIP } from "node:net";
|
|
3
5
|
import { fetchMcpResponse } from "../http-fetch.js";
|
|
4
6
|
import { URL } from "node:url";
|
|
@@ -21,6 +23,9 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
21
23
|
assertPersistenceNamespace(options.persistenceNamespace);
|
|
22
24
|
const clientMetadata = getClientMetadata(options.client);
|
|
23
25
|
const requestedScope = clientMetadata?.scope;
|
|
26
|
+
const configuredClient = normalizeConfiguredClient(options.client);
|
|
27
|
+
const requestedTokenMethod = normalizeOAuthTokenEndpointAuthMethod(options.client.tokenEndpointAuthMethod);
|
|
28
|
+
const configuredTokenMethod = requestedTokenMethod ?? configuredClient?.tokenEndpointAuthMethod;
|
|
24
29
|
const sessionStore = options.sessionStore ?? createAuthStoreSessionStore(options.authStore, options.persistenceNamespace);
|
|
25
30
|
const clientStore = options.authStore === undefined ? null : createAuthStoreClientStore(options.authStore, options.persistenceNamespace);
|
|
26
31
|
const now = options.now ?? Date.now;
|
|
@@ -38,8 +43,8 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
38
43
|
}
|
|
39
44
|
const initialGrant = options.initialGrant === undefined ? undefined : {
|
|
40
45
|
resource: canonicalizeResourceIndicator(options.initialGrant.resource),
|
|
41
|
-
tokens:
|
|
42
|
-
client:
|
|
46
|
+
tokens: normalizeImportedTokens(options.initialGrant.tokens, now),
|
|
47
|
+
client: configuredClient
|
|
43
48
|
};
|
|
44
49
|
if (initialGrant !== undefined && (initialGrant.tokens === undefined || initialGrant.client === null))
|
|
45
50
|
throw new Error("OAuth initial grant requires valid tokens and the original client ID");
|
|
@@ -122,6 +127,10 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
122
127
|
const canonicalResource = canonicalizeResourceIndicator(resource);
|
|
123
128
|
return withOAuthSessionTransaction(sessionStore, canonicalResource, async () => {
|
|
124
129
|
let session = await loadSession(canonicalResource);
|
|
130
|
+
if (session !== null)
|
|
131
|
+
assertRegistrationIssuer(session.client, session.authorizationServer);
|
|
132
|
+
if (configuredClient !== null && discovery !== undefined)
|
|
133
|
+
assertRegistrationIssuer(configuredClient, discovery.authorizationServer);
|
|
125
134
|
if (session !== null && initialGrant?.resource === canonicalResource)
|
|
126
135
|
initialGrantConsumed = true;
|
|
127
136
|
signal?.throwIfAborted();
|
|
@@ -147,13 +156,16 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
147
156
|
if (forceRefresh && rejectedTokens !== undefined && (rejectedTokens === null || session?.tokens === undefined || !sameTokenGrant(session.tokens, rejectedTokens)))
|
|
148
157
|
forceRefresh = false;
|
|
149
158
|
const sessionDiscovery = resolveDiscovery(discovery, session);
|
|
150
|
-
if ((options.client.mode === "static" || initialGrant !== undefined) && session !== null && (session.tokens !== undefined || session.refreshState === "pending")) {
|
|
151
|
-
const configured =
|
|
159
|
+
if ((options.client.mode === "static" || configuredClient?.registration !== undefined || initialGrant !== undefined) && session !== null && (session.tokens !== undefined || session.refreshState === "pending")) {
|
|
160
|
+
const configured = configuredClient;
|
|
152
161
|
if (configured === null || configured.clientId !== session.client.clientId || configured.clientSecret !== session.client.clientSecret)
|
|
153
162
|
throw new Error("Stored session belongs to a different OAuth client; use separate persistence or explicitly reset it");
|
|
154
163
|
}
|
|
155
164
|
if (requestedScope !== undefined && session?.tokens !== undefined && normalizeOAuthScope(session.tokens.scope ?? session.requestedScope) !== requestedScope)
|
|
156
165
|
throw new Error("Stored session does not match the requested OAuth scope; authorize again or select separate persistence");
|
|
166
|
+
if (configuredTokenMethod !== undefined && session !== null && (session.tokens !== undefined || session.refreshState === "pending") &&
|
|
167
|
+
(session.client.tokenEndpointAuthMethod ?? (session.client.clientSecret === undefined ? "none" : "client_secret_post")) !== configuredTokenMethod)
|
|
168
|
+
throw new Error("Stored session does not match the requested OAuth token endpoint authentication; select separate persistence or reset it");
|
|
157
169
|
if (session?.refreshState === "pending") {
|
|
158
170
|
if (!allowInteractive || options.allowInteractive === false || sessionDiscovery === undefined)
|
|
159
171
|
throw new Error("OAuth refresh outcome is unknown; authorize again before using this resource");
|
|
@@ -165,6 +177,11 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
165
177
|
if (session?.tokens?.refreshToken !== undefined &&
|
|
166
178
|
sessionDiscovery !== undefined &&
|
|
167
179
|
(forceRefresh || isExpired(session.tokens, now))) {
|
|
180
|
+
if (hasExpiredClientSecret(session.client, now)) {
|
|
181
|
+
if (!allowInteractive || options.allowInteractive === false || options.client.mode === "static" || configuredClient?.registration !== undefined)
|
|
182
|
+
throw new Error("OAuth client secret has expired; authorize again or update the imported registration");
|
|
183
|
+
return authorizeSession(canonicalResource, clearSessionTokens(session), sessionDiscovery, fetch, signal);
|
|
184
|
+
}
|
|
168
185
|
session = await refreshSession(canonicalResource, session, sessionDiscovery, fetch, signal);
|
|
169
186
|
if (session?.tokens !== undefined && !isExpired(session.tokens, now)) {
|
|
170
187
|
return session;
|
|
@@ -188,6 +205,7 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
188
205
|
if (session.tokens?.refreshToken === undefined) {
|
|
189
206
|
return session;
|
|
190
207
|
}
|
|
208
|
+
assertTokenEndpointAuthentication(session.client, discovery.authorizationServerMetadata);
|
|
191
209
|
const pendingSession = { ...clearSessionTokens(session), refreshState: "pending" };
|
|
192
210
|
await saveSession(resource, pendingSession);
|
|
193
211
|
signal?.throwIfAborted();
|
|
@@ -199,6 +217,7 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
199
217
|
tokenEndpoint: requireOwnString(discovery.authorizationServerMetadata, "token_endpoint", "Authorization server metadata"),
|
|
200
218
|
clientId: session.client.clientId,
|
|
201
219
|
clientSecret: session.client.clientSecret,
|
|
220
|
+
tokenEndpointAuthMethod: session.client.tokenEndpointAuthMethod,
|
|
202
221
|
refreshToken: session.tokens.refreshToken,
|
|
203
222
|
resource,
|
|
204
223
|
fetch, signal,
|
|
@@ -264,6 +283,7 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
264
283
|
let resolvedClient = null;
|
|
265
284
|
try {
|
|
266
285
|
resolvedClient = await resolveClient(currentSession, discovery, loopback.redirectUri, fetch, signal);
|
|
286
|
+
assertTokenEndpointAuthentication(resolvedClient.client, discovery.authorizationServerMetadata);
|
|
267
287
|
const sessionWithoutTokens = {
|
|
268
288
|
resource,
|
|
269
289
|
authorizationServer: discovery.authorizationServer,
|
|
@@ -287,6 +307,7 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
287
307
|
tokenEndpoint: requireOwnString(discovery.authorizationServerMetadata, "token_endpoint", "Authorization server metadata"),
|
|
288
308
|
clientId: resolvedClient.client.clientId,
|
|
289
309
|
clientSecret: resolvedClient.client.clientSecret,
|
|
310
|
+
tokenEndpointAuthMethod: resolvedClient.client.tokenEndpointAuthMethod,
|
|
290
311
|
code,
|
|
291
312
|
codeVerifier: verifier,
|
|
292
313
|
redirectUri: loopback.redirectUri,
|
|
@@ -327,11 +348,13 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
327
348
|
}
|
|
328
349
|
async function resolveClient(existingSession, discovery, redirectUri, fetch, parentSignal) {
|
|
329
350
|
parentSignal?.throwIfAborted();
|
|
330
|
-
|
|
331
|
-
if (options.client.mode === "static") {
|
|
351
|
+
if (options.client.mode === "static" || configuredClient?.registration !== undefined) {
|
|
332
352
|
if (configuredClient === null) {
|
|
333
353
|
throw new Error("OAuth client_id must not be blank");
|
|
334
354
|
}
|
|
355
|
+
assertRegistrationIssuer(configuredClient, discovery.authorizationServer);
|
|
356
|
+
if (hasExpiredClientSecret(configuredClient, now))
|
|
357
|
+
throw new Error("OAuth client secret has expired; update the imported registration");
|
|
335
358
|
return {
|
|
336
359
|
kind: "static",
|
|
337
360
|
fromStoredRegistration: false,
|
|
@@ -346,7 +369,14 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
346
369
|
client: configuredClient
|
|
347
370
|
};
|
|
348
371
|
}
|
|
349
|
-
|
|
372
|
+
let storedClient = await loadRegisteredClient(discovery.authorizationServer);
|
|
373
|
+
if (storedClient !== null) {
|
|
374
|
+
assertRegistrationIssuer(storedClient, discovery.authorizationServer);
|
|
375
|
+
if (hasExpiredClientSecret(storedClient, now) || !registrationMatchesRedirect(storedClient, redirectUri)) {
|
|
376
|
+
await clearRegisteredClient(discovery.authorizationServer);
|
|
377
|
+
storedClient = null;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
350
380
|
if (storedClient !== null) {
|
|
351
381
|
return {
|
|
352
382
|
kind: "dynamic",
|
|
@@ -355,7 +385,7 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
355
385
|
};
|
|
356
386
|
}
|
|
357
387
|
if (registrationEndpoint === undefined) {
|
|
358
|
-
if (existingSession !== null && existingSession.client.clientId.length > 0) {
|
|
388
|
+
if (existingSession !== null && existingSession.client.clientId.length > 0 && !hasExpiredClientSecret(existingSession.client, now) && registrationMatchesRedirect(existingSession.client, redirectUri)) {
|
|
359
389
|
return {
|
|
360
390
|
kind: "dynamic",
|
|
361
391
|
fromStoredRegistration: true,
|
|
@@ -364,7 +394,7 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
364
394
|
}
|
|
365
395
|
throw new Error("Authorization server metadata is missing registration_endpoint");
|
|
366
396
|
}
|
|
367
|
-
if (existingSession !== null && existingSession.client.clientId.length > 0) {
|
|
397
|
+
if (existingSession !== null && existingSession.client.clientId.length > 0 && !hasExpiredClientSecret(existingSession.client, now) && registrationMatchesRedirect(existingSession.client, redirectUri)) {
|
|
368
398
|
const isConfiguredStaticFallback = configuredClient !== null &&
|
|
369
399
|
existingSession.client.clientId === configuredClient.clientId &&
|
|
370
400
|
existingSession.client.clientSecret === configuredClient.clientSecret;
|
|
@@ -377,7 +407,12 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
377
407
|
};
|
|
378
408
|
}
|
|
379
409
|
}
|
|
380
|
-
const
|
|
410
|
+
const supported = getSupportedTokenAuthMethods(discovery.authorizationServerMetadata);
|
|
411
|
+
const registrationMethod = requestedTokenMethod ?? (supported === undefined ? "none" :
|
|
412
|
+
["none", "client_secret_basic", "client_secret_post"].find(method => supported.includes(method)));
|
|
413
|
+
if (registrationMethod === undefined || (supported !== undefined && !supported.includes(registrationMethod)))
|
|
414
|
+
throw new Error("Authorization server does not support the requested OAuth token endpoint authentication");
|
|
415
|
+
const registrationBody = buildClientRegistrationBody(clientMetadata, redirectUri, registrationMethod);
|
|
381
416
|
const deadline = AbortSignal.timeout(30_000);
|
|
382
417
|
const signal = parentSignal === undefined ? deadline : AbortSignal.any([parentSignal, deadline]);
|
|
383
418
|
const response = await fetchMcpResponse(fetch, registrationEndpoint, {
|
|
@@ -389,29 +424,34 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
389
424
|
signal
|
|
390
425
|
});
|
|
391
426
|
const payload = await readOAuthJsonObjectResponse(response, signal);
|
|
392
|
-
const
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
const clientSecret = getOwnString(payload, "client_secret");
|
|
427
|
+
const registration = parseOAuthClientRegistration(payload);
|
|
428
|
+
const registeredSecret = getOwnString(registration, "client_secret");
|
|
429
|
+
const responseMethod = normalizeOAuthTokenEndpointAuthMethod(getOwnEntry(registration, "token_endpoint_auth_method")) ??
|
|
430
|
+
requestedTokenMethod ?? (supported === undefined ? undefined : normalizeOAuthTokenEndpointAuthMethod(registrationMethod));
|
|
397
431
|
const registeredClient = {
|
|
398
|
-
clientId:
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
432
|
+
clientId: registration.client_id.trim(),
|
|
433
|
+
...(registeredSecret === undefined ? {} : { clientSecret: registeredSecret.trim() }),
|
|
434
|
+
...(responseMethod === undefined ? {} : { tokenEndpointAuthMethod: responseMethod }),
|
|
435
|
+
registration
|
|
402
436
|
};
|
|
403
|
-
|
|
437
|
+
assertRegistrationIssuer(registeredClient, discovery.authorizationServer);
|
|
438
|
+
if (hasExpiredClientSecret(registeredClient, now))
|
|
439
|
+
throw new Error("OAuth client secret has expired in the registration response");
|
|
440
|
+
if (!registrationMatchesRedirect(registeredClient, redirectUri, true))
|
|
441
|
+
throw new Error("OAuth registration response does not match the requested redirect URI");
|
|
442
|
+
const clientWithRedirect = { ...registeredClient, requestedRedirectUri: redirectUri };
|
|
443
|
+
await saveRegisteredClient(discovery.authorizationServer, clientWithRedirect);
|
|
404
444
|
return {
|
|
405
445
|
kind: "dynamic",
|
|
406
446
|
fromStoredRegistration: false,
|
|
407
|
-
client:
|
|
447
|
+
client: clientWithRedirect
|
|
408
448
|
};
|
|
409
449
|
}
|
|
410
450
|
async function loadSession(resource) {
|
|
411
451
|
return normalizeLoadedSession(await sessionStore.load(resource));
|
|
412
452
|
}
|
|
413
453
|
async function saveSession(resource, session) {
|
|
414
|
-
await sessionStore.save(resource, session);
|
|
454
|
+
await sessionStore.save(resource, structuredClone(session));
|
|
415
455
|
}
|
|
416
456
|
async function clearSession(resource) {
|
|
417
457
|
await sessionStore.clear(resource);
|
|
@@ -424,7 +464,7 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
424
464
|
return null;
|
|
425
465
|
}
|
|
426
466
|
const client = await clientStore.load(issuer);
|
|
427
|
-
const normalizedClient = client === null ? null :
|
|
467
|
+
const normalizedClient = client === null ? null : normalizeStoredOAuthClient(client);
|
|
428
468
|
if (client !== null && normalizedClient === null) {
|
|
429
469
|
await clientStore.clear(issuer);
|
|
430
470
|
return null;
|
|
@@ -503,7 +543,7 @@ function normalizeLoadedSession(session) {
|
|
|
503
543
|
const refreshState = getOwnEntry(session, "refreshState");
|
|
504
544
|
if (refreshState !== undefined && (refreshState !== "pending" || getOwnEntry(session, "tokens") !== undefined))
|
|
505
545
|
throw new Error("Stored OAuth refresh state is invalid");
|
|
506
|
-
const client =
|
|
546
|
+
const client = normalizeStoredOAuthClient(getOwnEntry(session, "client"));
|
|
507
547
|
if (client === null) {
|
|
508
548
|
return { ...session, client: { clientId: "" }, tokens: undefined };
|
|
509
549
|
}
|
|
@@ -513,24 +553,20 @@ function normalizeLoadedSession(session) {
|
|
|
513
553
|
tokens: normalizeStoredTokens(getOwnEntry(session, "tokens"))
|
|
514
554
|
};
|
|
515
555
|
}
|
|
516
|
-
function
|
|
517
|
-
if (!isObjectRecord(value))
|
|
518
|
-
return
|
|
519
|
-
|
|
520
|
-
const
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
return null;
|
|
531
|
-
}
|
|
532
|
-
const normalizedClientSecret = clientSecret.trim();
|
|
533
|
-
return { clientId: normalizedClientId, clientSecret: normalizedClientSecret };
|
|
556
|
+
function normalizeImportedTokens(value, now) {
|
|
557
|
+
if (!isObjectRecord(value))
|
|
558
|
+
return undefined;
|
|
559
|
+
const absolute = getOwnEntry(value, "expiresAt");
|
|
560
|
+
const lifetime = getOwnEntry(value, "expiresIn");
|
|
561
|
+
const issuedAt = getOwnEntry(value, "issuedAt");
|
|
562
|
+
if (lifetime !== undefined && (typeof lifetime !== "number" || !Number.isSafeInteger(lifetime) || lifetime < 0))
|
|
563
|
+
throw new Error("OAuth initial grant has invalid relative expiry");
|
|
564
|
+
if (issuedAt !== undefined && (typeof issuedAt !== "number" || !Number.isSafeInteger(issuedAt) ||
|
|
565
|
+
Math.abs(issuedAt) > MAX_JS_DATE_MS))
|
|
566
|
+
throw new Error("OAuth initial grant has invalid issuance time");
|
|
567
|
+
const expiresAt = absolute !== undefined && absolute !== null ? absolute :
|
|
568
|
+
lifetime === undefined ? null : (issuedAt === undefined ? now() : issuedAt) + lifetime * 1000;
|
|
569
|
+
return normalizeStoredTokens({ ...value, expiresAt });
|
|
534
570
|
}
|
|
535
571
|
function normalizeStoredTokens(value) {
|
|
536
572
|
if (value === undefined || !isObjectRecord(value)) {
|
|
@@ -581,12 +617,12 @@ function getClientMetadata(client) {
|
|
|
581
617
|
};
|
|
582
618
|
}
|
|
583
619
|
function normalizeConfiguredClient(client) {
|
|
584
|
-
const
|
|
585
|
-
|
|
620
|
+
const registration = client.registration === undefined ? undefined : parseOAuthClientRegistration(client.registration);
|
|
621
|
+
const clientId = normalizeOptionalOAuthString(client.clientId) ?? registration?.client_id.trim();
|
|
622
|
+
if (clientId === undefined)
|
|
586
623
|
return null;
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
return clientSecret === undefined ? { clientId } : { clientId, clientSecret };
|
|
624
|
+
const clientSecret = normalizeOptionalOAuthString(client.clientSecret) ?? (registration === undefined ? undefined : getOwnString(registration, "client_secret")?.trim());
|
|
625
|
+
return normalizeStoredOAuthClient({ clientId, clientSecret, registration, tokenEndpointAuthMethod: client.tokenEndpointAuthMethod });
|
|
590
626
|
}
|
|
591
627
|
function normalizeOptionalOAuthString(value) {
|
|
592
628
|
if (value === undefined) {
|
|
@@ -693,12 +729,39 @@ function assertRequestMatchesResource(requestUrl, resource) {
|
|
|
693
729
|
throw new Error(`OAuth request URL ${requestUrl} does not match discovered resource ${resource}`);
|
|
694
730
|
}
|
|
695
731
|
}
|
|
696
|
-
function
|
|
732
|
+
function assertRegistrationIssuer(client, issuer) {
|
|
733
|
+
const registrationIssuer = client.registration === undefined ? undefined : getOwnString(client.registration, "issuer");
|
|
734
|
+
if (registrationIssuer !== undefined && registrationIssuer !== issuer)
|
|
735
|
+
throw new Error("OAuth client registration issuer does not match the authorization server");
|
|
736
|
+
}
|
|
737
|
+
function hasExpiredClientSecret(client, now) {
|
|
738
|
+
if (client.clientSecret === undefined || client.tokenEndpointAuthMethod === "none" || client.registration === undefined)
|
|
739
|
+
return false;
|
|
740
|
+
const expiry = getOwnEntry(client.registration, "client_secret_expires_at");
|
|
741
|
+
return typeof expiry === "number" && expiry !== 0 && expiry <= now() / 1000;
|
|
742
|
+
}
|
|
743
|
+
function getSupportedTokenAuthMethods(metadata) {
|
|
744
|
+
const value = getOwnEntry(metadata, "token_endpoint_auth_methods_supported");
|
|
745
|
+
if (value === undefined)
|
|
746
|
+
return undefined;
|
|
747
|
+
if (!Array.isArray(value) || value.length > 128 || value.some(method => typeof method !== "string"))
|
|
748
|
+
throw new Error("Invalid OAuth token endpoint authentication metadata");
|
|
749
|
+
return value;
|
|
750
|
+
}
|
|
751
|
+
function assertTokenEndpointAuthentication(client, metadata) {
|
|
752
|
+
const method = client.tokenEndpointAuthMethod ?? (client.clientSecret === undefined ? "none" : "client_secret_post");
|
|
753
|
+
if (method !== "none" && client.clientSecret === undefined)
|
|
754
|
+
throw new Error("OAuth token endpoint authentication requires a client secret");
|
|
755
|
+
const supported = getSupportedTokenAuthMethods(metadata);
|
|
756
|
+
if (supported !== undefined && !supported.includes(method))
|
|
757
|
+
throw new Error("Authorization server does not support the requested OAuth token endpoint authentication");
|
|
758
|
+
}
|
|
759
|
+
function buildClientRegistrationBody(metadata, redirectUri, tokenEndpointAuthMethod) {
|
|
697
760
|
const body = {
|
|
698
761
|
redirect_uris: [redirectUri],
|
|
699
762
|
grant_types: ["authorization_code", "refresh_token"],
|
|
700
763
|
response_types: ["code"],
|
|
701
|
-
token_endpoint_auth_method:
|
|
764
|
+
token_endpoint_auth_method: tokenEndpointAuthMethod
|
|
702
765
|
};
|
|
703
766
|
const clientName = metadata === undefined ? undefined : getOwnString(metadata, "clientName");
|
|
704
767
|
const scope = metadata === undefined ? undefined : getOwnString(metadata, "scope");
|
package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/token-auth-method.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export function normalizeOAuthTokenEndpointAuthMethod(value) {
|
|
2
|
+
if (value === undefined || value === null)
|
|
3
|
+
return undefined;
|
|
4
|
+
if (value !== "none" && value !== "client_secret_post" && value !== "client_secret_basic")
|
|
5
|
+
throw new Error("Unsupported OAuth token endpoint authentication method");
|
|
6
|
+
return value;
|
|
7
|
+
}
|
package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/token-endpoint.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { OAuthMetadataFetch, StoredOAuthTokens } from "./types.js";
|
|
1
|
+
import type { OAuthMetadataFetch, StoredOAuthTokens, OAuthTokenEndpointAuthMethod } from "./types.js";
|
|
2
2
|
interface OAuthErrorShape {
|
|
3
3
|
error: string;
|
|
4
4
|
error_description?: string;
|
|
@@ -22,6 +22,7 @@ export declare function exchangeAuthorizationCode(input: {
|
|
|
22
22
|
tokenEndpoint: string;
|
|
23
23
|
clientId: string;
|
|
24
24
|
clientSecret?: string;
|
|
25
|
+
tokenEndpointAuthMethod?: OAuthTokenEndpointAuthMethod;
|
|
25
26
|
code: string;
|
|
26
27
|
codeVerifier: string;
|
|
27
28
|
redirectUri: string;
|
|
@@ -34,6 +35,7 @@ export declare function refreshAccessToken(input: {
|
|
|
34
35
|
tokenEndpoint: string;
|
|
35
36
|
clientId: string;
|
|
36
37
|
clientSecret?: string;
|
|
38
|
+
tokenEndpointAuthMethod?: OAuthTokenEndpointAuthMethod;
|
|
37
39
|
refreshToken: string;
|
|
38
40
|
resource: string;
|
|
39
41
|
fetch: OAuthMetadataFetch;
|
package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/token-endpoint.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { normalizeOAuthTokenEndpointAuthMethod } from "./token-auth-method.js";
|
|
1
2
|
import { canonicalizeResourceIndicator } from "../resource-indicator.js";
|
|
2
3
|
import { readBoundedResponseText } from "../http-response.js";
|
|
3
4
|
import { fetchMcpResponse } from "../http-fetch.js";
|
|
@@ -40,6 +41,7 @@ export async function exchangeAuthorizationCode(input) {
|
|
|
40
41
|
tokenEndpoint: input.tokenEndpoint,
|
|
41
42
|
clientId: input.clientId,
|
|
42
43
|
clientSecret: input.clientSecret,
|
|
44
|
+
tokenEndpointAuthMethod: input.tokenEndpointAuthMethod,
|
|
43
45
|
params: {
|
|
44
46
|
grant_type: "authorization_code",
|
|
45
47
|
code: input.code,
|
|
@@ -58,6 +60,7 @@ export async function refreshAccessToken(input) {
|
|
|
58
60
|
tokenEndpoint: input.tokenEndpoint,
|
|
59
61
|
clientId: input.clientId,
|
|
60
62
|
clientSecret: input.clientSecret,
|
|
63
|
+
tokenEndpointAuthMethod: input.tokenEndpointAuthMethod,
|
|
61
64
|
params: {
|
|
62
65
|
grant_type: "refresh_token",
|
|
63
66
|
refresh_token: input.refreshToken,
|
|
@@ -69,21 +72,28 @@ export async function refreshAccessToken(input) {
|
|
|
69
72
|
});
|
|
70
73
|
}
|
|
71
74
|
async function requestTokens(input) {
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
const method = normalizeOAuthTokenEndpointAuthMethod(input.tokenEndpointAuthMethod) ??
|
|
76
|
+
(input.clientSecret === undefined ? "none" : "client_secret_post");
|
|
77
|
+
if (method !== "none" && (input.clientSecret === undefined || input.clientSecret.trim() === ""))
|
|
78
|
+
throw new Error("OAuth token endpoint authentication requires a client secret");
|
|
79
|
+
const body = new URLSearchParams(input.params);
|
|
80
|
+
const headers = new Headers({ "Content-Type": "application/x-www-form-urlencoded" });
|
|
81
|
+
if (method === "client_secret_basic") {
|
|
82
|
+
const encoded = new URLSearchParams({ credential: input.clientId }).toString().slice("credential=".length);
|
|
83
|
+
const encodedSecret = new URLSearchParams({ credential: input.clientSecret }).toString().slice("credential=".length);
|
|
84
|
+
headers.set("Authorization", `Basic ${Buffer.from(`${encoded}:${encodedSecret}`).toString("base64")}`);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
body.set("client_id", input.clientId);
|
|
88
|
+
if (method === "client_secret_post")
|
|
89
|
+
body.set("client_secret", input.clientSecret);
|
|
78
90
|
}
|
|
79
91
|
input.signal?.throwIfAborted();
|
|
80
92
|
const deadline = AbortSignal.timeout(30_000);
|
|
81
93
|
const signal = input.signal === undefined ? deadline : AbortSignal.any([input.signal, deadline]);
|
|
82
94
|
const response = await fetchMcpResponse(input.fetch, input.tokenEndpoint, {
|
|
83
95
|
method: "POST",
|
|
84
|
-
headers
|
|
85
|
-
"Content-Type": "application/x-www-form-urlencoded"
|
|
86
|
-
},
|
|
96
|
+
headers,
|
|
87
97
|
body: body.toString(),
|
|
88
98
|
signal
|
|
89
99
|
});
|
|
@@ -70,13 +70,39 @@ export interface StoredOAuthTokens {
|
|
|
70
70
|
expiresAt: number | null;
|
|
71
71
|
scope?: string;
|
|
72
72
|
}
|
|
73
|
+
/** Import-time lifetimes are normalized once into persisted epoch milliseconds. */
|
|
74
|
+
export interface ImportedOAuthTokens extends Omit<StoredOAuthTokens, "expiresAt"> {
|
|
75
|
+
expiresAt?: number | null;
|
|
76
|
+
/** Remaining lifetime at import, or total lifetime when issuedAt is provided. */
|
|
77
|
+
expiresIn?: number;
|
|
78
|
+
/** Original issuance time in Unix epoch milliseconds. */
|
|
79
|
+
issuedAt?: number;
|
|
80
|
+
}
|
|
81
|
+
/** Full RFC 7591 response, including JSON provider extensions. */
|
|
82
|
+
export interface OAuthClientRegistration extends Record<string, unknown> {
|
|
83
|
+
client_id: string;
|
|
84
|
+
client_secret?: string | null;
|
|
85
|
+
redirect_uris?: string[] | null;
|
|
86
|
+
grant_types?: string[] | null;
|
|
87
|
+
response_types?: string[] | null;
|
|
88
|
+
contacts?: string[] | null;
|
|
89
|
+
client_id_issued_at?: number | null;
|
|
90
|
+
client_secret_expires_at?: number | null;
|
|
91
|
+
token_endpoint_auth_method?: string | null;
|
|
92
|
+
}
|
|
93
|
+
export interface StoredOAuthClient {
|
|
94
|
+
clientId: string;
|
|
95
|
+
/** Actual listener URI submitted when this native client was registered. */
|
|
96
|
+
requestedRedirectUri?: string;
|
|
97
|
+
clientSecret?: string;
|
|
98
|
+
registration?: OAuthClientRegistration;
|
|
99
|
+
tokenEndpointAuthMethod?: OAuthTokenEndpointAuthMethod;
|
|
100
|
+
}
|
|
101
|
+
export type OAuthTokenEndpointAuthMethod = "none" | "client_secret_post" | "client_secret_basic";
|
|
73
102
|
export interface StoredOAuthSession {
|
|
74
103
|
resource: string;
|
|
75
104
|
authorizationServer: string;
|
|
76
|
-
client:
|
|
77
|
-
clientId: string;
|
|
78
|
-
clientSecret?: string;
|
|
79
|
-
};
|
|
105
|
+
client: StoredOAuthClient;
|
|
80
106
|
tokens?: StoredOAuthTokens;
|
|
81
107
|
/** A refresh was begun; its winning response may not have been persisted. */
|
|
82
108
|
refreshState?: "pending";
|
|
@@ -104,11 +130,16 @@ export interface DefaultOAuthClientProviderOptions {
|
|
|
104
130
|
clientId?: string;
|
|
105
131
|
clientSecret?: string;
|
|
106
132
|
metadata?: OAuthClientMetadata;
|
|
133
|
+
/** Import a complete registration owned by the caller. */
|
|
134
|
+
registration?: OAuthClientRegistration;
|
|
135
|
+
tokenEndpointAuthMethod?: OAuthTokenEndpointAuthMethod;
|
|
107
136
|
} | {
|
|
108
137
|
mode: "static";
|
|
109
138
|
clientId: string;
|
|
110
139
|
clientSecret?: string;
|
|
111
140
|
metadata?: OAuthClientMetadata;
|
|
141
|
+
registration?: OAuthClientRegistration;
|
|
142
|
+
tokenEndpointAuthMethod?: OAuthTokenEndpointAuthMethod;
|
|
112
143
|
};
|
|
113
144
|
/** Disable interactive authorization while allowing cached tokens and silent refresh. */
|
|
114
145
|
allowInteractive?: boolean;
|
|
@@ -119,7 +150,7 @@ export interface DefaultOAuthClientProviderOptions {
|
|
|
119
150
|
/** Import an existing grant for one resource. Persisted sessions take precedence. */
|
|
120
151
|
initialGrant?: {
|
|
121
152
|
resource: string;
|
|
122
|
-
tokens:
|
|
153
|
+
tokens: ImportedOAuthTokens;
|
|
123
154
|
};
|
|
124
155
|
browser: {
|
|
125
156
|
openBrowser?(url: string): Promise<void>;
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
export { createAuthStoreSessionStore, } from "./client/auth-store-session-store.js";
|
|
2
|
+
export { parseOAuthClientRegistration } from "./client/client-registration.js";
|
|
2
3
|
export { createDefaultOAuthClientProvider, createOAuthClientProvider, } from "./client/default-oauth-client-provider.js";
|
|
3
4
|
export { buildSuccessPage, createLoopbackAuthorizationSession, extractCodeFromInput, } from "./client/loopback-authorization.js";
|
|
4
5
|
export { generateCodeChallenge, generateCodeVerifier, } from "./client/pkce.js";
|
|
5
6
|
export { OAuthError, } from "./client/token-endpoint.js";
|
|
6
7
|
export { canonicalizeResourceIndicator, } from "./resource-indicator.js";
|
|
7
8
|
export { createJwksTokenVerifier, } from "./server/jwks-token-verifier.js";
|
|
8
|
-
export type { DefaultOAuthClientProviderOptions, OAuthAuthorizationServerMetadata, OAuthClientMetadata, OAuthClientProvider, OAuthClientProviderOptions, OAuthDiscoveryResult, OAuthMetadataFetch, OAuthProtectedResourceMetadata, OAuthSessionStore, OAuthUnauthorizedChallenge, StoredOAuthSession, StoredOAuthTokens, } from "./client/types.js";
|
|
9
|
+
export type { DefaultOAuthClientProviderOptions, OAuthAuthorizationServerMetadata, OAuthClientMetadata, OAuthClientRegistration, OAuthClientProvider, OAuthClientProviderOptions, OAuthDiscoveryResult, OAuthMetadataFetch, OAuthProtectedResourceMetadata, OAuthSessionStore, OAuthUnauthorizedChallenge, OAuthTokenEndpointAuthMethod, StoredOAuthSession, StoredOAuthClient, StoredOAuthTokens, ImportedOAuthTokens, } from "./client/types.js";
|
|
9
10
|
export type { JwksTokenVerifier, JwksTokenVerifierOptions, JwksVerifiedAccessToken, } from "./server/jwks-token-verifier.js";
|
|
10
11
|
export type { LoopbackAuthorizationOptions, LoopbackAuthorizationSession, OAuthLandingPage, } from "./client/loopback-authorization.js";
|
|
11
12
|
export { readBoundedResponseText } from "./http-response.js";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { createAuthStoreSessionStore, } from "./client/auth-store-session-store.js";
|
|
2
|
+
export { parseOAuthClientRegistration } from "./client/client-registration.js";
|
|
2
3
|
export { createDefaultOAuthClientProvider, createOAuthClientProvider, } from "./client/default-oauth-client-provider.js";
|
|
3
4
|
export { buildSuccessPage, createLoopbackAuthorizationSession, extractCodeFromInput, } from "./client/loopback-authorization.js";
|
|
4
5
|
export { generateCodeChallenge, generateCodeVerifier, } from "./client/pkce.js";
|
|
@@ -173,13 +173,39 @@ interface StoredOAuthTokens {
|
|
|
173
173
|
expiresAt: number | null;
|
|
174
174
|
scope?: string;
|
|
175
175
|
}
|
|
176
|
+
/** Import-time lifetimes are normalized once into persisted epoch milliseconds. */
|
|
177
|
+
interface ImportedOAuthTokens extends Omit<StoredOAuthTokens, "expiresAt"> {
|
|
178
|
+
expiresAt?: number | null;
|
|
179
|
+
/** Remaining lifetime at import, or total lifetime when issuedAt is provided. */
|
|
180
|
+
expiresIn?: number;
|
|
181
|
+
/** Original issuance time in Unix epoch milliseconds. */
|
|
182
|
+
issuedAt?: number;
|
|
183
|
+
}
|
|
184
|
+
/** Full RFC 7591 response, including JSON provider extensions. */
|
|
185
|
+
interface OAuthClientRegistration extends Record<string, unknown> {
|
|
186
|
+
client_id: string;
|
|
187
|
+
client_secret?: string | null;
|
|
188
|
+
redirect_uris?: string[] | null;
|
|
189
|
+
grant_types?: string[] | null;
|
|
190
|
+
response_types?: string[] | null;
|
|
191
|
+
contacts?: string[] | null;
|
|
192
|
+
client_id_issued_at?: number | null;
|
|
193
|
+
client_secret_expires_at?: number | null;
|
|
194
|
+
token_endpoint_auth_method?: string | null;
|
|
195
|
+
}
|
|
196
|
+
interface StoredOAuthClient {
|
|
197
|
+
clientId: string;
|
|
198
|
+
/** Actual listener URI submitted when this native client was registered. */
|
|
199
|
+
requestedRedirectUri?: string;
|
|
200
|
+
clientSecret?: string;
|
|
201
|
+
registration?: OAuthClientRegistration;
|
|
202
|
+
tokenEndpointAuthMethod?: OAuthTokenEndpointAuthMethod;
|
|
203
|
+
}
|
|
204
|
+
type OAuthTokenEndpointAuthMethod = "none" | "client_secret_post" | "client_secret_basic";
|
|
176
205
|
interface StoredOAuthSession {
|
|
177
206
|
resource: string;
|
|
178
207
|
authorizationServer: string;
|
|
179
|
-
client:
|
|
180
|
-
clientId: string;
|
|
181
|
-
clientSecret?: string;
|
|
182
|
-
};
|
|
208
|
+
client: StoredOAuthClient;
|
|
183
209
|
tokens?: StoredOAuthTokens;
|
|
184
210
|
/** A refresh was begun; its winning response may not have been persisted. */
|
|
185
211
|
refreshState?: "pending";
|
|
@@ -207,11 +233,16 @@ interface DefaultOAuthClientProviderOptions {
|
|
|
207
233
|
clientId?: string;
|
|
208
234
|
clientSecret?: string;
|
|
209
235
|
metadata?: OAuthClientMetadata;
|
|
236
|
+
/** Import a complete registration owned by the caller. */
|
|
237
|
+
registration?: OAuthClientRegistration;
|
|
238
|
+
tokenEndpointAuthMethod?: OAuthTokenEndpointAuthMethod;
|
|
210
239
|
} | {
|
|
211
240
|
mode: "static";
|
|
212
241
|
clientId: string;
|
|
213
242
|
clientSecret?: string;
|
|
214
243
|
metadata?: OAuthClientMetadata;
|
|
244
|
+
registration?: OAuthClientRegistration;
|
|
245
|
+
tokenEndpointAuthMethod?: OAuthTokenEndpointAuthMethod;
|
|
215
246
|
};
|
|
216
247
|
/** Disable interactive authorization while allowing cached tokens and silent refresh. */
|
|
217
248
|
allowInteractive?: boolean;
|
|
@@ -222,7 +253,7 @@ interface DefaultOAuthClientProviderOptions {
|
|
|
222
253
|
/** Import an existing grant for one resource. Persisted sessions take precedence. */
|
|
223
254
|
initialGrant?: {
|
|
224
255
|
resource: string;
|
|
225
|
-
tokens:
|
|
256
|
+
tokens: ImportedOAuthTokens;
|
|
226
257
|
};
|
|
227
258
|
browser: {
|
|
228
259
|
openBrowser?(url: string): Promise<void>;
|