tiny-http-mcp-server 0.1.32 → 0.1.34

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.
@@ -18,7 +18,7 @@
18
18
  },
19
19
  {
20
20
  "name": "tiny-http-mcp-server",
21
- "version": "0.1.32",
21
+ "version": "0.1.34",
22
22
  "license": "MIT"
23
23
  },
24
24
  {
@@ -112,6 +112,23 @@ Discovery binds an expired or explicitly rejected grant before silent refresh,
112
112
  using the original configured client. Persisted sessions take precedence,
113
113
  including sessions whose tokens have been cleared; an import cannot revive them.
114
114
  Input tokens are copied and invalid expiry values fail before authorization.
115
+ Pass a complete DCR response as `client.registration` (or validate untrusted JSON
116
+ with `parseOAuthClientRegistration`). Dynamic clients infer their original ID
117
+ and secret from that response and reuse it without registering another app.
118
+ Explicit ID/secret values must agree with the imported response. Sessions and
119
+ native registration stores retain arrays, issuance/expiry timestamps and JSON
120
+ provider metadata. Registration input is copied, bounded to 64 KiB and 64
121
+ levels, and rejects invalid standard field types and non-JSON extensions.
122
+ Set `client.tokenEndpointAuthMethod` to `none`, `client_secret_post` or
123
+ `client_secret_basic`; a full registration can supply the same field as
124
+ `token_endpoint_auth_method`. Public clients never transmit a stored secret.
125
+ Basic credentials are individually form-encoded before Base64 encoding and
126
+ are omitted from the form body. Cached grants retain their registered method;
127
+ an explicitly different configured method requires separate persistence or a
128
+ reset. Native DCR chooses a supported method, preferring public PKCE when
129
+ advertised. Unsupported methods and missing confidential secrets fail before
130
+ token requests. Existing clients without a method keep the previous default:
131
+ body authentication when a secret is present, public authentication otherwise.
115
132
  Static clients and dynamic initial-grant imports require cached grants to match
116
133
  the original normalized client ID and secret. A different client configuration
117
134
  fails before attaching or refreshing credentials and retains the stored record;
@@ -1,9 +1,5 @@
1
1
  import { type CreateSecretStoreInput } from "auth-store";
2
- import type { OAuthSessionStore } from "./types.js";
3
- interface StoredOAuthClient {
4
- clientId: string;
5
- clientSecret?: string;
6
- }
2
+ import type { OAuthSessionStore, StoredOAuthClient } from "./types.js";
7
3
  export interface OAuthClientStore {
8
4
  load(issuer: string): Promise<StoredOAuthClient | null>;
9
5
  save(issuer: string, client: StoredOAuthClient): Promise<void>;
@@ -12,4 +8,3 @@ export interface OAuthClientStore {
12
8
  export declare function createAuthStoreSessionStore(options?: CreateSecretStoreInput, namespace?: string): OAuthSessionStore;
13
9
  export declare function createAuthStoreClientStore(options: CreateSecretStoreInput, namespace?: string): OAuthClientStore;
14
10
  export declare function assertPersistenceNamespace(namespace: string | undefined): void;
15
- export {};
@@ -1,3 +1,4 @@
1
+ import { normalizeStoredOAuthClient } from "./client-registration.js";
1
2
  import crypto from "node:crypto";
2
3
  import path from "node:path";
3
4
  import { createSecretStore } from "auth-store";
@@ -62,15 +63,10 @@ export function createAuthStoreClientStore(options, namespace) {
62
63
  catch {
63
64
  throw new Error("Stored OAuth client must be valid JSON; reset the store explicitly to recover");
64
65
  }
65
- const clientId = isObjectRecord(parsed) ? getOwnString(parsed, "clientId") : undefined;
66
- if (clientId !== undefined) {
67
- const client = { clientId };
68
- if (isObjectRecord(parsed) &&
69
- Object.prototype.hasOwnProperty.call(parsed, "clientSecret")) {
70
- client.clientSecret = getOwnEntry(parsed, "clientSecret");
71
- }
72
- return client;
73
- }
66
+ // Preserve all registration metadata; provider normalization validates its
67
+ // identity and schema before it can authorize a request.
68
+ if (isObjectRecord(parsed) && typeof getOwnEntry(parsed, "clientId") === "string")
69
+ return parsed;
74
70
  throw new Error("Stored OAuth client must be a JSON object with clientId");
75
71
  },
76
72
  async save(issuer, client) {
@@ -142,21 +138,13 @@ function isStoredOAuthSession(value) {
142
138
  }
143
139
  return (isNonBlankOwnString(value, "resource") &&
144
140
  isNonBlankOwnString(value, "authorizationServer") &&
145
- isStoredOAuthClient(getOwnEntry(value, "client")) &&
141
+ normalizeStoredOAuthClient(getOwnEntry(value, "client")) !== null &&
146
142
  isStoredOAuthDiscovery(getOwnEntry(value, "discovery")) &&
147
143
  (getOwnEntry(value, "requestedScope") === undefined || isNonBlankOwnString(value, "requestedScope")) &&
148
144
  (getOwnEntry(value, "refreshState") === undefined ||
149
145
  (getOwnEntry(value, "refreshState") === "pending" && getOwnEntry(value, "tokens") === undefined)) &&
150
146
  isStoredOAuthTokensOrMissing(getOwnEntry(value, "tokens")));
151
147
  }
152
- function isStoredOAuthClient(value) {
153
- if (!isObjectRecord(value) || !isNonBlankOwnString(value, "clientId")) {
154
- return false;
155
- }
156
- const clientSecret = getOwnEntry(value, "clientSecret");
157
- return (clientSecret === undefined ||
158
- (typeof clientSecret === "string" && clientSecret.trim().length > 0));
159
- }
160
148
  function isStoredOAuthDiscovery(value) {
161
149
  if (!isObjectRecord(value)) {
162
150
  return false;
@@ -0,0 +1,4 @@
1
+ import type { OAuthClientRegistration, StoredOAuthClient } from "./types.js";
2
+ /** Validate and copy a bounded JSON DCR response without quoting credential input. */
3
+ export declare function parseOAuthClientRegistration(value: unknown): OAuthClientRegistration;
4
+ export declare function normalizeStoredOAuthClient(value: unknown): StoredOAuthClient | null;
@@ -0,0 +1,103 @@
1
+ import { normalizeOAuthScope } from "./scope.js";
2
+ import { normalizeOAuthTokenEndpointAuthMethod } from "./token-auth-method.js";
3
+ /** Validate and copy a bounded JSON DCR response without quoting credential input. */
4
+ export function parseOAuthClientRegistration(value) {
5
+ const invalid = () => new Error("Invalid OAuth client registration metadata");
6
+ let nodes = 0;
7
+ function copy(input, depth) {
8
+ if (++nodes > 20_000 || depth > 64)
9
+ throw invalid();
10
+ if (input === null || typeof input === "boolean" || typeof input === "string")
11
+ return input;
12
+ if (typeof input === "number" && Number.isFinite(input))
13
+ return input;
14
+ if (typeof input !== "object" || input === null)
15
+ throw invalid();
16
+ const descriptors = Object.getOwnPropertyDescriptors(input);
17
+ if (Array.isArray(input)) {
18
+ const length = descriptors.length?.value;
19
+ if (length > 20_000)
20
+ throw invalid();
21
+ const result = [];
22
+ for (let index = 0; index < length; index++) {
23
+ const descriptor = descriptors[String(index)];
24
+ if (descriptor === undefined || !Object.hasOwn(descriptor, "value"))
25
+ throw invalid();
26
+ result.push(copy(descriptor.value, depth + 1));
27
+ }
28
+ return result;
29
+ }
30
+ if (Object.getPrototypeOf(input) !== Object.prototype && Object.getPrototypeOf(input) !== null)
31
+ throw invalid();
32
+ return Object.fromEntries(Object.entries(descriptors).filter(([, descriptor]) => descriptor.enumerable).map(([key, descriptor]) => {
33
+ if (!Object.hasOwn(descriptor, "value"))
34
+ throw invalid();
35
+ return [key, copy(descriptor.value, depth + 1)];
36
+ }));
37
+ }
38
+ let result;
39
+ try {
40
+ result = copy(value, 0);
41
+ }
42
+ catch {
43
+ throw invalid();
44
+ }
45
+ if (typeof result !== "object" || result === null || Array.isArray(result))
46
+ throw invalid();
47
+ const record = result;
48
+ if (!Object.hasOwn(record, "client_id") || typeof record.client_id !== "string" || record.client_id.trim() === "")
49
+ throw new Error("OAuth client registration response missing client_id");
50
+ for (const key of ["client_id", "client_secret", "token_endpoint_auth_method", "application_type", "client_name", "client_uri", "logo_uri", "scope",
51
+ "tos_uri", "policy_uri", "jwks_uri", "software_id", "software_version", "software_statement", "registration_access_token", "registration_client_uri", "issuer"]) {
52
+ if (Object.hasOwn(record, key) && record[key] !== null && typeof record[key] !== "string")
53
+ throw invalid();
54
+ }
55
+ if (typeof record.client_secret === "string" && record.client_secret.trim() === "")
56
+ throw invalid();
57
+ for (const key of ["redirect_uris", "grant_types", "response_types", "contacts"]) {
58
+ const entry = record[key];
59
+ if (Object.hasOwn(record, key) && entry !== null && (!Array.isArray(entry) || entry.some(item => typeof item !== "string")))
60
+ throw invalid();
61
+ }
62
+ for (const key of ["client_id_issued_at", "client_secret_expires_at"]) {
63
+ const entry = record[key];
64
+ if (Object.hasOwn(record, key) && entry !== null && (typeof entry !== "number" || !Number.isSafeInteger(entry) || entry < 0))
65
+ throw invalid();
66
+ }
67
+ try {
68
+ normalizeOAuthScope(Object.hasOwn(record, "scope") && record.scope !== null ? record.scope : undefined);
69
+ }
70
+ catch {
71
+ throw invalid();
72
+ }
73
+ if (Buffer.byteLength(JSON.stringify(record), "utf8") > 64 * 1024)
74
+ throw invalid();
75
+ return record;
76
+ }
77
+ export function normalizeStoredOAuthClient(value) {
78
+ if (typeof value !== "object" || value === null || Array.isArray(value))
79
+ return null;
80
+ const record = value;
81
+ const clientId = Object.hasOwn(record, "clientId") ? record.clientId : undefined;
82
+ const clientSecret = Object.hasOwn(record, "clientSecret") ? record.clientSecret : undefined;
83
+ if (typeof clientId !== "string" || clientId.trim() === "" ||
84
+ (clientSecret !== undefined && (typeof clientSecret !== "string" || clientSecret.trim() === "")))
85
+ return null;
86
+ const client = { clientId: clientId.trim(), ...(clientSecret === undefined ? {} : { clientSecret: clientSecret.trim() }) };
87
+ const method = normalizeOAuthTokenEndpointAuthMethod(Object.hasOwn(record, "tokenEndpointAuthMethod") ? record.tokenEndpointAuthMethod : undefined);
88
+ if (Object.hasOwn(record, "registration") && record.registration !== undefined) {
89
+ const registration = parseOAuthClientRegistration(record.registration);
90
+ const registeredSecret = Object.hasOwn(registration, "client_secret") ? registration.client_secret?.trim() : undefined;
91
+ if (registration.client_id.trim() !== client.clientId || registeredSecret !== client.clientSecret)
92
+ throw new Error("OAuth client registration does not match the client identity");
93
+ client.registration = registration;
94
+ const registrationMethod = normalizeOAuthTokenEndpointAuthMethod(Object.hasOwn(registration, "token_endpoint_auth_method") ? registration.token_endpoint_auth_method : undefined);
95
+ if (method !== undefined && registrationMethod !== undefined && method !== registrationMethod)
96
+ throw new Error("OAuth token endpoint authentication conflicts with the client registration");
97
+ if (registrationMethod !== undefined)
98
+ client.tokenEndpointAuthMethod = registrationMethod;
99
+ }
100
+ if (method !== undefined)
101
+ client.tokenEndpointAuthMethod = method;
102
+ return client;
103
+ }
@@ -1,4 +1,6 @@
1
+ import { normalizeStoredOAuthClient, parseOAuthClientRegistration } 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;
@@ -39,7 +44,7 @@ export function createDefaultOAuthClientProvider(options) {
39
44
  const initialGrant = options.initialGrant === undefined ? undefined : {
40
45
  resource: canonicalizeResourceIndicator(options.initialGrant.resource),
41
46
  tokens: normalizeStoredTokens(options.initialGrant.tokens),
42
- client: normalizeConfiguredClient(options.client)
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");
@@ -147,13 +152,16 @@ export function createDefaultOAuthClientProvider(options) {
147
152
  if (forceRefresh && rejectedTokens !== undefined && (rejectedTokens === null || session?.tokens === undefined || !sameTokenGrant(session.tokens, rejectedTokens)))
148
153
  forceRefresh = false;
149
154
  const sessionDiscovery = resolveDiscovery(discovery, session);
150
- if ((options.client.mode === "static" || initialGrant !== undefined) && session !== null && (session.tokens !== undefined || session.refreshState === "pending")) {
151
- const configured = normalizeConfiguredClient(options.client);
155
+ if ((options.client.mode === "static" || configuredClient?.registration !== undefined || initialGrant !== undefined) && session !== null && (session.tokens !== undefined || session.refreshState === "pending")) {
156
+ const configured = configuredClient;
152
157
  if (configured === null || configured.clientId !== session.client.clientId || configured.clientSecret !== session.client.clientSecret)
153
158
  throw new Error("Stored session belongs to a different OAuth client; use separate persistence or explicitly reset it");
154
159
  }
155
160
  if (requestedScope !== undefined && session?.tokens !== undefined && normalizeOAuthScope(session.tokens.scope ?? session.requestedScope) !== requestedScope)
156
161
  throw new Error("Stored session does not match the requested OAuth scope; authorize again or select separate persistence");
162
+ if (configuredTokenMethod !== undefined && session !== null && (session.tokens !== undefined || session.refreshState === "pending") &&
163
+ (session.client.tokenEndpointAuthMethod ?? (session.client.clientSecret === undefined ? "none" : "client_secret_post")) !== configuredTokenMethod)
164
+ throw new Error("Stored session does not match the requested OAuth token endpoint authentication; select separate persistence or reset it");
157
165
  if (session?.refreshState === "pending") {
158
166
  if (!allowInteractive || options.allowInteractive === false || sessionDiscovery === undefined)
159
167
  throw new Error("OAuth refresh outcome is unknown; authorize again before using this resource");
@@ -188,6 +196,7 @@ export function createDefaultOAuthClientProvider(options) {
188
196
  if (session.tokens?.refreshToken === undefined) {
189
197
  return session;
190
198
  }
199
+ assertTokenEndpointAuthentication(session.client, discovery.authorizationServerMetadata);
191
200
  const pendingSession = { ...clearSessionTokens(session), refreshState: "pending" };
192
201
  await saveSession(resource, pendingSession);
193
202
  signal?.throwIfAborted();
@@ -199,6 +208,7 @@ export function createDefaultOAuthClientProvider(options) {
199
208
  tokenEndpoint: requireOwnString(discovery.authorizationServerMetadata, "token_endpoint", "Authorization server metadata"),
200
209
  clientId: session.client.clientId,
201
210
  clientSecret: session.client.clientSecret,
211
+ tokenEndpointAuthMethod: session.client.tokenEndpointAuthMethod,
202
212
  refreshToken: session.tokens.refreshToken,
203
213
  resource,
204
214
  fetch, signal,
@@ -264,6 +274,7 @@ export function createDefaultOAuthClientProvider(options) {
264
274
  let resolvedClient = null;
265
275
  try {
266
276
  resolvedClient = await resolveClient(currentSession, discovery, loopback.redirectUri, fetch, signal);
277
+ assertTokenEndpointAuthentication(resolvedClient.client, discovery.authorizationServerMetadata);
267
278
  const sessionWithoutTokens = {
268
279
  resource,
269
280
  authorizationServer: discovery.authorizationServer,
@@ -287,6 +298,7 @@ export function createDefaultOAuthClientProvider(options) {
287
298
  tokenEndpoint: requireOwnString(discovery.authorizationServerMetadata, "token_endpoint", "Authorization server metadata"),
288
299
  clientId: resolvedClient.client.clientId,
289
300
  clientSecret: resolvedClient.client.clientSecret,
301
+ tokenEndpointAuthMethod: resolvedClient.client.tokenEndpointAuthMethod,
290
302
  code,
291
303
  codeVerifier: verifier,
292
304
  redirectUri: loopback.redirectUri,
@@ -327,8 +339,7 @@ export function createDefaultOAuthClientProvider(options) {
327
339
  }
328
340
  async function resolveClient(existingSession, discovery, redirectUri, fetch, parentSignal) {
329
341
  parentSignal?.throwIfAborted();
330
- const configuredClient = normalizeConfiguredClient(options.client);
331
- if (options.client.mode === "static") {
342
+ if (options.client.mode === "static" || configuredClient?.registration !== undefined) {
332
343
  if (configuredClient === null) {
333
344
  throw new Error("OAuth client_id must not be blank");
334
345
  }
@@ -377,7 +388,12 @@ export function createDefaultOAuthClientProvider(options) {
377
388
  };
378
389
  }
379
390
  }
380
- const registrationBody = buildClientRegistrationBody(clientMetadata, redirectUri);
391
+ const supported = getSupportedTokenAuthMethods(discovery.authorizationServerMetadata);
392
+ const registrationMethod = requestedTokenMethod ?? (supported === undefined ? "none" :
393
+ ["none", "client_secret_basic", "client_secret_post"].find(method => supported.includes(method)));
394
+ if (registrationMethod === undefined || (supported !== undefined && !supported.includes(registrationMethod)))
395
+ throw new Error("Authorization server does not support the requested OAuth token endpoint authentication");
396
+ const registrationBody = buildClientRegistrationBody(clientMetadata, redirectUri, registrationMethod);
381
397
  const deadline = AbortSignal.timeout(30_000);
382
398
  const signal = parentSignal === undefined ? deadline : AbortSignal.any([parentSignal, deadline]);
383
399
  const response = await fetchMcpResponse(fetch, registrationEndpoint, {
@@ -389,16 +405,15 @@ export function createDefaultOAuthClientProvider(options) {
389
405
  signal
390
406
  });
391
407
  const payload = await readOAuthJsonObjectResponse(response, signal);
392
- const clientId = getOwnString(payload, "client_id");
393
- if (clientId === undefined || clientId.trim().length === 0) {
394
- throw new Error("OAuth client registration response missing client_id");
395
- }
396
- const clientSecret = getOwnString(payload, "client_secret");
408
+ const registration = parseOAuthClientRegistration(payload);
409
+ const registeredSecret = getOwnString(registration, "client_secret");
410
+ const responseMethod = normalizeOAuthTokenEndpointAuthMethod(getOwnEntry(registration, "token_endpoint_auth_method")) ??
411
+ requestedTokenMethod ?? (supported === undefined ? undefined : normalizeOAuthTokenEndpointAuthMethod(registrationMethod));
397
412
  const registeredClient = {
398
- clientId: clientId.trim(),
399
- clientSecret: clientSecret !== undefined && clientSecret.trim().length > 0
400
- ? clientSecret.trim()
401
- : undefined
413
+ clientId: registration.client_id.trim(),
414
+ ...(registeredSecret === undefined ? {} : { clientSecret: registeredSecret.trim() }),
415
+ ...(responseMethod === undefined ? {} : { tokenEndpointAuthMethod: responseMethod }),
416
+ registration
402
417
  };
403
418
  await saveRegisteredClient(discovery.authorizationServer, registeredClient);
404
419
  return {
@@ -411,7 +426,7 @@ export function createDefaultOAuthClientProvider(options) {
411
426
  return normalizeLoadedSession(await sessionStore.load(resource));
412
427
  }
413
428
  async function saveSession(resource, session) {
414
- await sessionStore.save(resource, session);
429
+ await sessionStore.save(resource, structuredClone(session));
415
430
  }
416
431
  async function clearSession(resource) {
417
432
  await sessionStore.clear(resource);
@@ -424,7 +439,7 @@ export function createDefaultOAuthClientProvider(options) {
424
439
  return null;
425
440
  }
426
441
  const client = await clientStore.load(issuer);
427
- const normalizedClient = client === null ? null : normalizeStoredClient(client);
442
+ const normalizedClient = client === null ? null : normalizeStoredOAuthClient(client);
428
443
  if (client !== null && normalizedClient === null) {
429
444
  await clientStore.clear(issuer);
430
445
  return null;
@@ -503,7 +518,7 @@ function normalizeLoadedSession(session) {
503
518
  const refreshState = getOwnEntry(session, "refreshState");
504
519
  if (refreshState !== undefined && (refreshState !== "pending" || getOwnEntry(session, "tokens") !== undefined))
505
520
  throw new Error("Stored OAuth refresh state is invalid");
506
- const client = normalizeStoredClient(getOwnEntry(session, "client"));
521
+ const client = normalizeStoredOAuthClient(getOwnEntry(session, "client"));
507
522
  if (client === null) {
508
523
  return { ...session, client: { clientId: "" }, tokens: undefined };
509
524
  }
@@ -513,25 +528,6 @@ function normalizeLoadedSession(session) {
513
528
  tokens: normalizeStoredTokens(getOwnEntry(session, "tokens"))
514
529
  };
515
530
  }
516
- function normalizeStoredClient(value) {
517
- if (!isObjectRecord(value)) {
518
- return null;
519
- }
520
- const clientId = getOwnString(value, "clientId");
521
- if (clientId === undefined || clientId.trim().length === 0) {
522
- return null;
523
- }
524
- const normalizedClientId = clientId.trim();
525
- const clientSecret = getOwnEntry(value, "clientSecret");
526
- if (clientSecret === undefined) {
527
- return { clientId: normalizedClientId };
528
- }
529
- if (typeof clientSecret !== "string" || clientSecret.trim().length === 0) {
530
- return null;
531
- }
532
- const normalizedClientSecret = clientSecret.trim();
533
- return { clientId: normalizedClientId, clientSecret: normalizedClientSecret };
534
- }
535
531
  function normalizeStoredTokens(value) {
536
532
  if (value === undefined || !isObjectRecord(value)) {
537
533
  return undefined;
@@ -581,12 +577,12 @@ function getClientMetadata(client) {
581
577
  };
582
578
  }
583
579
  function normalizeConfiguredClient(client) {
584
- const clientId = normalizeOptionalOAuthString(client.clientId);
585
- if (clientId === undefined) {
580
+ const registration = client.registration === undefined ? undefined : parseOAuthClientRegistration(client.registration);
581
+ const clientId = normalizeOptionalOAuthString(client.clientId) ?? registration?.client_id.trim();
582
+ if (clientId === undefined)
586
583
  return null;
587
- }
588
- const clientSecret = normalizeOptionalOAuthString(client.clientSecret);
589
- return clientSecret === undefined ? { clientId } : { clientId, clientSecret };
584
+ const clientSecret = normalizeOptionalOAuthString(client.clientSecret) ?? (registration === undefined ? undefined : getOwnString(registration, "client_secret")?.trim());
585
+ return normalizeStoredOAuthClient({ clientId, clientSecret, registration, tokenEndpointAuthMethod: client.tokenEndpointAuthMethod });
590
586
  }
591
587
  function normalizeOptionalOAuthString(value) {
592
588
  if (value === undefined) {
@@ -693,12 +689,28 @@ function assertRequestMatchesResource(requestUrl, resource) {
693
689
  throw new Error(`OAuth request URL ${requestUrl} does not match discovered resource ${resource}`);
694
690
  }
695
691
  }
696
- function buildClientRegistrationBody(metadata, redirectUri) {
692
+ function getSupportedTokenAuthMethods(metadata) {
693
+ const value = getOwnEntry(metadata, "token_endpoint_auth_methods_supported");
694
+ if (value === undefined)
695
+ return undefined;
696
+ if (!Array.isArray(value) || value.length > 128 || value.some(method => typeof method !== "string"))
697
+ throw new Error("Invalid OAuth token endpoint authentication metadata");
698
+ return value;
699
+ }
700
+ function assertTokenEndpointAuthentication(client, metadata) {
701
+ const method = client.tokenEndpointAuthMethod ?? (client.clientSecret === undefined ? "none" : "client_secret_post");
702
+ if (method !== "none" && client.clientSecret === undefined)
703
+ throw new Error("OAuth token endpoint authentication requires a client secret");
704
+ const supported = getSupportedTokenAuthMethods(metadata);
705
+ if (supported !== undefined && !supported.includes(method))
706
+ throw new Error("Authorization server does not support the requested OAuth token endpoint authentication");
707
+ }
708
+ function buildClientRegistrationBody(metadata, redirectUri, tokenEndpointAuthMethod) {
697
709
  const body = {
698
710
  redirect_uris: [redirectUri],
699
711
  grant_types: ["authorization_code", "refresh_token"],
700
712
  response_types: ["code"],
701
- token_endpoint_auth_method: "none"
713
+ token_endpoint_auth_method: tokenEndpointAuthMethod
702
714
  };
703
715
  const clientName = metadata === undefined ? undefined : getOwnString(metadata, "clientName");
704
716
  const scope = metadata === undefined ? undefined : getOwnString(metadata, "scope");
@@ -0,0 +1,2 @@
1
+ import type { OAuthTokenEndpointAuthMethod } from "./types.js";
2
+ export declare function normalizeOAuthTokenEndpointAuthMethod(value: unknown): OAuthTokenEndpointAuthMethod | undefined;
@@ -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
+ }
@@ -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;
@@ -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 body = new URLSearchParams({
73
- client_id: input.clientId,
74
- ...input.params
75
- });
76
- if (input.clientSecret !== undefined) {
77
- body.set("client_secret", input.clientSecret);
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,29 @@ export interface StoredOAuthTokens {
70
70
  expiresAt: number | null;
71
71
  scope?: string;
72
72
  }
73
+ /** Full RFC 7591 response, including JSON provider extensions. */
74
+ export interface OAuthClientRegistration extends Record<string, unknown> {
75
+ client_id: string;
76
+ client_secret?: string | null;
77
+ redirect_uris?: string[] | null;
78
+ grant_types?: string[] | null;
79
+ response_types?: string[] | null;
80
+ contacts?: string[] | null;
81
+ client_id_issued_at?: number | null;
82
+ client_secret_expires_at?: number | null;
83
+ token_endpoint_auth_method?: string | null;
84
+ }
85
+ export interface StoredOAuthClient {
86
+ clientId: string;
87
+ clientSecret?: string;
88
+ registration?: OAuthClientRegistration;
89
+ tokenEndpointAuthMethod?: OAuthTokenEndpointAuthMethod;
90
+ }
91
+ export type OAuthTokenEndpointAuthMethod = "none" | "client_secret_post" | "client_secret_basic";
73
92
  export interface StoredOAuthSession {
74
93
  resource: string;
75
94
  authorizationServer: string;
76
- client: {
77
- clientId: string;
78
- clientSecret?: string;
79
- };
95
+ client: StoredOAuthClient;
80
96
  tokens?: StoredOAuthTokens;
81
97
  /** A refresh was begun; its winning response may not have been persisted. */
82
98
  refreshState?: "pending";
@@ -104,11 +120,16 @@ export interface DefaultOAuthClientProviderOptions {
104
120
  clientId?: string;
105
121
  clientSecret?: string;
106
122
  metadata?: OAuthClientMetadata;
123
+ /** Import a complete registration owned by the caller. */
124
+ registration?: OAuthClientRegistration;
125
+ tokenEndpointAuthMethod?: OAuthTokenEndpointAuthMethod;
107
126
  } | {
108
127
  mode: "static";
109
128
  clientId: string;
110
129
  clientSecret?: string;
111
130
  metadata?: OAuthClientMetadata;
131
+ registration?: OAuthClientRegistration;
132
+ tokenEndpointAuthMethod?: OAuthTokenEndpointAuthMethod;
112
133
  };
113
134
  /** Disable interactive authorization while allowing cached tokens and silent refresh. */
114
135
  allowInteractive?: boolean;
@@ -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, } 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,29 @@ interface StoredOAuthTokens {
173
173
  expiresAt: number | null;
174
174
  scope?: string;
175
175
  }
176
+ /** Full RFC 7591 response, including JSON provider extensions. */
177
+ interface OAuthClientRegistration extends Record<string, unknown> {
178
+ client_id: string;
179
+ client_secret?: string | null;
180
+ redirect_uris?: string[] | null;
181
+ grant_types?: string[] | null;
182
+ response_types?: string[] | null;
183
+ contacts?: string[] | null;
184
+ client_id_issued_at?: number | null;
185
+ client_secret_expires_at?: number | null;
186
+ token_endpoint_auth_method?: string | null;
187
+ }
188
+ interface StoredOAuthClient {
189
+ clientId: string;
190
+ clientSecret?: string;
191
+ registration?: OAuthClientRegistration;
192
+ tokenEndpointAuthMethod?: OAuthTokenEndpointAuthMethod;
193
+ }
194
+ type OAuthTokenEndpointAuthMethod = "none" | "client_secret_post" | "client_secret_basic";
176
195
  interface StoredOAuthSession {
177
196
  resource: string;
178
197
  authorizationServer: string;
179
- client: {
180
- clientId: string;
181
- clientSecret?: string;
182
- };
198
+ client: StoredOAuthClient;
183
199
  tokens?: StoredOAuthTokens;
184
200
  /** A refresh was begun; its winning response may not have been persisted. */
185
201
  refreshState?: "pending";
@@ -207,11 +223,16 @@ interface DefaultOAuthClientProviderOptions {
207
223
  clientId?: string;
208
224
  clientSecret?: string;
209
225
  metadata?: OAuthClientMetadata;
226
+ /** Import a complete registration owned by the caller. */
227
+ registration?: OAuthClientRegistration;
228
+ tokenEndpointAuthMethod?: OAuthTokenEndpointAuthMethod;
210
229
  } | {
211
230
  mode: "static";
212
231
  clientId: string;
213
232
  clientSecret?: string;
214
233
  metadata?: OAuthClientMetadata;
234
+ registration?: OAuthClientRegistration;
235
+ tokenEndpointAuthMethod?: OAuthTokenEndpointAuthMethod;
215
236
  };
216
237
  /** Disable interactive authorization while allowing cached tokens and silent refresh. */
217
238
  allowInteractive?: boolean;
@@ -3395,6 +3395,141 @@ var SubscriptionManager = class {
3395
3395
  }
3396
3396
  };
3397
3397
 
3398
+ // ../mcp-oauth/dist/client/scope.js
3399
+ function normalizeOAuthScope(scope) {
3400
+ if (scope === void 0)
3401
+ return void 0;
3402
+ if (typeof scope !== "string" || [...scope].some((char) => char.charCodeAt(0) < 32 || char.charCodeAt(0) > 126 || char === '"' || char === "\\"))
3403
+ throw new Error("Invalid OAuth scope syntax");
3404
+ const normalized = [...new Set(scope.split(" ").filter(Boolean))].sort().join(" ");
3405
+ return normalized || void 0;
3406
+ }
3407
+
3408
+ // ../mcp-oauth/dist/client/token-auth-method.js
3409
+ function normalizeOAuthTokenEndpointAuthMethod(value) {
3410
+ if (value === void 0 || value === null)
3411
+ return void 0;
3412
+ if (value !== "none" && value !== "client_secret_post" && value !== "client_secret_basic")
3413
+ throw new Error("Unsupported OAuth token endpoint authentication method");
3414
+ return value;
3415
+ }
3416
+
3417
+ // ../mcp-oauth/dist/client/client-registration.js
3418
+ function parseOAuthClientRegistration(value) {
3419
+ const invalid = () => new Error("Invalid OAuth client registration metadata");
3420
+ let nodes = 0;
3421
+ function copy(input, depth) {
3422
+ if (++nodes > 2e4 || depth > 64)
3423
+ throw invalid();
3424
+ if (input === null || typeof input === "boolean" || typeof input === "string")
3425
+ return input;
3426
+ if (typeof input === "number" && Number.isFinite(input))
3427
+ return input;
3428
+ if (typeof input !== "object" || input === null)
3429
+ throw invalid();
3430
+ const descriptors = Object.getOwnPropertyDescriptors(input);
3431
+ if (Array.isArray(input)) {
3432
+ const length = descriptors.length?.value;
3433
+ if (length > 2e4)
3434
+ throw invalid();
3435
+ const result2 = [];
3436
+ for (let index = 0; index < length; index++) {
3437
+ const descriptor = descriptors[String(index)];
3438
+ if (descriptor === void 0 || !Object.hasOwn(descriptor, "value"))
3439
+ throw invalid();
3440
+ result2.push(copy(descriptor.value, depth + 1));
3441
+ }
3442
+ return result2;
3443
+ }
3444
+ if (Object.getPrototypeOf(input) !== Object.prototype && Object.getPrototypeOf(input) !== null)
3445
+ throw invalid();
3446
+ return Object.fromEntries(Object.entries(descriptors).filter(([, descriptor]) => descriptor.enumerable).map(([key2, descriptor]) => {
3447
+ if (!Object.hasOwn(descriptor, "value"))
3448
+ throw invalid();
3449
+ return [key2, copy(descriptor.value, depth + 1)];
3450
+ }));
3451
+ }
3452
+ let result;
3453
+ try {
3454
+ result = copy(value, 0);
3455
+ } catch {
3456
+ throw invalid();
3457
+ }
3458
+ if (typeof result !== "object" || result === null || Array.isArray(result))
3459
+ throw invalid();
3460
+ const record2 = result;
3461
+ if (!Object.hasOwn(record2, "client_id") || typeof record2.client_id !== "string" || record2.client_id.trim() === "")
3462
+ throw new Error("OAuth client registration response missing client_id");
3463
+ for (const key2 of [
3464
+ "client_id",
3465
+ "client_secret",
3466
+ "token_endpoint_auth_method",
3467
+ "application_type",
3468
+ "client_name",
3469
+ "client_uri",
3470
+ "logo_uri",
3471
+ "scope",
3472
+ "tos_uri",
3473
+ "policy_uri",
3474
+ "jwks_uri",
3475
+ "software_id",
3476
+ "software_version",
3477
+ "software_statement",
3478
+ "registration_access_token",
3479
+ "registration_client_uri",
3480
+ "issuer"
3481
+ ]) {
3482
+ if (Object.hasOwn(record2, key2) && record2[key2] !== null && typeof record2[key2] !== "string")
3483
+ throw invalid();
3484
+ }
3485
+ if (typeof record2.client_secret === "string" && record2.client_secret.trim() === "")
3486
+ throw invalid();
3487
+ for (const key2 of ["redirect_uris", "grant_types", "response_types", "contacts"]) {
3488
+ const entry = record2[key2];
3489
+ if (Object.hasOwn(record2, key2) && entry !== null && (!Array.isArray(entry) || entry.some((item) => typeof item !== "string")))
3490
+ throw invalid();
3491
+ }
3492
+ for (const key2 of ["client_id_issued_at", "client_secret_expires_at"]) {
3493
+ const entry = record2[key2];
3494
+ if (Object.hasOwn(record2, key2) && entry !== null && (typeof entry !== "number" || !Number.isSafeInteger(entry) || entry < 0))
3495
+ throw invalid();
3496
+ }
3497
+ try {
3498
+ normalizeOAuthScope(Object.hasOwn(record2, "scope") && record2.scope !== null ? record2.scope : void 0);
3499
+ } catch {
3500
+ throw invalid();
3501
+ }
3502
+ if (Buffer.byteLength(JSON.stringify(record2), "utf8") > 64 * 1024)
3503
+ throw invalid();
3504
+ return record2;
3505
+ }
3506
+ function normalizeStoredOAuthClient(value) {
3507
+ if (typeof value !== "object" || value === null || Array.isArray(value))
3508
+ return null;
3509
+ const record2 = value;
3510
+ const clientId = Object.hasOwn(record2, "clientId") ? record2.clientId : void 0;
3511
+ const clientSecret = Object.hasOwn(record2, "clientSecret") ? record2.clientSecret : void 0;
3512
+ if (typeof clientId !== "string" || clientId.trim() === "" || clientSecret !== void 0 && (typeof clientSecret !== "string" || clientSecret.trim() === ""))
3513
+ return null;
3514
+ const client = { clientId: clientId.trim(), ...clientSecret === void 0 ? {} : { clientSecret: clientSecret.trim() } };
3515
+ const method = normalizeOAuthTokenEndpointAuthMethod(Object.hasOwn(record2, "tokenEndpointAuthMethod") ? record2.tokenEndpointAuthMethod : void 0);
3516
+ if (Object.hasOwn(record2, "registration") && record2.registration !== void 0) {
3517
+ const registration = parseOAuthClientRegistration(record2.registration);
3518
+ const registeredSecret = Object.hasOwn(registration, "client_secret") ? registration.client_secret?.trim() : void 0;
3519
+ if (registration.client_id.trim() !== client.clientId || registeredSecret !== client.clientSecret)
3520
+ throw new Error("OAuth client registration does not match the client identity");
3521
+ client.registration = registration;
3522
+ const registrationMethod = normalizeOAuthTokenEndpointAuthMethod(Object.hasOwn(registration, "token_endpoint_auth_method") ? registration.token_endpoint_auth_method : void 0);
3523
+ if (method !== void 0 && registrationMethod !== void 0 && method !== registrationMethod)
3524
+ throw new Error("OAuth token endpoint authentication conflicts with the client registration");
3525
+ if (registrationMethod !== void 0)
3526
+ client.tokenEndpointAuthMethod = registrationMethod;
3527
+ }
3528
+ if (method !== void 0)
3529
+ client.tokenEndpointAuthMethod = method;
3530
+ return client;
3531
+ }
3532
+
3398
3533
  // ../mcp-oauth/dist/client/auth-store-session-store.js
3399
3534
  import crypto from "node:crypto";
3400
3535
  import path4 from "node:path";
@@ -4128,14 +4263,8 @@ function createAuthStoreClientStore(options, namespace) {
4128
4263
  } catch {
4129
4264
  throw new Error("Stored OAuth client must be valid JSON; reset the store explicitly to recover");
4130
4265
  }
4131
- const clientId = isObjectRecord(parsed) ? getOwnString(parsed, "clientId") : void 0;
4132
- if (clientId !== void 0) {
4133
- const client = { clientId };
4134
- if (isObjectRecord(parsed) && Object.prototype.hasOwnProperty.call(parsed, "clientSecret")) {
4135
- client.clientSecret = getOwnEntry3(parsed, "clientSecret");
4136
- }
4137
- return client;
4138
- }
4266
+ if (isObjectRecord(parsed) && typeof getOwnEntry3(parsed, "clientId") === "string")
4267
+ return parsed;
4139
4268
  throw new Error("Stored OAuth client must be a JSON object with clientId");
4140
4269
  },
4141
4270
  async save(issuer, client) {
@@ -4201,14 +4330,7 @@ function isStoredOAuthSession(value) {
4201
4330
  if (!isObjectRecord(value)) {
4202
4331
  return false;
4203
4332
  }
4204
- return isNonBlankOwnString(value, "resource") && isNonBlankOwnString(value, "authorizationServer") && isStoredOAuthClient(getOwnEntry3(value, "client")) && isStoredOAuthDiscovery(getOwnEntry3(value, "discovery")) && (getOwnEntry3(value, "requestedScope") === void 0 || isNonBlankOwnString(value, "requestedScope")) && (getOwnEntry3(value, "refreshState") === void 0 || getOwnEntry3(value, "refreshState") === "pending" && getOwnEntry3(value, "tokens") === void 0) && isStoredOAuthTokensOrMissing(getOwnEntry3(value, "tokens"));
4205
- }
4206
- function isStoredOAuthClient(value) {
4207
- if (!isObjectRecord(value) || !isNonBlankOwnString(value, "clientId")) {
4208
- return false;
4209
- }
4210
- const clientSecret = getOwnEntry3(value, "clientSecret");
4211
- return clientSecret === void 0 || typeof clientSecret === "string" && clientSecret.trim().length > 0;
4333
+ return isNonBlankOwnString(value, "resource") && isNonBlankOwnString(value, "authorizationServer") && normalizeStoredOAuthClient(getOwnEntry3(value, "client")) !== null && isStoredOAuthDiscovery(getOwnEntry3(value, "discovery")) && (getOwnEntry3(value, "requestedScope") === void 0 || isNonBlankOwnString(value, "requestedScope")) && (getOwnEntry3(value, "refreshState") === void 0 || getOwnEntry3(value, "refreshState") === "pending" && getOwnEntry3(value, "tokens") === void 0) && isStoredOAuthTokensOrMissing(getOwnEntry3(value, "tokens"));
4212
4334
  }
4213
4335
  function isStoredOAuthDiscovery(value) {
4214
4336
  if (!isObjectRecord(value)) {
@@ -4242,16 +4364,6 @@ function isNonBlankOwnString(record2, key2) {
4242
4364
  return value !== void 0 && value.trim().length > 0;
4243
4365
  }
4244
4366
 
4245
- // ../mcp-oauth/dist/client/scope.js
4246
- function normalizeOAuthScope(scope) {
4247
- if (scope === void 0)
4248
- return void 0;
4249
- if (typeof scope !== "string" || [...scope].some((char) => char.charCodeAt(0) < 32 || char.charCodeAt(0) > 126 || char === '"' || char === "\\"))
4250
- throw new Error("Invalid OAuth scope syntax");
4251
- const normalized = [...new Set(scope.split(" ").filter(Boolean))].sort().join(" ");
4252
- return normalized || void 0;
4253
- }
4254
-
4255
4367
  // ../mcp-oauth/dist/client/default-oauth-client-provider.js
4256
4368
  import { isIP } from "node:net";
4257
4369
 
@@ -4665,6 +4777,7 @@ async function exchangeAuthorizationCode(input) {
4665
4777
  tokenEndpoint: input.tokenEndpoint,
4666
4778
  clientId: input.clientId,
4667
4779
  clientSecret: input.clientSecret,
4780
+ tokenEndpointAuthMethod: input.tokenEndpointAuthMethod,
4668
4781
  params: {
4669
4782
  grant_type: "authorization_code",
4670
4783
  code: input.code,
@@ -4683,6 +4796,7 @@ async function refreshAccessToken(input) {
4683
4796
  tokenEndpoint: input.tokenEndpoint,
4684
4797
  clientId: input.clientId,
4685
4798
  clientSecret: input.clientSecret,
4799
+ tokenEndpointAuthMethod: input.tokenEndpointAuthMethod,
4686
4800
  params: {
4687
4801
  grant_type: "refresh_token",
4688
4802
  refresh_token: input.refreshToken,
@@ -4694,21 +4808,26 @@ async function refreshAccessToken(input) {
4694
4808
  });
4695
4809
  }
4696
4810
  async function requestTokens(input) {
4697
- const body = new URLSearchParams({
4698
- client_id: input.clientId,
4699
- ...input.params
4700
- });
4701
- if (input.clientSecret !== void 0) {
4702
- body.set("client_secret", input.clientSecret);
4811
+ const method = normalizeOAuthTokenEndpointAuthMethod(input.tokenEndpointAuthMethod) ?? (input.clientSecret === void 0 ? "none" : "client_secret_post");
4812
+ if (method !== "none" && (input.clientSecret === void 0 || input.clientSecret.trim() === ""))
4813
+ throw new Error("OAuth token endpoint authentication requires a client secret");
4814
+ const body = new URLSearchParams(input.params);
4815
+ const headers = new Headers({ "Content-Type": "application/x-www-form-urlencoded" });
4816
+ if (method === "client_secret_basic") {
4817
+ const encoded = new URLSearchParams({ credential: input.clientId }).toString().slice("credential=".length);
4818
+ const encodedSecret = new URLSearchParams({ credential: input.clientSecret }).toString().slice("credential=".length);
4819
+ headers.set("Authorization", `Basic ${Buffer.from(`${encoded}:${encodedSecret}`).toString("base64")}`);
4820
+ } else {
4821
+ body.set("client_id", input.clientId);
4822
+ if (method === "client_secret_post")
4823
+ body.set("client_secret", input.clientSecret);
4703
4824
  }
4704
4825
  input.signal?.throwIfAborted();
4705
4826
  const deadline = AbortSignal.timeout(3e4);
4706
4827
  const signal = input.signal === void 0 ? deadline : AbortSignal.any([input.signal, deadline]);
4707
4828
  const response = await fetchMcpResponse(input.fetch, input.tokenEndpoint, {
4708
4829
  method: "POST",
4709
- headers: {
4710
- "Content-Type": "application/x-www-form-urlencoded"
4711
- },
4830
+ headers,
4712
4831
  body: body.toString(),
4713
4832
  signal
4714
4833
  });
@@ -4861,6 +4980,9 @@ function createDefaultOAuthClientProvider(options) {
4861
4980
  assertPersistenceNamespace(options.persistenceNamespace);
4862
4981
  const clientMetadata = getClientMetadata(options.client);
4863
4982
  const requestedScope = clientMetadata?.scope;
4983
+ const configuredClient = normalizeConfiguredClient(options.client);
4984
+ const requestedTokenMethod = normalizeOAuthTokenEndpointAuthMethod(options.client.tokenEndpointAuthMethod);
4985
+ const configuredTokenMethod = requestedTokenMethod ?? configuredClient?.tokenEndpointAuthMethod;
4864
4986
  const sessionStore = options.sessionStore ?? createAuthStoreSessionStore(options.authStore, options.persistenceNamespace);
4865
4987
  const clientStore = options.authStore === void 0 ? null : createAuthStoreClientStore(options.authStore, options.persistenceNamespace);
4866
4988
  const now = options.now ?? Date.now;
@@ -4878,7 +5000,7 @@ function createDefaultOAuthClientProvider(options) {
4878
5000
  const initialGrant = options.initialGrant === void 0 ? void 0 : {
4879
5001
  resource: canonicalizeResourceIndicator(options.initialGrant.resource),
4880
5002
  tokens: normalizeStoredTokens(options.initialGrant.tokens),
4881
- client: normalizeConfiguredClient(options.client)
5003
+ client: configuredClient
4882
5004
  };
4883
5005
  if (initialGrant !== void 0 && (initialGrant.tokens === void 0 || initialGrant.client === null))
4884
5006
  throw new Error("OAuth initial grant requires valid tokens and the original client ID");
@@ -4982,13 +5104,15 @@ function createDefaultOAuthClientProvider(options) {
4982
5104
  if (forceRefresh && rejectedTokens !== void 0 && (rejectedTokens === null || session?.tokens === void 0 || !sameTokenGrant(session.tokens, rejectedTokens)))
4983
5105
  forceRefresh = false;
4984
5106
  const sessionDiscovery = resolveDiscovery(discovery, session);
4985
- if ((options.client.mode === "static" || initialGrant !== void 0) && session !== null && (session.tokens !== void 0 || session.refreshState === "pending")) {
4986
- const configured = normalizeConfiguredClient(options.client);
5107
+ if ((options.client.mode === "static" || configuredClient?.registration !== void 0 || initialGrant !== void 0) && session !== null && (session.tokens !== void 0 || session.refreshState === "pending")) {
5108
+ const configured = configuredClient;
4987
5109
  if (configured === null || configured.clientId !== session.client.clientId || configured.clientSecret !== session.client.clientSecret)
4988
5110
  throw new Error("Stored session belongs to a different OAuth client; use separate persistence or explicitly reset it");
4989
5111
  }
4990
5112
  if (requestedScope !== void 0 && session?.tokens !== void 0 && normalizeOAuthScope(session.tokens.scope ?? session.requestedScope) !== requestedScope)
4991
5113
  throw new Error("Stored session does not match the requested OAuth scope; authorize again or select separate persistence");
5114
+ if (configuredTokenMethod !== void 0 && session !== null && (session.tokens !== void 0 || session.refreshState === "pending") && (session.client.tokenEndpointAuthMethod ?? (session.client.clientSecret === void 0 ? "none" : "client_secret_post")) !== configuredTokenMethod)
5115
+ throw new Error("Stored session does not match the requested OAuth token endpoint authentication; select separate persistence or reset it");
4992
5116
  if (session?.refreshState === "pending") {
4993
5117
  if (!allowInteractive || options.allowInteractive === false || sessionDiscovery === void 0)
4994
5118
  throw new Error("OAuth refresh outcome is unknown; authorize again before using this resource");
@@ -5021,6 +5145,7 @@ function createDefaultOAuthClientProvider(options) {
5021
5145
  if (session.tokens?.refreshToken === void 0) {
5022
5146
  return session;
5023
5147
  }
5148
+ assertTokenEndpointAuthentication(session.client, discovery.authorizationServerMetadata);
5024
5149
  const pendingSession = { ...clearSessionTokens(session), refreshState: "pending" };
5025
5150
  await saveSession(resource, pendingSession);
5026
5151
  signal?.throwIfAborted();
@@ -5032,6 +5157,7 @@ function createDefaultOAuthClientProvider(options) {
5032
5157
  tokenEndpoint: requireOwnString(discovery.authorizationServerMetadata, "token_endpoint", "Authorization server metadata"),
5033
5158
  clientId: session.client.clientId,
5034
5159
  clientSecret: session.client.clientSecret,
5160
+ tokenEndpointAuthMethod: session.client.tokenEndpointAuthMethod,
5035
5161
  refreshToken: session.tokens.refreshToken,
5036
5162
  resource,
5037
5163
  fetch: fetch2,
@@ -5095,6 +5221,7 @@ function createDefaultOAuthClientProvider(options) {
5095
5221
  let resolvedClient = null;
5096
5222
  try {
5097
5223
  resolvedClient = await resolveClient(currentSession, discovery, loopback.redirectUri, fetch2, signal);
5224
+ assertTokenEndpointAuthentication(resolvedClient.client, discovery.authorizationServerMetadata);
5098
5225
  const sessionWithoutTokens = {
5099
5226
  resource,
5100
5227
  authorizationServer: discovery.authorizationServer,
@@ -5118,6 +5245,7 @@ function createDefaultOAuthClientProvider(options) {
5118
5245
  tokenEndpoint: requireOwnString(discovery.authorizationServerMetadata, "token_endpoint", "Authorization server metadata"),
5119
5246
  clientId: resolvedClient.client.clientId,
5120
5247
  clientSecret: resolvedClient.client.clientSecret,
5248
+ tokenEndpointAuthMethod: resolvedClient.client.tokenEndpointAuthMethod,
5121
5249
  code,
5122
5250
  codeVerifier: verifier,
5123
5251
  redirectUri: loopback.redirectUri,
@@ -5157,8 +5285,7 @@ function createDefaultOAuthClientProvider(options) {
5157
5285
  }
5158
5286
  async function resolveClient(existingSession, discovery, redirectUri, fetch2, parentSignal) {
5159
5287
  parentSignal?.throwIfAborted();
5160
- const configuredClient = normalizeConfiguredClient(options.client);
5161
- if (options.client.mode === "static") {
5288
+ if (options.client.mode === "static" || configuredClient?.registration !== void 0) {
5162
5289
  if (configuredClient === null) {
5163
5290
  throw new Error("OAuth client_id must not be blank");
5164
5291
  }
@@ -5205,7 +5332,11 @@ function createDefaultOAuthClientProvider(options) {
5205
5332
  };
5206
5333
  }
5207
5334
  }
5208
- const registrationBody = buildClientRegistrationBody(clientMetadata, redirectUri);
5335
+ const supported = getSupportedTokenAuthMethods(discovery.authorizationServerMetadata);
5336
+ const registrationMethod = requestedTokenMethod ?? (supported === void 0 ? "none" : ["none", "client_secret_basic", "client_secret_post"].find((method) => supported.includes(method)));
5337
+ if (registrationMethod === void 0 || supported !== void 0 && !supported.includes(registrationMethod))
5338
+ throw new Error("Authorization server does not support the requested OAuth token endpoint authentication");
5339
+ const registrationBody = buildClientRegistrationBody(clientMetadata, redirectUri, registrationMethod);
5209
5340
  const deadline = AbortSignal.timeout(3e4);
5210
5341
  const signal = parentSignal === void 0 ? deadline : AbortSignal.any([parentSignal, deadline]);
5211
5342
  const response = await fetchMcpResponse(fetch2, registrationEndpoint, {
@@ -5217,14 +5348,14 @@ function createDefaultOAuthClientProvider(options) {
5217
5348
  signal
5218
5349
  });
5219
5350
  const payload = await readOAuthJsonObjectResponse(response, signal);
5220
- const clientId = getOwnString2(payload, "client_id");
5221
- if (clientId === void 0 || clientId.trim().length === 0) {
5222
- throw new Error("OAuth client registration response missing client_id");
5223
- }
5224
- const clientSecret = getOwnString2(payload, "client_secret");
5351
+ const registration = parseOAuthClientRegistration(payload);
5352
+ const registeredSecret = getOwnString2(registration, "client_secret");
5353
+ const responseMethod = normalizeOAuthTokenEndpointAuthMethod(getOwnEntry6(registration, "token_endpoint_auth_method")) ?? requestedTokenMethod ?? (supported === void 0 ? void 0 : normalizeOAuthTokenEndpointAuthMethod(registrationMethod));
5225
5354
  const registeredClient = {
5226
- clientId: clientId.trim(),
5227
- clientSecret: clientSecret !== void 0 && clientSecret.trim().length > 0 ? clientSecret.trim() : void 0
5355
+ clientId: registration.client_id.trim(),
5356
+ ...registeredSecret === void 0 ? {} : { clientSecret: registeredSecret.trim() },
5357
+ ...responseMethod === void 0 ? {} : { tokenEndpointAuthMethod: responseMethod },
5358
+ registration
5228
5359
  };
5229
5360
  await saveRegisteredClient(discovery.authorizationServer, registeredClient);
5230
5361
  return {
@@ -5237,7 +5368,7 @@ function createDefaultOAuthClientProvider(options) {
5237
5368
  return normalizeLoadedSession(await sessionStore.load(resource));
5238
5369
  }
5239
5370
  async function saveSession(resource, session) {
5240
- await sessionStore.save(resource, session);
5371
+ await sessionStore.save(resource, structuredClone(session));
5241
5372
  }
5242
5373
  async function clearSession(resource) {
5243
5374
  await sessionStore.clear(resource);
@@ -5250,7 +5381,7 @@ function createDefaultOAuthClientProvider(options) {
5250
5381
  return null;
5251
5382
  }
5252
5383
  const client = await clientStore.load(issuer);
5253
- const normalizedClient = client === null ? null : normalizeStoredClient(client);
5384
+ const normalizedClient = client === null ? null : normalizeStoredOAuthClient(client);
5254
5385
  if (client !== null && normalizedClient === null) {
5255
5386
  await clientStore.clear(issuer);
5256
5387
  return null;
@@ -5323,7 +5454,7 @@ function normalizeLoadedSession(session) {
5323
5454
  const refreshState = getOwnEntry6(session, "refreshState");
5324
5455
  if (refreshState !== void 0 && (refreshState !== "pending" || getOwnEntry6(session, "tokens") !== void 0))
5325
5456
  throw new Error("Stored OAuth refresh state is invalid");
5326
- const client = normalizeStoredClient(getOwnEntry6(session, "client"));
5457
+ const client = normalizeStoredOAuthClient(getOwnEntry6(session, "client"));
5327
5458
  if (client === null) {
5328
5459
  return { ...session, client: { clientId: "" }, tokens: void 0 };
5329
5460
  }
@@ -5333,25 +5464,6 @@ function normalizeLoadedSession(session) {
5333
5464
  tokens: normalizeStoredTokens(getOwnEntry6(session, "tokens"))
5334
5465
  };
5335
5466
  }
5336
- function normalizeStoredClient(value) {
5337
- if (!isObjectRecord3(value)) {
5338
- return null;
5339
- }
5340
- const clientId = getOwnString2(value, "clientId");
5341
- if (clientId === void 0 || clientId.trim().length === 0) {
5342
- return null;
5343
- }
5344
- const normalizedClientId = clientId.trim();
5345
- const clientSecret = getOwnEntry6(value, "clientSecret");
5346
- if (clientSecret === void 0) {
5347
- return { clientId: normalizedClientId };
5348
- }
5349
- if (typeof clientSecret !== "string" || clientSecret.trim().length === 0) {
5350
- return null;
5351
- }
5352
- const normalizedClientSecret = clientSecret.trim();
5353
- return { clientId: normalizedClientId, clientSecret: normalizedClientSecret };
5354
- }
5355
5467
  function normalizeStoredTokens(value) {
5356
5468
  if (value === void 0 || !isObjectRecord3(value)) {
5357
5469
  return void 0;
@@ -5387,12 +5499,12 @@ function getClientMetadata(client) {
5387
5499
  };
5388
5500
  }
5389
5501
  function normalizeConfiguredClient(client) {
5390
- const clientId = normalizeOptionalOAuthString(client.clientId);
5391
- if (clientId === void 0) {
5502
+ const registration = client.registration === void 0 ? void 0 : parseOAuthClientRegistration(client.registration);
5503
+ const clientId = normalizeOptionalOAuthString(client.clientId) ?? registration?.client_id.trim();
5504
+ if (clientId === void 0)
5392
5505
  return null;
5393
- }
5394
- const clientSecret = normalizeOptionalOAuthString(client.clientSecret);
5395
- return clientSecret === void 0 ? { clientId } : { clientId, clientSecret };
5506
+ const clientSecret = normalizeOptionalOAuthString(client.clientSecret) ?? (registration === void 0 ? void 0 : getOwnString2(registration, "client_secret")?.trim());
5507
+ return normalizeStoredOAuthClient({ clientId, clientSecret, registration, tokenEndpointAuthMethod: client.tokenEndpointAuthMethod });
5396
5508
  }
5397
5509
  function normalizeOptionalOAuthString(value) {
5398
5510
  if (value === void 0) {
@@ -5492,12 +5604,28 @@ function assertRequestMatchesResource(requestUrl, resource) {
5492
5604
  throw new Error(`OAuth request URL ${requestUrl} does not match discovered resource ${resource}`);
5493
5605
  }
5494
5606
  }
5495
- function buildClientRegistrationBody(metadata, redirectUri) {
5607
+ function getSupportedTokenAuthMethods(metadata) {
5608
+ const value = getOwnEntry6(metadata, "token_endpoint_auth_methods_supported");
5609
+ if (value === void 0)
5610
+ return void 0;
5611
+ if (!Array.isArray(value) || value.length > 128 || value.some((method) => typeof method !== "string"))
5612
+ throw new Error("Invalid OAuth token endpoint authentication metadata");
5613
+ return value;
5614
+ }
5615
+ function assertTokenEndpointAuthentication(client, metadata) {
5616
+ const method = client.tokenEndpointAuthMethod ?? (client.clientSecret === void 0 ? "none" : "client_secret_post");
5617
+ if (method !== "none" && client.clientSecret === void 0)
5618
+ throw new Error("OAuth token endpoint authentication requires a client secret");
5619
+ const supported = getSupportedTokenAuthMethods(metadata);
5620
+ if (supported !== void 0 && !supported.includes(method))
5621
+ throw new Error("Authorization server does not support the requested OAuth token endpoint authentication");
5622
+ }
5623
+ function buildClientRegistrationBody(metadata, redirectUri, tokenEndpointAuthMethod) {
5496
5624
  const body = {
5497
5625
  redirect_uris: [redirectUri],
5498
5626
  grant_types: ["authorization_code", "refresh_token"],
5499
5627
  response_types: ["code"],
5500
- token_endpoint_auth_method: "none"
5628
+ token_endpoint_auth_method: tokenEndpointAuthMethod
5501
5629
  };
5502
5630
  const clientName = metadata === void 0 ? void 0 : getOwnString2(metadata, "clientName");
5503
5631
  const scope = metadata === void 0 ? void 0 : getOwnString2(metadata, "scope");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tiny-http-mcp-server",
3
- "version": "0.1.32",
3
+ "version": "0.1.34",
4
4
  "description": "Minimal MCP server over HTTP built on tiny-stdio-mcp-server",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",