ofw-mcp 2.20.1 → 2.20.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bundle.js CHANGED
@@ -2335,11 +2335,23 @@ var require_websocket = __commonJS({
2335
2335
  this._isServer = false;
2336
2336
  this._redirects = 0;
2337
2337
  if (protocols === void 0) {
2338
- protocols = [];
2338
+ if (!options || options.protocols === void 0) {
2339
+ protocols = [];
2340
+ } else if (Array.isArray(options.protocols)) {
2341
+ protocols = options.protocols;
2342
+ } else {
2343
+ protocols = [options.protocols];
2344
+ }
2339
2345
  } else if (!Array.isArray(protocols)) {
2340
2346
  if (typeof protocols === "object" && protocols !== null) {
2341
2347
  options = protocols;
2342
- protocols = [];
2348
+ if (options.protocols === void 0) {
2349
+ protocols = [];
2350
+ } else if (Array.isArray(options.protocols)) {
2351
+ protocols = options.protocols;
2352
+ } else {
2353
+ protocols = [options.protocols];
2354
+ }
2343
2355
  } else {
2344
2356
  protocols = [protocols];
2345
2357
  }
@@ -2536,7 +2548,6 @@ var require_websocket = __commonJS({
2536
2548
  }
2537
2549
  return;
2538
2550
  }
2539
- this._readyState = _WebSocket.CLOSING;
2540
2551
  this._sender.close(code2, data, !this._isServer, (err) => {
2541
2552
  if (err) return;
2542
2553
  this._closeFrameSent = true;
@@ -2544,6 +2555,7 @@ var require_websocket = __commonJS({
2544
2555
  this._socket.end();
2545
2556
  }
2546
2557
  });
2558
+ this._readyState = _WebSocket.CLOSING;
2547
2559
  setCloseTimer(this);
2548
2560
  }
2549
2561
  /**
@@ -2768,6 +2780,7 @@ var require_websocket = __commonJS({
2768
2780
  socketPath: void 0,
2769
2781
  hostname: void 0,
2770
2782
  protocol: void 0,
2783
+ protocols: void 0,
2771
2784
  timeout: void 0,
2772
2785
  method: "GET",
2773
2786
  host: void 0,
@@ -21552,8 +21565,8 @@ function currencyCode2(_params) {
21552
21565
  return _stringFormat(ZodCustomStringFormat, "currency_code", currencyCode, _params);
21553
21566
  }
21554
21567
  function hash(alg, params) {
21555
- const enc6 = params?.enc ?? "hex";
21556
- const format = `${alg}_${enc6}`;
21568
+ const enc7 = params?.enc ?? "hex";
21569
+ const format = `${alg}_${enc7}`;
21557
21570
  const regex = regexes_exports[format];
21558
21571
  if (!regex)
21559
21572
  throw new Error(`Unrecognized hash format: ${format}`);
@@ -24428,7 +24441,7 @@ var OAuthTokenRevocationRequestSchema = object({
24428
24441
  token_type_hint: string2().optional()
24429
24442
  }).strip();
24430
24443
 
24431
- // node_modules/@modelcontextprotocol/server/dist/src-BHSMhZ_W.mjs
24444
+ // node_modules/@modelcontextprotocol/server/dist/src-Cqbh3MYc.mjs
24432
24445
  var BRANDS = /* @__PURE__ */ Symbol.for("mcp.sdk.errorBrands");
24433
24446
  function stampErrorBrands(instance, ctor) {
24434
24447
  const brands = /* @__PURE__ */ new Set();
@@ -26671,7 +26684,8 @@ function build() {
26671
26684
  ListResourceTemplatesResultResponseSchema: wireResultResponse(ListResourceTemplatesResultSchema$1),
26672
26685
  ReadResourceResultResponseSchema: wireResultResponse(union([ReadResourceResultSchema$1, InputRequiredResultSchema])),
26673
26686
  CompleteResultResponseSchema: wireResultResponse(CompleteResultSchema$1),
26674
- DiscoverResultResponseSchema: wireResultResponse(DiscoverResultSchema$1)
26687
+ DiscoverResultResponseSchema: wireResultResponse(DiscoverResultSchema$1),
26688
+ SubscriptionsListenResultResponseSchema: wireResultResponse(SubscriptionsListenResultSchema$1)
26675
26689
  };
26676
26690
  }
26677
26691
  var memo2;
@@ -27179,6 +27193,9 @@ function isSpecRequestMethod(method) {
27179
27193
  function isSpecNotificationMethod(method) {
27180
27194
  return ALL_CODECS.some((codec2) => codec2.hasNotificationMethod(method));
27181
27195
  }
27196
+ function isExtensionReusedRequestMethod(method) {
27197
+ return method === "tasks/get" || method === "tasks/cancel";
27198
+ }
27182
27199
  var ALL_CODECS = [rev2025Codec, rev2026Codec];
27183
27200
  function isPlainObject$3(value) {
27184
27201
  return value !== null && typeof value === "object" && !Array.isArray(value);
@@ -28187,6 +28204,8 @@ var Protocol = class {
28187
28204
  _transport;
28188
28205
  _requestMessageId = 0;
28189
28206
  _requestHandlers = /* @__PURE__ */ new Map();
28207
+ /** Methods registered with an explicit schema; the era gate in `_onrequest` reads it for the Tasks extension names. */
28208
+ _customSchemaRequestMethods = /* @__PURE__ */ new Set();
28190
28209
  _requestHandlerAbortControllers = /* @__PURE__ */ new Map();
28191
28210
  _notificationHandlers = /* @__PURE__ */ new Map();
28192
28211
  _responseHandlers = /* @__PURE__ */ new Map();
@@ -28470,7 +28489,7 @@ var Protocol = class {
28470
28489
  return;
28471
28490
  }
28472
28491
  }
28473
- if (isSpecRequestMethod(request.method) && !codec2.hasRequestMethod(request.method)) {
28492
+ if (isSpecRequestMethod(request.method) && !codec2.hasRequestMethod(request.method) && !(isExtensionReusedRequestMethod(request.method) && this._customSchemaRequestMethods.has(request.method))) {
28474
28493
  sendErrorResponse(ProtocolErrorCode.MethodNotFound, "Method not found");
28475
28494
  return;
28476
28495
  }
@@ -28603,8 +28622,11 @@ var Protocol = class {
28603
28622
  }
28604
28623
  request(request, schemaOrOptions, maybeOptions) {
28605
28624
  const codec2 = this._resolveOutboundCodec(request.method);
28625
+ if (isStandardSchema(schemaOrOptions)) {
28626
+ if (!isExtensionReusedRequestMethod(request.method)) this._assertOutboundRequestInEra(codec2, request.method);
28627
+ return this._requestWithSchemaViaCodec(codec2, request, schemaOrOptions, maybeOptions);
28628
+ }
28606
28629
  this._assertOutboundRequestInEra(codec2, request.method);
28607
- if (isStandardSchema(schemaOrOptions)) return this._requestWithSchemaViaCodec(codec2, request, schemaOrOptions, maybeOptions);
28608
28630
  const validate2 = codecResultValidator(codec2, request.method);
28609
28631
  if (validate2 === void 0) throw new TypeError(`'${request.method}' is not a spec method; pass a result schema as the second argument to request().`);
28610
28632
  return this._requestWithSchemaViaCodec(codec2, request, validate2, schemaOrOptions);
@@ -28848,6 +28870,8 @@ var Protocol = class {
28848
28870
  return maybeHandler(parsed.data, ctx);
28849
28871
  };
28850
28872
  else throw new TypeError("setRequestHandler: handler is required");
28873
+ if (typeof schemasOrHandler === "function") this._customSchemaRequestMethods.delete(method);
28874
+ else this._customSchemaRequestMethods.add(method);
28851
28875
  this._requestHandlers.set(method, this._wrapHandler(method, stored));
28852
28876
  }
28853
28877
  /**
@@ -28877,6 +28901,7 @@ var Protocol = class {
28877
28901
  */
28878
28902
  removeRequestHandler(method) {
28879
28903
  this._requestHandlers.delete(method);
28904
+ this._customSchemaRequestMethods.delete(method);
28880
28905
  }
28881
28906
  /**
28882
28907
  * Asserts that a request handler has not already been set for the given method, in preparation for a new one being automatically installed.
@@ -36119,7 +36144,7 @@ var Ajv = import_ajv.Ajv;
36119
36144
  // node_modules/@modelcontextprotocol/server/dist/shimsNode.mjs
36120
36145
  import process2 from "node:process";
36121
36146
 
36122
- // node_modules/@modelcontextprotocol/server/dist/mcp-DYuW2ZSs.mjs
36147
+ // node_modules/@modelcontextprotocol/server/dist/mcp-DIH4cS6P.mjs
36123
36148
  var COMPLETABLE_SYMBOL = /* @__PURE__ */ Symbol.for("mcp.completable");
36124
36149
  function isCompletable(schema) {
36125
36150
  return !!schema && typeof schema === "object" && COMPLETABLE_SYMBOL in schema;
@@ -36184,7 +36209,7 @@ var StdioListenRouter = class {
36184
36209
  _serverCapabilities;
36185
36210
  /**
36186
36211
  * The serving instance's identity, stamped onto the graceful-close
36187
- * results' `_meta` (the spec's `SubscriptionsListenResultMeta` extends
36212
+ * results' `_meta` (the spec's `SubscriptionsListenResultMetaObject` extends
36188
36213
  * `ResultMetaObject`). Handed over together with the capabilities.
36189
36214
  */
36190
36215
  _serverInfo;
@@ -36516,6 +36541,11 @@ var Server = class extends Protocol {
36516
36541
  if (modernProtocolVersions(this._supportedProtocolVersions).length > 0) this.setRequestHandler("server/discover", () => this._ondiscover());
36517
36542
  if (this._capabilities.logging) this._registerLoggingHandler();
36518
36543
  }
36544
+ /** Attaches to the given transport; rejects while this instance is connected to another one. */
36545
+ async connect(transport) {
36546
+ if (this.transport !== void 0) throw new SdkError(SdkErrorCode.AlreadyConnected, "Use a separate Server instance per connection: this instance is already connected to a transport. To connect it to a new transport, call close() first.");
36547
+ await super.connect(transport);
36548
+ }
36519
36549
  /**
36520
36550
  * Registers the built-in `logging/setLevel` request handler.
36521
36551
  *
@@ -37069,21 +37099,41 @@ var Server = class extends Protocol {
37069
37099
  function discoverAdvertisedCapabilities(capabilities) {
37070
37100
  return { ...capabilities };
37071
37101
  }
37102
+ function toolInputElementCount(value, max) {
37103
+ let count = 0;
37104
+ const stack = [value];
37105
+ while (stack.length > 0) {
37106
+ const node2 = stack.pop();
37107
+ if (node2 === null || typeof node2 !== "object") continue;
37108
+ if (Array.isArray(node2)) for (const child of node2) {
37109
+ if (++count > max) return count;
37110
+ if (child !== null && typeof child === "object") stack.push(child);
37111
+ }
37112
+ else for (const key in node2) {
37113
+ if (!Object.prototype.hasOwnProperty.call(node2, key)) continue;
37114
+ if (++count > max) return count;
37115
+ const child = node2[key];
37116
+ if (child !== null && typeof child === "object") stack.push(child);
37117
+ }
37118
+ }
37119
+ return count;
37120
+ }
37121
+ function resolveMaxToolInputElements(value) {
37122
+ if (value === void 0 || value === Infinity) return void 0;
37123
+ if (typeof value !== "number" || Number.isNaN(value) || value < 1) throw new RangeError(`maxToolInputElements must be a number of at least 1, or Infinity, got ${String(value)}`);
37124
+ return value;
37125
+ }
37072
37126
  var McpServer = class {
37073
37127
  /**
37074
37128
  * The underlying {@linkcode Server} instance, useful for advanced operations like sending notifications.
37075
37129
  */
37076
37130
  server;
37131
+ _maxToolInputElements;
37077
37132
  _registeredResources = {};
37078
37133
  _registeredResourceTemplates = {};
37079
37134
  _registeredTools = {};
37080
37135
  _registeredPrompts = {};
37081
- /**
37082
- * Per-tool JSON-converted `inputSchema`, memoized so the SEP-2243
37083
- * registration-time scan and the pre-dispatch validation step share one
37084
- * conversion instead of paying it twice per request under the
37085
- * per-request-factory `createMcpHandler` model.
37086
- */
37136
+ /** Per-tool JSON-converted `inputSchema`, filled on first use by `toolInputSchemaJson()`. */
37087
37137
  _toolInputSchemaJson = {};
37088
37138
  /**
37089
37139
  * The JSON-serialized `inputSchema` of a registered tool, or `undefined`
@@ -37108,6 +37158,7 @@ var McpServer = class {
37108
37158
  }
37109
37159
  constructor(serverInfo, options) {
37110
37160
  this.server = new Server(serverInfo, options);
37161
+ this._maxToolInputElements = resolveMaxToolInputElements(options?.maxToolInputElements);
37111
37162
  if (options?.capabilities?.tools) this.setToolRequestHandlers();
37112
37163
  if (options?.capabilities?.resources) this.setResourceRequestHandlers();
37113
37164
  if (options?.capabilities?.prompts) this.setPromptRequestHandlers();
@@ -37180,7 +37231,7 @@ var McpServer = class {
37180
37231
  name,
37181
37232
  title: tool.title,
37182
37233
  description: tool.description,
37183
- inputSchema: tool.inputSchema ? standardSchemaToJsonSchema(tool.inputSchema, "input") : EMPTY_OBJECT_JSON_SCHEMA,
37234
+ inputSchema: tool.inputSchema ? convertListedInputSchema(name, tool.inputSchema) : EMPTY_OBJECT_JSON_SCHEMA,
37184
37235
  annotations: tool.annotations,
37185
37236
  icons: tool.icons,
37186
37237
  execution: tool.execution,
@@ -37225,6 +37276,7 @@ var McpServer = class {
37225
37276
  * Validates tool input arguments against the tool's input schema.
37226
37277
  */
37227
37278
  async validateToolInput(tool, args, toolName) {
37279
+ if (this._maxToolInputElements !== void 0 && toolInputElementCount(args, this._maxToolInputElements) > this._maxToolInputElements) throw new ProtocolError(ProtocolErrorCode.InvalidParams, `Invalid arguments for tool ${toolName}: arguments contain more than the maximum of ${this._maxToolInputElements} elements`);
37228
37280
  if (!tool.inputSchema) return;
37229
37281
  const parseResult = await validateStandardSchema(tool.inputSchema, args ?? {});
37230
37282
  if (!parseResult.success) throw new ProtocolError(ProtocolErrorCode.InvalidParams, `Input validation error: Invalid arguments for tool ${toolName}: ${parseResult.error}`);
@@ -37499,20 +37551,19 @@ var McpServer = class {
37499
37551
  }
37500
37552
  _createRegisteredTool(name, title, description, inputSchema, outputSchema, annotations, icons, execution, scopeChallenge, _meta, handler) {
37501
37553
  validateAndWarnToolName(name);
37502
- if (inputSchema !== void 0) try {
37503
- const json2 = standardSchemaToJsonSchema(inputSchema, "input");
37504
- this._toolInputSchemaJson[name] = json2;
37505
- const scan = scanXMcpHeaderDeclarations(json2);
37506
- if (!scan.valid) console.warn(`[mcp-sdk] tool '${name}' carries an invalid x-mcp-header declaration and will be excluded by conforming Streamable HTTP clients: ${scan.reason}`);
37507
- } catch {
37508
- }
37509
37554
  let currentHandler = handler;
37555
+ let outputSchemaJson;
37510
37556
  const registeredTool = {
37511
37557
  title,
37512
37558
  description,
37513
37559
  inputSchema,
37514
37560
  outputSchema,
37515
- outputSchemaJson: convertOutputSchemaJson(outputSchema),
37561
+ get outputSchemaJson() {
37562
+ return outputSchemaJson ??= convertOutputSchemaJson(registeredTool.outputSchema);
37563
+ },
37564
+ set outputSchemaJson(value) {
37565
+ outputSchemaJson = value;
37566
+ },
37516
37567
  annotations,
37517
37568
  icons,
37518
37569
  execution,
@@ -37639,6 +37690,12 @@ var EMPTY_OBJECT_JSON_SCHEMA = {
37639
37690
  type: "object",
37640
37691
  properties: {}
37641
37692
  };
37693
+ function convertListedInputSchema(name, inputSchema) {
37694
+ const json2 = standardSchemaToJsonSchema(inputSchema, "input");
37695
+ const scan = scanXMcpHeaderDeclarations(json2);
37696
+ if (!scan.valid) console.warn(`[mcp-sdk] tool '${name}' carries an invalid x-mcp-header declaration and will be excluded by conforming Streamable HTTP clients: ${scan.reason}`);
37697
+ return json2;
37698
+ }
37642
37699
  function convertOutputSchemaJson(outputSchema) {
37643
37700
  if (outputSchema === void 0) return void 0;
37644
37701
  try {
@@ -37651,7 +37708,7 @@ function createPromptHandler(name, argsSchema, callback) {
37651
37708
  if (argsSchema) {
37652
37709
  const typedCallback = callback;
37653
37710
  return async (args, ctx) => {
37654
- const parseResult = await validateStandardSchema(argsSchema, args);
37711
+ const parseResult = await validateStandardSchema(argsSchema, args ?? {});
37655
37712
  if (!parseResult.success) throw new ProtocolError(ProtocolErrorCode.InvalidParams, `Invalid arguments for prompt ${name}: ${parseResult.error}`);
37656
37713
  return typedCallback(parseResult.data, ctx);
37657
37714
  };
@@ -38402,6 +38459,7 @@ function escapeKey(key) {
38402
38459
  var DEFAULT_REQUEST_KEY = "confirmation";
38403
38460
  var DEFAULT_CONFIRMATION_LABEL = "Confirm this action should proceed.";
38404
38461
  var UNSUPPORTED_NOTE = "Nothing was done because this client cannot show a confirmation prompt (it declares no MCP elicitation capability), and this action is never taken without one";
38462
+ var DISABLED_NOTE = "Nothing was done because confirmation prompts are turned off for this server, and this action is never taken without one";
38405
38463
  var STATE_PREFIX = "mcpu.confirm.v1.";
38406
38464
  function bindingKey(binding) {
38407
38465
  if (binding.ttlSeconds !== void 0 && !(Number.isFinite(binding.ttlSeconds) && binding.ttlSeconds > 0)) {
@@ -38474,6 +38532,15 @@ ${JSON.stringify(preview, null, 2)}`,
38474
38532
  };
38475
38533
  const response = inputResponse(ctx.mcpReq.inputResponses, requestKey);
38476
38534
  const accepted = acceptedContent(ctx.mcpReq.inputResponses, requestKey, confirmationSchema);
38535
+ if (options.elicitation === false) {
38536
+ return textResult({
38537
+ confirmed: false,
38538
+ dispatched: false,
38539
+ action: options.action,
38540
+ reason: "confirmation-unsupported",
38541
+ note: options.unsupportedNote ? `${DISABLED_NOTE}. ${options.unsupportedNote}` : `${DISABLED_NOTE}.`
38542
+ });
38543
+ }
38477
38544
  if (response.kind === "missing") {
38478
38545
  if (callerAcceptsFormElicitation(ctx) === false) {
38479
38546
  return textResult({
@@ -38668,7 +38735,7 @@ async function tokenConfirmation(action, fb) {
38668
38735
  }
38669
38736
  async function requireConfirmationWithFallback(ctx, options) {
38670
38737
  const { tokenFallback, ...confirmation } = options;
38671
- if (tokenFallback && callerAcceptsFormElicitation(ctx) === false) {
38738
+ if (tokenFallback && (options.elicitation === false || callerAcceptsFormElicitation(ctx) === false)) {
38672
38739
  return tokenConfirmation(options.action, tokenFallback);
38673
38740
  }
38674
38741
  return requireConfirmation(ctx, confirmation);
@@ -38830,6 +38897,20 @@ function readConfirmMode(env = process.env) {
38830
38897
  }
38831
38898
  return "refuse";
38832
38899
  }
38900
+ function readConfirmElicitation(env = process.env) {
38901
+ const raw = readEnvVar("MCP_CONFIRM_ELICITATION", { env })?.trim().toLowerCase();
38902
+ if (!raw || raw === "on")
38903
+ return "on";
38904
+ if (raw === "off")
38905
+ return "off";
38906
+ const key = `elicitation:${raw}`;
38907
+ if (!warned.has(key)) {
38908
+ warned.add(key);
38909
+ process.stderr.write(`MCP_CONFIRM_ELICITATION="${raw}" is not one of on, off; treating it as on.
38910
+ `);
38911
+ }
38912
+ return "on";
38913
+ }
38833
38914
  function readConfirmTtl(env) {
38834
38915
  const raw = readEnvVar("MCP_CONFIRM_TTL_SECONDS", { env })?.trim();
38835
38916
  if (!raw)
@@ -38874,6 +38955,7 @@ function spentTokenStoreFromEnv(env = process.env) {
38874
38955
  }
38875
38956
  var CONFIRM_TOKEN_AUTO_INSTRUCTION = "Nothing has been done yet. Review this preview; if it is what was intended, call again with the same arguments plus confirmToken. (This server runs with MCP_CONFIRM_MODE=auto, so the user's approval in chat is not required.)";
38876
38957
  var REFUSE_HINT = "Set MCP_CONFIRM_MODE=ask-user on the server to allow two-step confirmation instead.";
38958
+ var ELICITATION_OFF_HINT = "MCP_CONFIRM_ELICITATION=off on the server turns confirmation prompts off.";
38877
38959
  var BAD_TTL_HINT = "MCP_CONFIRM_TTL_SECONDS on the server is not a positive whole number of seconds; fix it to allow two-step confirmation.";
38878
38960
  function boundArgs(args) {
38879
38961
  if (args === null || typeof args !== "object" || Array.isArray(args) || !("confirmToken" in args))
@@ -38887,10 +38969,13 @@ function confirmationFromEnv(options) {
38887
38969
  const spent = callerSpent ?? spentTokenStoreFromEnv(env);
38888
38970
  const mode = readConfirmMode(env);
38889
38971
  const ttlSeconds = readConfirmTtl(env);
38972
+ const elicitationOff = readConfirmElicitation(env) === "off";
38890
38973
  const bound = args === void 0 ? void 0 : boundArgs(args);
38891
- const confirmation = bound === void 0 || rest.binding ? rest : { ...rest, binding: { key: confirmKeyFromEnv(env), args: bound, ttlSeconds: ttlSeconds ?? DEFAULT_TTL_SECONDS2 } };
38974
+ const bindingAdded = bound === void 0 || rest.binding ? rest : { ...rest, binding: { key: confirmKeyFromEnv(env), args: bound, ttlSeconds: ttlSeconds ?? DEFAULT_TTL_SECONDS2 } };
38975
+ const confirmation = elicitationOff ? { ...bindingAdded, elicitation: false } : bindingAdded;
38892
38976
  if (mode === "refuse" || ttlSeconds === void 0) {
38893
- const hint = mode === "refuse" ? REFUSE_HINT : BAD_TTL_HINT;
38977
+ const reason = mode === "refuse" ? REFUSE_HINT : BAD_TTL_HINT;
38978
+ const hint = elicitationOff ? `${ELICITATION_OFF_HINT} ${reason}` : reason;
38894
38979
  return {
38895
38980
  ...confirmation,
38896
38981
  unsupportedNote: confirmation.unsupportedNote ? `${confirmation.unsupportedNote} ${hint}` : hint
@@ -38999,7 +39084,10 @@ function surfaceToolHints(server) {
38999
39084
  ));
39000
39085
  }
39001
39086
  async function createMcpServer(opts) {
39002
- const server = new McpServer({ name: opts.name, version: opts.version }, { supportedProtocolVersions: [...SERVER_PROTOCOL_VERSIONS] });
39087
+ const server = new McpServer({ name: opts.name, version: opts.version }, {
39088
+ supportedProtocolVersions: [...SERVER_PROTOCOL_VERSIONS],
39089
+ ...opts.maxToolInputElements !== void 0 ? { maxToolInputElements: opts.maxToolInputElements } : {}
39090
+ });
39003
39091
  if (opts.surfaceHints !== false)
39004
39092
  surfaceToolHints(server);
39005
39093
  if (opts.banner !== void 0) {
@@ -40327,6 +40415,60 @@ var KNOWN_CAPABILITIES = /* @__PURE__ */ new Set([
40327
40415
  "graphql",
40328
40416
  "write_cookies"
40329
40417
  ]);
40418
+ var ACCOUNT_KEY_FRAME = "account-key";
40419
+ var ACCOUNT_ATTEST_FRAME = "account-attest";
40420
+ var BRIDGE_ROLE_FRAME = "bridge-role";
40421
+ var BRIDGE_SERVE_FRAME = "bridge-serve";
40422
+ var ROOM_PING_FRAME = "room-ping";
40423
+ var ROOM_PONG_FRAME = "room-pong";
40424
+ var BRIDGE_ROLE_LABEL_MAX = 64;
40425
+ var ROOM_FRAME_ACCEPTS = Object.freeze({
40426
+ [BRIDGE_ROLE_FRAME]: BRIDGE_ROLE_FRAME,
40427
+ [BRIDGE_SERVE_FRAME]: BRIDGE_SERVE_FRAME,
40428
+ [ROOM_PING_FRAME]: ROOM_PING_FRAME,
40429
+ [ROOM_PONG_FRAME]: ROOM_PING_FRAME
40430
+ });
40431
+
40432
+ // node_modules/@fetchproxy/protocol/dist/mcp-id.js
40433
+ var ID_RE = /^([^:]+):([^:]+):([0-9a-f]{16})$/;
40434
+ function generateMcpId(serverName, version2) {
40435
+ if (serverName.includes(":")) {
40436
+ throw new Error(`serverName cannot contain ':': ${serverName}`);
40437
+ }
40438
+ if (version2.includes(":")) {
40439
+ throw new Error(`version cannot contain ':': ${version2}`);
40440
+ }
40441
+ const rand = randomHex(8);
40442
+ return `${serverName}:${version2}:${rand}`;
40443
+ }
40444
+ function isValidMcpId(id) {
40445
+ return typeof id === "string" && ID_RE.test(id);
40446
+ }
40447
+ function randomHex(bytes) {
40448
+ const arr = new Uint8Array(bytes);
40449
+ globalThis.crypto.getRandomValues(arr);
40450
+ return Array.from(arr, (b) => b.toString(16).padStart(2, "0")).join("");
40451
+ }
40452
+
40453
+ // node_modules/@fetchproxy/protocol/dist/account-attest.js
40454
+ var ACCOUNT_ATTEST_CONSENTS = Object.freeze([
40455
+ "silent",
40456
+ "confirm",
40457
+ "confirm-each"
40458
+ ]);
40459
+ var NO_SCOPE_DIGEST = "0".repeat(64);
40460
+ var ACCOUNT_ID_RE = /^[A-Za-z0-9_.\-]{1,128}$/;
40461
+ var HEX64_RE = /^[0-9a-f]{64}$/;
40462
+ var ACCOUNT_KID_RE = /^[0-9a-f]{16}$/;
40463
+ var enc2 = new TextEncoder();
40464
+
40465
+ // node_modules/@fetchproxy/protocol/dist/awaiting-approval.js
40466
+ var AWAITING_APPROVAL_REASON_PREFIX = "awaiting-approval:";
40467
+ function parseAwaitingApprovalReason(reason) {
40468
+ if (!reason.startsWith(AWAITING_APPROVAL_REASON_PREFIX))
40469
+ return null;
40470
+ return reason.slice(AWAITING_APPROVAL_REASON_PREFIX.length).trim();
40471
+ }
40330
40472
 
40331
40473
  // node_modules/@fetchproxy/protocol/dist/capability-availability.js
40332
40474
  var CAPABILITY_UNAVAILABLE_CODE = "capability_unavailable";
@@ -40352,27 +40494,6 @@ function parseUnsupportedCapabilityReason(reason) {
40352
40494
  return rest.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
40353
40495
  }
40354
40496
 
40355
- // node_modules/@fetchproxy/protocol/dist/mcp-id.js
40356
- var ID_RE = /^([^:]+):([^:]+):([0-9a-f]{16})$/;
40357
- function generateMcpId(serverName, version2) {
40358
- if (serverName.includes(":")) {
40359
- throw new Error(`serverName cannot contain ':': ${serverName}`);
40360
- }
40361
- if (version2.includes(":")) {
40362
- throw new Error(`version cannot contain ':': ${version2}`);
40363
- }
40364
- const rand = randomHex(8);
40365
- return `${serverName}:${version2}:${rand}`;
40366
- }
40367
- function isValidMcpId(id) {
40368
- return typeof id === "string" && ID_RE.test(id);
40369
- }
40370
- function randomHex(bytes) {
40371
- const arr = new Uint8Array(bytes);
40372
- globalThis.crypto.getRandomValues(arr);
40373
- return Array.from(arr, (b) => b.toString(16).padStart(2, "0")).join("");
40374
- }
40375
-
40376
40497
  // node_modules/@fetchproxy/protocol/dist/json-pointer.js
40377
40498
  function matchesDeclaredKey(requested, declared) {
40378
40499
  for (const decl of declared) {
@@ -40968,6 +41089,16 @@ function validateFrame(raw) {
40968
41089
  return { type: "extension-disconnected" };
40969
41090
  if (t === "peer-gone")
40970
41091
  return validatePeerGone(raw);
41092
+ if (t === ACCOUNT_KEY_FRAME)
41093
+ return validateAccountKey(raw);
41094
+ if (t === ACCOUNT_ATTEST_FRAME)
41095
+ return validateAccountAttest(raw);
41096
+ if (t === BRIDGE_ROLE_FRAME)
41097
+ return validateBridgeRole(raw);
41098
+ if (t === BRIDGE_SERVE_FRAME || t === ROOM_PING_FRAME || t === ROOM_PONG_FRAME) {
41099
+ assertExactFields(raw, TYPE_ONLY_FIELDS, t);
41100
+ return { type: t };
41101
+ }
40971
41102
  throw new ProtocolError3(`unknown frame type: ${String(t)}`);
40972
41103
  }
40973
41104
  function validateHello(raw) {
@@ -41171,6 +41302,165 @@ function validateHelloRejected(raw) {
41171
41302
  }
41172
41303
  return { type: "hello-rejected", mcpId: raw.mcpId, reason: raw.reason };
41173
41304
  }
41305
+ var DISPLAY_FORBIDDEN_RE = /[\u0000-\u001f\u007f-\u009f\u200e\u200f\u202a-\u202e\u2066-\u2069]/;
41306
+ function assertExactFields(raw, allowed, label) {
41307
+ for (const k of Object.keys(raw)) {
41308
+ if (!allowed.includes(k)) {
41309
+ throw new ProtocolError3(`${label}: unexpected field ${JSON.stringify(k)}`);
41310
+ }
41311
+ }
41312
+ }
41313
+ function assertMatch(x, re, label, what) {
41314
+ assertString(x, label);
41315
+ if (!re.test(x))
41316
+ throw new ProtocolError3(`${label}: must be ${what}`);
41317
+ }
41318
+ function assertPositiveSafeInt(x, label) {
41319
+ if (typeof x !== "number" || !Number.isSafeInteger(x) || x <= 0) {
41320
+ throw new ProtocolError3(`${label}: expected positive safe integer`);
41321
+ }
41322
+ }
41323
+ function assertDisplayString(x, label, min, max) {
41324
+ assertString(x, label);
41325
+ if (x.length < min || x.length > max) {
41326
+ throw new ProtocolError3(`${label}: must be ${min}-${max} characters`);
41327
+ }
41328
+ if (DISPLAY_FORBIDDEN_RE.test(x)) {
41329
+ throw new ProtocolError3(`${label}: control or bidi-override character`);
41330
+ }
41331
+ }
41332
+ var ACCOUNT_KEY_FIELDS = [
41333
+ "type",
41334
+ "accountId",
41335
+ "slug",
41336
+ "displayName",
41337
+ "confirmedBy",
41338
+ "tokenId",
41339
+ "kid",
41340
+ "publicKey",
41341
+ "generation",
41342
+ "bridgedRegistrations"
41343
+ ];
41344
+ function validateAccountKey(raw) {
41345
+ const L = "account-key";
41346
+ assertMatch(raw.accountId, ACCOUNT_ID_RE, `${L}.accountId`, "an account id");
41347
+ assertMatch(raw.slug, ACCOUNT_ID_RE, `${L}.slug`, "a slug");
41348
+ assertDisplayString(raw.displayName, `${L}.displayName`, 0, 128);
41349
+ assertDisplayString(raw.confirmedBy, `${L}.confirmedBy`, 1, 256);
41350
+ assertMatch(raw.tokenId, ACCOUNT_ID_RE, `${L}.tokenId`, "a token id");
41351
+ assertMatch(raw.kid, ACCOUNT_KID_RE, `${L}.kid`, "16 lowercase hex");
41352
+ assertBase64Bytes(raw.publicKey, `${L}.publicKey`, 32);
41353
+ assertPositiveSafeInt(raw.generation, `${L}.generation`);
41354
+ const n = raw.bridgedRegistrations;
41355
+ if (typeof n !== "number" || !Number.isSafeInteger(n) || n < 0) {
41356
+ throw new ProtocolError3(`${L}.bridgedRegistrations: expected non-negative safe integer`);
41357
+ }
41358
+ assertExactFields(raw, ACCOUNT_KEY_FIELDS, L);
41359
+ return {
41360
+ type: ACCOUNT_KEY_FRAME,
41361
+ accountId: raw.accountId,
41362
+ slug: raw.slug,
41363
+ displayName: raw.displayName,
41364
+ confirmedBy: raw.confirmedBy,
41365
+ tokenId: raw.tokenId,
41366
+ kid: raw.kid,
41367
+ publicKey: raw.publicKey,
41368
+ generation: raw.generation,
41369
+ bridgedRegistrations: n
41370
+ };
41371
+ }
41372
+ var ACCOUNT_ATTEST_FIELDS = [
41373
+ "type",
41374
+ "mcpId",
41375
+ "accountId",
41376
+ "generation",
41377
+ "tokenId",
41378
+ "kid",
41379
+ "registrationId",
41380
+ "slug",
41381
+ "identityHash",
41382
+ "identityEd25519Pub",
41383
+ "scopeDigest",
41384
+ "consent",
41385
+ "notAfter",
41386
+ "sig"
41387
+ ];
41388
+ function validateAccountAttest(raw) {
41389
+ const L = "account-attest";
41390
+ if (!isValidMcpId(raw.mcpId))
41391
+ throw new ProtocolError3(`${L}.mcpId: invalid format`);
41392
+ assertMatch(raw.accountId, ACCOUNT_ID_RE, `${L}.accountId`, "an account id");
41393
+ assertPositiveSafeInt(raw.generation, `${L}.generation`);
41394
+ assertMatch(raw.tokenId, ACCOUNT_ID_RE, `${L}.tokenId`, "a token id");
41395
+ assertMatch(raw.kid, ACCOUNT_KID_RE, `${L}.kid`, "16 lowercase hex");
41396
+ assertMatch(raw.registrationId, ACCOUNT_ID_RE, `${L}.registrationId`, "a registration id");
41397
+ assertMatch(raw.slug, ACCOUNT_ID_RE, `${L}.slug`, "a slug");
41398
+ assertMatch(raw.identityHash, HEX64_RE, `${L}.identityHash`, "64 lowercase hex");
41399
+ assertBase64Bytes(raw.identityEd25519Pub, `${L}.identityEd25519Pub`, 32);
41400
+ assertMatch(raw.scopeDigest, HEX64_RE, `${L}.scopeDigest`, "64 lowercase hex");
41401
+ assertString(raw.consent, `${L}.consent`);
41402
+ const consent = raw.consent;
41403
+ const known = ACCOUNT_ATTEST_CONSENTS.find((c) => c === consent);
41404
+ if (known === void 0) {
41405
+ throw new ProtocolError3(`${L}.consent: must be one of ${ACCOUNT_ATTEST_CONSENTS.join(", ")}`);
41406
+ }
41407
+ assertPositiveSafeInt(raw.notAfter, `${L}.notAfter`);
41408
+ assertBase64Bytes(raw.sig, `${L}.sig`, 64);
41409
+ assertExactFields(raw, ACCOUNT_ATTEST_FIELDS, L);
41410
+ return {
41411
+ type: ACCOUNT_ATTEST_FRAME,
41412
+ mcpId: raw.mcpId,
41413
+ accountId: raw.accountId,
41414
+ generation: raw.generation,
41415
+ tokenId: raw.tokenId,
41416
+ kid: raw.kid,
41417
+ registrationId: raw.registrationId,
41418
+ slug: raw.slug,
41419
+ identityHash: raw.identityHash,
41420
+ identityEd25519Pub: raw.identityEd25519Pub,
41421
+ scopeDigest: raw.scopeDigest,
41422
+ consent: known,
41423
+ notAfter: raw.notAfter,
41424
+ sig: raw.sig
41425
+ };
41426
+ }
41427
+ var TYPE_ONLY_FIELDS = ["type"];
41428
+ var BRIDGE_ROLE_FIELDS = ["type", "role", "canServe", "serving"];
41429
+ var BRIDGE_ROLE_SERVING_FIELDS = ["label", "since"];
41430
+ function validateBridgeRole(raw) {
41431
+ const L = BRIDGE_ROLE_FRAME;
41432
+ const role = raw.role;
41433
+ if (role !== "serving" && role !== "standby") {
41434
+ throw new ProtocolError3(`${L}.role: must be one of serving, standby`);
41435
+ }
41436
+ const canServe = raw.canServe;
41437
+ assertBoolean(canServe, `${L}.canServe`);
41438
+ assertExactFields(raw, BRIDGE_ROLE_FIELDS, L);
41439
+ const serving = raw.serving;
41440
+ if (role === "serving") {
41441
+ if (serving !== void 0) {
41442
+ throw new ProtocolError3(`${L}.serving: must be absent when role is serving`);
41443
+ }
41444
+ return { type: BRIDGE_ROLE_FRAME, role, canServe };
41445
+ }
41446
+ if (serving === void 0) {
41447
+ throw new ProtocolError3(`${L}.serving: required when role is standby`);
41448
+ }
41449
+ assertObject(serving, `${L}.serving`);
41450
+ const label = serving.label;
41451
+ assertDisplayString(label, `${L}.serving.label`, 1, BRIDGE_ROLE_LABEL_MAX);
41452
+ const since = serving.since;
41453
+ if (typeof since !== "number" || !Number.isSafeInteger(since) || since < 0) {
41454
+ throw new ProtocolError3(`${L}.serving.since: expected non-negative safe integer (Unix ms)`);
41455
+ }
41456
+ assertExactFields(serving, BRIDGE_ROLE_SERVING_FIELDS, `${L}.serving`);
41457
+ return {
41458
+ type: BRIDGE_ROLE_FRAME,
41459
+ role,
41460
+ canServe,
41461
+ serving: { label, since }
41462
+ };
41463
+ }
41174
41464
  function validateInnerFrame(raw) {
41175
41465
  assertObject(raw, "inner");
41176
41466
  const t = raw.type;
@@ -41638,10 +41928,10 @@ function validateInnerResponse(raw) {
41638
41928
  }
41639
41929
 
41640
41930
  // node_modules/@fetchproxy/protocol/dist/pair-code.js
41641
- var enc2 = new TextEncoder();
41931
+ var enc3 = new TextEncoder();
41642
41932
  var PAIR_LABEL = "fetchproxy/4/pair";
41643
41933
  async function pairTranscript(mcpIdentityPub, extIdentityPub, mcpNonce, extNonce, mcpSessionPub) {
41644
- const label = enc2.encode(PAIR_LABEL);
41934
+ const label = enc3.encode(PAIR_LABEL);
41645
41935
  const parts = [mcpIdentityPub, extIdentityPub, mcpNonce, extNonce, mcpSessionPub];
41646
41936
  const out = new Uint8Array(label.length + 1 + parts.reduce((n, p) => n + p.length, 0));
41647
41937
  out.set(label, 0);
@@ -41662,7 +41952,7 @@ function digits(hash2) {
41662
41952
  }
41663
41953
 
41664
41954
  // node_modules/@fetchproxy/protocol/dist/seal.js
41665
- var enc3 = new TextEncoder();
41955
+ var enc4 = new TextEncoder();
41666
41956
  var dec = new TextDecoder();
41667
41957
  function randomIv() {
41668
41958
  const iv = new Uint8Array(12);
@@ -41675,14 +41965,14 @@ function base64Length(byteLength) {
41675
41965
  return Math.ceil(byteLength / 3) * 4;
41676
41966
  }
41677
41967
  function encodeInnerFrame(inner) {
41678
- return enc3.encode(JSON.stringify(inner));
41968
+ return enc4.encode(JSON.stringify(inner));
41679
41969
  }
41680
41970
  function plaintextOf(inner) {
41681
41971
  return inner instanceof Uint8Array ? inner : encodeInnerFrame(inner);
41682
41972
  }
41683
41973
  function sealedFrameWireBytes(mcpId, seq, inner) {
41684
41974
  const plaintext = plaintextOf(inner).length;
41685
- const envelope = enc3.encode(JSON.stringify({ type: "frame", mcpId, seq, iv: "", ciphertext: "" })).length;
41975
+ const envelope = enc4.encode(JSON.stringify({ type: "frame", mcpId, seq, iv: "", ciphertext: "" })).length;
41686
41976
  return envelope + IV_B64_BYTES + base64Length(plaintext + AES_GCM_TAG_BYTES);
41687
41977
  }
41688
41978
  var MAX_FRAME_BYTES = 42 * 1024 * 1024;
@@ -42022,14 +42312,23 @@ var FetchproxyHelloRejectedError = class extends Error {
42022
42312
  platform;
42023
42313
  /**
42024
42314
  * #418: the remedy, when the reason is one this package knows how to
42025
- * explain — today only `unsupported-capability`, which blames the browser
42026
- * rather than the MCP. `null` otherwise.
42315
+ * explain — `unsupported-capability`, which blames the browser rather than
42316
+ * the MCP, and (D12) `awaiting-approval`, which says to approve in the
42317
+ * browser. `null` otherwise.
42027
42318
  */
42028
42319
  hint;
42320
+ /**
42321
+ * D12: `true` only for an `awaiting-approval:` refusal — the extension has
42322
+ * queued an approval card and nobody is at the browser yet, so the same call
42323
+ * succeeds once the person approves. Every other refusal is the extension's
42324
+ * final answer, and retrying unchanged is refused the same way.
42325
+ */
42326
+ retryable;
42029
42327
  constructor(info) {
42030
42328
  const unavailable = parseUnsupportedCapabilityReason(info.reason);
42329
+ const awaiting = parseAwaitingApprovalReason(info.reason);
42031
42330
  const platform = info.platform ?? null;
42032
- const hint = unavailable !== null ? `${platform ? `this browser (${platform})` : "this browser"} cannot serve any of the capabilities this MCP needs (${unavailable.join(", ")}) \u2014 ContextMint Bridge checked for the browser APIs and they are missing. Nothing is wrong with the MCP or your pairing, and updating will not change it: use this MCP from a browser that provides them (for example Chrome).` : null;
42331
+ const hint = awaiting !== null ? `The extension is waiting for you to approve this MCP` + (awaiting !== "" ? ` (${awaiting})` : "") + ` \u2014 open ContextMint Bridge in your browser, approve it there, then retry. Nothing is wrong with the MCP or your sign-in.` : unavailable !== null ? `${platform ? `this browser (${platform})` : "this browser"} cannot serve any of the capabilities this MCP needs (${unavailable.join(", ")}) \u2014 ContextMint Bridge checked for the browser APIs and they are missing. Nothing is wrong with the MCP or your pairing, and updating will not change it: use this MCP from a browser that provides them (for example Chrome).` : null;
42033
42332
  super(`fetchproxy: the extension refused the connection for "${info.mcpId}": ${info.reason}. ` + (hint !== null ? hint : `This is the extension's own reason \u2014 it is not a timeout, and retrying unchanged will be refused the same way.`));
42034
42333
  this.name = "FetchproxyHelloRejectedError";
42035
42334
  this.mcpId = info.mcpId;
@@ -42037,6 +42336,7 @@ var FetchproxyHelloRejectedError = class extends Error {
42037
42336
  this.unavailableCapabilities = unavailable ?? [];
42038
42337
  this.platform = platform;
42039
42338
  this.hint = hint;
42339
+ this.retryable = awaiting !== null;
42040
42340
  Object.setPrototypeOf(this, new.target.prototype);
42041
42341
  }
42042
42342
  };
@@ -42589,7 +42889,7 @@ function originVerdict(origin, allowLocal) {
42589
42889
  }
42590
42890
  var HANDSHAKE_TIMEOUT_MS = 15e3;
42591
42891
  var MAX_PAYLOAD_BYTES = MAX_FRAME_BYTES;
42592
- var enc4 = new TextEncoder();
42892
+ var enc5 = new TextEncoder();
42593
42893
  var OWN_DECRYPT_FAILURES_BEFORE_REHANDSHAKE = 3;
42594
42894
  async function startHost(opts) {
42595
42895
  const allowLocalOrigins = envAllowsLocalOrigins();
@@ -42660,10 +42960,20 @@ async function startHost(opts) {
42660
42960
  let resolveOwnSession;
42661
42961
  let rejectOwnSession;
42662
42962
  let ownSessionReady;
42963
+ let ownRetryableRefusal = false;
42964
+ let ownSessionSettled = false;
42663
42965
  function resetSessionPromise() {
42966
+ ownRetryableRefusal = false;
42967
+ ownSessionSettled = false;
42664
42968
  ownSessionReady = new Promise((resolve7, reject) => {
42665
- resolveOwnSession = resolve7;
42666
- rejectOwnSession = reject;
42969
+ resolveOwnSession = (st) => {
42970
+ ownSessionSettled = true;
42971
+ resolve7(st);
42972
+ };
42973
+ rejectOwnSession = (e) => {
42974
+ ownSessionSettled = true;
42975
+ reject(e);
42976
+ };
42667
42977
  });
42668
42978
  ownSessionReady.catch(() => {
42669
42979
  });
@@ -42930,13 +43240,15 @@ async function startHost(opts) {
42930
43240
  const extPub = fromB64(frame.extensionSessionPub);
42931
43241
  const shared = await ecdhX25519(held.priv, extPub);
42932
43242
  const salt = await transcriptHash(held.nonce, extNonce, held.pub, extPub);
42933
- const key = await hkdfSha256(shared, salt, enc4.encode(HKDF_SESSION_INFO), 32);
43243
+ const key = await hkdfSha256(shared, salt, enc5.encode(HKDF_SESSION_INFO), 32);
42934
43244
  if (extensionWs !== ws)
42935
43245
  return;
42936
43246
  ownSession = new SessionState(key);
42937
43247
  ownSessionInfo = sessionInfoFromHello(verifiedHello);
42938
43248
  ownDecryptFailures = 0;
42939
43249
  ownPendingPairCode = null;
43250
+ if (ownRetryableRefusal)
43251
+ resetSessionPromise();
42940
43252
  resolveOwnSession(ownSession);
42941
43253
  } else {
42942
43254
  const slot = peers.get(frame.mcpId);
@@ -43005,11 +43317,17 @@ async function startHost(opts) {
43005
43317
  }
43006
43318
  if (frame.type === "hello-rejected" && identified === "extension") {
43007
43319
  if (frame.mcpId === opts.ownMcpId) {
43008
- rejectOwnSession(new FetchproxyHelloRejectedError({
43320
+ const refusal = new FetchproxyHelloRejectedError({
43009
43321
  mcpId: frame.mcpId,
43010
43322
  reason: frame.reason,
43011
43323
  platform: extensionHello?.platform ?? null
43012
- }));
43324
+ });
43325
+ if (ownRetryableRefusal)
43326
+ resetSessionPromise();
43327
+ const settlesPending = !ownSessionSettled;
43328
+ rejectOwnSession(refusal);
43329
+ if (refusal.retryable && settlesPending)
43330
+ ownRetryableRefusal = true;
43013
43331
  } else {
43014
43332
  const slot = peers.get(frame.mcpId);
43015
43333
  if (slot?.helloFrame.accepts?.includes("hello-rejected")) {
@@ -43125,7 +43443,7 @@ async function startHost(opts) {
43125
43443
 
43126
43444
  // node_modules/@fetchproxy/server/dist/peer.js
43127
43445
  var PEER_DIAL_TIMEOUT_MS = 5e3;
43128
- var enc5 = new TextEncoder();
43446
+ var enc6 = new TextEncoder();
43129
43447
  function notSentError() {
43130
43448
  return new Error("fetchproxy: request not sent \u2014 the connection to the bridge host closed before it went out, so nothing reached the browser. It is safe to retry.");
43131
43449
  }
@@ -43225,7 +43543,9 @@ async function startPeer(opts) {
43225
43543
  let rejectFirstReady;
43226
43544
  let sessionPromise;
43227
43545
  let sessionPromiseSettled = false;
43546
+ let retryableRefusal = false;
43228
43547
  function resetSessionPromise() {
43548
+ retryableRefusal = false;
43229
43549
  sessionPromiseSettled = false;
43230
43550
  sessionPromise = new Promise((resolve7, reject) => {
43231
43551
  resolveFirstReady = (st) => {
@@ -43322,6 +43642,8 @@ async function startPeer(opts) {
43322
43642
  raw = JSON.parse(data.toString());
43323
43643
  const frame = validateFrame(raw);
43324
43644
  if (frame.type === "hello" && frame.role === "extension") {
43645
+ if (retryableRefusal)
43646
+ resetSessionPromise();
43325
43647
  extensionHello = frame;
43326
43648
  const mintedKeypair = await generateSessionKeypair();
43327
43649
  const mintedHello = await buildServerHello({
@@ -43351,6 +43673,8 @@ async function startPeer(opts) {
43351
43673
  session = null;
43352
43674
  if (sessionPromiseSettled)
43353
43675
  resetSessionPromise();
43676
+ } else if (retryableRefusal) {
43677
+ resetSessionPromise();
43354
43678
  }
43355
43679
  extensionDisconnectListeners.forEach((cb) => cb());
43356
43680
  return;
@@ -43372,7 +43696,7 @@ async function startPeer(opts) {
43372
43696
  const extNonce = fromB64(authenticated.sessionNonce);
43373
43697
  const shared = await ecdhX25519(held.priv, extPub);
43374
43698
  const salt = await transcriptHash(held.nonce, extNonce, held.pub, extPub);
43375
- const sessionKey = await hkdfSha256(shared, salt, enc5.encode(HKDF_SESSION_INFO), 32);
43699
+ const sessionKey = await hkdfSha256(shared, salt, enc6.encode(HKDF_SESSION_INFO), 32);
43376
43700
  if (sessionEphemeral !== held) {
43377
43701
  console.warn(`[fetchproxy] ${opts.serverName}: dropping a session derived against an ephemeral that was superseded while it was being derived`);
43378
43702
  return;
@@ -43385,16 +43709,24 @@ async function startPeer(opts) {
43385
43709
  if (isRenegotiation) {
43386
43710
  renegotiateListeners.forEach((cb) => cb());
43387
43711
  } else {
43712
+ if (retryableRefusal)
43713
+ resetSessionPromise();
43388
43714
  resolveFirstReady(session);
43389
43715
  }
43390
43716
  return;
43391
43717
  }
43392
43718
  if (frame.type === "hello-rejected" && frame.mcpId === opts.mcpId) {
43393
- rejectFirstReady(new FetchproxyHelloRejectedError({
43719
+ const refusal = new FetchproxyHelloRejectedError({
43394
43720
  mcpId: frame.mcpId,
43395
43721
  reason: frame.reason,
43396
43722
  platform: extensionHello?.platform ?? null
43397
- }));
43723
+ });
43724
+ if (retryableRefusal)
43725
+ resetSessionPromise();
43726
+ const settlesPending = !sessionPromiseSettled;
43727
+ rejectFirstReady(refusal);
43728
+ if (refusal.retryable && settlesPending)
43729
+ retryableRefusal = true;
43398
43730
  }
43399
43731
  if (frame.type === "pair-pending" && frame.mcpId === opts.mcpId) {
43400
43732
  const hello2 = extensionHello;
@@ -43558,8 +43890,9 @@ function classifyFetchError(error62, code2) {
43558
43890
 
43559
43891
  // node_modules/@fetchproxy/server/dist/classify-bridge-error.js
43560
43892
  function classifyBridgeError(err) {
43561
- if (err instanceof FetchproxyHelloRejectedError)
43562
- return "hello_rejected";
43893
+ if (err instanceof FetchproxyHelloRejectedError) {
43894
+ return err.retryable ? "awaiting_approval" : "hello_rejected";
43895
+ }
43563
43896
  if (err instanceof FetchproxySessionNotReadyError)
43564
43897
  return "session_not_ready";
43565
43898
  if (err instanceof FetchproxyTimeoutError)
@@ -46330,7 +46663,7 @@ async function loginWithPassword(username, password) {
46330
46663
  // package.json
46331
46664
  var package_default = {
46332
46665
  name: "ofw-mcp",
46333
- version: "2.20.1",
46666
+ version: "2.20.3",
46334
46667
  license: "MIT",
46335
46668
  mcpName: "io.github.chrischall/ofw-mcp",
46336
46669
  description: "OurFamilyWizard MCP server for Claude \u2014 developed and maintained by AI (Claude Code)",
@@ -46364,14 +46697,14 @@ var package_default = {
46364
46697
  typecheck: "tsc -p tsconfig.json --noEmit"
46365
46698
  },
46366
46699
  dependencies: {
46367
- "@chrischall/mcp-utils": "^2.13.0",
46368
- "@fetchproxy/bootstrap": "^3.4.1",
46369
- "@modelcontextprotocol/server": "^2.2.0",
46700
+ "@chrischall/mcp-utils": "^2.15.0",
46701
+ "@fetchproxy/bootstrap": "^3.6.0",
46702
+ "@modelcontextprotocol/server": "^2.3.0",
46370
46703
  dotenv: "^18.0.0",
46371
46704
  zod: "^4.6.5"
46372
46705
  },
46373
46706
  devDependencies: {
46374
- "@modelcontextprotocol/client": "^2.2.0",
46707
+ "@modelcontextprotocol/client": "^2.3.0",
46375
46708
  "@types/node": "^26.0.0",
46376
46709
  "@vitest/coverage-v8": "^5.0.0",
46377
46710
  esbuild: "^0.28.0",
@@ -46519,10 +46852,10 @@ var OFW_REFRESH_SENTINEL = "ofw";
46519
46852
  async function throwIfEdgeBlocked(response, method, path) {
46520
46853
  if (response.ok) return;
46521
46854
  let edge = detectEdgeBlock({ headers: response.headers, status: response.status });
46522
- if (edge === null && response.status === 429 && !/json/i.test(response.headers.get("content-type") ?? "")) {
46855
+ if (edge === null && (response.status === 401 || response.status === 429) && !/json/i.test(response.headers.get("content-type") ?? "")) {
46523
46856
  let body = "";
46524
46857
  try {
46525
- body = await response.clone().text();
46858
+ body = await (response.status === 401 ? response.clone() : response).text();
46526
46859
  } catch {
46527
46860
  }
46528
46861
  edge = detectEdgeBlock({ body, headers: response.headers, status: response.status });
@@ -46585,63 +46918,73 @@ var OFWClient = class {
46585
46918
  return this.tokenManager;
46586
46919
  }
46587
46920
  async request(method, path, body) {
46588
- const response = await this.fetchAuthed(method, path, body, "application/json");
46589
- const text = await response.text();
46590
- if (debugLogEnabled()) {
46591
- console.error(`[ofw-debug] response body: ${text || "<empty>"}`);
46592
- }
46593
- return text ? JSON.parse(text) : null;
46921
+ return this.fetchAuthed(method, path, body, "application/json", async (response) => {
46922
+ const text = await response.text();
46923
+ if (debugLogEnabled()) {
46924
+ console.error(`[ofw-debug] response body: ${text || "<empty>"}`);
46925
+ }
46926
+ return text ? JSON.parse(text) : null;
46927
+ });
46594
46928
  }
46595
46929
  /** Like `request`, but returns the raw bytes plus Content-Type/-Disposition metadata. */
46596
46930
  async requestBinary(method, path) {
46597
- const response = await this.fetchAuthed(method, path, void 0, "application/octet-stream");
46598
- return {
46931
+ return this.fetchAuthed(method, path, void 0, "application/octet-stream", async (response) => ({
46599
46932
  body: Buffer.from(await response.arrayBuffer()),
46600
46933
  contentType: response.headers.get("content-type"),
46601
46934
  suggestedFileName: parseContentDispositionFilename(response.headers.get("content-disposition") ?? "") ?? null
46602
- };
46935
+ }));
46603
46936
  }
46604
46937
  // Authenticated fetch for both JSON and binary callers. Auth (proactive
46605
46938
  // refresh inside the skew window + one 401-replay, guarded against a
46606
46939
  // double-refresh under concurrency) is delegated to the shared TokenManager's
46607
46940
  // `withAuth`. The 429 wait-and-replay and the non-2xx → throw remain here.
46608
- async fetchAuthed(method, path, body, accept) {
46941
+ async fetchAuthed(method, path, body, accept, consume) {
46609
46942
  let attempt = 0;
46610
- let response = await this.getTokenManager().withAuth(
46611
- (token) => this.fetchOnce(method, path, body, accept, token, attempt++ > 0)
46612
- );
46613
- await throwIfEdgeBlocked(response, method, path);
46943
+ let result;
46944
+ const call = async (retry) => {
46945
+ let authAttempt = 0;
46946
+ return this.getTokenManager().withAuth((token) => {
46947
+ const firstAuthAttempt = authAttempt++ === 0;
46948
+ return this.fetchOnce(method, path, body, accept, token, async (response2) => {
46949
+ await throwIfEdgeBlocked(response2, method, path);
46950
+ if (response2.status === 401 && firstAuthAttempt) {
46951
+ await releaseBody(response2);
46952
+ return;
46953
+ }
46954
+ if (response2.status === 429) {
46955
+ await releaseBody(response2);
46956
+ return;
46957
+ }
46958
+ if (!response2.ok) {
46959
+ const errorBody = await response2.text();
46960
+ const edge = detectEdgeBlock({ body: errorBody, headers: response2.headers, status: response2.status });
46961
+ if (edge !== null) {
46962
+ throw new EdgeBlockedError(response2.status, edge.vendor, { service: "OurFamilyWizard", method, path });
46963
+ }
46964
+ const safeBody = redactSecrets(errorBody).replace(/\s+/g, " ").trim().slice(0, 4e3);
46965
+ throw new Error(
46966
+ `OFW API error: ${response2.status} ${response2.statusText} for ${method} ${path}` + (safeBody ? ` \u2014 ${safeBody}` : "")
46967
+ );
46968
+ }
46969
+ result = await consume(response2);
46970
+ }, retry || attempt++ > 0);
46971
+ });
46972
+ };
46973
+ const response = await call(false);
46614
46974
  if (response.status === 429) {
46615
- await releaseBody(response);
46616
46975
  await new Promise((r) => setTimeout(r, 2e3));
46617
- response = await this.getTokenManager().withAuth(
46618
- (token) => this.fetchOnce(method, path, body, accept, token, true)
46619
- );
46620
- await throwIfEdgeBlocked(response, method, path);
46621
- if (response.status === 429) {
46622
- await releaseBody(response);
46976
+ if ((await call(true)).status === 429) {
46623
46977
  throw new Error("Rate limited by OFW API");
46624
46978
  }
46625
46979
  }
46626
- if (!response.ok) {
46627
- const errorBody = await response.text();
46628
- const edge = detectEdgeBlock({ body: errorBody, headers: response.headers, status: response.status });
46629
- if (edge !== null) {
46630
- throw new EdgeBlockedError(response.status, edge.vendor, { service: "OurFamilyWizard", method, path });
46631
- }
46632
- const safeBody = redactSecrets(errorBody).replace(/\s+/g, " ").trim().slice(0, 4e3);
46633
- throw new Error(
46634
- `OFW API error: ${response.status} ${response.statusText} for ${method} ${path}` + (safeBody ? ` \u2014 ${safeBody}` : "")
46635
- );
46636
- }
46637
- return response;
46980
+ return result;
46638
46981
  }
46639
46982
  // A single OFW API fetch with the bearer token supplied by `withAuth`.
46640
46983
  // Carries the per-request timeout (AbortController + setTimeout so vitest
46641
46984
  // fake timers can drive it and we attach a clear error message) and the
46642
- // OFW_DEBUG_LOG instrumentation. Returns the raw Response — 401/429/non-2xx
46643
- // handling lives in the callers (`withAuth` and `fetchAuthed`).
46644
- async fetchOnce(method, path, body, accept, token, isRetry = false) {
46985
+ // OFW_DEBUG_LOG instrumentation. The handler completes body consumption or
46986
+ // release before this attempt returns its Response to `withAuth`.
46987
+ async fetchOnce(method, path, body, accept, token, handleResponse, isRetry = false) {
46645
46988
  const isFormData = body instanceof FormData;
46646
46989
  const headers = {
46647
46990
  ...OFW_PROTOCOL_HEADERS,
@@ -46661,22 +47004,31 @@ var OFWClient = class {
46661
47004
  const ac = new AbortController();
46662
47005
  const timer = setTimeout(() => ac.abort(), timeoutMs);
46663
47006
  const startedAt = Date.now();
46664
- let response;
47007
+ const signal = withAmbientCancellation(ac.signal);
47008
+ let onAbort;
47009
+ const aborted2 = new Promise((_resolve, reject) => {
47010
+ onAbort = () => reject(signal.reason);
47011
+ if (signal.aborted) onAbort();
47012
+ else signal.addEventListener("abort", onAbort, { once: true });
47013
+ });
46665
47014
  try {
46666
- response = await fetch(url2, {
46667
- method,
46668
- headers,
46669
- // THE CALLER'S CANCELLATION, folded in with our timeout (mcp-utils
46670
- // `cancel`). Until this the only thing that could stop an OFW
46671
- // request was the timeout below, so a cancelled tool call held it
46672
- // open for the full budget while the child burned the CPU
46673
- // mcp-host meters it on. The timeout diagnosis below is unaffected
46674
- // because it asks `ac.signal`, OUR controller — a caller's abort
46675
- // falls through to the generic path rather than being reported as
46676
- // OFW being slow.
46677
- signal: withAmbientCancellation(ac.signal),
46678
- ...body !== void 0 ? { body: isFormData ? body : JSON.stringify(body) } : {}
46679
- });
47015
+ return await Promise.race([aborted2, (async () => {
47016
+ signal.throwIfAborted();
47017
+ const response = await fetch(url2, {
47018
+ method,
47019
+ headers,
47020
+ // The caller's cancellation and our timeout both cover headers,
47021
+ // body reads and releases; only our controller diagnoses slowness.
47022
+ signal,
47023
+ ...body !== void 0 ? { body: isFormData ? body : JSON.stringify(body) } : {}
47024
+ });
47025
+ if (debugLogEnabled()) {
47026
+ console.error(`[ofw-debug] \u2190 ${response.status} ${response.statusText} (${Date.now() - startedAt}ms)`);
47027
+ }
47028
+ await handleResponse(response);
47029
+ signal.throwIfAborted();
47030
+ return response;
47031
+ })()]);
46680
47032
  } catch (err) {
46681
47033
  const elapsed = Date.now() - startedAt;
46682
47034
  if (ac.signal.aborted) {
@@ -46693,11 +47045,8 @@ var OFWClient = class {
46693
47045
  throw err;
46694
47046
  } finally {
46695
47047
  clearTimeout(timer);
47048
+ signal.removeEventListener("abort", onAbort);
46696
47049
  }
46697
- if (debugLogEnabled()) {
46698
- console.error(`[ofw-debug] \u2190 ${response.status} ${response.statusText} (${Date.now() - startedAt}ms)`);
46699
- }
46700
- return response;
46701
47050
  }
46702
47051
  };
46703
47052
  var client = new OFWClient();
@@ -50942,7 +51291,7 @@ function registerCalendarTools(server, client2) {
50942
51291
  });
50943
51292
  });
50944
51293
  if (allowWrites) server.registerTool("ofw_update_event", {
50945
- description: "Update an existing OurFamilyWizard calendar event. Fetches the event, applies the given changes, and writes the merged result back (OFW has no partial update). A change to an event the co-parent can see (shared before or after the change) is confirmed first; the confirmation is bound to the event exactly as read, so if it changes on OFW in between (say the co-parent edited it) the update is refused instead of overwriting their edit. " + CONFIRM_NOTE,
51294
+ description: "Update an existing OurFamilyWizard calendar event. Fetches the event, applies the given changes, and writes the merged result back (OFW has no partial update). A change to an event the co-parent can see (shared before or after the change) is confirmed first; the confirmation is bound to the event exactly as read, so if it changes on OFW in between (say the co-parent edited it) the update is refused instead of overwriting their edit. If the write fails without a definitive answer, or lands but cannot be re-read to confirm it, the result is EVENT_UNCONFIRMED: the change may already be on OFW, so do NOT retry until ofw_list_events shows whether it landed. " + CONFIRM_NOTE,
50946
51295
  annotations: { destructiveHint: true },
50947
51296
  inputSchema: external_exports.object({
50948
51297
  eventId: external_exports.string().describe("Event id \u2014 the `id` from ofw_list_events / eventRecurrenceId from ofw_create_event"),
@@ -50991,13 +51340,30 @@ function registerCalendarTools(server, client2) {
50991
51340
  });
50992
51341
  if (gate) return gate;
50993
51342
  }
50994
- await client2.request("PUT", `/pub/v3/events/${id}`, payload);
50995
- const rawAfter = await client2.request("GET", `/pub/v3/events/${id}`);
50996
- const event = parseLenient(eventDetailSchema, rawAfter, { label: "ofw-mcp", context: `GET /pub/v3/events/${eventId} (post-update)`, mode: "strict" });
50997
- return jsonResponse({ note: "Event updated; returning re-fetched event state.", event });
51343
+ try {
51344
+ await requestWrite(client2, "PUT", `/pub/v3/events/${id}`, payload);
51345
+ } catch (e) {
51346
+ if (!(e instanceof UnconfirmedWriteError)) throw e;
51347
+ return unconfirmedWriteResponse(e, {
51348
+ result: "EVENT_UNCONFIRMED",
51349
+ what: `update event ${eventId}`,
51350
+ checkWith: "ofw_list_events for this event and date range"
51351
+ });
51352
+ }
51353
+ try {
51354
+ const rawAfter = await client2.request("GET", `/pub/v3/events/${id}`);
51355
+ const event = parseLenient(eventDetailSchema, rawAfter, { label: "ofw-mcp", context: `GET /pub/v3/events/${eventId} (post-update)`, mode: "strict" });
51356
+ return jsonResponse({ note: "Event updated; returning re-fetched event state.", event });
51357
+ } catch (e) {
51358
+ return unconfirmedWriteResponse(new UnconfirmedWriteError(null, e), {
51359
+ result: "EVENT_UNCONFIRMED",
51360
+ what: `verify the update to event ${eventId}`,
51361
+ checkWith: "ofw_list_events for this event and date range"
51362
+ });
51363
+ }
50998
51364
  });
50999
51365
  if (allowWrites) server.registerTool("ofw_delete_event", {
51000
- description: "Delete an OurFamilyWizard calendar event. Reads the event first; deleting one the co-parent can see is confirmed first, with a preview of exactly which event (title, date, time) is removed, and is refused if the event changed on OFW after that preview. " + CONFIRM_NOTE,
51366
+ description: "Delete an OurFamilyWizard calendar event. Reads the event first; deleting one the co-parent can see is confirmed first, with a preview of exactly which event (title, date, time) is removed, and is refused if the event changed on OFW after that preview. If the request fails without a definitive answer the result is EVENT_UNCONFIRMED: the event may already be gone, so do NOT retry until ofw_list_events shows whether it is still there. " + CONFIRM_NOTE,
51001
51367
  annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
51002
51368
  inputSchema: external_exports.object({
51003
51369
  eventId: external_exports.string().describe("Event id \u2014 the `id` from ofw_list_events / eventRecurrenceId from ofw_create_event"),
@@ -51030,7 +51396,16 @@ function registerCalendarTools(server, client2) {
51030
51396
  });
51031
51397
  if (gate) return gate;
51032
51398
  }
51033
- await client2.request("DELETE", `/pub/v3/events/${id}?includeFuture=${includeFuture}`);
51399
+ try {
51400
+ await requestWrite(client2, "DELETE", `/pub/v3/events/${id}?includeFuture=${includeFuture}`);
51401
+ } catch (e) {
51402
+ if (!(e instanceof UnconfirmedWriteError)) throw e;
51403
+ return unconfirmedWriteResponse(e, {
51404
+ result: "EVENT_UNCONFIRMED",
51405
+ what: `delete event ${args.eventId}${includeFuture ? " and its future occurrences" : ""}`,
51406
+ checkWith: "ofw_list_events for this event and date range (including future occurrences if requested)"
51407
+ });
51408
+ }
51034
51409
  return textResponse(`Event ${args.eventId} ("${current.title}") deleted`);
51035
51410
  });
51036
51411
  }
@@ -52206,7 +52581,7 @@ var tools = selectToolRegistrars({
52206
52581
  });
52207
52582
  await runMcp({
52208
52583
  name: "ofw",
52209
- version: "2.20.1",
52584
+ version: "2.20.3",
52210
52585
  // x-release-please-version
52211
52586
  deps: client,
52212
52587
  tools,
@@ -52214,7 +52589,7 @@ await runMcp({
52214
52589
  });
52215
52590
  /*! Bundled license information:
52216
52591
 
52217
- @modelcontextprotocol/server/dist/src-BHSMhZ_W.mjs:
52592
+ @modelcontextprotocol/server/dist/src-Cqbh3MYc.mjs:
52218
52593
  (*!
52219
52594
  * content-type
52220
52595
  * Copyright(c) 2015 Douglas Christopher Wilson