ofw-mcp 2.20.1 → 2.20.2

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
  };
@@ -38999,7 +39056,10 @@ function surfaceToolHints(server) {
38999
39056
  ));
39000
39057
  }
39001
39058
  async function createMcpServer(opts) {
39002
- const server = new McpServer({ name: opts.name, version: opts.version }, { supportedProtocolVersions: [...SERVER_PROTOCOL_VERSIONS] });
39059
+ const server = new McpServer({ name: opts.name, version: opts.version }, {
39060
+ supportedProtocolVersions: [...SERVER_PROTOCOL_VERSIONS],
39061
+ ...opts.maxToolInputElements !== void 0 ? { maxToolInputElements: opts.maxToolInputElements } : {}
39062
+ });
39003
39063
  if (opts.surfaceHints !== false)
39004
39064
  surfaceToolHints(server);
39005
39065
  if (opts.banner !== void 0) {
@@ -40327,6 +40387,49 @@ var KNOWN_CAPABILITIES = /* @__PURE__ */ new Set([
40327
40387
  "graphql",
40328
40388
  "write_cookies"
40329
40389
  ]);
40390
+ var ACCOUNT_KEY_FRAME = "account-key";
40391
+ var ACCOUNT_ATTEST_FRAME = "account-attest";
40392
+
40393
+ // node_modules/@fetchproxy/protocol/dist/mcp-id.js
40394
+ var ID_RE = /^([^:]+):([^:]+):([0-9a-f]{16})$/;
40395
+ function generateMcpId(serverName, version2) {
40396
+ if (serverName.includes(":")) {
40397
+ throw new Error(`serverName cannot contain ':': ${serverName}`);
40398
+ }
40399
+ if (version2.includes(":")) {
40400
+ throw new Error(`version cannot contain ':': ${version2}`);
40401
+ }
40402
+ const rand = randomHex(8);
40403
+ return `${serverName}:${version2}:${rand}`;
40404
+ }
40405
+ function isValidMcpId(id) {
40406
+ return typeof id === "string" && ID_RE.test(id);
40407
+ }
40408
+ function randomHex(bytes) {
40409
+ const arr = new Uint8Array(bytes);
40410
+ globalThis.crypto.getRandomValues(arr);
40411
+ return Array.from(arr, (b) => b.toString(16).padStart(2, "0")).join("");
40412
+ }
40413
+
40414
+ // node_modules/@fetchproxy/protocol/dist/account-attest.js
40415
+ var ACCOUNT_ATTEST_CONSENTS = Object.freeze([
40416
+ "silent",
40417
+ "confirm",
40418
+ "confirm-each"
40419
+ ]);
40420
+ var NO_SCOPE_DIGEST = "0".repeat(64);
40421
+ var ACCOUNT_ID_RE = /^[A-Za-z0-9_.\-]{1,128}$/;
40422
+ var HEX64_RE = /^[0-9a-f]{64}$/;
40423
+ var ACCOUNT_KID_RE = /^[0-9a-f]{16}$/;
40424
+ var enc2 = new TextEncoder();
40425
+
40426
+ // node_modules/@fetchproxy/protocol/dist/awaiting-approval.js
40427
+ var AWAITING_APPROVAL_REASON_PREFIX = "awaiting-approval:";
40428
+ function parseAwaitingApprovalReason(reason) {
40429
+ if (!reason.startsWith(AWAITING_APPROVAL_REASON_PREFIX))
40430
+ return null;
40431
+ return reason.slice(AWAITING_APPROVAL_REASON_PREFIX.length).trim();
40432
+ }
40330
40433
 
40331
40434
  // node_modules/@fetchproxy/protocol/dist/capability-availability.js
40332
40435
  var CAPABILITY_UNAVAILABLE_CODE = "capability_unavailable";
@@ -40352,27 +40455,6 @@ function parseUnsupportedCapabilityReason(reason) {
40352
40455
  return rest.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
40353
40456
  }
40354
40457
 
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
40458
  // node_modules/@fetchproxy/protocol/dist/json-pointer.js
40377
40459
  function matchesDeclaredKey(requested, declared) {
40378
40460
  for (const decl of declared) {
@@ -40968,6 +41050,10 @@ function validateFrame(raw) {
40968
41050
  return { type: "extension-disconnected" };
40969
41051
  if (t === "peer-gone")
40970
41052
  return validatePeerGone(raw);
41053
+ if (t === ACCOUNT_KEY_FRAME)
41054
+ return validateAccountKey(raw);
41055
+ if (t === ACCOUNT_ATTEST_FRAME)
41056
+ return validateAccountAttest(raw);
40971
41057
  throw new ProtocolError3(`unknown frame type: ${String(t)}`);
40972
41058
  }
40973
41059
  function validateHello(raw) {
@@ -41171,6 +41257,128 @@ function validateHelloRejected(raw) {
41171
41257
  }
41172
41258
  return { type: "hello-rejected", mcpId: raw.mcpId, reason: raw.reason };
41173
41259
  }
41260
+ var DISPLAY_FORBIDDEN_RE = /[\u0000-\u001f\u007f-\u009f\u200e\u200f\u202a-\u202e\u2066-\u2069]/;
41261
+ function assertExactFields(raw, allowed, label) {
41262
+ for (const k of Object.keys(raw)) {
41263
+ if (!allowed.includes(k)) {
41264
+ throw new ProtocolError3(`${label}: unexpected field ${JSON.stringify(k)}`);
41265
+ }
41266
+ }
41267
+ }
41268
+ function assertMatch(x, re, label, what) {
41269
+ assertString(x, label);
41270
+ if (!re.test(x))
41271
+ throw new ProtocolError3(`${label}: must be ${what}`);
41272
+ }
41273
+ function assertPositiveSafeInt(x, label) {
41274
+ if (typeof x !== "number" || !Number.isSafeInteger(x) || x <= 0) {
41275
+ throw new ProtocolError3(`${label}: expected positive safe integer`);
41276
+ }
41277
+ }
41278
+ function assertDisplayString(x, label, min, max) {
41279
+ assertString(x, label);
41280
+ if (x.length < min || x.length > max) {
41281
+ throw new ProtocolError3(`${label}: must be ${min}-${max} characters`);
41282
+ }
41283
+ if (DISPLAY_FORBIDDEN_RE.test(x)) {
41284
+ throw new ProtocolError3(`${label}: control or bidi-override character`);
41285
+ }
41286
+ }
41287
+ var ACCOUNT_KEY_FIELDS = [
41288
+ "type",
41289
+ "accountId",
41290
+ "slug",
41291
+ "displayName",
41292
+ "confirmedBy",
41293
+ "tokenId",
41294
+ "kid",
41295
+ "publicKey",
41296
+ "generation",
41297
+ "bridgedRegistrations"
41298
+ ];
41299
+ function validateAccountKey(raw) {
41300
+ const L = "account-key";
41301
+ assertMatch(raw.accountId, ACCOUNT_ID_RE, `${L}.accountId`, "an account id");
41302
+ assertMatch(raw.slug, ACCOUNT_ID_RE, `${L}.slug`, "a slug");
41303
+ assertDisplayString(raw.displayName, `${L}.displayName`, 0, 128);
41304
+ assertDisplayString(raw.confirmedBy, `${L}.confirmedBy`, 1, 256);
41305
+ assertMatch(raw.tokenId, ACCOUNT_ID_RE, `${L}.tokenId`, "a token id");
41306
+ assertMatch(raw.kid, ACCOUNT_KID_RE, `${L}.kid`, "16 lowercase hex");
41307
+ assertBase64Bytes(raw.publicKey, `${L}.publicKey`, 32);
41308
+ assertPositiveSafeInt(raw.generation, `${L}.generation`);
41309
+ const n = raw.bridgedRegistrations;
41310
+ if (typeof n !== "number" || !Number.isSafeInteger(n) || n < 0) {
41311
+ throw new ProtocolError3(`${L}.bridgedRegistrations: expected non-negative safe integer`);
41312
+ }
41313
+ assertExactFields(raw, ACCOUNT_KEY_FIELDS, L);
41314
+ return {
41315
+ type: ACCOUNT_KEY_FRAME,
41316
+ accountId: raw.accountId,
41317
+ slug: raw.slug,
41318
+ displayName: raw.displayName,
41319
+ confirmedBy: raw.confirmedBy,
41320
+ tokenId: raw.tokenId,
41321
+ kid: raw.kid,
41322
+ publicKey: raw.publicKey,
41323
+ generation: raw.generation,
41324
+ bridgedRegistrations: n
41325
+ };
41326
+ }
41327
+ var ACCOUNT_ATTEST_FIELDS = [
41328
+ "type",
41329
+ "mcpId",
41330
+ "accountId",
41331
+ "generation",
41332
+ "tokenId",
41333
+ "kid",
41334
+ "registrationId",
41335
+ "slug",
41336
+ "identityHash",
41337
+ "identityEd25519Pub",
41338
+ "scopeDigest",
41339
+ "consent",
41340
+ "notAfter",
41341
+ "sig"
41342
+ ];
41343
+ function validateAccountAttest(raw) {
41344
+ const L = "account-attest";
41345
+ if (!isValidMcpId(raw.mcpId))
41346
+ throw new ProtocolError3(`${L}.mcpId: invalid format`);
41347
+ assertMatch(raw.accountId, ACCOUNT_ID_RE, `${L}.accountId`, "an account id");
41348
+ assertPositiveSafeInt(raw.generation, `${L}.generation`);
41349
+ assertMatch(raw.tokenId, ACCOUNT_ID_RE, `${L}.tokenId`, "a token id");
41350
+ assertMatch(raw.kid, ACCOUNT_KID_RE, `${L}.kid`, "16 lowercase hex");
41351
+ assertMatch(raw.registrationId, ACCOUNT_ID_RE, `${L}.registrationId`, "a registration id");
41352
+ assertMatch(raw.slug, ACCOUNT_ID_RE, `${L}.slug`, "a slug");
41353
+ assertMatch(raw.identityHash, HEX64_RE, `${L}.identityHash`, "64 lowercase hex");
41354
+ assertBase64Bytes(raw.identityEd25519Pub, `${L}.identityEd25519Pub`, 32);
41355
+ assertMatch(raw.scopeDigest, HEX64_RE, `${L}.scopeDigest`, "64 lowercase hex");
41356
+ assertString(raw.consent, `${L}.consent`);
41357
+ const consent = raw.consent;
41358
+ const known = ACCOUNT_ATTEST_CONSENTS.find((c) => c === consent);
41359
+ if (known === void 0) {
41360
+ throw new ProtocolError3(`${L}.consent: must be one of ${ACCOUNT_ATTEST_CONSENTS.join(", ")}`);
41361
+ }
41362
+ assertPositiveSafeInt(raw.notAfter, `${L}.notAfter`);
41363
+ assertBase64Bytes(raw.sig, `${L}.sig`, 64);
41364
+ assertExactFields(raw, ACCOUNT_ATTEST_FIELDS, L);
41365
+ return {
41366
+ type: ACCOUNT_ATTEST_FRAME,
41367
+ mcpId: raw.mcpId,
41368
+ accountId: raw.accountId,
41369
+ generation: raw.generation,
41370
+ tokenId: raw.tokenId,
41371
+ kid: raw.kid,
41372
+ registrationId: raw.registrationId,
41373
+ slug: raw.slug,
41374
+ identityHash: raw.identityHash,
41375
+ identityEd25519Pub: raw.identityEd25519Pub,
41376
+ scopeDigest: raw.scopeDigest,
41377
+ consent: known,
41378
+ notAfter: raw.notAfter,
41379
+ sig: raw.sig
41380
+ };
41381
+ }
41174
41382
  function validateInnerFrame(raw) {
41175
41383
  assertObject(raw, "inner");
41176
41384
  const t = raw.type;
@@ -41638,10 +41846,10 @@ function validateInnerResponse(raw) {
41638
41846
  }
41639
41847
 
41640
41848
  // node_modules/@fetchproxy/protocol/dist/pair-code.js
41641
- var enc2 = new TextEncoder();
41849
+ var enc3 = new TextEncoder();
41642
41850
  var PAIR_LABEL = "fetchproxy/4/pair";
41643
41851
  async function pairTranscript(mcpIdentityPub, extIdentityPub, mcpNonce, extNonce, mcpSessionPub) {
41644
- const label = enc2.encode(PAIR_LABEL);
41852
+ const label = enc3.encode(PAIR_LABEL);
41645
41853
  const parts = [mcpIdentityPub, extIdentityPub, mcpNonce, extNonce, mcpSessionPub];
41646
41854
  const out = new Uint8Array(label.length + 1 + parts.reduce((n, p) => n + p.length, 0));
41647
41855
  out.set(label, 0);
@@ -41662,7 +41870,7 @@ function digits(hash2) {
41662
41870
  }
41663
41871
 
41664
41872
  // node_modules/@fetchproxy/protocol/dist/seal.js
41665
- var enc3 = new TextEncoder();
41873
+ var enc4 = new TextEncoder();
41666
41874
  var dec = new TextDecoder();
41667
41875
  function randomIv() {
41668
41876
  const iv = new Uint8Array(12);
@@ -41675,14 +41883,14 @@ function base64Length(byteLength) {
41675
41883
  return Math.ceil(byteLength / 3) * 4;
41676
41884
  }
41677
41885
  function encodeInnerFrame(inner) {
41678
- return enc3.encode(JSON.stringify(inner));
41886
+ return enc4.encode(JSON.stringify(inner));
41679
41887
  }
41680
41888
  function plaintextOf(inner) {
41681
41889
  return inner instanceof Uint8Array ? inner : encodeInnerFrame(inner);
41682
41890
  }
41683
41891
  function sealedFrameWireBytes(mcpId, seq, inner) {
41684
41892
  const plaintext = plaintextOf(inner).length;
41685
- const envelope = enc3.encode(JSON.stringify({ type: "frame", mcpId, seq, iv: "", ciphertext: "" })).length;
41893
+ const envelope = enc4.encode(JSON.stringify({ type: "frame", mcpId, seq, iv: "", ciphertext: "" })).length;
41686
41894
  return envelope + IV_B64_BYTES + base64Length(plaintext + AES_GCM_TAG_BYTES);
41687
41895
  }
41688
41896
  var MAX_FRAME_BYTES = 42 * 1024 * 1024;
@@ -42022,14 +42230,23 @@ var FetchproxyHelloRejectedError = class extends Error {
42022
42230
  platform;
42023
42231
  /**
42024
42232
  * #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.
42233
+ * explain — `unsupported-capability`, which blames the browser rather than
42234
+ * the MCP, and (D12) `awaiting-approval`, which says to approve in the
42235
+ * browser. `null` otherwise.
42027
42236
  */
42028
42237
  hint;
42238
+ /**
42239
+ * D12: `true` only for an `awaiting-approval:` refusal — the extension has
42240
+ * queued an approval card and nobody is at the browser yet, so the same call
42241
+ * succeeds once the person approves. Every other refusal is the extension's
42242
+ * final answer, and retrying unchanged is refused the same way.
42243
+ */
42244
+ retryable;
42029
42245
  constructor(info) {
42030
42246
  const unavailable = parseUnsupportedCapabilityReason(info.reason);
42247
+ const awaiting = parseAwaitingApprovalReason(info.reason);
42031
42248
  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;
42249
+ 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
42250
  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
42251
  this.name = "FetchproxyHelloRejectedError";
42035
42252
  this.mcpId = info.mcpId;
@@ -42037,6 +42254,7 @@ var FetchproxyHelloRejectedError = class extends Error {
42037
42254
  this.unavailableCapabilities = unavailable ?? [];
42038
42255
  this.platform = platform;
42039
42256
  this.hint = hint;
42257
+ this.retryable = awaiting !== null;
42040
42258
  Object.setPrototypeOf(this, new.target.prototype);
42041
42259
  }
42042
42260
  };
@@ -42589,7 +42807,7 @@ function originVerdict(origin, allowLocal) {
42589
42807
  }
42590
42808
  var HANDSHAKE_TIMEOUT_MS = 15e3;
42591
42809
  var MAX_PAYLOAD_BYTES = MAX_FRAME_BYTES;
42592
- var enc4 = new TextEncoder();
42810
+ var enc5 = new TextEncoder();
42593
42811
  var OWN_DECRYPT_FAILURES_BEFORE_REHANDSHAKE = 3;
42594
42812
  async function startHost(opts) {
42595
42813
  const allowLocalOrigins = envAllowsLocalOrigins();
@@ -42660,10 +42878,20 @@ async function startHost(opts) {
42660
42878
  let resolveOwnSession;
42661
42879
  let rejectOwnSession;
42662
42880
  let ownSessionReady;
42881
+ let ownRetryableRefusal = false;
42882
+ let ownSessionSettled = false;
42663
42883
  function resetSessionPromise() {
42884
+ ownRetryableRefusal = false;
42885
+ ownSessionSettled = false;
42664
42886
  ownSessionReady = new Promise((resolve7, reject) => {
42665
- resolveOwnSession = resolve7;
42666
- rejectOwnSession = reject;
42887
+ resolveOwnSession = (st) => {
42888
+ ownSessionSettled = true;
42889
+ resolve7(st);
42890
+ };
42891
+ rejectOwnSession = (e) => {
42892
+ ownSessionSettled = true;
42893
+ reject(e);
42894
+ };
42667
42895
  });
42668
42896
  ownSessionReady.catch(() => {
42669
42897
  });
@@ -42930,13 +43158,15 @@ async function startHost(opts) {
42930
43158
  const extPub = fromB64(frame.extensionSessionPub);
42931
43159
  const shared = await ecdhX25519(held.priv, extPub);
42932
43160
  const salt = await transcriptHash(held.nonce, extNonce, held.pub, extPub);
42933
- const key = await hkdfSha256(shared, salt, enc4.encode(HKDF_SESSION_INFO), 32);
43161
+ const key = await hkdfSha256(shared, salt, enc5.encode(HKDF_SESSION_INFO), 32);
42934
43162
  if (extensionWs !== ws)
42935
43163
  return;
42936
43164
  ownSession = new SessionState(key);
42937
43165
  ownSessionInfo = sessionInfoFromHello(verifiedHello);
42938
43166
  ownDecryptFailures = 0;
42939
43167
  ownPendingPairCode = null;
43168
+ if (ownRetryableRefusal)
43169
+ resetSessionPromise();
42940
43170
  resolveOwnSession(ownSession);
42941
43171
  } else {
42942
43172
  const slot = peers.get(frame.mcpId);
@@ -43005,11 +43235,17 @@ async function startHost(opts) {
43005
43235
  }
43006
43236
  if (frame.type === "hello-rejected" && identified === "extension") {
43007
43237
  if (frame.mcpId === opts.ownMcpId) {
43008
- rejectOwnSession(new FetchproxyHelloRejectedError({
43238
+ const refusal = new FetchproxyHelloRejectedError({
43009
43239
  mcpId: frame.mcpId,
43010
43240
  reason: frame.reason,
43011
43241
  platform: extensionHello?.platform ?? null
43012
- }));
43242
+ });
43243
+ if (ownRetryableRefusal)
43244
+ resetSessionPromise();
43245
+ const settlesPending = !ownSessionSettled;
43246
+ rejectOwnSession(refusal);
43247
+ if (refusal.retryable && settlesPending)
43248
+ ownRetryableRefusal = true;
43013
43249
  } else {
43014
43250
  const slot = peers.get(frame.mcpId);
43015
43251
  if (slot?.helloFrame.accepts?.includes("hello-rejected")) {
@@ -43125,7 +43361,7 @@ async function startHost(opts) {
43125
43361
 
43126
43362
  // node_modules/@fetchproxy/server/dist/peer.js
43127
43363
  var PEER_DIAL_TIMEOUT_MS = 5e3;
43128
- var enc5 = new TextEncoder();
43364
+ var enc6 = new TextEncoder();
43129
43365
  function notSentError() {
43130
43366
  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
43367
  }
@@ -43225,7 +43461,9 @@ async function startPeer(opts) {
43225
43461
  let rejectFirstReady;
43226
43462
  let sessionPromise;
43227
43463
  let sessionPromiseSettled = false;
43464
+ let retryableRefusal = false;
43228
43465
  function resetSessionPromise() {
43466
+ retryableRefusal = false;
43229
43467
  sessionPromiseSettled = false;
43230
43468
  sessionPromise = new Promise((resolve7, reject) => {
43231
43469
  resolveFirstReady = (st) => {
@@ -43322,6 +43560,8 @@ async function startPeer(opts) {
43322
43560
  raw = JSON.parse(data.toString());
43323
43561
  const frame = validateFrame(raw);
43324
43562
  if (frame.type === "hello" && frame.role === "extension") {
43563
+ if (retryableRefusal)
43564
+ resetSessionPromise();
43325
43565
  extensionHello = frame;
43326
43566
  const mintedKeypair = await generateSessionKeypair();
43327
43567
  const mintedHello = await buildServerHello({
@@ -43351,6 +43591,8 @@ async function startPeer(opts) {
43351
43591
  session = null;
43352
43592
  if (sessionPromiseSettled)
43353
43593
  resetSessionPromise();
43594
+ } else if (retryableRefusal) {
43595
+ resetSessionPromise();
43354
43596
  }
43355
43597
  extensionDisconnectListeners.forEach((cb) => cb());
43356
43598
  return;
@@ -43372,7 +43614,7 @@ async function startPeer(opts) {
43372
43614
  const extNonce = fromB64(authenticated.sessionNonce);
43373
43615
  const shared = await ecdhX25519(held.priv, extPub);
43374
43616
  const salt = await transcriptHash(held.nonce, extNonce, held.pub, extPub);
43375
- const sessionKey = await hkdfSha256(shared, salt, enc5.encode(HKDF_SESSION_INFO), 32);
43617
+ const sessionKey = await hkdfSha256(shared, salt, enc6.encode(HKDF_SESSION_INFO), 32);
43376
43618
  if (sessionEphemeral !== held) {
43377
43619
  console.warn(`[fetchproxy] ${opts.serverName}: dropping a session derived against an ephemeral that was superseded while it was being derived`);
43378
43620
  return;
@@ -43385,16 +43627,24 @@ async function startPeer(opts) {
43385
43627
  if (isRenegotiation) {
43386
43628
  renegotiateListeners.forEach((cb) => cb());
43387
43629
  } else {
43630
+ if (retryableRefusal)
43631
+ resetSessionPromise();
43388
43632
  resolveFirstReady(session);
43389
43633
  }
43390
43634
  return;
43391
43635
  }
43392
43636
  if (frame.type === "hello-rejected" && frame.mcpId === opts.mcpId) {
43393
- rejectFirstReady(new FetchproxyHelloRejectedError({
43637
+ const refusal = new FetchproxyHelloRejectedError({
43394
43638
  mcpId: frame.mcpId,
43395
43639
  reason: frame.reason,
43396
43640
  platform: extensionHello?.platform ?? null
43397
- }));
43641
+ });
43642
+ if (retryableRefusal)
43643
+ resetSessionPromise();
43644
+ const settlesPending = !sessionPromiseSettled;
43645
+ rejectFirstReady(refusal);
43646
+ if (refusal.retryable && settlesPending)
43647
+ retryableRefusal = true;
43398
43648
  }
43399
43649
  if (frame.type === "pair-pending" && frame.mcpId === opts.mcpId) {
43400
43650
  const hello2 = extensionHello;
@@ -43558,8 +43808,9 @@ function classifyFetchError(error62, code2) {
43558
43808
 
43559
43809
  // node_modules/@fetchproxy/server/dist/classify-bridge-error.js
43560
43810
  function classifyBridgeError(err) {
43561
- if (err instanceof FetchproxyHelloRejectedError)
43562
- return "hello_rejected";
43811
+ if (err instanceof FetchproxyHelloRejectedError) {
43812
+ return err.retryable ? "awaiting_approval" : "hello_rejected";
43813
+ }
43563
43814
  if (err instanceof FetchproxySessionNotReadyError)
43564
43815
  return "session_not_ready";
43565
43816
  if (err instanceof FetchproxyTimeoutError)
@@ -46330,7 +46581,7 @@ async function loginWithPassword(username, password) {
46330
46581
  // package.json
46331
46582
  var package_default = {
46332
46583
  name: "ofw-mcp",
46333
- version: "2.20.1",
46584
+ version: "2.20.2",
46334
46585
  license: "MIT",
46335
46586
  mcpName: "io.github.chrischall/ofw-mcp",
46336
46587
  description: "OurFamilyWizard MCP server for Claude \u2014 developed and maintained by AI (Claude Code)",
@@ -46364,14 +46615,14 @@ var package_default = {
46364
46615
  typecheck: "tsc -p tsconfig.json --noEmit"
46365
46616
  },
46366
46617
  dependencies: {
46367
- "@chrischall/mcp-utils": "^2.13.0",
46618
+ "@chrischall/mcp-utils": "^2.14.0",
46368
46619
  "@fetchproxy/bootstrap": "^3.4.1",
46369
- "@modelcontextprotocol/server": "^2.2.0",
46620
+ "@modelcontextprotocol/server": "^2.3.0",
46370
46621
  dotenv: "^18.0.0",
46371
46622
  zod: "^4.6.5"
46372
46623
  },
46373
46624
  devDependencies: {
46374
- "@modelcontextprotocol/client": "^2.2.0",
46625
+ "@modelcontextprotocol/client": "^2.3.0",
46375
46626
  "@types/node": "^26.0.0",
46376
46627
  "@vitest/coverage-v8": "^5.0.0",
46377
46628
  esbuild: "^0.28.0",
@@ -46519,10 +46770,10 @@ var OFW_REFRESH_SENTINEL = "ofw";
46519
46770
  async function throwIfEdgeBlocked(response, method, path) {
46520
46771
  if (response.ok) return;
46521
46772
  let edge = detectEdgeBlock({ headers: response.headers, status: response.status });
46522
- if (edge === null && response.status === 429 && !/json/i.test(response.headers.get("content-type") ?? "")) {
46773
+ if (edge === null && (response.status === 401 || response.status === 429) && !/json/i.test(response.headers.get("content-type") ?? "")) {
46523
46774
  let body = "";
46524
46775
  try {
46525
- body = await response.clone().text();
46776
+ body = await (response.status === 401 ? response.clone() : response).text();
46526
46777
  } catch {
46527
46778
  }
46528
46779
  edge = detectEdgeBlock({ body, headers: response.headers, status: response.status });
@@ -46585,63 +46836,73 @@ var OFWClient = class {
46585
46836
  return this.tokenManager;
46586
46837
  }
46587
46838
  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;
46839
+ return this.fetchAuthed(method, path, body, "application/json", async (response) => {
46840
+ const text = await response.text();
46841
+ if (debugLogEnabled()) {
46842
+ console.error(`[ofw-debug] response body: ${text || "<empty>"}`);
46843
+ }
46844
+ return text ? JSON.parse(text) : null;
46845
+ });
46594
46846
  }
46595
46847
  /** Like `request`, but returns the raw bytes plus Content-Type/-Disposition metadata. */
46596
46848
  async requestBinary(method, path) {
46597
- const response = await this.fetchAuthed(method, path, void 0, "application/octet-stream");
46598
- return {
46849
+ return this.fetchAuthed(method, path, void 0, "application/octet-stream", async (response) => ({
46599
46850
  body: Buffer.from(await response.arrayBuffer()),
46600
46851
  contentType: response.headers.get("content-type"),
46601
46852
  suggestedFileName: parseContentDispositionFilename(response.headers.get("content-disposition") ?? "") ?? null
46602
- };
46853
+ }));
46603
46854
  }
46604
46855
  // Authenticated fetch for both JSON and binary callers. Auth (proactive
46605
46856
  // refresh inside the skew window + one 401-replay, guarded against a
46606
46857
  // double-refresh under concurrency) is delegated to the shared TokenManager's
46607
46858
  // `withAuth`. The 429 wait-and-replay and the non-2xx → throw remain here.
46608
- async fetchAuthed(method, path, body, accept) {
46859
+ async fetchAuthed(method, path, body, accept, consume) {
46609
46860
  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);
46861
+ let result;
46862
+ const call = async (retry) => {
46863
+ let authAttempt = 0;
46864
+ return this.getTokenManager().withAuth((token) => {
46865
+ const firstAuthAttempt = authAttempt++ === 0;
46866
+ return this.fetchOnce(method, path, body, accept, token, async (response2) => {
46867
+ await throwIfEdgeBlocked(response2, method, path);
46868
+ if (response2.status === 401 && firstAuthAttempt) {
46869
+ await releaseBody(response2);
46870
+ return;
46871
+ }
46872
+ if (response2.status === 429) {
46873
+ await releaseBody(response2);
46874
+ return;
46875
+ }
46876
+ if (!response2.ok) {
46877
+ const errorBody = await response2.text();
46878
+ const edge = detectEdgeBlock({ body: errorBody, headers: response2.headers, status: response2.status });
46879
+ if (edge !== null) {
46880
+ throw new EdgeBlockedError(response2.status, edge.vendor, { service: "OurFamilyWizard", method, path });
46881
+ }
46882
+ const safeBody = redactSecrets(errorBody).replace(/\s+/g, " ").trim().slice(0, 4e3);
46883
+ throw new Error(
46884
+ `OFW API error: ${response2.status} ${response2.statusText} for ${method} ${path}` + (safeBody ? ` \u2014 ${safeBody}` : "")
46885
+ );
46886
+ }
46887
+ result = await consume(response2);
46888
+ }, retry || attempt++ > 0);
46889
+ });
46890
+ };
46891
+ const response = await call(false);
46614
46892
  if (response.status === 429) {
46615
- await releaseBody(response);
46616
46893
  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);
46894
+ if ((await call(true)).status === 429) {
46623
46895
  throw new Error("Rate limited by OFW API");
46624
46896
  }
46625
46897
  }
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;
46898
+ return result;
46638
46899
  }
46639
46900
  // A single OFW API fetch with the bearer token supplied by `withAuth`.
46640
46901
  // Carries the per-request timeout (AbortController + setTimeout so vitest
46641
46902
  // 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) {
46903
+ // OFW_DEBUG_LOG instrumentation. The handler completes body consumption or
46904
+ // release before this attempt returns its Response to `withAuth`.
46905
+ async fetchOnce(method, path, body, accept, token, handleResponse, isRetry = false) {
46645
46906
  const isFormData = body instanceof FormData;
46646
46907
  const headers = {
46647
46908
  ...OFW_PROTOCOL_HEADERS,
@@ -46661,22 +46922,31 @@ var OFWClient = class {
46661
46922
  const ac = new AbortController();
46662
46923
  const timer = setTimeout(() => ac.abort(), timeoutMs);
46663
46924
  const startedAt = Date.now();
46664
- let response;
46925
+ const signal = withAmbientCancellation(ac.signal);
46926
+ let onAbort;
46927
+ const aborted2 = new Promise((_resolve, reject) => {
46928
+ onAbort = () => reject(signal.reason);
46929
+ if (signal.aborted) onAbort();
46930
+ else signal.addEventListener("abort", onAbort, { once: true });
46931
+ });
46665
46932
  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
- });
46933
+ return await Promise.race([aborted2, (async () => {
46934
+ signal.throwIfAborted();
46935
+ const response = await fetch(url2, {
46936
+ method,
46937
+ headers,
46938
+ // The caller's cancellation and our timeout both cover headers,
46939
+ // body reads and releases; only our controller diagnoses slowness.
46940
+ signal,
46941
+ ...body !== void 0 ? { body: isFormData ? body : JSON.stringify(body) } : {}
46942
+ });
46943
+ if (debugLogEnabled()) {
46944
+ console.error(`[ofw-debug] \u2190 ${response.status} ${response.statusText} (${Date.now() - startedAt}ms)`);
46945
+ }
46946
+ await handleResponse(response);
46947
+ signal.throwIfAborted();
46948
+ return response;
46949
+ })()]);
46680
46950
  } catch (err) {
46681
46951
  const elapsed = Date.now() - startedAt;
46682
46952
  if (ac.signal.aborted) {
@@ -46693,11 +46963,8 @@ var OFWClient = class {
46693
46963
  throw err;
46694
46964
  } finally {
46695
46965
  clearTimeout(timer);
46966
+ signal.removeEventListener("abort", onAbort);
46696
46967
  }
46697
- if (debugLogEnabled()) {
46698
- console.error(`[ofw-debug] \u2190 ${response.status} ${response.statusText} (${Date.now() - startedAt}ms)`);
46699
- }
46700
- return response;
46701
46968
  }
46702
46969
  };
46703
46970
  var client = new OFWClient();
@@ -50942,7 +51209,7 @@ function registerCalendarTools(server, client2) {
50942
51209
  });
50943
51210
  });
50944
51211
  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,
51212
+ 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
51213
  annotations: { destructiveHint: true },
50947
51214
  inputSchema: external_exports.object({
50948
51215
  eventId: external_exports.string().describe("Event id \u2014 the `id` from ofw_list_events / eventRecurrenceId from ofw_create_event"),
@@ -50991,13 +51258,30 @@ function registerCalendarTools(server, client2) {
50991
51258
  });
50992
51259
  if (gate) return gate;
50993
51260
  }
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 });
51261
+ try {
51262
+ await requestWrite(client2, "PUT", `/pub/v3/events/${id}`, payload);
51263
+ } catch (e) {
51264
+ if (!(e instanceof UnconfirmedWriteError)) throw e;
51265
+ return unconfirmedWriteResponse(e, {
51266
+ result: "EVENT_UNCONFIRMED",
51267
+ what: `update event ${eventId}`,
51268
+ checkWith: "ofw_list_events for this event and date range"
51269
+ });
51270
+ }
51271
+ try {
51272
+ const rawAfter = await client2.request("GET", `/pub/v3/events/${id}`);
51273
+ const event = parseLenient(eventDetailSchema, rawAfter, { label: "ofw-mcp", context: `GET /pub/v3/events/${eventId} (post-update)`, mode: "strict" });
51274
+ return jsonResponse({ note: "Event updated; returning re-fetched event state.", event });
51275
+ } catch (e) {
51276
+ return unconfirmedWriteResponse(new UnconfirmedWriteError(null, e), {
51277
+ result: "EVENT_UNCONFIRMED",
51278
+ what: `verify the update to event ${eventId}`,
51279
+ checkWith: "ofw_list_events for this event and date range"
51280
+ });
51281
+ }
50998
51282
  });
50999
51283
  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,
51284
+ 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
51285
  annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
51002
51286
  inputSchema: external_exports.object({
51003
51287
  eventId: external_exports.string().describe("Event id \u2014 the `id` from ofw_list_events / eventRecurrenceId from ofw_create_event"),
@@ -51030,7 +51314,16 @@ function registerCalendarTools(server, client2) {
51030
51314
  });
51031
51315
  if (gate) return gate;
51032
51316
  }
51033
- await client2.request("DELETE", `/pub/v3/events/${id}?includeFuture=${includeFuture}`);
51317
+ try {
51318
+ await requestWrite(client2, "DELETE", `/pub/v3/events/${id}?includeFuture=${includeFuture}`);
51319
+ } catch (e) {
51320
+ if (!(e instanceof UnconfirmedWriteError)) throw e;
51321
+ return unconfirmedWriteResponse(e, {
51322
+ result: "EVENT_UNCONFIRMED",
51323
+ what: `delete event ${args.eventId}${includeFuture ? " and its future occurrences" : ""}`,
51324
+ checkWith: "ofw_list_events for this event and date range (including future occurrences if requested)"
51325
+ });
51326
+ }
51034
51327
  return textResponse(`Event ${args.eventId} ("${current.title}") deleted`);
51035
51328
  });
51036
51329
  }
@@ -52206,7 +52499,7 @@ var tools = selectToolRegistrars({
52206
52499
  });
52207
52500
  await runMcp({
52208
52501
  name: "ofw",
52209
- version: "2.20.1",
52502
+ version: "2.20.2",
52210
52503
  // x-release-please-version
52211
52504
  deps: client,
52212
52505
  tools,
@@ -52214,7 +52507,7 @@ await runMcp({
52214
52507
  });
52215
52508
  /*! Bundled license information:
52216
52509
 
52217
- @modelcontextprotocol/server/dist/src-BHSMhZ_W.mjs:
52510
+ @modelcontextprotocol/server/dist/src-Cqbh3MYc.mjs:
52218
52511
  (*!
52219
52512
  * content-type
52220
52513
  * Copyright(c) 2015 Douglas Christopher Wilson