toolcraft 0.0.731 → 0.0.733
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 +9 -1
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/auth-store-session-store.js +1 -1
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/client-registration.js +5 -2
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/default-oauth-client-provider.js +4 -4
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/loopback-authorization.d.ts +9 -0
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/loopback-authorization.js +77 -1
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/token-endpoint.js +1 -1
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/types.d.ts +6 -0
- package/node_modules/tiny-http-mcp-server/package.json +1 -1
- package/node_modules/tiny-mcp-client/dist/index.browser.d.ts +6 -0
- package/node_modules/tiny-mcp-client/dist/index.d.ts +6 -0
- package/node_modules/tiny-mcp-client/dist/index.js +85 -6
- 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.111",
|
|
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.733",
|
|
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.733",
|
|
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.111",
|
|
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.733",
|
|
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.733",
|
|
127
127
|
"license": "MIT"
|
|
128
128
|
},
|
|
129
129
|
{
|
|
@@ -57,7 +57,8 @@ alone do not establish either native error type.
|
|
|
57
57
|
- `browser.readLine()` optional
|
|
58
58
|
- `browser.createServer()` optional
|
|
59
59
|
- `browser.landingPage` optional
|
|
60
|
-
- `browser.redirectUri`
|
|
60
|
+
- `browser.redirectUri` exact registered HTTP loopback callback with a fixed port, or HTTPS callback when `waitForCallback` is supplied
|
|
61
|
+
- `browser.waitForCallback({ authorizationUrl, redirectUri, signal })` lets a web host receive its HTTPS callback. Return the complete callback URL; the client checks its redirect, fixed query, state and issuer before exchanging the PKCE code. The host owns navigation and callback routing; no local listener starts. Observe `signal` to release canceled or timed-out host work.
|
|
61
62
|
- `browser.signal` optional cancellation signal
|
|
62
63
|
- `browser.timeoutMs` optional authorization deadline (default 120,000 ms)
|
|
63
64
|
- `sessionStore` optional
|
|
@@ -342,3 +343,10 @@ HTTP 403) are nonretryable `invalid_response` errors; authorization fails withou
|
|
|
342
343
|
waiting for consent that never started. Raw server errors remain transient.
|
|
343
344
|
`outcomeKnown: false` still withholds an uncertain rotating refresh family: a
|
|
344
345
|
nonretryable HTTP status alone does not prove a refresh token was unconsumed.
|
|
346
|
+
|
|
347
|
+
A host can use `browser: { redirectUri: "https://poe.com/oauth/mcp/callback", waitForCallback }`
|
|
348
|
+
with its registered OAuth app. The authorization server must allow that exact URL;
|
|
349
|
+
a desktop-only Google app still needs a web-client registration. Existing loopback
|
|
350
|
+
callbacks keep their local-listener behavior. Token responses with `scope: ""`
|
|
351
|
+
(such as Notion MCP) preserve an explicit empty grant, including on refresh and
|
|
352
|
+
reload; they never inherit a configured nonempty requested scope.
|
|
@@ -186,7 +186,7 @@ function isStoredOAuthTokensOrMissing(value) {
|
|
|
186
186
|
return false;
|
|
187
187
|
}
|
|
188
188
|
const scope = getOwnEntry(value, "scope");
|
|
189
|
-
return scope === undefined || (typeof scope === "string" && scope.trim().length > 0);
|
|
189
|
+
return scope === undefined || scope === "" || (typeof scope === "string" && scope.trim().length > 0);
|
|
190
190
|
}
|
|
191
191
|
function isNonBlankOwnString(record, key) {
|
|
192
192
|
const value = getOwnString(record, key);
|
package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/client-registration.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { copyBoundedOAuthJson } from "./bounded-json.js";
|
|
2
|
-
import { loopbackTarget } from "./loopback-authorization.js";
|
|
2
|
+
import { loopbackTarget, validateHostedOAuthRedirect } from "./loopback-authorization.js";
|
|
3
3
|
import { normalizeOAuthScope } from "./scope.js";
|
|
4
4
|
import { normalizeOAuthTokenEndpointAuthMethod } from "./token-auth-method.js";
|
|
5
5
|
/** Validate and copy a JSON DCR response without quoting credential input. */
|
|
@@ -51,7 +51,10 @@ export function normalizeStoredOAuthClient(value) {
|
|
|
51
51
|
try {
|
|
52
52
|
if (typeof requestedRedirectUri !== "string")
|
|
53
53
|
throw new Error("Invalid redirect identity");
|
|
54
|
-
|
|
54
|
+
if (new URL(requestedRedirectUri).protocol === "https:")
|
|
55
|
+
validateHostedOAuthRedirect(requestedRedirectUri);
|
|
56
|
+
else
|
|
57
|
+
loopbackTarget({ redirectUri: requestedRedirectUri });
|
|
55
58
|
}
|
|
56
59
|
catch {
|
|
57
60
|
throw new Error("Invalid stored OAuth registration redirect identity");
|
|
@@ -7,7 +7,7 @@ import { isIP } from "node:net";
|
|
|
7
7
|
import { fetchMcpResponse } from "../http-fetch.js";
|
|
8
8
|
import { URL } from "node:url";
|
|
9
9
|
import { createAuthStoreClientStore, createAuthStoreSessionStore, assertPersistenceNamespace } from "./auth-store-session-store.js";
|
|
10
|
-
import { createLoopbackAuthorizationSession,
|
|
10
|
+
import { createLoopbackAuthorizationSession, validateAuthorizationRedirect, snapshotLoopbackAuthorizationOptions } from "./loopback-authorization.js";
|
|
11
11
|
import { createAuthorizationState } from "./authorization-state.js";
|
|
12
12
|
import { generateCodeChallenge, generateCodeVerifier } from "./pkce.js";
|
|
13
13
|
import { exchangeAuthorizationCode, OAuthError, refreshAccessToken, isRetryableOAuthError, readOAuthJsonObjectResponse } from "./token-endpoint.js";
|
|
@@ -27,7 +27,7 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
27
27
|
const clientMode = options.client.mode;
|
|
28
28
|
const interactiveEnabled = options.allowInteractive !== false;
|
|
29
29
|
const sessionLockTimeoutMs = options.sessionLockTimeoutMs;
|
|
30
|
-
|
|
30
|
+
validateAuthorizationRedirect(browser);
|
|
31
31
|
assertPersistenceNamespace(options.persistenceNamespace);
|
|
32
32
|
const clientMetadata = getClientMetadata(options.client);
|
|
33
33
|
const requestedScope = clientMetadata?.scope;
|
|
@@ -647,7 +647,7 @@ function normalizeStoredTokens(value) {
|
|
|
647
647
|
const scope = getOwnEntry(value, "scope");
|
|
648
648
|
const normalizedAccessToken = accessToken?.trim();
|
|
649
649
|
const normalizedRefreshToken = typeof refreshToken === "string" ? refreshToken.trim() : undefined;
|
|
650
|
-
const normalizedScope = normalizeOAuthScope(scope);
|
|
650
|
+
const normalizedScope = scope === "" ? "" : normalizeOAuthScope(scope);
|
|
651
651
|
if (accessToken === undefined ||
|
|
652
652
|
normalizedAccessToken === undefined ||
|
|
653
653
|
normalizedAccessToken.length === 0 ||
|
|
@@ -668,7 +668,7 @@ function normalizeStoredTokens(value) {
|
|
|
668
668
|
tokenType,
|
|
669
669
|
expiresAt,
|
|
670
670
|
...(normalizedRefreshToken === undefined ? {} : { refreshToken: normalizedRefreshToken }),
|
|
671
|
-
...(normalizedScope === undefined
|
|
671
|
+
...(normalizedScope === undefined
|
|
672
672
|
? {}
|
|
673
673
|
: { scope: normalizedScope })
|
|
674
674
|
};
|
|
@@ -7,6 +7,12 @@ export interface OAuthLandingPage {
|
|
|
7
7
|
export interface LoopbackAuthorizationOptions {
|
|
8
8
|
openBrowser?: (url: string) => Promise<void>;
|
|
9
9
|
readLine?: () => Promise<string>;
|
|
10
|
+
/** Host receives the full callback URL; this mode never opens a local listener. */
|
|
11
|
+
waitForCallback?: (request: {
|
|
12
|
+
authorizationUrl: string;
|
|
13
|
+
redirectUri: string;
|
|
14
|
+
signal: AbortSignal;
|
|
15
|
+
}) => Promise<string>;
|
|
10
16
|
createServer?: () => http.Server;
|
|
11
17
|
landingPage?: OAuthLandingPage;
|
|
12
18
|
callbackPath?: string;
|
|
@@ -30,5 +36,8 @@ export declare function loopbackTarget(options: LoopbackAuthorizationOptions): {
|
|
|
30
36
|
host: string;
|
|
31
37
|
callbackPath: string;
|
|
32
38
|
};
|
|
39
|
+
/** Hosted redirects are explicit HTTPS URLs; local redirects retain loopback policy. */
|
|
40
|
+
export declare function validateAuthorizationRedirect(options: LoopbackAuthorizationOptions): void;
|
|
41
|
+
export declare function validateHostedOAuthRedirect(redirectUri: string, callbackPath?: string): void;
|
|
33
42
|
export declare function extractCodeFromInput(input: string): string | null;
|
|
34
43
|
export declare function buildSuccessPage(landingPage?: OAuthLandingPage): string;
|
|
@@ -5,7 +5,7 @@ export { OAuthAuthorizationError } from "./authorization-error.js";
|
|
|
5
5
|
const oauthCallbackParameters = ["code", "state", "iss", "error", "error_description", "error_uri"];
|
|
6
6
|
/** Capture declared configuration regardless of property enumerability. */
|
|
7
7
|
export function snapshotLoopbackAuthorizationOptions(options) {
|
|
8
|
-
return { ...options, openBrowser: options.openBrowser?.bind(options), readLine: options.readLine?.bind(options), createServer: options.createServer?.bind(options),
|
|
8
|
+
return { ...options, openBrowser: options.openBrowser?.bind(options), readLine: options.readLine?.bind(options), waitForCallback: options.waitForCallback?.bind(options), createServer: options.createServer?.bind(options),
|
|
9
9
|
callbackPath: options.callbackPath, redirectUri: options.redirectUri, signal: options.signal, timeoutMs: options.timeoutMs,
|
|
10
10
|
landingPage: options.landingPage === undefined ? undefined : { ...options.landingPage, title: options.landingPage.title, body: options.landingPage.body } };
|
|
11
11
|
}
|
|
@@ -16,6 +16,8 @@ export async function createLoopbackAuthorizationSession(options = {}) {
|
|
|
16
16
|
const timeoutMs = options.timeoutMs ?? 120_000;
|
|
17
17
|
if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 2_147_483_647)
|
|
18
18
|
throw new Error("OAuth authorization timeoutMs must be a positive supported timer interval");
|
|
19
|
+
if (options.waitForCallback !== undefined)
|
|
20
|
+
return createHostedAuthorizationSession(options, timeoutMs);
|
|
19
21
|
const target = loopbackTarget(options);
|
|
20
22
|
const server = options.createServer ? options.createServer() : http.createServer();
|
|
21
23
|
const controller = new AbortController();
|
|
@@ -88,6 +90,80 @@ export function loopbackTarget(options) {
|
|
|
88
90
|
throw new Error("Invalid OAuth loopback callback path");
|
|
89
91
|
return { port: 0, host: "127.0.0.1", callbackPath };
|
|
90
92
|
}
|
|
93
|
+
/** Hosted redirects are explicit HTTPS URLs; local redirects retain loopback policy. */
|
|
94
|
+
export function validateAuthorizationRedirect(options) {
|
|
95
|
+
if (options.waitForCallback === undefined) {
|
|
96
|
+
loopbackTarget(options);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (typeof options.waitForCallback !== "function" || options.redirectUri === undefined)
|
|
100
|
+
throw new Error("Hosted OAuth callbacks require redirectUri and waitForCallback");
|
|
101
|
+
validateHostedOAuthRedirect(options.redirectUri, options.callbackPath);
|
|
102
|
+
}
|
|
103
|
+
export function validateHostedOAuthRedirect(redirectUri, callbackPath) {
|
|
104
|
+
const url = new URL(redirectUri);
|
|
105
|
+
if (url.protocol !== "https:" || url.username || url.password || url.href.includes("#") || url.port === "0"
|
|
106
|
+
|| oauthCallbackParameters.some(name => url.searchParams.has(name))
|
|
107
|
+
|| [...redirectUri].some(char => char.codePointAt(0) <= 32)
|
|
108
|
+
|| (callbackPath !== undefined && callbackPath !== url.pathname))
|
|
109
|
+
throw new Error("Invalid hosted OAuth redirect URI");
|
|
110
|
+
}
|
|
111
|
+
function createHostedAuthorizationSession(options, timeoutMs) {
|
|
112
|
+
validateAuthorizationRedirect(options);
|
|
113
|
+
const redirectUri = options.redirectUri, controller = new AbortController();
|
|
114
|
+
let used = false;
|
|
115
|
+
const aborted = () => controller.abort(options.signal?.reason);
|
|
116
|
+
const timer = setTimeout(() => controller.abort(new Error("OAuth authorization timed out")), timeoutMs);
|
|
117
|
+
timer.unref?.();
|
|
118
|
+
const cleanup = () => { clearTimeout(timer); options.signal?.removeEventListener("abort", aborted); };
|
|
119
|
+
controller.signal.addEventListener("abort", cleanup, { once: true });
|
|
120
|
+
options.signal?.addEventListener("abort", aborted, { once: true });
|
|
121
|
+
if (options.signal?.aborted)
|
|
122
|
+
aborted();
|
|
123
|
+
return {
|
|
124
|
+
redirectUri,
|
|
125
|
+
async waitForCode(authorizationUrl) {
|
|
126
|
+
controller.signal.throwIfAborted();
|
|
127
|
+
if (used)
|
|
128
|
+
throw new Error("OAuth authorization session has already been used");
|
|
129
|
+
used = true;
|
|
130
|
+
try {
|
|
131
|
+
const authorization = new URL(authorizationUrl);
|
|
132
|
+
if (authorization.searchParams.getAll("redirect_uri").length !== 1 || authorization.searchParams.get("redirect_uri") !== redirectUri)
|
|
133
|
+
throw new Error("OAuth authorization redirect mismatch");
|
|
134
|
+
const callback = await new Promise((resolve, reject) => {
|
|
135
|
+
const onAbort = () => reject(controller.signal.reason);
|
|
136
|
+
controller.signal.addEventListener("abort", onAbort, { once: true });
|
|
137
|
+
void Promise.resolve().then(() => {
|
|
138
|
+
controller.signal.throwIfAborted();
|
|
139
|
+
return options.waitForCallback({ authorizationUrl, redirectUri, signal: controller.signal });
|
|
140
|
+
}).then(resolve, reject).finally(() => controller.signal.removeEventListener("abort", onAbort));
|
|
141
|
+
});
|
|
142
|
+
controller.signal.throwIfAborted();
|
|
143
|
+
const url = new URL(callback), base = new URL(redirectUri);
|
|
144
|
+
if (url.origin !== base.origin || url.pathname !== base.pathname || url.username || url.password || url.href.includes("#")
|
|
145
|
+
|| [...callback].some(char => char.codePointAt(0) <= 32))
|
|
146
|
+
throw new Error("OAuth callback redirect mismatch");
|
|
147
|
+
const params = readAuthorizationCallbackParameters(url);
|
|
148
|
+
for (const name of oauthCallbackParameters)
|
|
149
|
+
url.searchParams.delete(name);
|
|
150
|
+
if (url.searchParams.toString() !== base.searchParams.toString())
|
|
151
|
+
throw new Error("OAuth callback redirect mismatch");
|
|
152
|
+
const expected = readExpectedAuthorizationCallback(authorizationUrl);
|
|
153
|
+
if (expected.state === null)
|
|
154
|
+
throw new Error("OAuth authorization missing state");
|
|
155
|
+
validateAuthorizationCallbackBinding(params, expected);
|
|
156
|
+
if (params.error !== null)
|
|
157
|
+
throw new OAuthAuthorizationError(params.error, params.errorDescription ?? params.error);
|
|
158
|
+
return validateAuthorizationCallbackParameters(params, expected);
|
|
159
|
+
}
|
|
160
|
+
finally {
|
|
161
|
+
controller.abort(new Error("OAuth authorization session closed"));
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
close() { controller.abort(new Error("OAuth authorization session closed")); }
|
|
165
|
+
};
|
|
166
|
+
}
|
|
91
167
|
async function startServer(server, port, host, signal) {
|
|
92
168
|
signal.throwIfAborted();
|
|
93
169
|
return new Promise((resolve, reject) => {
|
package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/token-endpoint.js
CHANGED
|
@@ -147,7 +147,7 @@ async function requestTokens(input) {
|
|
|
147
147
|
const normalizedRefreshToken = typeof refreshToken === "string" && refreshToken.trim().length > 0
|
|
148
148
|
? refreshToken.trim()
|
|
149
149
|
: undefined;
|
|
150
|
-
const normalizedScope = normalizeOAuthScope(scope);
|
|
150
|
+
const normalizedScope = scope === "" ? "" : normalizeOAuthScope(scope);
|
|
151
151
|
if (scope !== undefined && normalizedScope === undefined)
|
|
152
152
|
throw new Error("Invalid OAuth scope syntax in token response");
|
|
153
153
|
return {
|
|
@@ -170,6 +170,12 @@ export interface DefaultOAuthClientProviderOptions {
|
|
|
170
170
|
};
|
|
171
171
|
browser: {
|
|
172
172
|
openBrowser?(url: string): Promise<void>;
|
|
173
|
+
/** Return the full hosted HTTPS callback URL; the client validates redirect, state and issuer. */
|
|
174
|
+
waitForCallback?(request: {
|
|
175
|
+
authorizationUrl: string;
|
|
176
|
+
redirectUri: string;
|
|
177
|
+
signal: AbortSignal;
|
|
178
|
+
}): Promise<string>;
|
|
173
179
|
/** Exact registered HTTP loopback redirect URI. */
|
|
174
180
|
redirectUri?: string;
|
|
175
181
|
signal?: AbortSignal;
|
|
@@ -273,6 +273,12 @@ interface DefaultOAuthClientProviderOptions {
|
|
|
273
273
|
};
|
|
274
274
|
browser: {
|
|
275
275
|
openBrowser?(url: string): Promise<void>;
|
|
276
|
+
/** Return the full hosted HTTPS callback URL; the client validates redirect, state and issuer. */
|
|
277
|
+
waitForCallback?(request: {
|
|
278
|
+
authorizationUrl: string;
|
|
279
|
+
redirectUri: string;
|
|
280
|
+
signal: AbortSignal;
|
|
281
|
+
}): Promise<string>;
|
|
276
282
|
/** Exact registered HTTP loopback redirect URI. */
|
|
277
283
|
redirectUri?: string;
|
|
278
284
|
signal?: AbortSignal;
|
|
@@ -273,6 +273,12 @@ interface DefaultOAuthClientProviderOptions {
|
|
|
273
273
|
};
|
|
274
274
|
browser: {
|
|
275
275
|
openBrowser?(url: string): Promise<void>;
|
|
276
|
+
/** Return the full hosted HTTPS callback URL; the client validates redirect, state and issuer. */
|
|
277
|
+
waitForCallback?(request: {
|
|
278
|
+
authorizationUrl: string;
|
|
279
|
+
redirectUri: string;
|
|
280
|
+
signal: AbortSignal;
|
|
281
|
+
}): Promise<string>;
|
|
276
282
|
/** Exact registered HTTP loopback redirect URI. */
|
|
277
283
|
redirectUri?: string;
|
|
278
284
|
signal?: AbortSignal;
|
|
@@ -3637,6 +3637,7 @@ function snapshotLoopbackAuthorizationOptions(options) {
|
|
|
3637
3637
|
...options,
|
|
3638
3638
|
openBrowser: options.openBrowser?.bind(options),
|
|
3639
3639
|
readLine: options.readLine?.bind(options),
|
|
3640
|
+
waitForCallback: options.waitForCallback?.bind(options),
|
|
3640
3641
|
createServer: options.createServer?.bind(options),
|
|
3641
3642
|
callbackPath: options.callbackPath,
|
|
3642
3643
|
redirectUri: options.redirectUri,
|
|
@@ -3651,6 +3652,8 @@ async function createLoopbackAuthorizationSession(options = {}) {
|
|
|
3651
3652
|
const timeoutMs = options.timeoutMs ?? 12e4;
|
|
3652
3653
|
if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 2147483647)
|
|
3653
3654
|
throw new Error("OAuth authorization timeoutMs must be a positive supported timer interval");
|
|
3655
|
+
if (options.waitForCallback !== void 0)
|
|
3656
|
+
return createHostedAuthorizationSession(options, timeoutMs);
|
|
3654
3657
|
const target = loopbackTarget(options);
|
|
3655
3658
|
const server = options.createServer ? options.createServer() : http.createServer();
|
|
3656
3659
|
const controller = new AbortController();
|
|
@@ -3716,6 +3719,79 @@ function loopbackTarget(options) {
|
|
|
3716
3719
|
throw new Error("Invalid OAuth loopback callback path");
|
|
3717
3720
|
return { port: 0, host: "127.0.0.1", callbackPath };
|
|
3718
3721
|
}
|
|
3722
|
+
function validateAuthorizationRedirect(options) {
|
|
3723
|
+
if (options.waitForCallback === void 0) {
|
|
3724
|
+
loopbackTarget(options);
|
|
3725
|
+
return;
|
|
3726
|
+
}
|
|
3727
|
+
if (typeof options.waitForCallback !== "function" || options.redirectUri === void 0)
|
|
3728
|
+
throw new Error("Hosted OAuth callbacks require redirectUri and waitForCallback");
|
|
3729
|
+
validateHostedOAuthRedirect(options.redirectUri, options.callbackPath);
|
|
3730
|
+
}
|
|
3731
|
+
function validateHostedOAuthRedirect(redirectUri, callbackPath) {
|
|
3732
|
+
const url = new URL(redirectUri);
|
|
3733
|
+
if (url.protocol !== "https:" || url.username || url.password || url.href.includes("#") || url.port === "0" || oauthCallbackParameters.some((name) => url.searchParams.has(name)) || [...redirectUri].some((char) => char.codePointAt(0) <= 32) || callbackPath !== void 0 && callbackPath !== url.pathname)
|
|
3734
|
+
throw new Error("Invalid hosted OAuth redirect URI");
|
|
3735
|
+
}
|
|
3736
|
+
function createHostedAuthorizationSession(options, timeoutMs) {
|
|
3737
|
+
validateAuthorizationRedirect(options);
|
|
3738
|
+
const redirectUri = options.redirectUri, controller = new AbortController();
|
|
3739
|
+
let used = false;
|
|
3740
|
+
const aborted = () => controller.abort(options.signal?.reason);
|
|
3741
|
+
const timer = setTimeout(() => controller.abort(new Error("OAuth authorization timed out")), timeoutMs);
|
|
3742
|
+
timer.unref?.();
|
|
3743
|
+
const cleanup = () => {
|
|
3744
|
+
clearTimeout(timer);
|
|
3745
|
+
options.signal?.removeEventListener("abort", aborted);
|
|
3746
|
+
};
|
|
3747
|
+
controller.signal.addEventListener("abort", cleanup, { once: true });
|
|
3748
|
+
options.signal?.addEventListener("abort", aborted, { once: true });
|
|
3749
|
+
if (options.signal?.aborted)
|
|
3750
|
+
aborted();
|
|
3751
|
+
return {
|
|
3752
|
+
redirectUri,
|
|
3753
|
+
async waitForCode(authorizationUrl) {
|
|
3754
|
+
controller.signal.throwIfAborted();
|
|
3755
|
+
if (used)
|
|
3756
|
+
throw new Error("OAuth authorization session has already been used");
|
|
3757
|
+
used = true;
|
|
3758
|
+
try {
|
|
3759
|
+
const authorization = new URL(authorizationUrl);
|
|
3760
|
+
if (authorization.searchParams.getAll("redirect_uri").length !== 1 || authorization.searchParams.get("redirect_uri") !== redirectUri)
|
|
3761
|
+
throw new Error("OAuth authorization redirect mismatch");
|
|
3762
|
+
const callback = await new Promise((resolve, reject) => {
|
|
3763
|
+
const onAbort = () => reject(controller.signal.reason);
|
|
3764
|
+
controller.signal.addEventListener("abort", onAbort, { once: true });
|
|
3765
|
+
void Promise.resolve().then(() => {
|
|
3766
|
+
controller.signal.throwIfAborted();
|
|
3767
|
+
return options.waitForCallback({ authorizationUrl, redirectUri, signal: controller.signal });
|
|
3768
|
+
}).then(resolve, reject).finally(() => controller.signal.removeEventListener("abort", onAbort));
|
|
3769
|
+
});
|
|
3770
|
+
controller.signal.throwIfAborted();
|
|
3771
|
+
const url = new URL(callback), base = new URL(redirectUri);
|
|
3772
|
+
if (url.origin !== base.origin || url.pathname !== base.pathname || url.username || url.password || url.href.includes("#") || [...callback].some((char) => char.codePointAt(0) <= 32))
|
|
3773
|
+
throw new Error("OAuth callback redirect mismatch");
|
|
3774
|
+
const params = readAuthorizationCallbackParameters(url);
|
|
3775
|
+
for (const name of oauthCallbackParameters)
|
|
3776
|
+
url.searchParams.delete(name);
|
|
3777
|
+
if (url.searchParams.toString() !== base.searchParams.toString())
|
|
3778
|
+
throw new Error("OAuth callback redirect mismatch");
|
|
3779
|
+
const expected = readExpectedAuthorizationCallback(authorizationUrl);
|
|
3780
|
+
if (expected.state === null)
|
|
3781
|
+
throw new Error("OAuth authorization missing state");
|
|
3782
|
+
validateAuthorizationCallbackBinding(params, expected);
|
|
3783
|
+
if (params.error !== null)
|
|
3784
|
+
throw new OAuthAuthorizationError(params.error, params.errorDescription ?? params.error);
|
|
3785
|
+
return validateAuthorizationCallbackParameters(params, expected);
|
|
3786
|
+
} finally {
|
|
3787
|
+
controller.abort(new Error("OAuth authorization session closed"));
|
|
3788
|
+
}
|
|
3789
|
+
},
|
|
3790
|
+
close() {
|
|
3791
|
+
controller.abort(new Error("OAuth authorization session closed"));
|
|
3792
|
+
}
|
|
3793
|
+
};
|
|
3794
|
+
}
|
|
3719
3795
|
async function startServer(server, port, host, signal) {
|
|
3720
3796
|
signal.throwIfAborted();
|
|
3721
3797
|
return new Promise((resolve, reject) => {
|
|
@@ -3975,7 +4051,10 @@ function normalizeStoredOAuthClient(value) {
|
|
|
3975
4051
|
try {
|
|
3976
4052
|
if (typeof requestedRedirectUri !== "string")
|
|
3977
4053
|
throw new Error("Invalid redirect identity");
|
|
3978
|
-
|
|
4054
|
+
if (new URL(requestedRedirectUri).protocol === "https:")
|
|
4055
|
+
validateHostedOAuthRedirect(requestedRedirectUri);
|
|
4056
|
+
else
|
|
4057
|
+
loopbackTarget({ redirectUri: requestedRedirectUri });
|
|
3979
4058
|
} catch {
|
|
3980
4059
|
throw new Error("Invalid stored OAuth registration redirect identity");
|
|
3981
4060
|
}
|
|
@@ -4871,7 +4950,7 @@ function isStoredOAuthTokensOrMissing(value) {
|
|
|
4871
4950
|
return false;
|
|
4872
4951
|
}
|
|
4873
4952
|
const scope = getOwnEntry4(value, "scope");
|
|
4874
|
-
return scope === void 0 || typeof scope === "string" && scope.trim().length > 0;
|
|
4953
|
+
return scope === void 0 || scope === "" || typeof scope === "string" && scope.trim().length > 0;
|
|
4875
4954
|
}
|
|
4876
4955
|
function isNonBlankOwnString(record2, key2) {
|
|
4877
4956
|
const value = getOwnString(record2, key2);
|
|
@@ -5292,7 +5371,7 @@ async function requestTokens(input) {
|
|
|
5292
5371
|
throw new Error("OAuth token response has invalid refresh_token");
|
|
5293
5372
|
const scope = getOwnEntry5(payload, "scope");
|
|
5294
5373
|
const normalizedRefreshToken = typeof refreshToken === "string" && refreshToken.trim().length > 0 ? refreshToken.trim() : void 0;
|
|
5295
|
-
const normalizedScope = normalizeOAuthScope(scope);
|
|
5374
|
+
const normalizedScope = scope === "" ? "" : normalizeOAuthScope(scope);
|
|
5296
5375
|
if (scope !== void 0 && normalizedScope === void 0)
|
|
5297
5376
|
throw new Error("Invalid OAuth scope syntax in token response");
|
|
5298
5377
|
return {
|
|
@@ -5425,7 +5504,7 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
5425
5504
|
const clientMode = options.client.mode;
|
|
5426
5505
|
const interactiveEnabled = options.allowInteractive !== false;
|
|
5427
5506
|
const sessionLockTimeoutMs = options.sessionLockTimeoutMs;
|
|
5428
|
-
|
|
5507
|
+
validateAuthorizationRedirect(browser);
|
|
5429
5508
|
assertPersistenceNamespace(options.persistenceNamespace);
|
|
5430
5509
|
const clientMetadata = getClientMetadata(options.client);
|
|
5431
5510
|
const requestedScope = clientMetadata?.scope;
|
|
@@ -6022,7 +6101,7 @@ function normalizeStoredTokens(value) {
|
|
|
6022
6101
|
const scope = getOwnEntry6(value, "scope");
|
|
6023
6102
|
const normalizedAccessToken = accessToken?.trim();
|
|
6024
6103
|
const normalizedRefreshToken = typeof refreshToken === "string" ? refreshToken.trim() : void 0;
|
|
6025
|
-
const normalizedScope = normalizeOAuthScope(scope);
|
|
6104
|
+
const normalizedScope = scope === "" ? "" : normalizeOAuthScope(scope);
|
|
6026
6105
|
if (accessToken === void 0 || normalizedAccessToken === void 0 || normalizedAccessToken.length === 0 || tokenType !== "Bearer" || !(expiresAt === null || typeof expiresAt === "number" && Number.isSafeInteger(expiresAt) && expiresAt <= MAX_JS_DATE_MS3 && Number.isFinite(new Date(expiresAt).getTime())) || refreshToken !== void 0 && (typeof refreshToken !== "string" || normalizedRefreshToken === void 0 || normalizedRefreshToken.length === 0)) {
|
|
6027
6106
|
return void 0;
|
|
6028
6107
|
}
|
|
@@ -6031,7 +6110,7 @@ function normalizeStoredTokens(value) {
|
|
|
6031
6110
|
tokenType,
|
|
6032
6111
|
expiresAt,
|
|
6033
6112
|
...normalizedRefreshToken === void 0 ? {} : { refreshToken: normalizedRefreshToken },
|
|
6034
|
-
...normalizedScope === void 0
|
|
6113
|
+
...normalizedScope === void 0 ? {} : { scope: normalizedScope }
|
|
6035
6114
|
};
|
|
6036
6115
|
}
|
|
6037
6116
|
function getClientMetadata(client) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "toolcraft",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.733",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -162,7 +162,7 @@
|
|
|
162
162
|
"yaml"
|
|
163
163
|
],
|
|
164
164
|
"optionalDependencies": {
|
|
165
|
-
"toolcraft-schema": "0.0.
|
|
165
|
+
"toolcraft-schema": "0.0.733",
|
|
166
166
|
"toolcraft-design": "*",
|
|
167
167
|
"@poe-code/frontmatter": "*",
|
|
168
168
|
"@poe-code/agent-mcp-config": "*",
|