toolcraft 0.0.349 → 0.0.351
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/composition.json +3 -3
- package/dist/composition.json +3 -3
- package/node_modules/tiny-http-mcp-server/dist/composition.json +1 -1
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/README.md +19 -3
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/default-oauth-client-provider.js +19 -9
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/resource-bound-store.js +9 -8
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/token-endpoint.js +4 -1
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/token-grant.js +4 -1
- package/node_modules/tiny-http-mcp-server/package.json +1 -1
- package/node_modules/tiny-mcp-client/README.md +5 -0
- package/node_modules/tiny-mcp-client/dist/index.js +37 -15
- package/node_modules/tiny-stdio-mcp-server/dist/composition.json +1 -1
- package/node_modules/toolcraft-schema/package.json +1 -1
- package/package.json +2 -2
package/composition.json
CHANGED
|
@@ -98,7 +98,7 @@
|
|
|
98
98
|
},
|
|
99
99
|
{
|
|
100
100
|
"name": "tiny-http-mcp-server",
|
|
101
|
-
"version": "0.1.
|
|
101
|
+
"version": "0.1.82",
|
|
102
102
|
"license": "MIT"
|
|
103
103
|
},
|
|
104
104
|
{
|
|
@@ -113,7 +113,7 @@
|
|
|
113
113
|
},
|
|
114
114
|
{
|
|
115
115
|
"name": "toolcraft",
|
|
116
|
-
"version": "0.0.
|
|
116
|
+
"version": "0.0.351",
|
|
117
117
|
"license": "MIT"
|
|
118
118
|
},
|
|
119
119
|
{
|
|
@@ -123,7 +123,7 @@
|
|
|
123
123
|
},
|
|
124
124
|
{
|
|
125
125
|
"name": "toolcraft-schema",
|
|
126
|
-
"version": "0.0.
|
|
126
|
+
"version": "0.0.351",
|
|
127
127
|
"license": "MIT"
|
|
128
128
|
},
|
|
129
129
|
{
|
package/dist/composition.json
CHANGED
|
@@ -98,7 +98,7 @@
|
|
|
98
98
|
},
|
|
99
99
|
{
|
|
100
100
|
"name": "tiny-http-mcp-server",
|
|
101
|
-
"version": "0.1.
|
|
101
|
+
"version": "0.1.82",
|
|
102
102
|
"license": "MIT"
|
|
103
103
|
},
|
|
104
104
|
{
|
|
@@ -113,7 +113,7 @@
|
|
|
113
113
|
},
|
|
114
114
|
{
|
|
115
115
|
"name": "toolcraft",
|
|
116
|
-
"version": "0.0.
|
|
116
|
+
"version": "0.0.351",
|
|
117
117
|
"license": "MIT"
|
|
118
118
|
},
|
|
119
119
|
{
|
|
@@ -123,7 +123,7 @@
|
|
|
123
123
|
},
|
|
124
124
|
{
|
|
125
125
|
"name": "toolcraft-schema",
|
|
126
|
-
"version": "0.0.
|
|
126
|
+
"version": "0.0.351",
|
|
127
127
|
"license": "MIT"
|
|
128
128
|
},
|
|
129
129
|
{
|
|
@@ -61,12 +61,16 @@ alone do not establish either native error type.
|
|
|
61
61
|
- `browser.timeoutMs` optional authorization deadline (default 120,000 ms)
|
|
62
62
|
- `sessionStore` optional
|
|
63
63
|
- `authStore` optional `auth-store` backend config for the default session store
|
|
64
|
-
- `now()` optional clock override
|
|
64
|
+
- `now()` optional clock override returning integer Unix epoch milliseconds within the JavaScript Date range
|
|
65
65
|
|
|
66
66
|
The default provider captures client, interaction, callback, landing-page and
|
|
67
67
|
lock policies when created. Create a new provider to change those settings.
|
|
68
68
|
Selected callbacks, stores, clocks and AbortSignals remain live host dependencies;
|
|
69
69
|
aborting the original signal still cancels authorization.
|
|
70
|
+
An invalid clock fails before attaching or refreshing a grant with a known
|
|
71
|
+
expiry or redeeming a time-limited client secret. Unknown access lifetimes and
|
|
72
|
+
unlimited secret lifetimes do not require a clock read. Valid negative epoch
|
|
73
|
+
times remain supported.
|
|
70
74
|
Native session and client persistence factories also capture file paths, salts,
|
|
71
75
|
Keychain identities, lock locations and selected filesystem/command handles.
|
|
72
76
|
Mutating these settings cannot redirect a later read or write.
|
|
@@ -94,6 +98,9 @@ authorization and code exchange. Credentials, fragments, port zero and reserved
|
|
|
94
98
|
OAuth callback query parameters, including `error_uri`, are rejected before binding a listener.
|
|
95
99
|
Fragment rejection includes a trailing empty `#`. Empty queries and percent-escaped
|
|
96
100
|
hash data remain valid and keep their exact registered spelling.
|
|
101
|
+
Direct initial grants and host-supplied or stored authorization, token and
|
|
102
|
+
registration endpoints also reject empty fragment components before consent or
|
|
103
|
+
credential redemption. Percent-escaped hashes remain ordinary path/query data.
|
|
97
104
|
`createDefaultOAuthClientProvider` also checks the configured redirect before
|
|
98
105
|
creating the provider. Imported tokens that cannot be sent as HTTP header
|
|
99
106
|
values fail with diagnostics that omit their contents. Omit
|
|
@@ -164,7 +171,14 @@ refresh retains the previous granted set. Mismatched responses never activate
|
|
|
164
171
|
credentials; an unusable refresh response retains the pending refresh record.
|
|
165
172
|
`resourceIdentity` selects a native-owned logical server within its optional
|
|
166
173
|
persistence namespace. One encrypted, locked document owns that identity's
|
|
167
|
-
current resource URL, sessions and registrations.
|
|
174
|
+
current resource URL, sessions and registrations. Explicit imports and resets
|
|
175
|
+
reject fragment components, including an empty trailing `#`; imported protected-
|
|
176
|
+
resource metadata and stored client issuers obey the same fragment-free policy.
|
|
177
|
+
Issuer identifiers must also omit query components, including an empty `?`.
|
|
178
|
+
Direct and stored authorization flows enforce the same issuer URL policy before
|
|
179
|
+
consent or token redemption. New discovery identity and endpoint policy checks
|
|
180
|
+
run before acquiring the session transaction, so invalid replacement metadata
|
|
181
|
+
cannot retire a valid old grant. Escaped delimiter data stays valid. Changing the URL retires its
|
|
168
182
|
credentials permanently; returning to the old URL does not restore them.
|
|
169
183
|
Retired identities also withhold stale initial grants; authorize again or select
|
|
170
184
|
a fresh explicit profile to import a new grant. This option requires native persistence; custom
|
|
@@ -187,7 +201,9 @@ using the original configured client. Persisted sessions take precedence,
|
|
|
187
201
|
including sessions whose tokens have been cleared; an import cannot revive them.
|
|
188
202
|
Input tokens are copied before any host clock anchors a relative lifetime;
|
|
189
203
|
clock mutation cannot replace the selected access/refresh/scope values. Invalid
|
|
190
|
-
expiry values fail before authorization.
|
|
204
|
+
expiry values fail before authorization. Relative-lifetime clock anchors must
|
|
205
|
+
also be valid epoch milliseconds; adding the lifetime cannot make an invalid
|
|
206
|
+
issuance clock acceptable. This applies to raw-grant parsing and token responses.
|
|
191
207
|
For a raw OAuth response, `parseOAuthTokenGrant(response, { issuedAt, expiresAt })`
|
|
192
208
|
returns normalized `StoredOAuthTokens`. Timing options are captured before the
|
|
193
209
|
host clock runs, so it cannot bypass their earlier validation. The parser accepts
|
|
@@ -40,7 +40,13 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
40
40
|
const sessionStore = resourceStores?.sessionStore ?? options.sessionStore ?? createAuthStoreSessionStore(options.authStore, options.persistenceNamespace);
|
|
41
41
|
const clientStore = resourceStores?.clientStore ??
|
|
42
42
|
(options.authStore === undefined ? null : createAuthStoreClientStore(options.authStore, options.persistenceNamespace));
|
|
43
|
-
const
|
|
43
|
+
const clock = options.now ?? Date.now;
|
|
44
|
+
const now = () => {
|
|
45
|
+
const timestamp = clock();
|
|
46
|
+
if (!Number.isSafeInteger(timestamp) || Math.abs(timestamp) > MAX_JS_DATE_MS)
|
|
47
|
+
throw new Error("OAuth clock must return valid epoch milliseconds");
|
|
48
|
+
return timestamp;
|
|
49
|
+
};
|
|
44
50
|
const registeredClients = new Map();
|
|
45
51
|
if (options.initialGrant !== undefined) {
|
|
46
52
|
let resource;
|
|
@@ -50,7 +56,7 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
50
56
|
catch {
|
|
51
57
|
throw new Error("OAuth initial grant resource must be an absolute HTTP URL");
|
|
52
58
|
}
|
|
53
|
-
if ((resource.protocol !== "http:" && resource.protocol !== "https:") || resource.username || resource.password || resource.
|
|
59
|
+
if ((resource.protocol !== "http:" && resource.protocol !== "https:") || resource.username || resource.password || resource.href.includes("#"))
|
|
54
60
|
throw new Error("OAuth initial grant resource must be an HTTP URL without credentials or fragments");
|
|
55
61
|
}
|
|
56
62
|
const initialGrant = options.initialGrant === undefined ? undefined : {
|
|
@@ -166,6 +172,11 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
166
172
|
};
|
|
167
173
|
async function ensureAuthorizedSession(resource, discovery, fetch, allowInteractive, forceRefresh = false, signal, rejectedTokens) {
|
|
168
174
|
signal?.throwIfAborted();
|
|
175
|
+
if (discovery !== undefined) {
|
|
176
|
+
if (getOwnString(discovery.authorizationServerMetadata, "issuer") !== discovery.authorizationServer)
|
|
177
|
+
throw new Error("OAuth discovery authorization-server issuer mismatch");
|
|
178
|
+
assertSecureOAuthFlowEndpoints(discovery.authorizationServerMetadata);
|
|
179
|
+
}
|
|
169
180
|
const canonicalResource = canonicalizeResourceIndicator(resource);
|
|
170
181
|
return withOAuthSessionTransaction(sessionStore, canonicalResource, async () => {
|
|
171
182
|
let session = await loadSession(canonicalResource);
|
|
@@ -181,9 +192,6 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
181
192
|
if (session !== null && initialGrant?.resource === canonicalResource)
|
|
182
193
|
initialGrantConsumed = true;
|
|
183
194
|
signal?.throwIfAborted();
|
|
184
|
-
if (discovery !== undefined && getOwnString(discovery.authorizationServerMetadata, "issuer") !== discovery.authorizationServer) {
|
|
185
|
-
throw new Error("OAuth discovery authorization-server issuer mismatch");
|
|
186
|
-
}
|
|
187
195
|
if (session !== null && (canonicalizeResourceIndicator(session.resource) !== canonicalResource
|
|
188
196
|
|| getOwnString(session.discovery.authorizationServerMetadata, "issuer") !== session.authorizationServer
|
|
189
197
|
|| (discovery !== undefined && discovery.authorizationServer !== session.authorizationServer))) {
|
|
@@ -192,7 +200,6 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
192
200
|
}
|
|
193
201
|
if (session === null && discovery !== undefined && !initialGrantConsumed && initialGrant?.resource === canonicalResource &&
|
|
194
202
|
initialGrant.tokens !== undefined && initialGrant.client !== null) {
|
|
195
|
-
assertSecureOAuthFlowEndpoints(discovery.authorizationServerMetadata);
|
|
196
203
|
session = { resource: canonicalResource, authorizationServer: discovery.authorizationServer,
|
|
197
204
|
client: initialGrant.client, tokens: initialGrant.tokens,
|
|
198
205
|
...(requestedScope === undefined ? {} : { requestedScope }), discovery: toStoredDiscovery(discovery) };
|
|
@@ -756,18 +763,21 @@ function isLoopbackHostname(hostname) {
|
|
|
756
763
|
}
|
|
757
764
|
function assertSecureUrl(value, label) {
|
|
758
765
|
const url = new URL(value);
|
|
759
|
-
if (url.username !== "" || url.password !== "" || url.
|
|
766
|
+
if (url.username !== "" || url.password !== "" || url.href.includes("#")) {
|
|
760
767
|
throw new Error(`${label} must not include credentials or fragment`);
|
|
761
768
|
}
|
|
762
769
|
if (url.protocol === "https:") {
|
|
763
|
-
return;
|
|
770
|
+
return url;
|
|
764
771
|
}
|
|
765
772
|
if (url.protocol === "http:" && isLoopbackHostname(url.hostname)) {
|
|
766
|
-
return;
|
|
773
|
+
return url;
|
|
767
774
|
}
|
|
768
775
|
throw new Error(`${label} must use https unless it targets a loopback host`);
|
|
769
776
|
}
|
|
770
777
|
function assertSecureOAuthFlowEndpoints(metadata) {
|
|
778
|
+
const issuer = assertSecureUrl(requireOwnString(metadata, "issuer", "Authorization server metadata"), "Authorization server issuer");
|
|
779
|
+
if (issuer.href.includes("?"))
|
|
780
|
+
throw new Error("Authorization server issuer must not include query or fragment");
|
|
771
781
|
const authorizationEndpoint = requireOwnString(metadata, "authorization_endpoint", "Authorization server metadata");
|
|
772
782
|
const tokenEndpoint = requireOwnString(metadata, "token_endpoint", "Authorization server metadata");
|
|
773
783
|
const registrationEndpoint = getOwnString(metadata, "registration_endpoint");
|
package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/resource-bound-store.js
CHANGED
|
@@ -18,7 +18,7 @@ export function createResourceBoundOAuthStores(options, namespace, identity) {
|
|
|
18
18
|
catch {
|
|
19
19
|
throw new Error("OAuth reset resource must be an absolute HTTP URL");
|
|
20
20
|
}
|
|
21
|
-
if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.
|
|
21
|
+
if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.href.includes("#"))
|
|
22
22
|
throw new Error("OAuth reset resource must be an HTTP URL without credentials or fragment");
|
|
23
23
|
await replace({ version: 1, resource: canonicalizeResourceIndicator(url), generation: 1, session: null, clients: {} }, options);
|
|
24
24
|
},
|
|
@@ -28,11 +28,12 @@ export function createResourceBoundOAuthStores(options, namespace, identity) {
|
|
|
28
28
|
try {
|
|
29
29
|
if (!isStoredOAuthSession(session) || session.tokens === undefined || session.refreshState !== undefined)
|
|
30
30
|
throw new Error("Invalid session");
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
31
|
+
if (typeof session.discovery.resourceMetadata.resource !== "string")
|
|
32
|
+
throw new Error("Invalid resource metadata");
|
|
33
|
+
const resource = new URL(session.resource), issuer = new URL(session.authorizationServer), advertisedResource = new URL(session.discovery.resourceMetadata.resource);
|
|
34
|
+
if ([resource, issuer, advertisedResource].some(url => !["http:", "https:"].includes(url.protocol) || url.username || url.password || url.href.includes("#")) ||
|
|
35
|
+
issuer.href.includes("?") || session.discovery.authorizationServerMetadata.issuer !== session.authorizationServer ||
|
|
36
|
+
canonicalizeResourceIndicator(advertisedResource) !== canonicalizeResourceIndicator(resource))
|
|
36
37
|
throw new Error("Invalid binding");
|
|
37
38
|
session.client = normalizeStoredOAuthClient(session.client);
|
|
38
39
|
if (session.client.registration !== undefined)
|
|
@@ -92,7 +93,7 @@ export function createResourceBoundOAuthStores(options, namespace, identity) {
|
|
|
92
93
|
catch {
|
|
93
94
|
throw new Error("Invalid stored OAuth resource identity URL");
|
|
94
95
|
}
|
|
95
|
-
if (!["http:", "https:"].includes(resourceUrl.protocol) || resourceUrl.username || resourceUrl.password || resourceUrl.
|
|
96
|
+
if (!["http:", "https:"].includes(resourceUrl.protocol) || resourceUrl.username || resourceUrl.password || resourceUrl.href.includes("#"))
|
|
96
97
|
throw new Error("Invalid stored OAuth resource identity URL");
|
|
97
98
|
for (const [issuer, client] of Object.entries(record.clients)) {
|
|
98
99
|
const normalized = normalizeStoredOAuthClient(client);
|
|
@@ -103,7 +104,7 @@ export function createResourceBoundOAuthStores(options, namespace, identity) {
|
|
|
103
104
|
catch {
|
|
104
105
|
throw new Error("Invalid stored OAuth resource client issuer");
|
|
105
106
|
}
|
|
106
|
-
if (normalized === null || !["http:", "https:"].includes(url.protocol) || url.username || url.password || url.
|
|
107
|
+
if (normalized === null || !["http:", "https:"].includes(url.protocol) || url.username || url.password || url.href.includes("?") || url.href.includes("#"))
|
|
107
108
|
throw new Error("Invalid stored OAuth resource client");
|
|
108
109
|
Object.defineProperty(record.clients, issuer, { value: normalized, enumerable: true, configurable: true, writable: true });
|
|
109
110
|
}
|
package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/token-endpoint.js
CHANGED
|
@@ -130,7 +130,10 @@ async function requestTokens(input) {
|
|
|
130
130
|
expiresIn < 0) {
|
|
131
131
|
throw new Error("OAuth token response has invalid expires_in");
|
|
132
132
|
}
|
|
133
|
-
|
|
133
|
+
const issuedAt = input.now();
|
|
134
|
+
if (!Number.isSafeInteger(issuedAt) || Math.abs(issuedAt) > MAX_JS_DATE_MS)
|
|
135
|
+
throw new Error("OAuth token response has invalid expires_in");
|
|
136
|
+
expiresAt = issuedAt + expiresIn * 1000;
|
|
134
137
|
if (!Number.isSafeInteger(expiresAt) ||
|
|
135
138
|
expiresAt > MAX_JS_DATE_MS ||
|
|
136
139
|
!Number.isFinite(new Date(expiresAt).getTime())) {
|
|
@@ -34,7 +34,10 @@ export function parseOAuthTokenGrant(value, options = {}) {
|
|
|
34
34
|
const absoluteSeconds = typeof seconds === "number" ? seconds * 1000 : undefined;
|
|
35
35
|
if (absoluteSeconds !== undefined && !validTimestamp(absoluteSeconds))
|
|
36
36
|
throw invalid();
|
|
37
|
-
const
|
|
37
|
+
const anchor = typeof lifetime === "number" ? options.issuedAt ?? (options.now ?? Date.now)() : undefined;
|
|
38
|
+
if (typeof lifetime === "number" && !validTimestamp(anchor))
|
|
39
|
+
throw invalid();
|
|
40
|
+
const relative = typeof lifetime === "number" ? anchor + lifetime * 1000 : undefined;
|
|
38
41
|
if (relative !== undefined && !validTimestamp(relative))
|
|
39
42
|
throw invalid();
|
|
40
43
|
const expiresAt = options.expiresAt ?? (typeof milliseconds === "number" ? milliseconds : undefined) ?? absoluteSeconds ?? relative ?? null;
|
|
@@ -63,6 +63,11 @@ embedded credentials or fragments; endpoint changes close the connection.
|
|
|
63
63
|
Empty fragment delimiters are rejected too; escaped hashes in an endpoint path
|
|
64
64
|
or query remain literal data.
|
|
65
65
|
Both transports accept the same headers, OAuth provider and response limits.
|
|
66
|
+
The original HTTP transport target must be an absolute HTTP/HTTPS URL without
|
|
67
|
+
credentials or a fragment, including an empty trailing `#`. Invalid targets fail
|
|
68
|
+
before OAuth provider construction or clock reads. Empty queries and escaped
|
|
69
|
+
path/query delimiters remain valid; accepted target strings keep their spelling.
|
|
70
|
+
|
|
66
71
|
Static HTTP headers are copied and validated when the transport is constructed,
|
|
67
72
|
before OAuth callbacks run. Invalid headers fail without reflecting their values.
|
|
68
73
|
Use `snapshotHttpTransportHeaders(headers)` to apply the same ownership and safe
|
|
@@ -4801,7 +4801,7 @@ function createResourceBoundOAuthStores(options, namespace, identity) {
|
|
|
4801
4801
|
} catch {
|
|
4802
4802
|
throw new Error("OAuth reset resource must be an absolute HTTP URL");
|
|
4803
4803
|
}
|
|
4804
|
-
if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.
|
|
4804
|
+
if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.href.includes("#"))
|
|
4805
4805
|
throw new Error("OAuth reset resource must be an HTTP URL without credentials or fragment");
|
|
4806
4806
|
await replace({ version: 1, resource: canonicalizeResourceIndicator(url), generation: 1, session: null, clients: {} }, options2);
|
|
4807
4807
|
},
|
|
@@ -4811,8 +4811,10 @@ function createResourceBoundOAuthStores(options, namespace, identity) {
|
|
|
4811
4811
|
try {
|
|
4812
4812
|
if (!isStoredOAuthSession(session) || session.tokens === void 0 || session.refreshState !== void 0)
|
|
4813
4813
|
throw new Error("Invalid session");
|
|
4814
|
-
|
|
4815
|
-
|
|
4814
|
+
if (typeof session.discovery.resourceMetadata.resource !== "string")
|
|
4815
|
+
throw new Error("Invalid resource metadata");
|
|
4816
|
+
const resource = new URL(session.resource), issuer = new URL(session.authorizationServer), advertisedResource = new URL(session.discovery.resourceMetadata.resource);
|
|
4817
|
+
if ([resource, issuer, advertisedResource].some((url) => !["http:", "https:"].includes(url.protocol) || url.username || url.password || url.href.includes("#")) || issuer.href.includes("?") || session.discovery.authorizationServerMetadata.issuer !== session.authorizationServer || canonicalizeResourceIndicator(advertisedResource) !== canonicalizeResourceIndicator(resource))
|
|
4816
4818
|
throw new Error("Invalid binding");
|
|
4817
4819
|
session.client = normalizeStoredOAuthClient(session.client);
|
|
4818
4820
|
if (session.client.registration !== void 0)
|
|
@@ -4868,7 +4870,7 @@ function createResourceBoundOAuthStores(options, namespace, identity) {
|
|
|
4868
4870
|
} catch {
|
|
4869
4871
|
throw new Error("Invalid stored OAuth resource identity URL");
|
|
4870
4872
|
}
|
|
4871
|
-
if (!["http:", "https:"].includes(resourceUrl.protocol) || resourceUrl.username || resourceUrl.password || resourceUrl.
|
|
4873
|
+
if (!["http:", "https:"].includes(resourceUrl.protocol) || resourceUrl.username || resourceUrl.password || resourceUrl.href.includes("#"))
|
|
4872
4874
|
throw new Error("Invalid stored OAuth resource identity URL");
|
|
4873
4875
|
for (const [issuer, client] of Object.entries(record2.clients)) {
|
|
4874
4876
|
const normalized = normalizeStoredOAuthClient(client);
|
|
@@ -4878,7 +4880,7 @@ function createResourceBoundOAuthStores(options, namespace, identity) {
|
|
|
4878
4880
|
} catch {
|
|
4879
4881
|
throw new Error("Invalid stored OAuth resource client issuer");
|
|
4880
4882
|
}
|
|
4881
|
-
if (normalized === null || !["http:", "https:"].includes(url.protocol) || url.username || url.password || url.
|
|
4883
|
+
if (normalized === null || !["http:", "https:"].includes(url.protocol) || url.username || url.password || url.href.includes("?") || url.href.includes("#"))
|
|
4882
4884
|
throw new Error("Invalid stored OAuth resource client");
|
|
4883
4885
|
Object.defineProperty(record2.clients, issuer, { value: normalized, enumerable: true, configurable: true, writable: true });
|
|
4884
4886
|
}
|
|
@@ -5178,7 +5180,10 @@ async function requestTokens(input) {
|
|
|
5178
5180
|
if (typeof expiresIn !== "number" || !Number.isFinite(expiresIn) || !Number.isInteger(expiresIn) || expiresIn < 0) {
|
|
5179
5181
|
throw new Error("OAuth token response has invalid expires_in");
|
|
5180
5182
|
}
|
|
5181
|
-
|
|
5183
|
+
const issuedAt = input.now();
|
|
5184
|
+
if (!Number.isSafeInteger(issuedAt) || Math.abs(issuedAt) > MAX_JS_DATE_MS2)
|
|
5185
|
+
throw new Error("OAuth token response has invalid expires_in");
|
|
5186
|
+
expiresAt = issuedAt + expiresIn * 1e3;
|
|
5182
5187
|
if (!Number.isSafeInteger(expiresAt) || expiresAt > MAX_JS_DATE_MS2 || !Number.isFinite(new Date(expiresAt).getTime())) {
|
|
5183
5188
|
throw new Error("OAuth token response has invalid expires_in");
|
|
5184
5189
|
}
|
|
@@ -5335,7 +5340,13 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
5335
5340
|
const resourceStores = options.resourceIdentity === void 0 ? void 0 : createResourceBoundOAuthStores(options.authStore ?? {}, options.persistenceNamespace, options.resourceIdentity);
|
|
5336
5341
|
const sessionStore = resourceStores?.sessionStore ?? options.sessionStore ?? createAuthStoreSessionStore(options.authStore, options.persistenceNamespace);
|
|
5337
5342
|
const clientStore = resourceStores?.clientStore ?? (options.authStore === void 0 ? null : createAuthStoreClientStore(options.authStore, options.persistenceNamespace));
|
|
5338
|
-
const
|
|
5343
|
+
const clock = options.now ?? Date.now;
|
|
5344
|
+
const now = () => {
|
|
5345
|
+
const timestamp = clock();
|
|
5346
|
+
if (!Number.isSafeInteger(timestamp) || Math.abs(timestamp) > MAX_JS_DATE_MS3)
|
|
5347
|
+
throw new Error("OAuth clock must return valid epoch milliseconds");
|
|
5348
|
+
return timestamp;
|
|
5349
|
+
};
|
|
5339
5350
|
const registeredClients = /* @__PURE__ */ new Map();
|
|
5340
5351
|
if (options.initialGrant !== void 0) {
|
|
5341
5352
|
let resource;
|
|
@@ -5344,7 +5355,7 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
5344
5355
|
} catch {
|
|
5345
5356
|
throw new Error("OAuth initial grant resource must be an absolute HTTP URL");
|
|
5346
5357
|
}
|
|
5347
|
-
if (resource.protocol !== "http:" && resource.protocol !== "https:" || resource.username || resource.password || resource.
|
|
5358
|
+
if (resource.protocol !== "http:" && resource.protocol !== "https:" || resource.username || resource.password || resource.href.includes("#"))
|
|
5348
5359
|
throw new Error("OAuth initial grant resource must be an HTTP URL without credentials or fragments");
|
|
5349
5360
|
}
|
|
5350
5361
|
const initialGrant = options.initialGrant === void 0 ? void 0 : {
|
|
@@ -5456,6 +5467,11 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
5456
5467
|
};
|
|
5457
5468
|
async function ensureAuthorizedSession(resource, discovery, fetch2, allowInteractive, forceRefresh = false, signal, rejectedTokens) {
|
|
5458
5469
|
signal?.throwIfAborted();
|
|
5470
|
+
if (discovery !== void 0) {
|
|
5471
|
+
if (getOwnString2(discovery.authorizationServerMetadata, "issuer") !== discovery.authorizationServer)
|
|
5472
|
+
throw new Error("OAuth discovery authorization-server issuer mismatch");
|
|
5473
|
+
assertSecureOAuthFlowEndpoints(discovery.authorizationServerMetadata);
|
|
5474
|
+
}
|
|
5459
5475
|
const canonicalResource = canonicalizeResourceIndicator(resource);
|
|
5460
5476
|
return withOAuthSessionTransaction(sessionStore, canonicalResource, async () => {
|
|
5461
5477
|
let session = await loadSession(canonicalResource);
|
|
@@ -5471,15 +5487,11 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
5471
5487
|
if (session !== null && initialGrant?.resource === canonicalResource)
|
|
5472
5488
|
initialGrantConsumed = true;
|
|
5473
5489
|
signal?.throwIfAborted();
|
|
5474
|
-
if (discovery !== void 0 && getOwnString2(discovery.authorizationServerMetadata, "issuer") !== discovery.authorizationServer) {
|
|
5475
|
-
throw new Error("OAuth discovery authorization-server issuer mismatch");
|
|
5476
|
-
}
|
|
5477
5490
|
if (session !== null && (canonicalizeResourceIndicator(session.resource) !== canonicalResource || getOwnString2(session.discovery.authorizationServerMetadata, "issuer") !== session.authorizationServer || discovery !== void 0 && discovery.authorizationServer !== session.authorizationServer)) {
|
|
5478
5491
|
await clearSession(canonicalResource);
|
|
5479
5492
|
session = null;
|
|
5480
5493
|
}
|
|
5481
5494
|
if (session === null && discovery !== void 0 && !initialGrantConsumed && initialGrant?.resource === canonicalResource && initialGrant.tokens !== void 0 && initialGrant.client !== null) {
|
|
5482
|
-
assertSecureOAuthFlowEndpoints(discovery.authorizationServerMetadata);
|
|
5483
5495
|
session = {
|
|
5484
5496
|
resource: canonicalResource,
|
|
5485
5497
|
authorizationServer: discovery.authorizationServer,
|
|
@@ -6011,18 +6023,21 @@ function isLoopbackHostname(hostname2) {
|
|
|
6011
6023
|
}
|
|
6012
6024
|
function assertSecureUrl(value, label) {
|
|
6013
6025
|
const url = new URL2(value);
|
|
6014
|
-
if (url.username !== "" || url.password !== "" || url.
|
|
6026
|
+
if (url.username !== "" || url.password !== "" || url.href.includes("#")) {
|
|
6015
6027
|
throw new Error(`${label} must not include credentials or fragment`);
|
|
6016
6028
|
}
|
|
6017
6029
|
if (url.protocol === "https:") {
|
|
6018
|
-
return;
|
|
6030
|
+
return url;
|
|
6019
6031
|
}
|
|
6020
6032
|
if (url.protocol === "http:" && isLoopbackHostname(url.hostname)) {
|
|
6021
|
-
return;
|
|
6033
|
+
return url;
|
|
6022
6034
|
}
|
|
6023
6035
|
throw new Error(`${label} must use https unless it targets a loopback host`);
|
|
6024
6036
|
}
|
|
6025
6037
|
function assertSecureOAuthFlowEndpoints(metadata) {
|
|
6038
|
+
const issuer = assertSecureUrl(requireOwnString(metadata, "issuer", "Authorization server metadata"), "Authorization server issuer");
|
|
6039
|
+
if (issuer.href.includes("?"))
|
|
6040
|
+
throw new Error("Authorization server issuer must not include query or fragment");
|
|
6026
6041
|
const authorizationEndpoint = requireOwnString(metadata, "authorization_endpoint", "Authorization server metadata");
|
|
6027
6042
|
const tokenEndpoint = requireOwnString(metadata, "token_endpoint", "Authorization server metadata");
|
|
6028
6043
|
const registrationEndpoint = getOwnString2(metadata, "registration_endpoint");
|
|
@@ -7741,6 +7756,13 @@ var HttpTransport = class {
|
|
|
7741
7756
|
}) {
|
|
7742
7757
|
if (!Number.isSafeInteger(maxResponseBytes) || maxResponseBytes < 1)
|
|
7743
7758
|
throw new Error("HTTP response byte limit must be a positive safe integer");
|
|
7759
|
+
try {
|
|
7760
|
+
const target = new URL(url);
|
|
7761
|
+
if (!["http:", "https:"].includes(target.protocol) || target.username || target.password || target.href.includes("#"))
|
|
7762
|
+
throw new Error("Invalid HTTP transport target");
|
|
7763
|
+
} catch {
|
|
7764
|
+
throw new Error("HTTP transport URL must be an absolute HTTP URL without credentials or fragment");
|
|
7765
|
+
}
|
|
7744
7766
|
this.maxResponseBytes = maxResponseBytes;
|
|
7745
7767
|
this.url = url;
|
|
7746
7768
|
this.mode = mode;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "toolcraft",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.351",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -158,7 +158,7 @@
|
|
|
158
158
|
"yaml"
|
|
159
159
|
],
|
|
160
160
|
"optionalDependencies": {
|
|
161
|
-
"toolcraft-schema": "0.0.
|
|
161
|
+
"toolcraft-schema": "0.0.351",
|
|
162
162
|
"toolcraft-design": "*",
|
|
163
163
|
"@poe-code/frontmatter": "*",
|
|
164
164
|
"@poe-code/agent-mcp-config": "*",
|