tiny-http-mcp-server 0.1.33 → 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.33",
21
+ "version": "0.1.34",
22
22
  "license": "MIT"
23
23
  },
24
24
  {
@@ -119,6 +119,16 @@ Explicit ID/secret values must agree with the imported response. Sessions and
119
119
  native registration stores retain arrays, issuance/expiry timestamps and JSON
120
120
  provider metadata. Registration input is copied, bounded to 64 KiB and 64
121
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.
122
132
  Static clients and dynamic initial-grant imports require cached grants to match
123
133
  the original normalized client ID and secret. A different client configuration
124
134
  fails before attaching or refreshing credentials and retains the stored record;
@@ -1,4 +1,5 @@
1
1
  import { normalizeOAuthScope } from "./scope.js";
2
+ import { normalizeOAuthTokenEndpointAuthMethod } from "./token-auth-method.js";
2
3
  /** Validate and copy a bounded JSON DCR response without quoting credential input. */
3
4
  export function parseOAuthClientRegistration(value) {
4
5
  const invalid = () => new Error("Invalid OAuth client registration metadata");
@@ -83,12 +84,20 @@ export function normalizeStoredOAuthClient(value) {
83
84
  (clientSecret !== undefined && (typeof clientSecret !== "string" || clientSecret.trim() === "")))
84
85
  return null;
85
86
  const client = { clientId: clientId.trim(), ...(clientSecret === undefined ? {} : { clientSecret: clientSecret.trim() }) };
87
+ const method = normalizeOAuthTokenEndpointAuthMethod(Object.hasOwn(record, "tokenEndpointAuthMethod") ? record.tokenEndpointAuthMethod : undefined);
86
88
  if (Object.hasOwn(record, "registration") && record.registration !== undefined) {
87
89
  const registration = parseOAuthClientRegistration(record.registration);
88
90
  const registeredSecret = Object.hasOwn(registration, "client_secret") ? registration.client_secret?.trim() : undefined;
89
91
  if (registration.client_id.trim() !== client.clientId || registeredSecret !== client.clientSecret)
90
92
  throw new Error("OAuth client registration does not match the client identity");
91
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;
92
99
  }
100
+ if (method !== undefined)
101
+ client.tokenEndpointAuthMethod = method;
93
102
  return client;
94
103
  }
@@ -1,5 +1,6 @@
1
1
  import { normalizeStoredOAuthClient, parseOAuthClientRegistration } from "./client-registration.js";
2
2
  import { normalizeOAuthScope } from "./scope.js";
3
+ import { normalizeOAuthTokenEndpointAuthMethod } from "./token-auth-method.js";
3
4
  import { isIP } from "node:net";
4
5
  import { fetchMcpResponse } from "../http-fetch.js";
5
6
  import { URL } from "node:url";
@@ -23,6 +24,8 @@ export function createDefaultOAuthClientProvider(options) {
23
24
  const clientMetadata = getClientMetadata(options.client);
24
25
  const requestedScope = clientMetadata?.scope;
25
26
  const configuredClient = normalizeConfiguredClient(options.client);
27
+ const requestedTokenMethod = normalizeOAuthTokenEndpointAuthMethod(options.client.tokenEndpointAuthMethod);
28
+ const configuredTokenMethod = requestedTokenMethod ?? configuredClient?.tokenEndpointAuthMethod;
26
29
  const sessionStore = options.sessionStore ?? createAuthStoreSessionStore(options.authStore, options.persistenceNamespace);
27
30
  const clientStore = options.authStore === undefined ? null : createAuthStoreClientStore(options.authStore, options.persistenceNamespace);
28
31
  const now = options.now ?? Date.now;
@@ -156,6 +159,9 @@ export function createDefaultOAuthClientProvider(options) {
156
159
  }
157
160
  if (requestedScope !== undefined && session?.tokens !== undefined && normalizeOAuthScope(session.tokens.scope ?? session.requestedScope) !== requestedScope)
158
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");
159
165
  if (session?.refreshState === "pending") {
160
166
  if (!allowInteractive || options.allowInteractive === false || sessionDiscovery === undefined)
161
167
  throw new Error("OAuth refresh outcome is unknown; authorize again before using this resource");
@@ -190,6 +196,7 @@ export function createDefaultOAuthClientProvider(options) {
190
196
  if (session.tokens?.refreshToken === undefined) {
191
197
  return session;
192
198
  }
199
+ assertTokenEndpointAuthentication(session.client, discovery.authorizationServerMetadata);
193
200
  const pendingSession = { ...clearSessionTokens(session), refreshState: "pending" };
194
201
  await saveSession(resource, pendingSession);
195
202
  signal?.throwIfAborted();
@@ -201,6 +208,7 @@ export function createDefaultOAuthClientProvider(options) {
201
208
  tokenEndpoint: requireOwnString(discovery.authorizationServerMetadata, "token_endpoint", "Authorization server metadata"),
202
209
  clientId: session.client.clientId,
203
210
  clientSecret: session.client.clientSecret,
211
+ tokenEndpointAuthMethod: session.client.tokenEndpointAuthMethod,
204
212
  refreshToken: session.tokens.refreshToken,
205
213
  resource,
206
214
  fetch, signal,
@@ -266,6 +274,7 @@ export function createDefaultOAuthClientProvider(options) {
266
274
  let resolvedClient = null;
267
275
  try {
268
276
  resolvedClient = await resolveClient(currentSession, discovery, loopback.redirectUri, fetch, signal);
277
+ assertTokenEndpointAuthentication(resolvedClient.client, discovery.authorizationServerMetadata);
269
278
  const sessionWithoutTokens = {
270
279
  resource,
271
280
  authorizationServer: discovery.authorizationServer,
@@ -289,6 +298,7 @@ export function createDefaultOAuthClientProvider(options) {
289
298
  tokenEndpoint: requireOwnString(discovery.authorizationServerMetadata, "token_endpoint", "Authorization server metadata"),
290
299
  clientId: resolvedClient.client.clientId,
291
300
  clientSecret: resolvedClient.client.clientSecret,
301
+ tokenEndpointAuthMethod: resolvedClient.client.tokenEndpointAuthMethod,
292
302
  code,
293
303
  codeVerifier: verifier,
294
304
  redirectUri: loopback.redirectUri,
@@ -378,7 +388,12 @@ export function createDefaultOAuthClientProvider(options) {
378
388
  };
379
389
  }
380
390
  }
381
- 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);
382
397
  const deadline = AbortSignal.timeout(30_000);
383
398
  const signal = parentSignal === undefined ? deadline : AbortSignal.any([parentSignal, deadline]);
384
399
  const response = await fetchMcpResponse(fetch, registrationEndpoint, {
@@ -392,9 +407,12 @@ export function createDefaultOAuthClientProvider(options) {
392
407
  const payload = await readOAuthJsonObjectResponse(response, signal);
393
408
  const registration = parseOAuthClientRegistration(payload);
394
409
  const registeredSecret = getOwnString(registration, "client_secret");
410
+ const responseMethod = normalizeOAuthTokenEndpointAuthMethod(getOwnEntry(registration, "token_endpoint_auth_method")) ??
411
+ requestedTokenMethod ?? (supported === undefined ? undefined : normalizeOAuthTokenEndpointAuthMethod(registrationMethod));
395
412
  const registeredClient = {
396
413
  clientId: registration.client_id.trim(),
397
414
  ...(registeredSecret === undefined ? {} : { clientSecret: registeredSecret.trim() }),
415
+ ...(responseMethod === undefined ? {} : { tokenEndpointAuthMethod: responseMethod }),
398
416
  registration
399
417
  };
400
418
  await saveRegisteredClient(discovery.authorizationServer, registeredClient);
@@ -408,7 +426,7 @@ export function createDefaultOAuthClientProvider(options) {
408
426
  return normalizeLoadedSession(await sessionStore.load(resource));
409
427
  }
410
428
  async function saveSession(resource, session) {
411
- await sessionStore.save(resource, session);
429
+ await sessionStore.save(resource, structuredClone(session));
412
430
  }
413
431
  async function clearSession(resource) {
414
432
  await sessionStore.clear(resource);
@@ -564,7 +582,7 @@ function normalizeConfiguredClient(client) {
564
582
  if (clientId === undefined)
565
583
  return null;
566
584
  const clientSecret = normalizeOptionalOAuthString(client.clientSecret) ?? (registration === undefined ? undefined : getOwnString(registration, "client_secret")?.trim());
567
- return normalizeStoredOAuthClient({ clientId, clientSecret, registration });
585
+ return normalizeStoredOAuthClient({ clientId, clientSecret, registration, tokenEndpointAuthMethod: client.tokenEndpointAuthMethod });
568
586
  }
569
587
  function normalizeOptionalOAuthString(value) {
570
588
  if (value === undefined) {
@@ -671,12 +689,28 @@ function assertRequestMatchesResource(requestUrl, resource) {
671
689
  throw new Error(`OAuth request URL ${requestUrl} does not match discovered resource ${resource}`);
672
690
  }
673
691
  }
674
- 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) {
675
709
  const body = {
676
710
  redirect_uris: [redirectUri],
677
711
  grant_types: ["authorization_code", "refresh_token"],
678
712
  response_types: ["code"],
679
- token_endpoint_auth_method: "none"
713
+ token_endpoint_auth_method: tokenEndpointAuthMethod
680
714
  };
681
715
  const clientName = metadata === undefined ? undefined : getOwnString(metadata, "clientName");
682
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
  });
@@ -86,7 +86,9 @@ export interface StoredOAuthClient {
86
86
  clientId: string;
87
87
  clientSecret?: string;
88
88
  registration?: OAuthClientRegistration;
89
+ tokenEndpointAuthMethod?: OAuthTokenEndpointAuthMethod;
89
90
  }
91
+ export type OAuthTokenEndpointAuthMethod = "none" | "client_secret_post" | "client_secret_basic";
90
92
  export interface StoredOAuthSession {
91
93
  resource: string;
92
94
  authorizationServer: string;
@@ -120,12 +122,14 @@ export interface DefaultOAuthClientProviderOptions {
120
122
  metadata?: OAuthClientMetadata;
121
123
  /** Import a complete registration owned by the caller. */
122
124
  registration?: OAuthClientRegistration;
125
+ tokenEndpointAuthMethod?: OAuthTokenEndpointAuthMethod;
123
126
  } | {
124
127
  mode: "static";
125
128
  clientId: string;
126
129
  clientSecret?: string;
127
130
  metadata?: OAuthClientMetadata;
128
131
  registration?: OAuthClientRegistration;
132
+ tokenEndpointAuthMethod?: OAuthTokenEndpointAuthMethod;
129
133
  };
130
134
  /** Disable interactive authorization while allowing cached tokens and silent refresh. */
131
135
  allowInteractive?: boolean;
@@ -6,7 +6,7 @@ export { generateCodeChallenge, generateCodeVerifier, } from "./client/pkce.js";
6
6
  export { OAuthError, } from "./client/token-endpoint.js";
7
7
  export { canonicalizeResourceIndicator, } from "./resource-indicator.js";
8
8
  export { createJwksTokenVerifier, } from "./server/jwks-token-verifier.js";
9
- export type { DefaultOAuthClientProviderOptions, OAuthAuthorizationServerMetadata, OAuthClientMetadata, OAuthClientRegistration, OAuthClientProvider, OAuthClientProviderOptions, OAuthDiscoveryResult, OAuthMetadataFetch, OAuthProtectedResourceMetadata, OAuthSessionStore, OAuthUnauthorizedChallenge, StoredOAuthSession, StoredOAuthClient, 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";
10
10
  export type { JwksTokenVerifier, JwksTokenVerifierOptions, JwksVerifiedAccessToken, } from "./server/jwks-token-verifier.js";
11
11
  export type { LoopbackAuthorizationOptions, LoopbackAuthorizationSession, OAuthLandingPage, } from "./client/loopback-authorization.js";
12
12
  export { readBoundedResponseText } from "./http-response.js";
@@ -189,7 +189,9 @@ interface StoredOAuthClient {
189
189
  clientId: string;
190
190
  clientSecret?: string;
191
191
  registration?: OAuthClientRegistration;
192
+ tokenEndpointAuthMethod?: OAuthTokenEndpointAuthMethod;
192
193
  }
194
+ type OAuthTokenEndpointAuthMethod = "none" | "client_secret_post" | "client_secret_basic";
193
195
  interface StoredOAuthSession {
194
196
  resource: string;
195
197
  authorizationServer: string;
@@ -223,12 +225,14 @@ interface DefaultOAuthClientProviderOptions {
223
225
  metadata?: OAuthClientMetadata;
224
226
  /** Import a complete registration owned by the caller. */
225
227
  registration?: OAuthClientRegistration;
228
+ tokenEndpointAuthMethod?: OAuthTokenEndpointAuthMethod;
226
229
  } | {
227
230
  mode: "static";
228
231
  clientId: string;
229
232
  clientSecret?: string;
230
233
  metadata?: OAuthClientMetadata;
231
234
  registration?: OAuthClientRegistration;
235
+ tokenEndpointAuthMethod?: OAuthTokenEndpointAuthMethod;
232
236
  };
233
237
  /** Disable interactive authorization while allowing cached tokens and silent refresh. */
234
238
  allowInteractive?: boolean;
@@ -3405,6 +3405,15 @@ function normalizeOAuthScope(scope) {
3405
3405
  return normalized || void 0;
3406
3406
  }
3407
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
+
3408
3417
  // ../mcp-oauth/dist/client/client-registration.js
3409
3418
  function parseOAuthClientRegistration(value) {
3410
3419
  const invalid = () => new Error("Invalid OAuth client registration metadata");
@@ -3503,13 +3512,21 @@ function normalizeStoredOAuthClient(value) {
3503
3512
  if (typeof clientId !== "string" || clientId.trim() === "" || clientSecret !== void 0 && (typeof clientSecret !== "string" || clientSecret.trim() === ""))
3504
3513
  return null;
3505
3514
  const client = { clientId: clientId.trim(), ...clientSecret === void 0 ? {} : { clientSecret: clientSecret.trim() } };
3515
+ const method = normalizeOAuthTokenEndpointAuthMethod(Object.hasOwn(record2, "tokenEndpointAuthMethod") ? record2.tokenEndpointAuthMethod : void 0);
3506
3516
  if (Object.hasOwn(record2, "registration") && record2.registration !== void 0) {
3507
3517
  const registration = parseOAuthClientRegistration(record2.registration);
3508
3518
  const registeredSecret = Object.hasOwn(registration, "client_secret") ? registration.client_secret?.trim() : void 0;
3509
3519
  if (registration.client_id.trim() !== client.clientId || registeredSecret !== client.clientSecret)
3510
3520
  throw new Error("OAuth client registration does not match the client identity");
3511
3521
  client.registration = registration;
3512
- }
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;
3513
3530
  return client;
3514
3531
  }
3515
3532
 
@@ -4760,6 +4777,7 @@ async function exchangeAuthorizationCode(input) {
4760
4777
  tokenEndpoint: input.tokenEndpoint,
4761
4778
  clientId: input.clientId,
4762
4779
  clientSecret: input.clientSecret,
4780
+ tokenEndpointAuthMethod: input.tokenEndpointAuthMethod,
4763
4781
  params: {
4764
4782
  grant_type: "authorization_code",
4765
4783
  code: input.code,
@@ -4778,6 +4796,7 @@ async function refreshAccessToken(input) {
4778
4796
  tokenEndpoint: input.tokenEndpoint,
4779
4797
  clientId: input.clientId,
4780
4798
  clientSecret: input.clientSecret,
4799
+ tokenEndpointAuthMethod: input.tokenEndpointAuthMethod,
4781
4800
  params: {
4782
4801
  grant_type: "refresh_token",
4783
4802
  refresh_token: input.refreshToken,
@@ -4789,21 +4808,26 @@ async function refreshAccessToken(input) {
4789
4808
  });
4790
4809
  }
4791
4810
  async function requestTokens(input) {
4792
- const body = new URLSearchParams({
4793
- client_id: input.clientId,
4794
- ...input.params
4795
- });
4796
- if (input.clientSecret !== void 0) {
4797
- 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);
4798
4824
  }
4799
4825
  input.signal?.throwIfAborted();
4800
4826
  const deadline = AbortSignal.timeout(3e4);
4801
4827
  const signal = input.signal === void 0 ? deadline : AbortSignal.any([input.signal, deadline]);
4802
4828
  const response = await fetchMcpResponse(input.fetch, input.tokenEndpoint, {
4803
4829
  method: "POST",
4804
- headers: {
4805
- "Content-Type": "application/x-www-form-urlencoded"
4806
- },
4830
+ headers,
4807
4831
  body: body.toString(),
4808
4832
  signal
4809
4833
  });
@@ -4957,6 +4981,8 @@ function createDefaultOAuthClientProvider(options) {
4957
4981
  const clientMetadata = getClientMetadata(options.client);
4958
4982
  const requestedScope = clientMetadata?.scope;
4959
4983
  const configuredClient = normalizeConfiguredClient(options.client);
4984
+ const requestedTokenMethod = normalizeOAuthTokenEndpointAuthMethod(options.client.tokenEndpointAuthMethod);
4985
+ const configuredTokenMethod = requestedTokenMethod ?? configuredClient?.tokenEndpointAuthMethod;
4960
4986
  const sessionStore = options.sessionStore ?? createAuthStoreSessionStore(options.authStore, options.persistenceNamespace);
4961
4987
  const clientStore = options.authStore === void 0 ? null : createAuthStoreClientStore(options.authStore, options.persistenceNamespace);
4962
4988
  const now = options.now ?? Date.now;
@@ -5085,6 +5111,8 @@ function createDefaultOAuthClientProvider(options) {
5085
5111
  }
5086
5112
  if (requestedScope !== void 0 && session?.tokens !== void 0 && normalizeOAuthScope(session.tokens.scope ?? session.requestedScope) !== requestedScope)
5087
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");
5088
5116
  if (session?.refreshState === "pending") {
5089
5117
  if (!allowInteractive || options.allowInteractive === false || sessionDiscovery === void 0)
5090
5118
  throw new Error("OAuth refresh outcome is unknown; authorize again before using this resource");
@@ -5117,6 +5145,7 @@ function createDefaultOAuthClientProvider(options) {
5117
5145
  if (session.tokens?.refreshToken === void 0) {
5118
5146
  return session;
5119
5147
  }
5148
+ assertTokenEndpointAuthentication(session.client, discovery.authorizationServerMetadata);
5120
5149
  const pendingSession = { ...clearSessionTokens(session), refreshState: "pending" };
5121
5150
  await saveSession(resource, pendingSession);
5122
5151
  signal?.throwIfAborted();
@@ -5128,6 +5157,7 @@ function createDefaultOAuthClientProvider(options) {
5128
5157
  tokenEndpoint: requireOwnString(discovery.authorizationServerMetadata, "token_endpoint", "Authorization server metadata"),
5129
5158
  clientId: session.client.clientId,
5130
5159
  clientSecret: session.client.clientSecret,
5160
+ tokenEndpointAuthMethod: session.client.tokenEndpointAuthMethod,
5131
5161
  refreshToken: session.tokens.refreshToken,
5132
5162
  resource,
5133
5163
  fetch: fetch2,
@@ -5191,6 +5221,7 @@ function createDefaultOAuthClientProvider(options) {
5191
5221
  let resolvedClient = null;
5192
5222
  try {
5193
5223
  resolvedClient = await resolveClient(currentSession, discovery, loopback.redirectUri, fetch2, signal);
5224
+ assertTokenEndpointAuthentication(resolvedClient.client, discovery.authorizationServerMetadata);
5194
5225
  const sessionWithoutTokens = {
5195
5226
  resource,
5196
5227
  authorizationServer: discovery.authorizationServer,
@@ -5214,6 +5245,7 @@ function createDefaultOAuthClientProvider(options) {
5214
5245
  tokenEndpoint: requireOwnString(discovery.authorizationServerMetadata, "token_endpoint", "Authorization server metadata"),
5215
5246
  clientId: resolvedClient.client.clientId,
5216
5247
  clientSecret: resolvedClient.client.clientSecret,
5248
+ tokenEndpointAuthMethod: resolvedClient.client.tokenEndpointAuthMethod,
5217
5249
  code,
5218
5250
  codeVerifier: verifier,
5219
5251
  redirectUri: loopback.redirectUri,
@@ -5300,7 +5332,11 @@ function createDefaultOAuthClientProvider(options) {
5300
5332
  };
5301
5333
  }
5302
5334
  }
5303
- 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);
5304
5340
  const deadline = AbortSignal.timeout(3e4);
5305
5341
  const signal = parentSignal === void 0 ? deadline : AbortSignal.any([parentSignal, deadline]);
5306
5342
  const response = await fetchMcpResponse(fetch2, registrationEndpoint, {
@@ -5314,9 +5350,11 @@ function createDefaultOAuthClientProvider(options) {
5314
5350
  const payload = await readOAuthJsonObjectResponse(response, signal);
5315
5351
  const registration = parseOAuthClientRegistration(payload);
5316
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));
5317
5354
  const registeredClient = {
5318
5355
  clientId: registration.client_id.trim(),
5319
5356
  ...registeredSecret === void 0 ? {} : { clientSecret: registeredSecret.trim() },
5357
+ ...responseMethod === void 0 ? {} : { tokenEndpointAuthMethod: responseMethod },
5320
5358
  registration
5321
5359
  };
5322
5360
  await saveRegisteredClient(discovery.authorizationServer, registeredClient);
@@ -5330,7 +5368,7 @@ function createDefaultOAuthClientProvider(options) {
5330
5368
  return normalizeLoadedSession(await sessionStore.load(resource));
5331
5369
  }
5332
5370
  async function saveSession(resource, session) {
5333
- await sessionStore.save(resource, session);
5371
+ await sessionStore.save(resource, structuredClone(session));
5334
5372
  }
5335
5373
  async function clearSession(resource) {
5336
5374
  await sessionStore.clear(resource);
@@ -5466,7 +5504,7 @@ function normalizeConfiguredClient(client) {
5466
5504
  if (clientId === void 0)
5467
5505
  return null;
5468
5506
  const clientSecret = normalizeOptionalOAuthString(client.clientSecret) ?? (registration === void 0 ? void 0 : getOwnString2(registration, "client_secret")?.trim());
5469
- return normalizeStoredOAuthClient({ clientId, clientSecret, registration });
5507
+ return normalizeStoredOAuthClient({ clientId, clientSecret, registration, tokenEndpointAuthMethod: client.tokenEndpointAuthMethod });
5470
5508
  }
5471
5509
  function normalizeOptionalOAuthString(value) {
5472
5510
  if (value === void 0) {
@@ -5566,12 +5604,28 @@ function assertRequestMatchesResource(requestUrl, resource) {
5566
5604
  throw new Error(`OAuth request URL ${requestUrl} does not match discovered resource ${resource}`);
5567
5605
  }
5568
5606
  }
5569
- 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) {
5570
5624
  const body = {
5571
5625
  redirect_uris: [redirectUri],
5572
5626
  grant_types: ["authorization_code", "refresh_token"],
5573
5627
  response_types: ["code"],
5574
- token_endpoint_auth_method: "none"
5628
+ token_endpoint_auth_method: tokenEndpointAuthMethod
5575
5629
  };
5576
5630
  const clientName = metadata === void 0 ? void 0 : getOwnString2(metadata, "clientName");
5577
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.33",
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",