tiny-http-mcp-server 0.1.42 → 0.1.43
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/composition.json +1 -1
- package/node_modules/mcp-oauth/README.md +17 -0
- package/node_modules/mcp-oauth/dist/client/bounded-json.d.ts +2 -0
- package/node_modules/mcp-oauth/dist/client/bounded-json.js +46 -0
- package/node_modules/mcp-oauth/dist/client/client-registration.js +2 -41
- package/node_modules/mcp-oauth/dist/client/resource-bound-store.d.ts +6 -1
- package/node_modules/mcp-oauth/dist/client/resource-bound-store.js +44 -13
- package/node_modules/mcp-oauth/dist/client/token-grant.d.ts +10 -0
- package/node_modules/mcp-oauth/dist/client/token-grant.js +52 -0
- package/node_modules/mcp-oauth/dist/index.d.ts +2 -0
- package/node_modules/mcp-oauth/dist/index.js +1 -0
- package/node_modules/tiny-mcp-client/dist/index.js +98 -61
- package/package.json +1 -1
package/dist/composition.json
CHANGED
|
@@ -142,6 +142,23 @@ Discovery binds an expired or explicitly rejected grant before silent refresh,
|
|
|
142
142
|
using the original configured client. Persisted sessions take precedence,
|
|
143
143
|
including sessions whose tokens have been cleared; an import cannot revive them.
|
|
144
144
|
Input tokens are copied and invalid expiry values fail before authorization.
|
|
145
|
+
For a raw OAuth response, `parseOAuthTokenGrant(response, { issuedAt, expiresAt })`
|
|
146
|
+
returns normalized `StoredOAuthTokens`. It accepts `access_token`, `refresh_token`,
|
|
147
|
+
`token_type`, `scope`, `expires_in` (seconds), `expires_at` (epoch seconds), and
|
|
148
|
+
`expiresAt` (epoch milliseconds). Numeric absolute expiry wins over relative
|
|
149
|
+
lifetime; the options timestamp wins over response timestamps. The optional
|
|
150
|
+
`now` clock anchors a new import. JSON is bounded to 64 KiB/64 levels; malformed
|
|
151
|
+
credentials, scope, timing, accessors and non-JSON metadata are rejected without
|
|
152
|
+
quoting the input. The parser makes no network or storage requests.
|
|
153
|
+
For named native persistence, `createResourceBoundOAuthStores(authStore,
|
|
154
|
+
namespace, identity).importSession(session, { signal, timeoutMs })` explicitly
|
|
155
|
+
replaces the bound grant and original client in one locked document. It takes
|
|
156
|
+
an owned, bounded session snapshot before waiting; validates resource/issuer
|
|
157
|
+
binding; marks full registrations as caller-owned; and retires previous clients.
|
|
158
|
+
Its durable marker suppresses stale automatic environment imports after tokens
|
|
159
|
+
are cleared. Like reset, explicit import can recover a corrupt old document
|
|
160
|
+
without decrypting it. The default lock wait is 30 seconds. A session must
|
|
161
|
+
contain a complete usable grant and matching validated discovery metadata.
|
|
145
162
|
Pass a complete DCR response as `client.registration` (or validate untrusted JSON
|
|
146
163
|
with `parseOAuthClientRegistration`). Dynamic clients infer their original ID
|
|
147
164
|
and secret from that response and reuse it without registering another app.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/** Copy credential JSON without invoking accessors or custom serialization. */
|
|
2
|
+
export function copyBoundedOAuthJson(value, message) {
|
|
3
|
+
const invalid = () => new Error(message);
|
|
4
|
+
let nodes = 0;
|
|
5
|
+
function copy(input, depth) {
|
|
6
|
+
if (++nodes > 20_000 || depth > 64)
|
|
7
|
+
throw invalid();
|
|
8
|
+
if (input === null || typeof input === "boolean" || typeof input === "string")
|
|
9
|
+
return input;
|
|
10
|
+
if (typeof input === "number" && Number.isFinite(input))
|
|
11
|
+
return input;
|
|
12
|
+
if (typeof input !== "object" || input === null)
|
|
13
|
+
throw invalid();
|
|
14
|
+
const descriptors = Object.getOwnPropertyDescriptors(input);
|
|
15
|
+
if (Array.isArray(input)) {
|
|
16
|
+
const length = descriptors.length?.value;
|
|
17
|
+
if (length > 20_000)
|
|
18
|
+
throw invalid();
|
|
19
|
+
const result = [];
|
|
20
|
+
for (let index = 0; index < length; index++) {
|
|
21
|
+
const descriptor = descriptors[String(index)];
|
|
22
|
+
if (descriptor === undefined || !Object.hasOwn(descriptor, "value"))
|
|
23
|
+
throw invalid();
|
|
24
|
+
result.push(copy(descriptor.value, depth + 1));
|
|
25
|
+
}
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
if (Object.getPrototypeOf(input) !== Object.prototype && Object.getPrototypeOf(input) !== null)
|
|
29
|
+
throw invalid();
|
|
30
|
+
return Object.fromEntries(Object.entries(descriptors).filter(([, descriptor]) => descriptor.enumerable).map(([key, descriptor]) => {
|
|
31
|
+
if (!Object.hasOwn(descriptor, "value"))
|
|
32
|
+
throw invalid();
|
|
33
|
+
return [key, copy(descriptor.value, depth + 1)];
|
|
34
|
+
}));
|
|
35
|
+
}
|
|
36
|
+
let result;
|
|
37
|
+
try {
|
|
38
|
+
result = copy(value, 0);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
throw invalid();
|
|
42
|
+
}
|
|
43
|
+
if (Buffer.byteLength(JSON.stringify(result), "utf8") > 64 * 1024)
|
|
44
|
+
throw invalid();
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
@@ -1,48 +1,11 @@
|
|
|
1
|
+
import { copyBoundedOAuthJson } from "./bounded-json.js";
|
|
1
2
|
import { loopbackTarget } from "./loopback-authorization.js";
|
|
2
3
|
import { normalizeOAuthScope } from "./scope.js";
|
|
3
4
|
import { normalizeOAuthTokenEndpointAuthMethod } from "./token-auth-method.js";
|
|
4
5
|
/** Validate and copy a bounded JSON DCR response without quoting credential input. */
|
|
5
6
|
export function parseOAuthClientRegistration(value) {
|
|
6
7
|
const invalid = () => new Error("Invalid OAuth client registration metadata");
|
|
7
|
-
|
|
8
|
-
function copy(input, depth) {
|
|
9
|
-
if (++nodes > 20_000 || depth > 64)
|
|
10
|
-
throw invalid();
|
|
11
|
-
if (input === null || typeof input === "boolean" || typeof input === "string")
|
|
12
|
-
return input;
|
|
13
|
-
if (typeof input === "number" && Number.isFinite(input))
|
|
14
|
-
return input;
|
|
15
|
-
if (typeof input !== "object" || input === null)
|
|
16
|
-
throw invalid();
|
|
17
|
-
const descriptors = Object.getOwnPropertyDescriptors(input);
|
|
18
|
-
if (Array.isArray(input)) {
|
|
19
|
-
const length = descriptors.length?.value;
|
|
20
|
-
if (length > 20_000)
|
|
21
|
-
throw invalid();
|
|
22
|
-
const result = [];
|
|
23
|
-
for (let index = 0; index < length; index++) {
|
|
24
|
-
const descriptor = descriptors[String(index)];
|
|
25
|
-
if (descriptor === undefined || !Object.hasOwn(descriptor, "value"))
|
|
26
|
-
throw invalid();
|
|
27
|
-
result.push(copy(descriptor.value, depth + 1));
|
|
28
|
-
}
|
|
29
|
-
return result;
|
|
30
|
-
}
|
|
31
|
-
if (Object.getPrototypeOf(input) !== Object.prototype && Object.getPrototypeOf(input) !== null)
|
|
32
|
-
throw invalid();
|
|
33
|
-
return Object.fromEntries(Object.entries(descriptors).filter(([, descriptor]) => descriptor.enumerable).map(([key, descriptor]) => {
|
|
34
|
-
if (!Object.hasOwn(descriptor, "value"))
|
|
35
|
-
throw invalid();
|
|
36
|
-
return [key, copy(descriptor.value, depth + 1)];
|
|
37
|
-
}));
|
|
38
|
-
}
|
|
39
|
-
let result;
|
|
40
|
-
try {
|
|
41
|
-
result = copy(value, 0);
|
|
42
|
-
}
|
|
43
|
-
catch {
|
|
44
|
-
throw invalid();
|
|
45
|
-
}
|
|
8
|
+
const result = copyBoundedOAuthJson(value, "Invalid OAuth client registration metadata");
|
|
46
9
|
if (typeof result !== "object" || result === null || Array.isArray(result))
|
|
47
10
|
throw invalid();
|
|
48
11
|
const record = result;
|
|
@@ -71,8 +34,6 @@ export function parseOAuthClientRegistration(value) {
|
|
|
71
34
|
catch {
|
|
72
35
|
throw invalid();
|
|
73
36
|
}
|
|
74
|
-
if (Buffer.byteLength(JSON.stringify(record), "utf8") > 64 * 1024)
|
|
75
|
-
throw invalid();
|
|
76
37
|
return record;
|
|
77
38
|
}
|
|
78
39
|
export function normalizeStoredOAuthClient(value) {
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import type { CreateSecretStoreInput } from "auth-store";
|
|
2
2
|
import { type OAuthClientStore } from "./auth-store-session-store.js";
|
|
3
|
-
import type { OAuthSessionStore } from "./types.js";
|
|
3
|
+
import type { OAuthSessionStore, StoredOAuthSession } from "./types.js";
|
|
4
4
|
export interface ResourceBoundOAuthStores {
|
|
5
5
|
readonly sessionStore: OAuthSessionStore;
|
|
6
6
|
readonly clientStore: OAuthClientStore;
|
|
7
7
|
readonly initialGrantAllowed: boolean;
|
|
8
8
|
/** Retire grants/clients even when their document cannot be decrypted or parsed. */
|
|
9
|
+
/** Explicitly replace the bound grant and original client in one transaction. */
|
|
10
|
+
importSession(session: StoredOAuthSession, options?: {
|
|
11
|
+
signal?: AbortSignal;
|
|
12
|
+
timeoutMs?: number;
|
|
13
|
+
}): Promise<void>;
|
|
9
14
|
reset(resource: string, options?: {
|
|
10
15
|
signal?: AbortSignal;
|
|
11
16
|
timeoutMs?: number;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { copyBoundedOAuthJson } from "./bounded-json.js";
|
|
1
2
|
import { canonicalizeResourceIndicator } from "../resource-indicator.js";
|
|
2
3
|
import { assertPersistenceNamespace, createNamedSecretStore, isStoredOAuthSession } from "./auth-store-session-store.js";
|
|
3
4
|
import { normalizeStoredOAuthClient } from "./client-registration.js";
|
|
@@ -19,21 +20,51 @@ export function createResourceBoundOAuthStores(options, namespace, identity) {
|
|
|
19
20
|
}
|
|
20
21
|
if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.hash)
|
|
21
22
|
throw new Error("OAuth reset resource must be an HTTP URL without credentials or fragment");
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
23
|
+
await replace({ version: 1, resource: canonicalizeResourceIndicator(url), generation: 1, session: null, clients: {} }, options);
|
|
24
|
+
},
|
|
25
|
+
async importSession(value, options = {}) {
|
|
26
|
+
options.signal?.throwIfAborted();
|
|
27
|
+
const session = copyBoundedOAuthJson(value, "Invalid OAuth import session");
|
|
28
|
+
try {
|
|
29
|
+
if (!isStoredOAuthSession(session) || session.tokens === undefined || session.refreshState !== undefined)
|
|
30
|
+
throw new Error("Invalid session");
|
|
31
|
+
const resource = new URL(session.resource), issuer = new URL(session.authorizationServer);
|
|
32
|
+
if ([resource, issuer].some(url => !["http:", "https:"].includes(url.protocol) || url.username || url.password || url.hash) ||
|
|
33
|
+
session.discovery.authorizationServerMetadata.issuer !== session.authorizationServer ||
|
|
34
|
+
typeof session.discovery.resourceMetadata.resource !== "string" ||
|
|
35
|
+
canonicalizeResourceIndicator(session.discovery.resourceMetadata.resource) !== canonicalizeResourceIndicator(resource))
|
|
36
|
+
throw new Error("Invalid binding");
|
|
37
|
+
session.client = normalizeStoredOAuthClient(session.client);
|
|
38
|
+
if (session.client.registration !== undefined)
|
|
39
|
+
session.client.registrationOwnership = "caller";
|
|
40
|
+
if (session.client.registration?.issuer !== undefined && session.client.registration.issuer !== null &&
|
|
41
|
+
session.client.registration.issuer !== session.authorizationServer)
|
|
42
|
+
throw new Error("Invalid registration issuer");
|
|
43
|
+
new Headers({ Authorization: `Bearer ${session.tokens.accessToken}` });
|
|
44
|
+
session.resource = canonicalizeResourceIndicator(resource);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
throw new Error("Invalid OAuth import session or resource binding");
|
|
48
|
+
}
|
|
49
|
+
await replace({ version: 1, resource: session.resource, generation: 1, session,
|
|
50
|
+
clients: { [session.authorizationServer]: session.client } }, options);
|
|
35
51
|
}
|
|
36
52
|
};
|
|
53
|
+
async function replace(record, options) {
|
|
54
|
+
options.signal?.throwIfAborted();
|
|
55
|
+
const timeoutMs = options.timeoutMs ?? 30_000;
|
|
56
|
+
if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 2_147_483_647)
|
|
57
|
+
throw new Error("OAuth replacement timeoutMs must be a positive supported timer interval");
|
|
58
|
+
if (store.withLock === undefined)
|
|
59
|
+
throw new Error("OAuth resource identity backend must support transaction locks");
|
|
60
|
+
// Strict reconciliation reads are bypassed only for explicit replacement,
|
|
61
|
+
// while the raw stable backend lock still serializes every identity writer.
|
|
62
|
+
await store.withLock(async () => {
|
|
63
|
+
options.signal?.throwIfAborted();
|
|
64
|
+
await store.set(JSON.stringify(record));
|
|
65
|
+
result.initialGrantAllowed = false;
|
|
66
|
+
}, { signal: options.signal, timeoutMs });
|
|
67
|
+
}
|
|
37
68
|
async function read() {
|
|
38
69
|
const raw = await store.get();
|
|
39
70
|
if (raw === null)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { StoredOAuthTokens } from "./types.js";
|
|
2
|
+
export interface OAuthTokenGrantImportOptions {
|
|
3
|
+
/** Absolute Unix epoch milliseconds; a numeric value takes precedence. */
|
|
4
|
+
readonly expiresAt?: number | null;
|
|
5
|
+
/** Original Unix epoch milliseconds for a delayed relative-lifetime import. */
|
|
6
|
+
readonly issuedAt?: number;
|
|
7
|
+
readonly now?: () => number;
|
|
8
|
+
}
|
|
9
|
+
/** Validate a raw RFC token response and anchor its lifetime once for persistence. */
|
|
10
|
+
export declare function parseOAuthTokenGrant(value: unknown, options?: OAuthTokenGrantImportOptions): StoredOAuthTokens;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { copyBoundedOAuthJson } from "./bounded-json.js";
|
|
2
|
+
import { normalizeOAuthScope } from "./scope.js";
|
|
3
|
+
/** Validate a raw RFC token response and anchor its lifetime once for persistence. */
|
|
4
|
+
export function parseOAuthTokenGrant(value, options = {}) {
|
|
5
|
+
const invalid = () => new Error("Invalid OAuth token grant");
|
|
6
|
+
const result = copyBoundedOAuthJson(value, "Invalid OAuth token grant");
|
|
7
|
+
if (typeof result !== "object" || result === null || Array.isArray(result))
|
|
8
|
+
throw invalid();
|
|
9
|
+
const record = result;
|
|
10
|
+
const own = (key) => Object.hasOwn(record, key) ? record[key] : undefined;
|
|
11
|
+
const access = own("access_token"), refresh = own("refresh_token"), type = own("token_type");
|
|
12
|
+
if (typeof access !== "string" || access.trim() === "" || typeof type !== "string" || type.toLowerCase() !== "bearer" ||
|
|
13
|
+
(refresh !== undefined && (typeof refresh !== "string" || refresh.trim() === "")))
|
|
14
|
+
throw invalid();
|
|
15
|
+
const accessToken = access.trim();
|
|
16
|
+
try {
|
|
17
|
+
new Headers({ Authorization: `Bearer ${accessToken}` });
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
throw invalid();
|
|
21
|
+
}
|
|
22
|
+
const lifetime = own("expires_in"), seconds = own("expires_at"), milliseconds = own("expiresAt");
|
|
23
|
+
if ((lifetime !== undefined && (typeof lifetime !== "number" || !Number.isSafeInteger(lifetime) || lifetime < 0)) ||
|
|
24
|
+
(seconds !== undefined && seconds !== null && (typeof seconds !== "number" || !Number.isSafeInteger(seconds))))
|
|
25
|
+
throw invalid();
|
|
26
|
+
const validTimestamp = (input) => typeof input === "number" && Number.isSafeInteger(input) && Math.abs(input) <= 8_640_000_000_000_000;
|
|
27
|
+
if ((milliseconds !== undefined && milliseconds !== null && !validTimestamp(milliseconds)) ||
|
|
28
|
+
(options.expiresAt !== undefined && options.expiresAt !== null && !validTimestamp(options.expiresAt)) ||
|
|
29
|
+
(options.issuedAt !== undefined && !validTimestamp(options.issuedAt)))
|
|
30
|
+
throw invalid();
|
|
31
|
+
// Validate every supplied timing field, even if a higher-precedence absolute
|
|
32
|
+
// value is selected. Reload uses only the resulting normalized timestamp.
|
|
33
|
+
const absoluteSeconds = typeof seconds === "number" ? seconds * 1000 : undefined;
|
|
34
|
+
if (absoluteSeconds !== undefined && !validTimestamp(absoluteSeconds))
|
|
35
|
+
throw invalid();
|
|
36
|
+
const relative = typeof lifetime === "number" ? (options.issuedAt ?? (options.now ?? Date.now)()) + lifetime * 1000 : undefined;
|
|
37
|
+
if (relative !== undefined && !validTimestamp(relative))
|
|
38
|
+
throw invalid();
|
|
39
|
+
const expiresAt = options.expiresAt ?? (typeof milliseconds === "number" ? milliseconds : undefined) ?? absoluteSeconds ?? relative ?? null;
|
|
40
|
+
let scope;
|
|
41
|
+
try {
|
|
42
|
+
scope = normalizeOAuthScope(own("scope"));
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
throw invalid();
|
|
46
|
+
}
|
|
47
|
+
if (own("scope") !== undefined && scope === undefined)
|
|
48
|
+
throw invalid();
|
|
49
|
+
return { accessToken, tokenType: "Bearer", expiresAt,
|
|
50
|
+
...(refresh === undefined ? {} : { refreshToken: refresh.trim() }),
|
|
51
|
+
...(scope === undefined ? {} : { scope }) };
|
|
52
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export { parseOAuthTokenGrant } from "./client/token-grant.js";
|
|
2
|
+
export type { OAuthTokenGrantImportOptions } from "./client/token-grant.js";
|
|
1
3
|
export { createAuthStoreSessionStore, } from "./client/auth-store-session-store.js";
|
|
2
4
|
export { createResourceBoundOAuthStores } from "./client/resource-bound-store.js";
|
|
3
5
|
export type { ResourceBoundOAuthStores } from "./client/resource-bound-store.js";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { parseOAuthTokenGrant } from "./client/token-grant.js";
|
|
1
2
|
export { createAuthStoreSessionStore, } from "./client/auth-store-session-store.js";
|
|
2
3
|
export { createResourceBoundOAuthStores } from "./client/resource-bound-store.js";
|
|
3
4
|
export { parseOAuthClientRegistration } from "./client/client-registration.js";
|
|
@@ -3395,6 +3395,62 @@ var SubscriptionManager = class {
|
|
|
3395
3395
|
}
|
|
3396
3396
|
};
|
|
3397
3397
|
|
|
3398
|
+
// ../mcp-oauth/dist/client/bounded-json.js
|
|
3399
|
+
function copyBoundedOAuthJson(value, message) {
|
|
3400
|
+
const invalid = () => new Error(message);
|
|
3401
|
+
let nodes = 0;
|
|
3402
|
+
function copy(input, depth) {
|
|
3403
|
+
if (++nodes > 2e4 || depth > 64)
|
|
3404
|
+
throw invalid();
|
|
3405
|
+
if (input === null || typeof input === "boolean" || typeof input === "string")
|
|
3406
|
+
return input;
|
|
3407
|
+
if (typeof input === "number" && Number.isFinite(input))
|
|
3408
|
+
return input;
|
|
3409
|
+
if (typeof input !== "object" || input === null)
|
|
3410
|
+
throw invalid();
|
|
3411
|
+
const descriptors = Object.getOwnPropertyDescriptors(input);
|
|
3412
|
+
if (Array.isArray(input)) {
|
|
3413
|
+
const length = descriptors.length?.value;
|
|
3414
|
+
if (length > 2e4)
|
|
3415
|
+
throw invalid();
|
|
3416
|
+
const result2 = [];
|
|
3417
|
+
for (let index = 0; index < length; index++) {
|
|
3418
|
+
const descriptor = descriptors[String(index)];
|
|
3419
|
+
if (descriptor === void 0 || !Object.hasOwn(descriptor, "value"))
|
|
3420
|
+
throw invalid();
|
|
3421
|
+
result2.push(copy(descriptor.value, depth + 1));
|
|
3422
|
+
}
|
|
3423
|
+
return result2;
|
|
3424
|
+
}
|
|
3425
|
+
if (Object.getPrototypeOf(input) !== Object.prototype && Object.getPrototypeOf(input) !== null)
|
|
3426
|
+
throw invalid();
|
|
3427
|
+
return Object.fromEntries(Object.entries(descriptors).filter(([, descriptor]) => descriptor.enumerable).map(([key2, descriptor]) => {
|
|
3428
|
+
if (!Object.hasOwn(descriptor, "value"))
|
|
3429
|
+
throw invalid();
|
|
3430
|
+
return [key2, copy(descriptor.value, depth + 1)];
|
|
3431
|
+
}));
|
|
3432
|
+
}
|
|
3433
|
+
let result;
|
|
3434
|
+
try {
|
|
3435
|
+
result = copy(value, 0);
|
|
3436
|
+
} catch {
|
|
3437
|
+
throw invalid();
|
|
3438
|
+
}
|
|
3439
|
+
if (Buffer.byteLength(JSON.stringify(result), "utf8") > 64 * 1024)
|
|
3440
|
+
throw invalid();
|
|
3441
|
+
return result;
|
|
3442
|
+
}
|
|
3443
|
+
|
|
3444
|
+
// ../mcp-oauth/dist/client/scope.js
|
|
3445
|
+
function normalizeOAuthScope(scope) {
|
|
3446
|
+
if (scope === void 0)
|
|
3447
|
+
return void 0;
|
|
3448
|
+
if (typeof scope !== "string" || [...scope].some((char) => char.charCodeAt(0) < 32 || char.charCodeAt(0) > 126 || char === '"' || char === "\\"))
|
|
3449
|
+
throw new Error("Invalid OAuth scope syntax");
|
|
3450
|
+
const normalized = [...new Set(scope.split(" ").filter(Boolean))].sort().join(" ");
|
|
3451
|
+
return normalized || void 0;
|
|
3452
|
+
}
|
|
3453
|
+
|
|
3398
3454
|
// ../mcp-oauth/dist/client/loopback-authorization.js
|
|
3399
3455
|
import http from "node:http";
|
|
3400
3456
|
|
|
@@ -3700,16 +3756,6 @@ function buildSuccessPage(landingPage) {
|
|
|
3700
3756
|
].join("");
|
|
3701
3757
|
}
|
|
3702
3758
|
|
|
3703
|
-
// ../mcp-oauth/dist/client/scope.js
|
|
3704
|
-
function normalizeOAuthScope(scope) {
|
|
3705
|
-
if (scope === void 0)
|
|
3706
|
-
return void 0;
|
|
3707
|
-
if (typeof scope !== "string" || [...scope].some((char) => char.charCodeAt(0) < 32 || char.charCodeAt(0) > 126 || char === '"' || char === "\\"))
|
|
3708
|
-
throw new Error("Invalid OAuth scope syntax");
|
|
3709
|
-
const normalized = [...new Set(scope.split(" ").filter(Boolean))].sort().join(" ");
|
|
3710
|
-
return normalized || void 0;
|
|
3711
|
-
}
|
|
3712
|
-
|
|
3713
3759
|
// ../mcp-oauth/dist/client/token-auth-method.js
|
|
3714
3760
|
function normalizeOAuthTokenEndpointAuthMethod(value) {
|
|
3715
3761
|
if (value === void 0 || value === null)
|
|
@@ -3722,44 +3768,7 @@ function normalizeOAuthTokenEndpointAuthMethod(value) {
|
|
|
3722
3768
|
// ../mcp-oauth/dist/client/client-registration.js
|
|
3723
3769
|
function parseOAuthClientRegistration(value) {
|
|
3724
3770
|
const invalid = () => new Error("Invalid OAuth client registration metadata");
|
|
3725
|
-
|
|
3726
|
-
function copy(input, depth) {
|
|
3727
|
-
if (++nodes > 2e4 || depth > 64)
|
|
3728
|
-
throw invalid();
|
|
3729
|
-
if (input === null || typeof input === "boolean" || typeof input === "string")
|
|
3730
|
-
return input;
|
|
3731
|
-
if (typeof input === "number" && Number.isFinite(input))
|
|
3732
|
-
return input;
|
|
3733
|
-
if (typeof input !== "object" || input === null)
|
|
3734
|
-
throw invalid();
|
|
3735
|
-
const descriptors = Object.getOwnPropertyDescriptors(input);
|
|
3736
|
-
if (Array.isArray(input)) {
|
|
3737
|
-
const length = descriptors.length?.value;
|
|
3738
|
-
if (length > 2e4)
|
|
3739
|
-
throw invalid();
|
|
3740
|
-
const result2 = [];
|
|
3741
|
-
for (let index = 0; index < length; index++) {
|
|
3742
|
-
const descriptor = descriptors[String(index)];
|
|
3743
|
-
if (descriptor === void 0 || !Object.hasOwn(descriptor, "value"))
|
|
3744
|
-
throw invalid();
|
|
3745
|
-
result2.push(copy(descriptor.value, depth + 1));
|
|
3746
|
-
}
|
|
3747
|
-
return result2;
|
|
3748
|
-
}
|
|
3749
|
-
if (Object.getPrototypeOf(input) !== Object.prototype && Object.getPrototypeOf(input) !== null)
|
|
3750
|
-
throw invalid();
|
|
3751
|
-
return Object.fromEntries(Object.entries(descriptors).filter(([, descriptor]) => descriptor.enumerable).map(([key2, descriptor]) => {
|
|
3752
|
-
if (!Object.hasOwn(descriptor, "value"))
|
|
3753
|
-
throw invalid();
|
|
3754
|
-
return [key2, copy(descriptor.value, depth + 1)];
|
|
3755
|
-
}));
|
|
3756
|
-
}
|
|
3757
|
-
let result;
|
|
3758
|
-
try {
|
|
3759
|
-
result = copy(value, 0);
|
|
3760
|
-
} catch {
|
|
3761
|
-
throw invalid();
|
|
3762
|
-
}
|
|
3771
|
+
const result = copyBoundedOAuthJson(value, "Invalid OAuth client registration metadata");
|
|
3763
3772
|
if (typeof result !== "object" || result === null || Array.isArray(result))
|
|
3764
3773
|
throw invalid();
|
|
3765
3774
|
const record2 = result;
|
|
@@ -3804,8 +3813,6 @@ function parseOAuthClientRegistration(value) {
|
|
|
3804
3813
|
} catch {
|
|
3805
3814
|
throw invalid();
|
|
3806
3815
|
}
|
|
3807
|
-
if (Buffer.byteLength(JSON.stringify(record2), "utf8") > 64 * 1024)
|
|
3808
|
-
throw invalid();
|
|
3809
3816
|
return record2;
|
|
3810
3817
|
}
|
|
3811
3818
|
function normalizeStoredOAuthClient(value) {
|
|
@@ -4739,19 +4746,49 @@ function createResourceBoundOAuthStores(options, namespace, identity) {
|
|
|
4739
4746
|
}
|
|
4740
4747
|
if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.hash)
|
|
4741
4748
|
throw new Error("OAuth reset resource must be an HTTP URL without credentials or fragment");
|
|
4742
|
-
|
|
4743
|
-
|
|
4744
|
-
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
4752
|
-
|
|
4749
|
+
await replace({ version: 1, resource: canonicalizeResourceIndicator(url), generation: 1, session: null, clients: {} }, options2);
|
|
4750
|
+
},
|
|
4751
|
+
async importSession(value, options2 = {}) {
|
|
4752
|
+
options2.signal?.throwIfAborted();
|
|
4753
|
+
const session = copyBoundedOAuthJson(value, "Invalid OAuth import session");
|
|
4754
|
+
try {
|
|
4755
|
+
if (!isStoredOAuthSession(session) || session.tokens === void 0 || session.refreshState !== void 0)
|
|
4756
|
+
throw new Error("Invalid session");
|
|
4757
|
+
const resource = new URL(session.resource), issuer = new URL(session.authorizationServer);
|
|
4758
|
+
if ([resource, issuer].some((url) => !["http:", "https:"].includes(url.protocol) || url.username || url.password || url.hash) || session.discovery.authorizationServerMetadata.issuer !== session.authorizationServer || typeof session.discovery.resourceMetadata.resource !== "string" || canonicalizeResourceIndicator(session.discovery.resourceMetadata.resource) !== canonicalizeResourceIndicator(resource))
|
|
4759
|
+
throw new Error("Invalid binding");
|
|
4760
|
+
session.client = normalizeStoredOAuthClient(session.client);
|
|
4761
|
+
if (session.client.registration !== void 0)
|
|
4762
|
+
session.client.registrationOwnership = "caller";
|
|
4763
|
+
if (session.client.registration?.issuer !== void 0 && session.client.registration.issuer !== null && session.client.registration.issuer !== session.authorizationServer)
|
|
4764
|
+
throw new Error("Invalid registration issuer");
|
|
4765
|
+
new Headers({ Authorization: `Bearer ${session.tokens.accessToken}` });
|
|
4766
|
+
session.resource = canonicalizeResourceIndicator(resource);
|
|
4767
|
+
} catch {
|
|
4768
|
+
throw new Error("Invalid OAuth import session or resource binding");
|
|
4769
|
+
}
|
|
4770
|
+
await replace({
|
|
4771
|
+
version: 1,
|
|
4772
|
+
resource: session.resource,
|
|
4773
|
+
generation: 1,
|
|
4774
|
+
session,
|
|
4775
|
+
clients: { [session.authorizationServer]: session.client }
|
|
4776
|
+
}, options2);
|
|
4753
4777
|
}
|
|
4754
4778
|
};
|
|
4779
|
+
async function replace(record2, options2) {
|
|
4780
|
+
options2.signal?.throwIfAborted();
|
|
4781
|
+
const timeoutMs = options2.timeoutMs ?? 3e4;
|
|
4782
|
+
if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 2147483647)
|
|
4783
|
+
throw new Error("OAuth replacement timeoutMs must be a positive supported timer interval");
|
|
4784
|
+
if (store.withLock === void 0)
|
|
4785
|
+
throw new Error("OAuth resource identity backend must support transaction locks");
|
|
4786
|
+
await store.withLock(async () => {
|
|
4787
|
+
options2.signal?.throwIfAborted();
|
|
4788
|
+
await store.set(JSON.stringify(record2));
|
|
4789
|
+
result.initialGrantAllowed = false;
|
|
4790
|
+
}, { signal: options2.signal, timeoutMs });
|
|
4791
|
+
}
|
|
4755
4792
|
async function read() {
|
|
4756
4793
|
const raw = await store.get();
|
|
4757
4794
|
if (raw === null)
|