toolcraft 0.0.343 → 0.0.345

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.68",
101
+ "version": "0.1.71",
102
102
  "license": "MIT"
103
103
  },
104
104
  {
@@ -113,7 +113,7 @@
113
113
  },
114
114
  {
115
115
  "name": "toolcraft",
116
- "version": "0.0.343",
116
+ "version": "0.0.345",
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.343",
126
+ "version": "0.0.345",
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.68",
101
+ "version": "0.1.71",
102
102
  "license": "MIT"
103
103
  },
104
104
  {
@@ -113,7 +113,7 @@
113
113
  },
114
114
  {
115
115
  "name": "toolcraft",
116
- "version": "0.0.343",
116
+ "version": "0.0.345",
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.343",
126
+ "version": "0.0.345",
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.68",
21
+ "version": "0.1.71",
22
22
  "license": "MIT"
23
23
  },
24
24
  {
@@ -24,10 +24,15 @@ const verifier = createJwksTokenVerifier({
24
24
 
25
25
  ## Public API
26
26
 
27
+ `OAuthError.is(value)` recognizes native token/challenge failures across separately
28
+ bundled package copies while preserving the SDK diagnostic fields. Error names
29
+ alone do not establish either native error type.
30
+
27
31
  - `createDefaultOAuthClientProvider(options)`: default MCP OAuth client provider with dynamic or static client registration.
28
32
  - `createOAuthClientProvider(options)`: lower-level provider constructor.
29
33
  - `createAuthStoreSessionStore(options)`: persisted OAuth session store backed by `auth-store`.
30
34
  - `createLoopbackAuthorizationSession(options)`: local callback server for browser authorization.
35
+ - `OAuthAuthorizationError`: a callback denial with its original `error`, `errorDescription` and message for host observers. Use `OAuthAuthorizationError.is(value)` to recognize errors across separately bundled package copies.
31
36
  - `generateCodeVerifier()` and `generateCodeChallenge(...)`: PKCE helpers.
32
37
  - `normalizeStoredOAuthClient(value)`: normalize a saved client identity, full registration and ownership marker.
33
38
  - `normalizeOAuthScope(value)`: validate scope syntax and normalize its case-sensitive set.
@@ -86,14 +91,21 @@ later environment changes cannot move a transaction between file and Keychain.
86
91
  Fixed redirects support `localhost`, `127.0.0.1`, and `::1` over HTTP. Their
87
92
  exact spelling, port, path and query are preserved through registration,
88
93
  authorization and code exchange. Credentials, fragments, port zero and reserved
89
- OAuth callback query parameters are rejected before binding a listener.
94
+ OAuth callback query parameters, including `error_uri`, are rejected before binding a listener.
90
95
  `createDefaultOAuthClientProvider` also checks the configured redirect before
91
96
  creating the provider. Imported tokens that cannot be sent as HTTP header
92
97
  values fail with diagnostics that omit their contents. Omit
93
98
  `redirectUri` to allocate a random loopback port. Standalone callback sessions
94
- accept the same `redirectUri`, `signal` and `timeoutMs` options. Cancellation,
99
+ accept the same `redirectUri`, `signal` and `timeoutMs` options. They capture configuration,
100
+ landing-page data and selected callbacks before invoking a host listener factory;
101
+ original signals and selected callback receivers/live method state remain active. Cancellation,
95
102
  timeout and explicit close settle pending code waits and release listeners.
96
- Always close a successful standalone session in `finally`.
103
+ Always close a standalone session in `finally`. Failed callback responses declare
104
+ plain UTF-8 text with content sniffing disabled; provider markup remains literal
105
+ text. Successful landing pages remain HTML with escaped custom title/body. Recognized
106
+ OAuth callback parameters must occur only once in HTTP callbacks or pasted URLs;
107
+ duplicates fail without reflecting values and code extraction returns null.
108
+ Unrecognized extension parameters remain ignored, including repeated fields.
97
109
 
98
110
  Native provider calls capture request option handles before host work. Unauthorized
99
111
  handling also owns complete discovery metadata, presented grant values, rejected
@@ -261,6 +273,8 @@ Before sending a refresh request, the provider persists a tokenless session with
261
273
  A successful response replaces it with the rotated grant. A crash, cancellation,
262
274
  network disconnect or incomplete response leaves the marker, so another process
263
275
  cannot replay a possibly consumed refresh token or revive the initial import.
276
+ An explicit `refresh_token` must be a nonempty string; malformed fields also
277
+ retain the pending marker. Omitting it preserves the previous refresh token.
264
278
  Such a session requires fresh authorization. Interactive unauthorized handling
265
279
  can recover it; headless requests fail with an explicit unknown-outcome error.
266
280
  Only complete OAuth error responses establish a rejected request and allow a
@@ -20,6 +20,14 @@ export interface LoopbackAuthorizationSession {
20
20
  waitForCode(authorizationUrl: string): Promise<string>;
21
21
  close(): void;
22
22
  }
23
+ /** Authorization callback denial, retaining the provider diagnostic for host observers. */
24
+ export declare class OAuthAuthorizationError extends Error {
25
+ readonly error: string;
26
+ readonly errorDescription: string;
27
+ /** Recognize errors from separately bundled copies of this package. */
28
+ static is(value: unknown): value is OAuthAuthorizationError;
29
+ constructor(error: string, errorDescription: string);
30
+ }
23
31
  export declare function createLoopbackAuthorizationSession(options?: LoopbackAuthorizationOptions): Promise<LoopbackAuthorizationSession>;
24
32
  export declare function loopbackTarget(options: LoopbackAuthorizationOptions): {
25
33
  port: number;
@@ -1,6 +1,28 @@
1
1
  import http from "node:http";
2
2
  import { parseAuthorizationState } from "./authorization-state.js";
3
+ const authorizationErrorBrand = Symbol.for("poe-platform.mcp-oauth.OAuthAuthorizationError");
4
+ const oauthCallbackParameters = ["code", "state", "iss", "error", "error_description", "error_uri"];
5
+ /** Authorization callback denial, retaining the provider diagnostic for host observers. */
6
+ export class OAuthAuthorizationError extends Error {
7
+ error;
8
+ errorDescription;
9
+ /** Recognize errors from separately bundled copies of this package. */
10
+ static is(value) {
11
+ return value instanceof Error && Object.getOwnPropertyDescriptor(value, authorizationErrorBrand)?.value === true;
12
+ }
13
+ constructor(error, errorDescription) {
14
+ super(`OAuth authorization failed: ${error} — ${errorDescription}`);
15
+ this.error = error;
16
+ this.errorDescription = errorDescription;
17
+ this.name = "OAuthAuthorizationError";
18
+ Object.defineProperty(this, authorizationErrorBrand, { value: true });
19
+ }
20
+ }
3
21
  export async function createLoopbackAuthorizationSession(options = {}) {
22
+ const selected = { ...options };
23
+ options = { ...selected, createServer: selected.createServer?.bind(options),
24
+ openBrowser: selected.openBrowser?.bind(options), readLine: selected.readLine?.bind(options),
25
+ landingPage: selected.landingPage === undefined ? undefined : { ...selected.landingPage } };
4
26
  options.signal?.throwIfAborted();
5
27
  const timeoutMs = options.timeoutMs ?? 120_000;
6
28
  if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 2_147_483_647)
@@ -63,10 +85,9 @@ export function loopbackTarget(options) {
63
85
  catch (cause) {
64
86
  throw new Error("Invalid OAuth loopback redirect URI", { cause });
65
87
  }
66
- const forbiddenQuery = ["code", "state", "error", "error_description", "iss"];
67
88
  if (url.protocol !== "http:" || !["localhost", "127.0.0.1", "[::1]"].includes(url.hostname)
68
89
  || url.username || url.password || url.hash || url.port === "0"
69
- || forbiddenQuery.some(name => url.searchParams.has(name))
90
+ || oauthCallbackParameters.some(name => url.searchParams.has(name))
70
91
  || [...options.redirectUri].some(char => char.codePointAt(0) <= 32)
71
92
  || (options.callbackPath !== undefined && options.callbackPath !== url.pathname))
72
93
  throw new Error("Invalid OAuth loopback redirect URI");
@@ -137,21 +158,18 @@ function waitForAuthorizationCode(server, authorizationUrl, options, callbackPat
137
158
  res.end("Not found");
138
159
  return;
139
160
  }
140
- const callbackParameters = {
141
- code: url.searchParams.get("code"), error: url.searchParams.get("error"),
142
- errorDescription: url.searchParams.get("error_description"), state: url.searchParams.get("state"), iss: url.searchParams.get("iss")
143
- };
144
161
  try {
162
+ const callbackParameters = readAuthorizationCallbackParameters(url);
145
163
  validateAuthorizationCallbackBinding(callbackParameters, expectedAuthorization);
146
164
  if (callbackParameters.error !== null)
147
- throw createAuthorizationError(callbackParameters.error, callbackParameters.errorDescription ?? callbackParameters.error);
165
+ throw new OAuthAuthorizationError(callbackParameters.error, callbackParameters.errorDescription ?? callbackParameters.error);
148
166
  const code = validateAuthorizationCallbackParameters(callbackParameters, expectedAuthorization);
149
167
  res.writeHead(200, { "Content-Type": "text/html" });
150
168
  res.end(buildSuccessPage(options.landingPage));
151
169
  settle(() => resolve(code));
152
170
  }
153
171
  catch (error) {
154
- res.writeHead(400);
172
+ res.writeHead(400, { "Content-Type": "text/plain; charset=utf-8", "X-Content-Type-Options": "nosniff" });
155
173
  res.end(error instanceof Error ? error.message : "Invalid OAuth callback");
156
174
  settle(() => reject(error));
157
175
  }
@@ -167,7 +185,7 @@ function waitForAuthorizationCode(server, authorizationUrl, options, callbackPat
167
185
  throw new Error("OAuth callback missing authorization code");
168
186
  validateAuthorizationCallbackBinding(callbackParameters, expectedAuthorization);
169
187
  if (callbackParameters.error !== null)
170
- throw createAuthorizationError(callbackParameters.error, callbackParameters.errorDescription ?? callbackParameters.error);
188
+ throw new OAuthAuthorizationError(callbackParameters.error, callbackParameters.errorDescription ?? callbackParameters.error);
171
189
  const code = validateAuthorizationCallbackParameters(callbackParameters, expectedAuthorization);
172
190
  settle(() => resolve(code));
173
191
  }).catch(error => settle(() => reject(error)));
@@ -179,22 +197,21 @@ function waitForAuthorizationCode(server, authorizationUrl, options, callbackPat
179
197
  });
180
198
  }
181
199
  export function extractCodeFromInput(input) {
182
- return extractCallbackParametersFromInput(input)?.code ?? null;
200
+ try {
201
+ return extractCallbackParametersFromInput(input)?.code ?? null;
202
+ }
203
+ catch {
204
+ return null;
205
+ }
183
206
  }
184
207
  function extractCallbackParametersFromInput(input) {
185
208
  const trimmed = input.replaceAll("\r", "").replaceAll("\n", "").trim();
186
209
  if (trimmed.length === 0) {
187
210
  return null;
188
211
  }
212
+ let url;
189
213
  try {
190
- const url = new URL(trimmed);
191
- return {
192
- code: url.searchParams.get("code"),
193
- error: url.searchParams.get("error"),
194
- errorDescription: url.searchParams.get("error_description"),
195
- state: url.searchParams.get("state"),
196
- iss: url.searchParams.get("iss")
197
- };
214
+ url = new URL(trimmed);
198
215
  }
199
216
  catch {
200
217
  return {
@@ -205,6 +222,15 @@ function extractCallbackParametersFromInput(input) {
205
222
  iss: null
206
223
  };
207
224
  }
225
+ return readAuthorizationCallbackParameters(url);
226
+ }
227
+ function readAuthorizationCallbackParameters(url) {
228
+ for (const parameter of oauthCallbackParameters) {
229
+ if (url.searchParams.getAll(parameter).length > 1)
230
+ throw new Error(`OAuth callback parameter '${parameter}' must occur only once`);
231
+ }
232
+ return { code: url.searchParams.get("code"), error: url.searchParams.get("error"),
233
+ errorDescription: url.searchParams.get("error_description"), state: url.searchParams.get("state"), iss: url.searchParams.get("iss") };
208
234
  }
209
235
  function readExpectedAuthorizationCallback(authorizationUrl) {
210
236
  const url = new URL(authorizationUrl);
@@ -244,9 +270,6 @@ function validateAuthorizationCallbackBinding(callback, expected) {
244
270
  throw new Error("OAuth callback issuer mismatch");
245
271
  }
246
272
  }
247
- function createAuthorizationError(error, description) {
248
- return new Error(`OAuth authorization failed: ${error} — ${description}`);
249
- }
250
273
  function escapeHtml(text) {
251
274
  return text
252
275
  .replaceAll("&", "&amp;")
@@ -5,6 +5,8 @@ interface OAuthErrorShape {
5
5
  error_uri?: string;
6
6
  }
7
7
  export declare class OAuthError extends Error {
8
+ /** Recognize errors from separately bundled copies of this package. */
9
+ static is(value: unknown): value is OAuthError;
8
10
  readonly error: string;
9
11
  readonly errorDescription: string | undefined;
10
12
  readonly errorUri: string | undefined;
@@ -4,7 +4,12 @@ import { readBoundedResponseText } from "../http-response.js";
4
4
  import { fetchMcpResponse } from "../http-fetch.js";
5
5
  import { normalizeOAuthScope } from "./scope.js";
6
6
  const MAX_JS_DATE_MS = 8_640_000_000_000_000;
7
+ const oauthErrorBrand = Symbol.for("poe-platform.mcp-oauth.OAuthError");
7
8
  export class OAuthError extends Error {
9
+ /** Recognize errors from separately bundled copies of this package. */
10
+ static is(value) {
11
+ return value instanceof Error && Object.getOwnPropertyDescriptor(value, oauthErrorBrand)?.value === true;
12
+ }
8
13
  error;
9
14
  errorDescription;
10
15
  errorUri;
@@ -20,6 +25,7 @@ export class OAuthError extends Error {
20
25
  const uri = Object.hasOwn(shape, "error_uri") ? shape.error_uri : undefined;
21
26
  super(description ?? (outcomeKnown ? shape.error : `OAuth HTTP response did not contain a valid error (HTTP ${status})`));
22
27
  this.name = "OAuthError";
28
+ Object.defineProperty(this, oauthErrorBrand, { value: true });
23
29
  this.error = shape.error;
24
30
  this.errorDescription = description;
25
31
  this.errorUri = uri;
@@ -132,6 +138,8 @@ async function requestTokens(input) {
132
138
  }
133
139
  }
134
140
  const refreshToken = getOwnEntry(payload, "refresh_token");
141
+ if (refreshToken !== undefined && (typeof refreshToken !== "string" || refreshToken.trim().length === 0))
142
+ throw new Error("OAuth token response has invalid refresh_token");
135
143
  const scope = getOwnEntry(payload, "scope");
136
144
  const normalizedRefreshToken = typeof refreshToken === "string" && refreshToken.trim().length > 0
137
145
  ? refreshToken.trim()
@@ -7,7 +7,7 @@ export { createResourceBoundOAuthStores } from "./client/resource-bound-store.js
7
7
  export type { ResourceBoundOAuthStores } from "./client/resource-bound-store.js";
8
8
  export { parseOAuthClientRegistration, normalizeStoredOAuthClient } from "./client/client-registration.js";
9
9
  export { createDefaultOAuthClientProvider, createOAuthClientProvider, } from "./client/default-oauth-client-provider.js";
10
- export { buildSuccessPage, createLoopbackAuthorizationSession, extractCodeFromInput, } from "./client/loopback-authorization.js";
10
+ export { buildSuccessPage, createLoopbackAuthorizationSession, extractCodeFromInput, OAuthAuthorizationError, } from "./client/loopback-authorization.js";
11
11
  export { generateCodeChallenge, generateCodeVerifier, } from "./client/pkce.js";
12
12
  export { OAuthError, } from "./client/token-endpoint.js";
13
13
  export { canonicalizeResourceIndicator, } from "./resource-indicator.js";
@@ -5,7 +5,7 @@ export { createAuthStoreSessionStore, } from "./client/auth-store-session-store.
5
5
  export { createResourceBoundOAuthStores } from "./client/resource-bound-store.js";
6
6
  export { parseOAuthClientRegistration, normalizeStoredOAuthClient } from "./client/client-registration.js";
7
7
  export { createDefaultOAuthClientProvider, createOAuthClientProvider, } from "./client/default-oauth-client-provider.js";
8
- export { buildSuccessPage, createLoopbackAuthorizationSession, extractCodeFromInput, } from "./client/loopback-authorization.js";
8
+ export { buildSuccessPage, createLoopbackAuthorizationSession, extractCodeFromInput, OAuthAuthorizationError, } from "./client/loopback-authorization.js";
9
9
  export { generateCodeChallenge, generateCodeVerifier, } from "./client/pkce.js";
10
10
  export { OAuthError, } from "./client/token-endpoint.js";
11
11
  export { canonicalizeResourceIndicator, } from "./resource-indicator.js";
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tiny-http-mcp-server",
3
- "version": "0.1.68",
3
+ "version": "0.1.71",
4
4
  "description": "Minimal MCP server over HTTP built on tiny-stdio-mcp-server",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -58,6 +58,10 @@ Legacy SSE mode opens a GET stream and posts messages to the endpoint announced
58
58
  by the server. Announced endpoints must remain on the original origin without
59
59
  embedded credentials or fragments; endpoint changes close the connection.
60
60
  Both transports accept the same headers, OAuth provider and response limits.
61
+ Static HTTP headers are copied and validated when the transport is constructed,
62
+ before OAuth callbacks run. Invalid headers fail without reflecting their values.
63
+ Use `snapshotHttpTransportHeaders(headers)` to apply the same ownership and safe
64
+ validation when preparing a registry before constructing transports.
61
65
  `maxResponseBytes` bounds a complete JSON body or each SSE event (default 16 MiB).
62
66
  Receive streams may carry many bounded events and keepalive comments; the limit
63
67
  does not cap their lifetime bytes. Use request deadlines and cancellation to
@@ -725,6 +725,8 @@ declare class StdioTransport implements McpTransport {
725
725
  private appendStderrOutput;
726
726
  dispose(reason?: Error): void;
727
727
  }
728
+ /** Own and validate static HTTP headers without reflecting malformed values. */
729
+ declare function snapshotHttpTransportHeaders(headers?: RequestInit["headers"]): Headers;
728
730
  declare class HttpTransport implements McpTransport {
729
731
  readonly readable: Readable;
730
732
  readonly writable: Writable;
@@ -872,5 +874,5 @@ declare class JsonRpcMessageLayer {
872
874
  private handleCancellationNotification;
873
875
  }
874
876
 
875
- export { ERROR_INTERNAL, ERROR_INVALID_PARAMS, ERROR_INVALID_REQUEST, ERROR_METHOD_NOT_FOUND, ERROR_PARSE, HttpTransport, HttpTransportError, JsonRpcMessageLayer, MCP_PROTOCOL_VERSIONS, McpClient, McpError, OAuthMetadataDiscovery, StdioTransport, createInMemoryTransportPair, createSdkTestPair, createTestPair, discoverOAuthMetadata, fetchMcpResponse };
877
+ export { ERROR_INTERNAL, ERROR_INVALID_PARAMS, ERROR_INVALID_REQUEST, ERROR_METHOD_NOT_FOUND, ERROR_PARSE, HttpTransport, HttpTransportError, JsonRpcMessageLayer, MCP_PROTOCOL_VERSIONS, McpClient, McpError, OAuthMetadataDiscovery, StdioTransport, createInMemoryTransportPair, createSdkTestPair, createTestPair, discoverOAuthMetadata, fetchMcpResponse, snapshotHttpTransportHeaders };
876
878
  export type { AudioContent, BlobResourceContents, CacheableResultMetadata, CallToolOptions, CallToolParams, CallToolResult, ClientCapabilities, CompleteArgument, CompleteParams, CompleteResult, Completion, ConnectResult, ContentItem, CreateMessageParams, CreateMessageResult, ElicitationParams, ElicitationResult, EmbeddedResource, GetPromptParams, GetPromptResult, HttpTransportFetch, HttpTransportOptions, ImageContent, InMemoryTransportPair, IncludeContext, InitializeParams, InitializeResult, JsonRpcErrorObject, JsonRpcErrorResponse, JsonRpcMessage, JsonRpcNotification, JsonRpcRequest, JsonRpcRequestOptions, JsonRpcResponse, JsonRpcSuccessResponse, LogLevel, LogMessage, McpClientConnection, McpClientOptions, McpProtocolVersion, McpRequestContext, McpSubscription, McpTransport, McpTransportClosedEvent, ModelHint, ModelPreferences, NotificationFilter, OAuthAuthorizationServerMetadata, OAuthClientProvider, OAuthClientProviderOptions, OAuthDiscoveryCache, OAuthDiscoveryResult, OAuthMetadataFetch, OAuthProtectedResourceMetadata, OAuthSessionStore, OAuthUnauthorizedChallenge, PaginatedParams, PaginatedResult, ProgressParams, ProgressToken, PromptMessage, PromptReference, ReadResourceParams, RequestId, ResourceContents, ResourceReference, ResultMetadata, Root, SamplingContent, SamplingMessage, SdkTestPair, ServerCapabilities, StdioSpawn, StdioTransportOptions, StoredOAuthSession, SubscriptionOptions, TextContent, TextResourceContents, Tool, ToolResultContent, ToolUseContent };
@@ -3516,7 +3516,32 @@ function getOwnEntry(record2, key2) {
3516
3516
  }
3517
3517
 
3518
3518
  // ../mcp-oauth/dist/client/loopback-authorization.js
3519
+ var authorizationErrorBrand = /* @__PURE__ */ Symbol.for("poe-platform.mcp-oauth.OAuthAuthorizationError");
3520
+ var oauthCallbackParameters = ["code", "state", "iss", "error", "error_description", "error_uri"];
3521
+ var OAuthAuthorizationError = class extends Error {
3522
+ error;
3523
+ errorDescription;
3524
+ /** Recognize errors from separately bundled copies of this package. */
3525
+ static is(value) {
3526
+ return value instanceof Error && Object.getOwnPropertyDescriptor(value, authorizationErrorBrand)?.value === true;
3527
+ }
3528
+ constructor(error, errorDescription) {
3529
+ super(`OAuth authorization failed: ${error} \u2014 ${errorDescription}`);
3530
+ this.error = error;
3531
+ this.errorDescription = errorDescription;
3532
+ this.name = "OAuthAuthorizationError";
3533
+ Object.defineProperty(this, authorizationErrorBrand, { value: true });
3534
+ }
3535
+ };
3519
3536
  async function createLoopbackAuthorizationSession(options = {}) {
3537
+ const selected = { ...options };
3538
+ options = {
3539
+ ...selected,
3540
+ createServer: selected.createServer?.bind(options),
3541
+ openBrowser: selected.openBrowser?.bind(options),
3542
+ readLine: selected.readLine?.bind(options),
3543
+ landingPage: selected.landingPage === void 0 ? void 0 : { ...selected.landingPage }
3544
+ };
3520
3545
  options.signal?.throwIfAborted();
3521
3546
  const timeoutMs = options.timeoutMs ?? 12e4;
3522
3547
  if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 2147483647)
@@ -3576,8 +3601,7 @@ function loopbackTarget(options) {
3576
3601
  } catch (cause) {
3577
3602
  throw new Error("Invalid OAuth loopback redirect URI", { cause });
3578
3603
  }
3579
- const forbiddenQuery = ["code", "state", "error", "error_description", "iss"];
3580
- if (url.protocol !== "http:" || !["localhost", "127.0.0.1", "[::1]"].includes(url.hostname) || url.username || url.password || url.hash || url.port === "0" || forbiddenQuery.some((name) => url.searchParams.has(name)) || [...options.redirectUri].some((char) => char.codePointAt(0) <= 32) || options.callbackPath !== void 0 && options.callbackPath !== url.pathname)
3604
+ if (url.protocol !== "http:" || !["localhost", "127.0.0.1", "[::1]"].includes(url.hostname) || url.username || url.password || url.hash || url.port === "0" || oauthCallbackParameters.some((name) => url.searchParams.has(name)) || [...options.redirectUri].some((char) => char.codePointAt(0) <= 32) || options.callbackPath !== void 0 && options.callbackPath !== url.pathname)
3581
3605
  throw new Error("Invalid OAuth loopback redirect URI");
3582
3606
  return { port: url.port ? Number(url.port) : 80, host: url.hostname === "[::1]" ? "::1" : url.hostname, callbackPath: url.pathname };
3583
3607
  }
@@ -3653,23 +3677,17 @@ function waitForAuthorizationCode(server, authorizationUrl, options, callbackPat
3653
3677
  res.end("Not found");
3654
3678
  return;
3655
3679
  }
3656
- const callbackParameters = {
3657
- code: url.searchParams.get("code"),
3658
- error: url.searchParams.get("error"),
3659
- errorDescription: url.searchParams.get("error_description"),
3660
- state: url.searchParams.get("state"),
3661
- iss: url.searchParams.get("iss")
3662
- };
3663
3680
  try {
3681
+ const callbackParameters = readAuthorizationCallbackParameters(url);
3664
3682
  validateAuthorizationCallbackBinding(callbackParameters, expectedAuthorization);
3665
3683
  if (callbackParameters.error !== null)
3666
- throw createAuthorizationError(callbackParameters.error, callbackParameters.errorDescription ?? callbackParameters.error);
3684
+ throw new OAuthAuthorizationError(callbackParameters.error, callbackParameters.errorDescription ?? callbackParameters.error);
3667
3685
  const code = validateAuthorizationCallbackParameters(callbackParameters, expectedAuthorization);
3668
3686
  res.writeHead(200, { "Content-Type": "text/html" });
3669
3687
  res.end(buildSuccessPage(options.landingPage));
3670
3688
  settle(() => resolve(code));
3671
3689
  } catch (error) {
3672
- res.writeHead(400);
3690
+ res.writeHead(400, { "Content-Type": "text/plain; charset=utf-8", "X-Content-Type-Options": "nosniff" });
3673
3691
  res.end(error instanceof Error ? error.message : "Invalid OAuth callback");
3674
3692
  settle(() => reject(error));
3675
3693
  }
@@ -3685,7 +3703,7 @@ function waitForAuthorizationCode(server, authorizationUrl, options, callbackPat
3685
3703
  throw new Error("OAuth callback missing authorization code");
3686
3704
  validateAuthorizationCallbackBinding(callbackParameters, expectedAuthorization);
3687
3705
  if (callbackParameters.error !== null)
3688
- throw createAuthorizationError(callbackParameters.error, callbackParameters.errorDescription ?? callbackParameters.error);
3706
+ throw new OAuthAuthorizationError(callbackParameters.error, callbackParameters.errorDescription ?? callbackParameters.error);
3689
3707
  const code = validateAuthorizationCallbackParameters(callbackParameters, expectedAuthorization);
3690
3708
  settle(() => resolve(code));
3691
3709
  }).catch((error) => settle(() => reject(error)));
@@ -3700,15 +3718,9 @@ function extractCallbackParametersFromInput(input) {
3700
3718
  if (trimmed.length === 0) {
3701
3719
  return null;
3702
3720
  }
3721
+ let url;
3703
3722
  try {
3704
- const url = new URL(trimmed);
3705
- return {
3706
- code: url.searchParams.get("code"),
3707
- error: url.searchParams.get("error"),
3708
- errorDescription: url.searchParams.get("error_description"),
3709
- state: url.searchParams.get("state"),
3710
- iss: url.searchParams.get("iss")
3711
- };
3723
+ url = new URL(trimmed);
3712
3724
  } catch {
3713
3725
  return {
3714
3726
  code: trimmed,
@@ -3718,6 +3730,20 @@ function extractCallbackParametersFromInput(input) {
3718
3730
  iss: null
3719
3731
  };
3720
3732
  }
3733
+ return readAuthorizationCallbackParameters(url);
3734
+ }
3735
+ function readAuthorizationCallbackParameters(url) {
3736
+ for (const parameter of oauthCallbackParameters) {
3737
+ if (url.searchParams.getAll(parameter).length > 1)
3738
+ throw new Error(`OAuth callback parameter '${parameter}' must occur only once`);
3739
+ }
3740
+ return {
3741
+ code: url.searchParams.get("code"),
3742
+ error: url.searchParams.get("error"),
3743
+ errorDescription: url.searchParams.get("error_description"),
3744
+ state: url.searchParams.get("state"),
3745
+ iss: url.searchParams.get("iss")
3746
+ };
3721
3747
  }
3722
3748
  function readExpectedAuthorizationCallback(authorizationUrl) {
3723
3749
  const url = new URL(authorizationUrl);
@@ -3754,9 +3780,6 @@ function validateAuthorizationCallbackBinding(callback, expected) {
3754
3780
  throw new Error("OAuth callback issuer mismatch");
3755
3781
  }
3756
3782
  }
3757
- function createAuthorizationError(error, description) {
3758
- return new Error(`OAuth authorization failed: ${error} \u2014 ${description}`);
3759
- }
3760
3783
  function escapeHtml(text) {
3761
3784
  return text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
3762
3785
  }
@@ -5038,7 +5061,12 @@ async function readBoundedResponseText(response, maxBytes, readers, signal) {
5038
5061
 
5039
5062
  // ../mcp-oauth/dist/client/token-endpoint.js
5040
5063
  var MAX_JS_DATE_MS2 = 864e13;
5064
+ var oauthErrorBrand = /* @__PURE__ */ Symbol.for("poe-platform.mcp-oauth.OAuthError");
5041
5065
  var OAuthError = class extends Error {
5066
+ /** Recognize errors from separately bundled copies of this package. */
5067
+ static is(value) {
5068
+ return value instanceof Error && Object.getOwnPropertyDescriptor(value, oauthErrorBrand)?.value === true;
5069
+ }
5042
5070
  error;
5043
5071
  errorDescription;
5044
5072
  errorUri;
@@ -5054,6 +5082,7 @@ var OAuthError = class extends Error {
5054
5082
  const uri = Object.hasOwn(shape, "error_uri") ? shape.error_uri : void 0;
5055
5083
  super(description ?? (outcomeKnown ? shape.error : `OAuth HTTP response did not contain a valid error (HTTP ${status})`));
5056
5084
  this.name = "OAuthError";
5085
+ Object.defineProperty(this, oauthErrorBrand, { value: true });
5057
5086
  this.error = shape.error;
5058
5087
  this.errorDescription = description;
5059
5088
  this.errorUri = uri;
@@ -5155,6 +5184,8 @@ async function requestTokens(input) {
5155
5184
  }
5156
5185
  }
5157
5186
  const refreshToken = getOwnEntry5(payload, "refresh_token");
5187
+ if (refreshToken !== void 0 && (typeof refreshToken !== "string" || refreshToken.trim().length === 0))
5188
+ throw new Error("OAuth token response has invalid refresh_token");
5158
5189
  const scope = getOwnEntry5(payload, "scope");
5159
5190
  const normalizedRefreshToken = typeof refreshToken === "string" && refreshToken.trim().length > 0 ? refreshToken.trim() : void 0;
5160
5191
  const normalizedScope = normalizeOAuthScope(scope);
@@ -7640,6 +7671,13 @@ var StdioTransport = class _StdioTransport {
7640
7671
  }
7641
7672
  }
7642
7673
  };
7674
+ function snapshotHttpTransportHeaders(headers) {
7675
+ try {
7676
+ return new Headers(headers);
7677
+ } catch {
7678
+ throw new Error("Invalid HTTP transport headers");
7679
+ }
7680
+ }
7643
7681
  var HttpTransport = class {
7644
7682
  readable;
7645
7683
  writable;
@@ -7689,7 +7727,7 @@ var HttpTransport = class {
7689
7727
  this.maxResponseBytes = maxResponseBytes;
7690
7728
  this.url = url;
7691
7729
  this.mode = mode;
7692
- this.headers = headers;
7730
+ this.headers = snapshotHttpTransportHeaders(headers);
7693
7731
  this.fetchImpl = fetchImpl;
7694
7732
  this.onWarning = onWarning;
7695
7733
  this.oauthProvider = oauth === void 0 ? void 0 : createOAuthClientProvider(oauth);
@@ -9249,7 +9287,8 @@ export {
9249
9287
  createSdkTestPair,
9250
9288
  createTestPair,
9251
9289
  discoverOAuthMetadata,
9252
- fetchMcpResponse
9290
+ fetchMcpResponse,
9291
+ snapshotHttpTransportHeaders
9253
9292
  };
9254
9293
  /*!
9255
9294
  The MCP project is undergoing a licensing transition from the MIT License to the Apache License, Version 2.0 ("Apache-2.0"). All new code and specification contributions to the project are licensed under Apache-2.0. Documentation contributions (excluding specifications) are licensed under CC-BY-4.0.
@@ -8,7 +8,7 @@
8
8
  },
9
9
  {
10
10
  "name": "toolcraft-schema",
11
- "version": "0.0.343",
11
+ "version": "0.0.345",
12
12
  "license": "MIT"
13
13
  }
14
14
  ]
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toolcraft-schema",
3
- "version": "0.0.343",
3
+ "version": "0.0.345",
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.343",
3
+ "version": "0.0.345",
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.343",
161
+ "toolcraft-schema": "0.0.345",
162
162
  "toolcraft-design": "*",
163
163
  "@poe-code/frontmatter": "*",
164
164
  "@poe-code/agent-mcp-config": "*",