toolcraft 0.0.732 → 0.0.734

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 CHANGED
@@ -98,7 +98,7 @@
98
98
  },
99
99
  {
100
100
  "name": "tiny-http-mcp-server",
101
- "version": "0.1.109",
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.732",
116
+ "version": "0.0.734",
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.732",
126
+ "version": "0.0.734",
127
127
  "license": "MIT"
128
128
  },
129
129
  {
@@ -98,7 +98,7 @@
98
98
  },
99
99
  {
100
100
  "name": "tiny-http-mcp-server",
101
- "version": "0.1.109",
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.732",
116
+ "version": "0.0.734",
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.732",
126
+ "version": "0.0.734",
127
127
  "license": "MIT"
128
128
  },
129
129
  {
@@ -18,7 +18,7 @@
18
18
  },
19
19
  {
20
20
  "name": "tiny-http-mcp-server",
21
- "version": "0.1.109",
21
+ "version": "0.1.111",
22
22
  "license": "MIT"
23
23
  },
24
24
  {
@@ -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` optional exact registered HTTP loopback callback with a fixed port
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);
@@ -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
- loopbackTarget({ redirectUri: requestedRedirectUri });
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, loopbackTarget, snapshotLoopbackAuthorizationOptions } from "./loopback-authorization.js";
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
- loopbackTarget(browser);
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 || normalizedScope.length === 0
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) => {
@@ -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;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tiny-http-mcp-server",
3
- "version": "0.1.109",
3
+ "version": "0.1.111",
4
4
  "description": "Minimal MCP server over HTTP built on tiny-stdio-mcp-server",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -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
- loopbackTarget({ redirectUri: requestedRedirectUri });
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
- loopbackTarget(browser);
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 || normalizedScope.length === 0 ? {} : { scope: normalizedScope }
6113
+ ...normalizedScope === void 0 ? {} : { scope: normalizedScope }
6035
6114
  };
6036
6115
  }
6037
6116
  function getClientMetadata(client) {
@@ -8,7 +8,7 @@
8
8
  },
9
9
  {
10
10
  "name": "toolcraft-schema",
11
- "version": "0.0.732",
11
+ "version": "0.0.734",
12
12
  "license": "MIT"
13
13
  }
14
14
  ]
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toolcraft-schema",
3
- "version": "0.0.732",
3
+ "version": "0.0.734",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toolcraft",
3
- "version": "0.0.732",
3
+ "version": "0.0.734",
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.732",
165
+ "toolcraft-schema": "0.0.734",
166
166
  "toolcraft-design": "*",
167
167
  "@poe-code/frontmatter": "*",
168
168
  "@poe-code/agent-mcp-config": "*",