tiny-http-mcp-server 0.1.32 → 0.1.33

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.33",
22
22
  "license": "MIT"
23
23
  },
24
24
  {
@@ -112,6 +112,13 @@ 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.
115
122
  Static clients and dynamic initial-grant imports require cached grants to match
116
123
  the original normalized client ID and secret. A different client configuration
117
124
  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,94 @@
1
+ import { normalizeOAuthScope } from "./scope.js";
2
+ /** Validate and copy a bounded JSON DCR response without quoting credential input. */
3
+ export function parseOAuthClientRegistration(value) {
4
+ const invalid = () => new Error("Invalid OAuth client registration metadata");
5
+ let nodes = 0;
6
+ function copy(input, depth) {
7
+ if (++nodes > 20_000 || depth > 64)
8
+ throw invalid();
9
+ if (input === null || typeof input === "boolean" || typeof input === "string")
10
+ return input;
11
+ if (typeof input === "number" && Number.isFinite(input))
12
+ return input;
13
+ if (typeof input !== "object" || input === null)
14
+ throw invalid();
15
+ const descriptors = Object.getOwnPropertyDescriptors(input);
16
+ if (Array.isArray(input)) {
17
+ const length = descriptors.length?.value;
18
+ if (length > 20_000)
19
+ throw invalid();
20
+ const result = [];
21
+ for (let index = 0; index < length; index++) {
22
+ const descriptor = descriptors[String(index)];
23
+ if (descriptor === undefined || !Object.hasOwn(descriptor, "value"))
24
+ throw invalid();
25
+ result.push(copy(descriptor.value, depth + 1));
26
+ }
27
+ return result;
28
+ }
29
+ if (Object.getPrototypeOf(input) !== Object.prototype && Object.getPrototypeOf(input) !== null)
30
+ throw invalid();
31
+ return Object.fromEntries(Object.entries(descriptors).filter(([, descriptor]) => descriptor.enumerable).map(([key, descriptor]) => {
32
+ if (!Object.hasOwn(descriptor, "value"))
33
+ throw invalid();
34
+ return [key, copy(descriptor.value, depth + 1)];
35
+ }));
36
+ }
37
+ let result;
38
+ try {
39
+ result = copy(value, 0);
40
+ }
41
+ catch {
42
+ throw invalid();
43
+ }
44
+ if (typeof result !== "object" || result === null || Array.isArray(result))
45
+ throw invalid();
46
+ const record = result;
47
+ if (!Object.hasOwn(record, "client_id") || typeof record.client_id !== "string" || record.client_id.trim() === "")
48
+ throw new Error("OAuth client registration response missing client_id");
49
+ for (const key of ["client_id", "client_secret", "token_endpoint_auth_method", "application_type", "client_name", "client_uri", "logo_uri", "scope",
50
+ "tos_uri", "policy_uri", "jwks_uri", "software_id", "software_version", "software_statement", "registration_access_token", "registration_client_uri", "issuer"]) {
51
+ if (Object.hasOwn(record, key) && record[key] !== null && typeof record[key] !== "string")
52
+ throw invalid();
53
+ }
54
+ if (typeof record.client_secret === "string" && record.client_secret.trim() === "")
55
+ throw invalid();
56
+ for (const key of ["redirect_uris", "grant_types", "response_types", "contacts"]) {
57
+ const entry = record[key];
58
+ if (Object.hasOwn(record, key) && entry !== null && (!Array.isArray(entry) || entry.some(item => typeof item !== "string")))
59
+ throw invalid();
60
+ }
61
+ for (const key of ["client_id_issued_at", "client_secret_expires_at"]) {
62
+ const entry = record[key];
63
+ if (Object.hasOwn(record, key) && entry !== null && (typeof entry !== "number" || !Number.isSafeInteger(entry) || entry < 0))
64
+ throw invalid();
65
+ }
66
+ try {
67
+ normalizeOAuthScope(Object.hasOwn(record, "scope") && record.scope !== null ? record.scope : undefined);
68
+ }
69
+ catch {
70
+ throw invalid();
71
+ }
72
+ if (Buffer.byteLength(JSON.stringify(record), "utf8") > 64 * 1024)
73
+ throw invalid();
74
+ return record;
75
+ }
76
+ export function normalizeStoredOAuthClient(value) {
77
+ if (typeof value !== "object" || value === null || Array.isArray(value))
78
+ return null;
79
+ const record = value;
80
+ const clientId = Object.hasOwn(record, "clientId") ? record.clientId : undefined;
81
+ const clientSecret = Object.hasOwn(record, "clientSecret") ? record.clientSecret : undefined;
82
+ if (typeof clientId !== "string" || clientId.trim() === "" ||
83
+ (clientSecret !== undefined && (typeof clientSecret !== "string" || clientSecret.trim() === "")))
84
+ return null;
85
+ const client = { clientId: clientId.trim(), ...(clientSecret === undefined ? {} : { clientSecret: clientSecret.trim() }) };
86
+ if (Object.hasOwn(record, "registration") && record.registration !== undefined) {
87
+ const registration = parseOAuthClientRegistration(record.registration);
88
+ const registeredSecret = Object.hasOwn(registration, "client_secret") ? registration.client_secret?.trim() : undefined;
89
+ if (registration.client_id.trim() !== client.clientId || registeredSecret !== client.clientSecret)
90
+ throw new Error("OAuth client registration does not match the client identity");
91
+ client.registration = registration;
92
+ }
93
+ return client;
94
+ }
@@ -1,3 +1,4 @@
1
+ import { normalizeStoredOAuthClient, parseOAuthClientRegistration } from "./client-registration.js";
1
2
  import { normalizeOAuthScope } from "./scope.js";
2
3
  import { isIP } from "node:net";
3
4
  import { fetchMcpResponse } from "../http-fetch.js";
@@ -21,6 +22,7 @@ export function createDefaultOAuthClientProvider(options) {
21
22
  assertPersistenceNamespace(options.persistenceNamespace);
22
23
  const clientMetadata = getClientMetadata(options.client);
23
24
  const requestedScope = clientMetadata?.scope;
25
+ const configuredClient = normalizeConfiguredClient(options.client);
24
26
  const sessionStore = options.sessionStore ?? createAuthStoreSessionStore(options.authStore, options.persistenceNamespace);
25
27
  const clientStore = options.authStore === undefined ? null : createAuthStoreClientStore(options.authStore, options.persistenceNamespace);
26
28
  const now = options.now ?? Date.now;
@@ -39,7 +41,7 @@ export function createDefaultOAuthClientProvider(options) {
39
41
  const initialGrant = options.initialGrant === undefined ? undefined : {
40
42
  resource: canonicalizeResourceIndicator(options.initialGrant.resource),
41
43
  tokens: normalizeStoredTokens(options.initialGrant.tokens),
42
- client: normalizeConfiguredClient(options.client)
44
+ client: configuredClient
43
45
  };
44
46
  if (initialGrant !== undefined && (initialGrant.tokens === undefined || initialGrant.client === null))
45
47
  throw new Error("OAuth initial grant requires valid tokens and the original client ID");
@@ -147,8 +149,8 @@ export function createDefaultOAuthClientProvider(options) {
147
149
  if (forceRefresh && rejectedTokens !== undefined && (rejectedTokens === null || session?.tokens === undefined || !sameTokenGrant(session.tokens, rejectedTokens)))
148
150
  forceRefresh = false;
149
151
  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);
152
+ if ((options.client.mode === "static" || configuredClient?.registration !== undefined || initialGrant !== undefined) && session !== null && (session.tokens !== undefined || session.refreshState === "pending")) {
153
+ const configured = configuredClient;
152
154
  if (configured === null || configured.clientId !== session.client.clientId || configured.clientSecret !== session.client.clientSecret)
153
155
  throw new Error("Stored session belongs to a different OAuth client; use separate persistence or explicitly reset it");
154
156
  }
@@ -327,8 +329,7 @@ export function createDefaultOAuthClientProvider(options) {
327
329
  }
328
330
  async function resolveClient(existingSession, discovery, redirectUri, fetch, parentSignal) {
329
331
  parentSignal?.throwIfAborted();
330
- const configuredClient = normalizeConfiguredClient(options.client);
331
- if (options.client.mode === "static") {
332
+ if (options.client.mode === "static" || configuredClient?.registration !== undefined) {
332
333
  if (configuredClient === null) {
333
334
  throw new Error("OAuth client_id must not be blank");
334
335
  }
@@ -389,16 +390,12 @@ export function createDefaultOAuthClientProvider(options) {
389
390
  signal
390
391
  });
391
392
  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");
393
+ const registration = parseOAuthClientRegistration(payload);
394
+ const registeredSecret = getOwnString(registration, "client_secret");
397
395
  const registeredClient = {
398
- clientId: clientId.trim(),
399
- clientSecret: clientSecret !== undefined && clientSecret.trim().length > 0
400
- ? clientSecret.trim()
401
- : undefined
396
+ clientId: registration.client_id.trim(),
397
+ ...(registeredSecret === undefined ? {} : { clientSecret: registeredSecret.trim() }),
398
+ registration
402
399
  };
403
400
  await saveRegisteredClient(discovery.authorizationServer, registeredClient);
404
401
  return {
@@ -424,7 +421,7 @@ export function createDefaultOAuthClientProvider(options) {
424
421
  return null;
425
422
  }
426
423
  const client = await clientStore.load(issuer);
427
- const normalizedClient = client === null ? null : normalizeStoredClient(client);
424
+ const normalizedClient = client === null ? null : normalizeStoredOAuthClient(client);
428
425
  if (client !== null && normalizedClient === null) {
429
426
  await clientStore.clear(issuer);
430
427
  return null;
@@ -503,7 +500,7 @@ function normalizeLoadedSession(session) {
503
500
  const refreshState = getOwnEntry(session, "refreshState");
504
501
  if (refreshState !== undefined && (refreshState !== "pending" || getOwnEntry(session, "tokens") !== undefined))
505
502
  throw new Error("Stored OAuth refresh state is invalid");
506
- const client = normalizeStoredClient(getOwnEntry(session, "client"));
503
+ const client = normalizeStoredOAuthClient(getOwnEntry(session, "client"));
507
504
  if (client === null) {
508
505
  return { ...session, client: { clientId: "" }, tokens: undefined };
509
506
  }
@@ -513,25 +510,6 @@ function normalizeLoadedSession(session) {
513
510
  tokens: normalizeStoredTokens(getOwnEntry(session, "tokens"))
514
511
  };
515
512
  }
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
513
  function normalizeStoredTokens(value) {
536
514
  if (value === undefined || !isObjectRecord(value)) {
537
515
  return undefined;
@@ -581,12 +559,12 @@ function getClientMetadata(client) {
581
559
  };
582
560
  }
583
561
  function normalizeConfiguredClient(client) {
584
- const clientId = normalizeOptionalOAuthString(client.clientId);
585
- if (clientId === undefined) {
562
+ const registration = client.registration === undefined ? undefined : parseOAuthClientRegistration(client.registration);
563
+ const clientId = normalizeOptionalOAuthString(client.clientId) ?? registration?.client_id.trim();
564
+ if (clientId === undefined)
586
565
  return null;
587
- }
588
- const clientSecret = normalizeOptionalOAuthString(client.clientSecret);
589
- return clientSecret === undefined ? { clientId } : { clientId, clientSecret };
566
+ const clientSecret = normalizeOptionalOAuthString(client.clientSecret) ?? (registration === undefined ? undefined : getOwnString(registration, "client_secret")?.trim());
567
+ return normalizeStoredOAuthClient({ clientId, clientSecret, registration });
590
568
  }
591
569
  function normalizeOptionalOAuthString(value) {
592
570
  if (value === undefined) {
@@ -70,13 +70,27 @@ 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
+ }
73
90
  export interface StoredOAuthSession {
74
91
  resource: string;
75
92
  authorizationServer: string;
76
- client: {
77
- clientId: string;
78
- clientSecret?: string;
79
- };
93
+ client: StoredOAuthClient;
80
94
  tokens?: StoredOAuthTokens;
81
95
  /** A refresh was begun; its winning response may not have been persisted. */
82
96
  refreshState?: "pending";
@@ -104,11 +118,14 @@ export interface DefaultOAuthClientProviderOptions {
104
118
  clientId?: string;
105
119
  clientSecret?: string;
106
120
  metadata?: OAuthClientMetadata;
121
+ /** Import a complete registration owned by the caller. */
122
+ registration?: OAuthClientRegistration;
107
123
  } | {
108
124
  mode: "static";
109
125
  clientId: string;
110
126
  clientSecret?: string;
111
127
  metadata?: OAuthClientMetadata;
128
+ registration?: OAuthClientRegistration;
112
129
  };
113
130
  /** Disable interactive authorization while allowing cached tokens and silent refresh. */
114
131
  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, 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,27 @@ 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
+ }
176
193
  interface StoredOAuthSession {
177
194
  resource: string;
178
195
  authorizationServer: string;
179
- client: {
180
- clientId: string;
181
- clientSecret?: string;
182
- };
196
+ client: StoredOAuthClient;
183
197
  tokens?: StoredOAuthTokens;
184
198
  /** A refresh was begun; its winning response may not have been persisted. */
185
199
  refreshState?: "pending";
@@ -207,11 +221,14 @@ interface DefaultOAuthClientProviderOptions {
207
221
  clientId?: string;
208
222
  clientSecret?: string;
209
223
  metadata?: OAuthClientMetadata;
224
+ /** Import a complete registration owned by the caller. */
225
+ registration?: OAuthClientRegistration;
210
226
  } | {
211
227
  mode: "static";
212
228
  clientId: string;
213
229
  clientSecret?: string;
214
230
  metadata?: OAuthClientMetadata;
231
+ registration?: OAuthClientRegistration;
215
232
  };
216
233
  /** Disable interactive authorization while allowing cached tokens and silent refresh. */
217
234
  allowInteractive?: boolean;
@@ -3395,6 +3395,124 @@ 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/client-registration.js
3409
+ function parseOAuthClientRegistration(value) {
3410
+ const invalid = () => new Error("Invalid OAuth client registration metadata");
3411
+ let nodes = 0;
3412
+ function copy(input, depth) {
3413
+ if (++nodes > 2e4 || depth > 64)
3414
+ throw invalid();
3415
+ if (input === null || typeof input === "boolean" || typeof input === "string")
3416
+ return input;
3417
+ if (typeof input === "number" && Number.isFinite(input))
3418
+ return input;
3419
+ if (typeof input !== "object" || input === null)
3420
+ throw invalid();
3421
+ const descriptors = Object.getOwnPropertyDescriptors(input);
3422
+ if (Array.isArray(input)) {
3423
+ const length = descriptors.length?.value;
3424
+ if (length > 2e4)
3425
+ throw invalid();
3426
+ const result2 = [];
3427
+ for (let index = 0; index < length; index++) {
3428
+ const descriptor = descriptors[String(index)];
3429
+ if (descriptor === void 0 || !Object.hasOwn(descriptor, "value"))
3430
+ throw invalid();
3431
+ result2.push(copy(descriptor.value, depth + 1));
3432
+ }
3433
+ return result2;
3434
+ }
3435
+ if (Object.getPrototypeOf(input) !== Object.prototype && Object.getPrototypeOf(input) !== null)
3436
+ throw invalid();
3437
+ return Object.fromEntries(Object.entries(descriptors).filter(([, descriptor]) => descriptor.enumerable).map(([key2, descriptor]) => {
3438
+ if (!Object.hasOwn(descriptor, "value"))
3439
+ throw invalid();
3440
+ return [key2, copy(descriptor.value, depth + 1)];
3441
+ }));
3442
+ }
3443
+ let result;
3444
+ try {
3445
+ result = copy(value, 0);
3446
+ } catch {
3447
+ throw invalid();
3448
+ }
3449
+ if (typeof result !== "object" || result === null || Array.isArray(result))
3450
+ throw invalid();
3451
+ const record2 = result;
3452
+ if (!Object.hasOwn(record2, "client_id") || typeof record2.client_id !== "string" || record2.client_id.trim() === "")
3453
+ throw new Error("OAuth client registration response missing client_id");
3454
+ for (const key2 of [
3455
+ "client_id",
3456
+ "client_secret",
3457
+ "token_endpoint_auth_method",
3458
+ "application_type",
3459
+ "client_name",
3460
+ "client_uri",
3461
+ "logo_uri",
3462
+ "scope",
3463
+ "tos_uri",
3464
+ "policy_uri",
3465
+ "jwks_uri",
3466
+ "software_id",
3467
+ "software_version",
3468
+ "software_statement",
3469
+ "registration_access_token",
3470
+ "registration_client_uri",
3471
+ "issuer"
3472
+ ]) {
3473
+ if (Object.hasOwn(record2, key2) && record2[key2] !== null && typeof record2[key2] !== "string")
3474
+ throw invalid();
3475
+ }
3476
+ if (typeof record2.client_secret === "string" && record2.client_secret.trim() === "")
3477
+ throw invalid();
3478
+ for (const key2 of ["redirect_uris", "grant_types", "response_types", "contacts"]) {
3479
+ const entry = record2[key2];
3480
+ if (Object.hasOwn(record2, key2) && entry !== null && (!Array.isArray(entry) || entry.some((item) => typeof item !== "string")))
3481
+ throw invalid();
3482
+ }
3483
+ for (const key2 of ["client_id_issued_at", "client_secret_expires_at"]) {
3484
+ const entry = record2[key2];
3485
+ if (Object.hasOwn(record2, key2) && entry !== null && (typeof entry !== "number" || !Number.isSafeInteger(entry) || entry < 0))
3486
+ throw invalid();
3487
+ }
3488
+ try {
3489
+ normalizeOAuthScope(Object.hasOwn(record2, "scope") && record2.scope !== null ? record2.scope : void 0);
3490
+ } catch {
3491
+ throw invalid();
3492
+ }
3493
+ if (Buffer.byteLength(JSON.stringify(record2), "utf8") > 64 * 1024)
3494
+ throw invalid();
3495
+ return record2;
3496
+ }
3497
+ function normalizeStoredOAuthClient(value) {
3498
+ if (typeof value !== "object" || value === null || Array.isArray(value))
3499
+ return null;
3500
+ const record2 = value;
3501
+ const clientId = Object.hasOwn(record2, "clientId") ? record2.clientId : void 0;
3502
+ const clientSecret = Object.hasOwn(record2, "clientSecret") ? record2.clientSecret : void 0;
3503
+ if (typeof clientId !== "string" || clientId.trim() === "" || clientSecret !== void 0 && (typeof clientSecret !== "string" || clientSecret.trim() === ""))
3504
+ return null;
3505
+ const client = { clientId: clientId.trim(), ...clientSecret === void 0 ? {} : { clientSecret: clientSecret.trim() } };
3506
+ if (Object.hasOwn(record2, "registration") && record2.registration !== void 0) {
3507
+ const registration = parseOAuthClientRegistration(record2.registration);
3508
+ const registeredSecret = Object.hasOwn(registration, "client_secret") ? registration.client_secret?.trim() : void 0;
3509
+ if (registration.client_id.trim() !== client.clientId || registeredSecret !== client.clientSecret)
3510
+ throw new Error("OAuth client registration does not match the client identity");
3511
+ client.registration = registration;
3512
+ }
3513
+ return client;
3514
+ }
3515
+
3398
3516
  // ../mcp-oauth/dist/client/auth-store-session-store.js
3399
3517
  import crypto from "node:crypto";
3400
3518
  import path4 from "node:path";
@@ -4128,14 +4246,8 @@ function createAuthStoreClientStore(options, namespace) {
4128
4246
  } catch {
4129
4247
  throw new Error("Stored OAuth client must be valid JSON; reset the store explicitly to recover");
4130
4248
  }
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
- }
4249
+ if (isObjectRecord(parsed) && typeof getOwnEntry3(parsed, "clientId") === "string")
4250
+ return parsed;
4139
4251
  throw new Error("Stored OAuth client must be a JSON object with clientId");
4140
4252
  },
4141
4253
  async save(issuer, client) {
@@ -4201,14 +4313,7 @@ function isStoredOAuthSession(value) {
4201
4313
  if (!isObjectRecord(value)) {
4202
4314
  return false;
4203
4315
  }
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;
4316
+ 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
4317
  }
4213
4318
  function isStoredOAuthDiscovery(value) {
4214
4319
  if (!isObjectRecord(value)) {
@@ -4242,16 +4347,6 @@ function isNonBlankOwnString(record2, key2) {
4242
4347
  return value !== void 0 && value.trim().length > 0;
4243
4348
  }
4244
4349
 
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
4350
  // ../mcp-oauth/dist/client/default-oauth-client-provider.js
4256
4351
  import { isIP } from "node:net";
4257
4352
 
@@ -4861,6 +4956,7 @@ function createDefaultOAuthClientProvider(options) {
4861
4956
  assertPersistenceNamespace(options.persistenceNamespace);
4862
4957
  const clientMetadata = getClientMetadata(options.client);
4863
4958
  const requestedScope = clientMetadata?.scope;
4959
+ const configuredClient = normalizeConfiguredClient(options.client);
4864
4960
  const sessionStore = options.sessionStore ?? createAuthStoreSessionStore(options.authStore, options.persistenceNamespace);
4865
4961
  const clientStore = options.authStore === void 0 ? null : createAuthStoreClientStore(options.authStore, options.persistenceNamespace);
4866
4962
  const now = options.now ?? Date.now;
@@ -4878,7 +4974,7 @@ function createDefaultOAuthClientProvider(options) {
4878
4974
  const initialGrant = options.initialGrant === void 0 ? void 0 : {
4879
4975
  resource: canonicalizeResourceIndicator(options.initialGrant.resource),
4880
4976
  tokens: normalizeStoredTokens(options.initialGrant.tokens),
4881
- client: normalizeConfiguredClient(options.client)
4977
+ client: configuredClient
4882
4978
  };
4883
4979
  if (initialGrant !== void 0 && (initialGrant.tokens === void 0 || initialGrant.client === null))
4884
4980
  throw new Error("OAuth initial grant requires valid tokens and the original client ID");
@@ -4982,8 +5078,8 @@ function createDefaultOAuthClientProvider(options) {
4982
5078
  if (forceRefresh && rejectedTokens !== void 0 && (rejectedTokens === null || session?.tokens === void 0 || !sameTokenGrant(session.tokens, rejectedTokens)))
4983
5079
  forceRefresh = false;
4984
5080
  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);
5081
+ if ((options.client.mode === "static" || configuredClient?.registration !== void 0 || initialGrant !== void 0) && session !== null && (session.tokens !== void 0 || session.refreshState === "pending")) {
5082
+ const configured = configuredClient;
4987
5083
  if (configured === null || configured.clientId !== session.client.clientId || configured.clientSecret !== session.client.clientSecret)
4988
5084
  throw new Error("Stored session belongs to a different OAuth client; use separate persistence or explicitly reset it");
4989
5085
  }
@@ -5157,8 +5253,7 @@ function createDefaultOAuthClientProvider(options) {
5157
5253
  }
5158
5254
  async function resolveClient(existingSession, discovery, redirectUri, fetch2, parentSignal) {
5159
5255
  parentSignal?.throwIfAborted();
5160
- const configuredClient = normalizeConfiguredClient(options.client);
5161
- if (options.client.mode === "static") {
5256
+ if (options.client.mode === "static" || configuredClient?.registration !== void 0) {
5162
5257
  if (configuredClient === null) {
5163
5258
  throw new Error("OAuth client_id must not be blank");
5164
5259
  }
@@ -5217,14 +5312,12 @@ function createDefaultOAuthClientProvider(options) {
5217
5312
  signal
5218
5313
  });
5219
5314
  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");
5315
+ const registration = parseOAuthClientRegistration(payload);
5316
+ const registeredSecret = getOwnString2(registration, "client_secret");
5225
5317
  const registeredClient = {
5226
- clientId: clientId.trim(),
5227
- clientSecret: clientSecret !== void 0 && clientSecret.trim().length > 0 ? clientSecret.trim() : void 0
5318
+ clientId: registration.client_id.trim(),
5319
+ ...registeredSecret === void 0 ? {} : { clientSecret: registeredSecret.trim() },
5320
+ registration
5228
5321
  };
5229
5322
  await saveRegisteredClient(discovery.authorizationServer, registeredClient);
5230
5323
  return {
@@ -5250,7 +5343,7 @@ function createDefaultOAuthClientProvider(options) {
5250
5343
  return null;
5251
5344
  }
5252
5345
  const client = await clientStore.load(issuer);
5253
- const normalizedClient = client === null ? null : normalizeStoredClient(client);
5346
+ const normalizedClient = client === null ? null : normalizeStoredOAuthClient(client);
5254
5347
  if (client !== null && normalizedClient === null) {
5255
5348
  await clientStore.clear(issuer);
5256
5349
  return null;
@@ -5323,7 +5416,7 @@ function normalizeLoadedSession(session) {
5323
5416
  const refreshState = getOwnEntry6(session, "refreshState");
5324
5417
  if (refreshState !== void 0 && (refreshState !== "pending" || getOwnEntry6(session, "tokens") !== void 0))
5325
5418
  throw new Error("Stored OAuth refresh state is invalid");
5326
- const client = normalizeStoredClient(getOwnEntry6(session, "client"));
5419
+ const client = normalizeStoredOAuthClient(getOwnEntry6(session, "client"));
5327
5420
  if (client === null) {
5328
5421
  return { ...session, client: { clientId: "" }, tokens: void 0 };
5329
5422
  }
@@ -5333,25 +5426,6 @@ function normalizeLoadedSession(session) {
5333
5426
  tokens: normalizeStoredTokens(getOwnEntry6(session, "tokens"))
5334
5427
  };
5335
5428
  }
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
5429
  function normalizeStoredTokens(value) {
5356
5430
  if (value === void 0 || !isObjectRecord3(value)) {
5357
5431
  return void 0;
@@ -5387,12 +5461,12 @@ function getClientMetadata(client) {
5387
5461
  };
5388
5462
  }
5389
5463
  function normalizeConfiguredClient(client) {
5390
- const clientId = normalizeOptionalOAuthString(client.clientId);
5391
- if (clientId === void 0) {
5464
+ const registration = client.registration === void 0 ? void 0 : parseOAuthClientRegistration(client.registration);
5465
+ const clientId = normalizeOptionalOAuthString(client.clientId) ?? registration?.client_id.trim();
5466
+ if (clientId === void 0)
5392
5467
  return null;
5393
- }
5394
- const clientSecret = normalizeOptionalOAuthString(client.clientSecret);
5395
- return clientSecret === void 0 ? { clientId } : { clientId, clientSecret };
5468
+ const clientSecret = normalizeOptionalOAuthString(client.clientSecret) ?? (registration === void 0 ? void 0 : getOwnString2(registration, "client_secret")?.trim());
5469
+ return normalizeStoredOAuthClient({ clientId, clientSecret, registration });
5396
5470
  }
5397
5471
  function normalizeOptionalOAuthString(value) {
5398
5472
  if (value === void 0) {
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.33",
4
4
  "description": "Minimal MCP server over HTTP built on tiny-stdio-mcp-server",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",