@chrischall/myatriumhealth-mcp 1.3.3 → 1.3.5

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.
@@ -7,7 +7,7 @@
7
7
  },
8
8
  "metadata": {
9
9
  "description": "MCP server for MyAtriumHealth (Atrium Health's Epic MyChart patient portal) — test results, medications, allergies, immunizations, health issues, visits and messages. Requests relay through the user's own signed-in browser tab via the ContextMint Bridge extension, reusing their existing session.",
10
- "version": "1.3.3"
10
+ "version": "1.3.5"
11
11
  },
12
12
  "plugins": [
13
13
  {
@@ -15,7 +15,7 @@
15
15
  "displayName": "MyAtriumHealth",
16
16
  "source": "./",
17
17
  "description": "Read MyAtriumHealth (Epic MyChart) records — test results, medications, allergies, immunizations, health issues, visits — by relaying requests through the user's signed-in browser tab via the ContextMint Bridge extension.",
18
- "version": "1.3.3",
18
+ "version": "1.3.5",
19
19
  "author": {
20
20
  "name": "Chris Hall"
21
21
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "myatriumhealth-mcp",
3
3
  "displayName": "MyAtriumHealth",
4
- "version": "1.3.3",
4
+ "version": "1.3.5",
5
5
  "description": "MyAtriumHealth (Epic MyChart) access for Claude — test results, medications, allergies, immunizations, visits",
6
6
  "author": {
7
7
  "name": "Chris Hall",
package/README.md CHANGED
@@ -125,7 +125,8 @@ first call prints a pair code — approve it once in the ContextMint Bridge popu
125
125
  | `MAH_DEVICE_FILE` | `~/.myatriumhealth-mcp/device.json` | Session state (0600). Holds the **live cookie jar** as well as the device token — treat as a credential. |
126
126
  | `MAH_WS_PORT` | `37149` | fetchproxy concentrator port (bridge mode only). The whole fleet shares this one port; override only when hosting. |
127
127
  | `MAH_READ_ONLY` | `false` | `true` refuses `mah_reply_message` sends (previews still work). The tool stays listed either way. |
128
- | `MCP_CONFIRM_MODE` | `ask-user` | What a reply does on a client that cannot show a confirmation prompt (claude.ai, Claude Desktop). `ask-user`: two steps — the first call sends nothing and returns a preview plus a token, and the model must get your approval in chat before calling again with it. `auto`: the same two steps, but the model may use the token after reviewing the preview itself. `refuse`: replies are refused on such clients. A client that can show prompts (Claude Code) always gets the real prompt. An unrecognised value is treated as `refuse`. |
128
+ | `MCP_CONFIRM_MODE` | `ask-user` | What a reply does on a client that cannot show a confirmation prompt (claude.ai, Claude Desktop). `ask-user`: two steps — the first call sends nothing and returns a preview plus a token, and the model must get your approval in chat before calling again with it. `auto`: the same two steps, but the model may use the token after reviewing the preview itself. `refuse`: replies are refused on such clients. A client that can show prompts (Claude Code) always gets the real prompt, unless `MCP_CONFIRM_ELICITATION=off`. An unrecognised value is treated as `refuse`. |
129
+ | `MCP_CONFIRM_ELICITATION` | `on` | `off` never shows a confirmation prompt, so every client gets the `MCP_CONFIRM_MODE` path. Set it for a client that claims to support prompts but never shows one (the reply hangs — opencode 2.0.x). Any other value stays `on`, with a warning on stderr. |
129
130
  | `MCP_CONFIRM_TTL_SECONDS` | `600` | How long a token stays valid. |
130
131
  | `MCP_CONFIRM_SECRET` | random per process | Signing key; set it only if tokens must survive a server restart. |
131
132
 
@@ -213,7 +214,8 @@ local organization returns HTTP 500. The client assembles this from
213
214
  `body`, and optionally `attachments`. It replies **as whichever patient is active**.
214
215
 
215
216
  - **It asks before it sends.** A client that can show a confirmation prompt (Claude
216
- Code) gets one with the thread, recipients, body and attachments. Elsewhere the first
217
+ Code) gets one with the thread, recipients, body and attachments, unless
218
+ `MCP_CONFIRM_ELICITATION=off`. Elsewhere the first
217
219
  call sends nothing and returns that preview plus a `confirmToken`; only a repeat call
218
220
  with the same arguments and that token sends. `MCP_CONFIRM_MODE` (above) decides
219
221
  whether the model must get your approval in chat before using it (`ask-user`, the
package/dist/bundle.js CHANGED
@@ -20693,8 +20693,8 @@ var require_schemas2 = __commonJS({
20693
20693
  return core._stringFormat(exports.ZodCustomStringFormat, "currency_code", regexes.currencyCode, _params);
20694
20694
  }
20695
20695
  function hash2(alg, params) {
20696
- const enc6 = params?.enc ?? "hex";
20697
- const format = `${alg}_${enc6}`;
20696
+ const enc7 = params?.enc ?? "hex";
20697
+ const format = `${alg}_${enc7}`;
20698
20698
  const regex = core.regexes[format];
20699
20699
  if (!regex)
20700
20700
  throw new Error(`Unrecognized hash format: ${format}`);
@@ -25993,11 +25993,23 @@ var require_websocket = __commonJS({
25993
25993
  this._isServer = false;
25994
25994
  this._redirects = 0;
25995
25995
  if (protocols === void 0) {
25996
- protocols = [];
25996
+ if (!options || options.protocols === void 0) {
25997
+ protocols = [];
25998
+ } else if (Array.isArray(options.protocols)) {
25999
+ protocols = options.protocols;
26000
+ } else {
26001
+ protocols = [options.protocols];
26002
+ }
25997
26003
  } else if (!Array.isArray(protocols)) {
25998
26004
  if (typeof protocols === "object" && protocols !== null) {
25999
26005
  options = protocols;
26000
- protocols = [];
26006
+ if (options.protocols === void 0) {
26007
+ protocols = [];
26008
+ } else if (Array.isArray(options.protocols)) {
26009
+ protocols = options.protocols;
26010
+ } else {
26011
+ protocols = [options.protocols];
26012
+ }
26001
26013
  } else {
26002
26014
  protocols = [protocols];
26003
26015
  }
@@ -26194,7 +26206,6 @@ var require_websocket = __commonJS({
26194
26206
  }
26195
26207
  return;
26196
26208
  }
26197
- this._readyState = _WebSocket.CLOSING;
26198
26209
  this._sender.close(code2, data, !this._isServer, (err) => {
26199
26210
  if (err) return;
26200
26211
  this._closeFrameSent = true;
@@ -26202,6 +26213,7 @@ var require_websocket = __commonJS({
26202
26213
  this._socket.end();
26203
26214
  }
26204
26215
  });
26216
+ this._readyState = _WebSocket.CLOSING;
26205
26217
  setCloseTimer(this);
26206
26218
  }
26207
26219
  /**
@@ -26426,6 +26438,7 @@ var require_websocket = __commonJS({
26426
26438
  socketPath: void 0,
26427
26439
  hostname: void 0,
26428
26440
  protocol: void 0,
26441
+ protocols: void 0,
26429
26442
  timeout: void 0,
26430
26443
  method: "GET",
26431
26444
  host: void 0,
@@ -28408,7 +28421,7 @@ var OAuthTokenRevocationRequestSchema = z.object({
28408
28421
  token_type_hint: z.string().optional()
28409
28422
  }).strip();
28410
28423
 
28411
- // node_modules/@modelcontextprotocol/server/dist/src-BHSMhZ_W.mjs
28424
+ // node_modules/@modelcontextprotocol/server/dist/src-Cqbh3MYc.mjs
28412
28425
  var z2 = __toESM(require_v4(), 1);
28413
28426
  var BRANDS = /* @__PURE__ */ Symbol.for("mcp.sdk.errorBrands");
28414
28427
  function stampErrorBrands(instance, ctor) {
@@ -30652,7 +30665,8 @@ function build() {
30652
30665
  ListResourceTemplatesResultResponseSchema: wireResultResponse(ListResourceTemplatesResultSchema$1),
30653
30666
  ReadResourceResultResponseSchema: wireResultResponse(z2.union([ReadResourceResultSchema$1, InputRequiredResultSchema])),
30654
30667
  CompleteResultResponseSchema: wireResultResponse(CompleteResultSchema$1),
30655
- DiscoverResultResponseSchema: wireResultResponse(DiscoverResultSchema$1)
30668
+ DiscoverResultResponseSchema: wireResultResponse(DiscoverResultSchema$1),
30669
+ SubscriptionsListenResultResponseSchema: wireResultResponse(SubscriptionsListenResultSchema$1)
30656
30670
  };
30657
30671
  }
30658
30672
  var memo;
@@ -31160,6 +31174,9 @@ function isSpecRequestMethod(method) {
31160
31174
  function isSpecNotificationMethod(method) {
31161
31175
  return ALL_CODECS.some((codec2) => codec2.hasNotificationMethod(method));
31162
31176
  }
31177
+ function isExtensionReusedRequestMethod(method) {
31178
+ return method === "tasks/get" || method === "tasks/cancel";
31179
+ }
31163
31180
  var ALL_CODECS = [rev2025Codec, rev2026Codec];
31164
31181
  function isPlainObject$3(value) {
31165
31182
  return value !== null && typeof value === "object" && !Array.isArray(value);
@@ -32168,6 +32185,8 @@ var Protocol = class {
32168
32185
  _transport;
32169
32186
  _requestMessageId = 0;
32170
32187
  _requestHandlers = /* @__PURE__ */ new Map();
32188
+ /** Methods registered with an explicit schema; the era gate in `_onrequest` reads it for the Tasks extension names. */
32189
+ _customSchemaRequestMethods = /* @__PURE__ */ new Set();
32171
32190
  _requestHandlerAbortControllers = /* @__PURE__ */ new Map();
32172
32191
  _notificationHandlers = /* @__PURE__ */ new Map();
32173
32192
  _responseHandlers = /* @__PURE__ */ new Map();
@@ -32451,7 +32470,7 @@ var Protocol = class {
32451
32470
  return;
32452
32471
  }
32453
32472
  }
32454
- if (isSpecRequestMethod(request.method) && !codec2.hasRequestMethod(request.method)) {
32473
+ if (isSpecRequestMethod(request.method) && !codec2.hasRequestMethod(request.method) && !(isExtensionReusedRequestMethod(request.method) && this._customSchemaRequestMethods.has(request.method))) {
32455
32474
  sendErrorResponse(ProtocolErrorCode.MethodNotFound, "Method not found");
32456
32475
  return;
32457
32476
  }
@@ -32584,8 +32603,11 @@ var Protocol = class {
32584
32603
  }
32585
32604
  request(request, schemaOrOptions, maybeOptions) {
32586
32605
  const codec2 = this._resolveOutboundCodec(request.method);
32606
+ if (isStandardSchema(schemaOrOptions)) {
32607
+ if (!isExtensionReusedRequestMethod(request.method)) this._assertOutboundRequestInEra(codec2, request.method);
32608
+ return this._requestWithSchemaViaCodec(codec2, request, schemaOrOptions, maybeOptions);
32609
+ }
32587
32610
  this._assertOutboundRequestInEra(codec2, request.method);
32588
- if (isStandardSchema(schemaOrOptions)) return this._requestWithSchemaViaCodec(codec2, request, schemaOrOptions, maybeOptions);
32589
32611
  const validate2 = codecResultValidator(codec2, request.method);
32590
32612
  if (validate2 === void 0) throw new TypeError(`'${request.method}' is not a spec method; pass a result schema as the second argument to request().`);
32591
32613
  return this._requestWithSchemaViaCodec(codec2, request, validate2, schemaOrOptions);
@@ -32829,6 +32851,8 @@ var Protocol = class {
32829
32851
  return maybeHandler(parsed.data, ctx);
32830
32852
  };
32831
32853
  else throw new TypeError("setRequestHandler: handler is required");
32854
+ if (typeof schemasOrHandler === "function") this._customSchemaRequestMethods.delete(method);
32855
+ else this._customSchemaRequestMethods.add(method);
32832
32856
  this._requestHandlers.set(method, this._wrapHandler(method, stored));
32833
32857
  }
32834
32858
  /**
@@ -32858,6 +32882,7 @@ var Protocol = class {
32858
32882
  */
32859
32883
  removeRequestHandler(method) {
32860
32884
  this._requestHandlers.delete(method);
32885
+ this._customSchemaRequestMethods.delete(method);
32861
32886
  }
32862
32887
  /**
32863
32888
  * Asserts that a request handler has not already been set for the given method, in preparation for a new one being automatically installed.
@@ -40100,7 +40125,7 @@ var Ajv = import_ajv.Ajv;
40100
40125
  // node_modules/@modelcontextprotocol/server/dist/shimsNode.mjs
40101
40126
  import process2 from "node:process";
40102
40127
 
40103
- // node_modules/@modelcontextprotocol/server/dist/mcp-DYuW2ZSs.mjs
40128
+ // node_modules/@modelcontextprotocol/server/dist/mcp-DIH4cS6P.mjs
40104
40129
  var COMPLETABLE_SYMBOL = /* @__PURE__ */ Symbol.for("mcp.completable");
40105
40130
  function isCompletable(schema) {
40106
40131
  return !!schema && typeof schema === "object" && COMPLETABLE_SYMBOL in schema;
@@ -40165,7 +40190,7 @@ var StdioListenRouter = class {
40165
40190
  _serverCapabilities;
40166
40191
  /**
40167
40192
  * The serving instance's identity, stamped onto the graceful-close
40168
- * results' `_meta` (the spec's `SubscriptionsListenResultMeta` extends
40193
+ * results' `_meta` (the spec's `SubscriptionsListenResultMetaObject` extends
40169
40194
  * `ResultMetaObject`). Handed over together with the capabilities.
40170
40195
  */
40171
40196
  _serverInfo;
@@ -40497,6 +40522,11 @@ var Server = class extends Protocol {
40497
40522
  if (modernProtocolVersions(this._supportedProtocolVersions).length > 0) this.setRequestHandler("server/discover", () => this._ondiscover());
40498
40523
  if (this._capabilities.logging) this._registerLoggingHandler();
40499
40524
  }
40525
+ /** Attaches to the given transport; rejects while this instance is connected to another one. */
40526
+ async connect(transport2) {
40527
+ 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.");
40528
+ await super.connect(transport2);
40529
+ }
40500
40530
  /**
40501
40531
  * Registers the built-in `logging/setLevel` request handler.
40502
40532
  *
@@ -41050,21 +41080,41 @@ var Server = class extends Protocol {
41050
41080
  function discoverAdvertisedCapabilities(capabilities) {
41051
41081
  return { ...capabilities };
41052
41082
  }
41083
+ function toolInputElementCount(value, max) {
41084
+ let count = 0;
41085
+ const stack = [value];
41086
+ while (stack.length > 0) {
41087
+ const node2 = stack.pop();
41088
+ if (node2 === null || typeof node2 !== "object") continue;
41089
+ if (Array.isArray(node2)) for (const child of node2) {
41090
+ if (++count > max) return count;
41091
+ if (child !== null && typeof child === "object") stack.push(child);
41092
+ }
41093
+ else for (const key in node2) {
41094
+ if (!Object.prototype.hasOwnProperty.call(node2, key)) continue;
41095
+ if (++count > max) return count;
41096
+ const child = node2[key];
41097
+ if (child !== null && typeof child === "object") stack.push(child);
41098
+ }
41099
+ }
41100
+ return count;
41101
+ }
41102
+ function resolveMaxToolInputElements(value) {
41103
+ if (value === void 0 || value === Infinity) return void 0;
41104
+ 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)}`);
41105
+ return value;
41106
+ }
41053
41107
  var McpServer = class {
41054
41108
  /**
41055
41109
  * The underlying {@linkcode Server} instance, useful for advanced operations like sending notifications.
41056
41110
  */
41057
41111
  server;
41112
+ _maxToolInputElements;
41058
41113
  _registeredResources = {};
41059
41114
  _registeredResourceTemplates = {};
41060
41115
  _registeredTools = {};
41061
41116
  _registeredPrompts = {};
41062
- /**
41063
- * Per-tool JSON-converted `inputSchema`, memoized so the SEP-2243
41064
- * registration-time scan and the pre-dispatch validation step share one
41065
- * conversion instead of paying it twice per request under the
41066
- * per-request-factory `createMcpHandler` model.
41067
- */
41117
+ /** Per-tool JSON-converted `inputSchema`, filled on first use by `toolInputSchemaJson()`. */
41068
41118
  _toolInputSchemaJson = {};
41069
41119
  /**
41070
41120
  * The JSON-serialized `inputSchema` of a registered tool, or `undefined`
@@ -41089,6 +41139,7 @@ var McpServer = class {
41089
41139
  }
41090
41140
  constructor(serverInfo, options) {
41091
41141
  this.server = new Server(serverInfo, options);
41142
+ this._maxToolInputElements = resolveMaxToolInputElements(options?.maxToolInputElements);
41092
41143
  if (options?.capabilities?.tools) this.setToolRequestHandlers();
41093
41144
  if (options?.capabilities?.resources) this.setResourceRequestHandlers();
41094
41145
  if (options?.capabilities?.prompts) this.setPromptRequestHandlers();
@@ -41161,7 +41212,7 @@ var McpServer = class {
41161
41212
  name,
41162
41213
  title: tool.title,
41163
41214
  description: tool.description,
41164
- inputSchema: tool.inputSchema ? standardSchemaToJsonSchema(tool.inputSchema, "input") : EMPTY_OBJECT_JSON_SCHEMA,
41215
+ inputSchema: tool.inputSchema ? convertListedInputSchema(name, tool.inputSchema) : EMPTY_OBJECT_JSON_SCHEMA,
41165
41216
  annotations: tool.annotations,
41166
41217
  icons: tool.icons,
41167
41218
  execution: tool.execution,
@@ -41206,6 +41257,7 @@ var McpServer = class {
41206
41257
  * Validates tool input arguments against the tool's input schema.
41207
41258
  */
41208
41259
  async validateToolInput(tool, args, toolName) {
41260
+ 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`);
41209
41261
  if (!tool.inputSchema) return;
41210
41262
  const parseResult = await validateStandardSchema(tool.inputSchema, args ?? {});
41211
41263
  if (!parseResult.success) throw new ProtocolError(ProtocolErrorCode.InvalidParams, `Input validation error: Invalid arguments for tool ${toolName}: ${parseResult.error}`);
@@ -41480,20 +41532,19 @@ var McpServer = class {
41480
41532
  }
41481
41533
  _createRegisteredTool(name, title, description, inputSchema, outputSchema, annotations, icons, execution, scopeChallenge, _meta, handler) {
41482
41534
  validateAndWarnToolName(name);
41483
- if (inputSchema !== void 0) try {
41484
- const json2 = standardSchemaToJsonSchema(inputSchema, "input");
41485
- this._toolInputSchemaJson[name] = json2;
41486
- const scan = scanXMcpHeaderDeclarations(json2);
41487
- 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}`);
41488
- } catch {
41489
- }
41490
41535
  let currentHandler = handler;
41536
+ let outputSchemaJson;
41491
41537
  const registeredTool = {
41492
41538
  title,
41493
41539
  description,
41494
41540
  inputSchema,
41495
41541
  outputSchema,
41496
- outputSchemaJson: convertOutputSchemaJson(outputSchema),
41542
+ get outputSchemaJson() {
41543
+ return outputSchemaJson ??= convertOutputSchemaJson(registeredTool.outputSchema);
41544
+ },
41545
+ set outputSchemaJson(value) {
41546
+ outputSchemaJson = value;
41547
+ },
41497
41548
  annotations,
41498
41549
  icons,
41499
41550
  execution,
@@ -41620,6 +41671,12 @@ var EMPTY_OBJECT_JSON_SCHEMA = {
41620
41671
  type: "object",
41621
41672
  properties: {}
41622
41673
  };
41674
+ function convertListedInputSchema(name, inputSchema) {
41675
+ const json2 = standardSchemaToJsonSchema(inputSchema, "input");
41676
+ const scan = scanXMcpHeaderDeclarations(json2);
41677
+ 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}`);
41678
+ return json2;
41679
+ }
41623
41680
  function convertOutputSchemaJson(outputSchema) {
41624
41681
  if (outputSchema === void 0) return void 0;
41625
41682
  try {
@@ -41632,7 +41689,7 @@ function createPromptHandler(name, argsSchema, callback) {
41632
41689
  if (argsSchema) {
41633
41690
  const typedCallback = callback;
41634
41691
  return async (args, ctx) => {
41635
- const parseResult = await validateStandardSchema(argsSchema, args);
41692
+ const parseResult = await validateStandardSchema(argsSchema, args ?? {});
41636
41693
  if (!parseResult.success) throw new ProtocolError(ProtocolErrorCode.InvalidParams, `Invalid arguments for prompt ${name}: ${parseResult.error}`);
41637
41694
  return typedCallback(parseResult.data, ctx);
41638
41695
  };
@@ -59989,8 +60046,8 @@ function currencyCode2(_params) {
59989
60046
  return _stringFormat(ZodCustomStringFormat, "currency_code", currencyCode, _params);
59990
60047
  }
59991
60048
  function hash(alg, params) {
59992
- const enc6 = params?.enc ?? "hex";
59993
- const format = `${alg}_${enc6}`;
60049
+ const enc7 = params?.enc ?? "hex";
60050
+ const format = `${alg}_${enc7}`;
59994
60051
  const regex = regexes_exports[format];
59995
60052
  if (!regex)
59996
60053
  throw new Error(`Unrecognized hash format: ${format}`);
@@ -62065,6 +62122,7 @@ function escapeKey(key) {
62065
62122
  var DEFAULT_REQUEST_KEY = "confirmation";
62066
62123
  var DEFAULT_CONFIRMATION_LABEL = "Confirm this action should proceed.";
62067
62124
  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";
62125
+ var DISABLED_NOTE = "Nothing was done because confirmation prompts are turned off for this server, and this action is never taken without one";
62068
62126
  var STATE_PREFIX = "mcpu.confirm.v1.";
62069
62127
  function bindingKey(binding) {
62070
62128
  if (binding.ttlSeconds !== void 0 && !(Number.isFinite(binding.ttlSeconds) && binding.ttlSeconds > 0)) {
@@ -62137,6 +62195,15 @@ ${JSON.stringify(preview, null, 2)}`,
62137
62195
  };
62138
62196
  const response = inputResponse(ctx.mcpReq.inputResponses, requestKey);
62139
62197
  const accepted = acceptedContent(ctx.mcpReq.inputResponses, requestKey, confirmationSchema);
62198
+ if (options.elicitation === false) {
62199
+ return textResult({
62200
+ confirmed: false,
62201
+ dispatched: false,
62202
+ action: options.action,
62203
+ reason: "confirmation-unsupported",
62204
+ note: options.unsupportedNote ? `${DISABLED_NOTE}. ${options.unsupportedNote}` : `${DISABLED_NOTE}.`
62205
+ });
62206
+ }
62140
62207
  if (response.kind === "missing") {
62141
62208
  if (callerAcceptsFormElicitation(ctx) === false) {
62142
62209
  return textResult({
@@ -62331,7 +62398,7 @@ async function tokenConfirmation(action, fb) {
62331
62398
  }
62332
62399
  async function requireConfirmationWithFallback(ctx, options) {
62333
62400
  const { tokenFallback, ...confirmation } = options;
62334
- if (tokenFallback && callerAcceptsFormElicitation(ctx) === false) {
62401
+ if (tokenFallback && (options.elicitation === false || callerAcceptsFormElicitation(ctx) === false)) {
62335
62402
  return tokenConfirmation(options.action, tokenFallback);
62336
62403
  }
62337
62404
  return requireConfirmation(ctx, confirmation);
@@ -62490,6 +62557,20 @@ function readConfirmMode(env = process.env) {
62490
62557
  }
62491
62558
  return "refuse";
62492
62559
  }
62560
+ function readConfirmElicitation(env = process.env) {
62561
+ const raw = readEnvVar("MCP_CONFIRM_ELICITATION", { env })?.trim().toLowerCase();
62562
+ if (!raw || raw === "on")
62563
+ return "on";
62564
+ if (raw === "off")
62565
+ return "off";
62566
+ const key = `elicitation:${raw}`;
62567
+ if (!warned.has(key)) {
62568
+ warned.add(key);
62569
+ process.stderr.write(`MCP_CONFIRM_ELICITATION="${raw}" is not one of on, off; treating it as on.
62570
+ `);
62571
+ }
62572
+ return "on";
62573
+ }
62493
62574
  function readConfirmTtl(env) {
62494
62575
  const raw = readEnvVar("MCP_CONFIRM_TTL_SECONDS", { env })?.trim();
62495
62576
  if (!raw)
@@ -62534,6 +62615,7 @@ function spentTokenStoreFromEnv(env = process.env) {
62534
62615
  }
62535
62616
  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.)";
62536
62617
  var REFUSE_HINT = "Set MCP_CONFIRM_MODE=ask-user on the server to allow two-step confirmation instead.";
62618
+ var ELICITATION_OFF_HINT = "MCP_CONFIRM_ELICITATION=off on the server turns confirmation prompts off.";
62537
62619
  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.";
62538
62620
  function boundArgs(args) {
62539
62621
  if (args === null || typeof args !== "object" || Array.isArray(args) || !("confirmToken" in args))
@@ -62547,10 +62629,13 @@ function confirmationFromEnv(options) {
62547
62629
  const spent = callerSpent ?? spentTokenStoreFromEnv(env);
62548
62630
  const mode = readConfirmMode(env);
62549
62631
  const ttlSeconds = readConfirmTtl(env);
62632
+ const elicitationOff = readConfirmElicitation(env) === "off";
62550
62633
  const bound = args === void 0 ? void 0 : boundArgs(args);
62551
- const confirmation = bound === void 0 || rest.binding ? rest : { ...rest, binding: { key: confirmKeyFromEnv(env), args: bound, ttlSeconds: ttlSeconds ?? DEFAULT_TTL_SECONDS2 } };
62634
+ const bindingAdded = bound === void 0 || rest.binding ? rest : { ...rest, binding: { key: confirmKeyFromEnv(env), args: bound, ttlSeconds: ttlSeconds ?? DEFAULT_TTL_SECONDS2 } };
62635
+ const confirmation = elicitationOff ? { ...bindingAdded, elicitation: false } : bindingAdded;
62552
62636
  if (mode === "refuse" || ttlSeconds === void 0) {
62553
- const hint = mode === "refuse" ? REFUSE_HINT : BAD_TTL_HINT;
62637
+ const reason = mode === "refuse" ? REFUSE_HINT : BAD_TTL_HINT;
62638
+ const hint = elicitationOff ? `${ELICITATION_OFF_HINT} ${reason}` : reason;
62554
62639
  return {
62555
62640
  ...confirmation,
62556
62641
  unsupportedNote: confirmation.unsupportedNote ? `${confirmation.unsupportedNote} ${hint}` : hint
@@ -62659,7 +62744,10 @@ function surfaceToolHints(server) {
62659
62744
  ));
62660
62745
  }
62661
62746
  async function createMcpServer(opts) {
62662
- const server = new McpServer({ name: opts.name, version: opts.version }, { supportedProtocolVersions: [...SERVER_PROTOCOL_VERSIONS] });
62747
+ const server = new McpServer({ name: opts.name, version: opts.version }, {
62748
+ supportedProtocolVersions: [...SERVER_PROTOCOL_VERSIONS],
62749
+ ...opts.maxToolInputElements !== void 0 ? { maxToolInputElements: opts.maxToolInputElements } : {}
62750
+ });
62663
62751
  if (opts.surfaceHints !== false)
62664
62752
  surfaceToolHints(server);
62665
62753
  if (opts.banner !== void 0) {
@@ -64482,6 +64570,60 @@ var KNOWN_CAPABILITIES = /* @__PURE__ */ new Set([
64482
64570
  "graphql",
64483
64571
  "write_cookies"
64484
64572
  ]);
64573
+ var ACCOUNT_KEY_FRAME = "account-key";
64574
+ var ACCOUNT_ATTEST_FRAME = "account-attest";
64575
+ var BRIDGE_ROLE_FRAME = "bridge-role";
64576
+ var BRIDGE_SERVE_FRAME = "bridge-serve";
64577
+ var ROOM_PING_FRAME = "room-ping";
64578
+ var ROOM_PONG_FRAME = "room-pong";
64579
+ var BRIDGE_ROLE_LABEL_MAX = 64;
64580
+ var ROOM_FRAME_ACCEPTS = Object.freeze({
64581
+ [BRIDGE_ROLE_FRAME]: BRIDGE_ROLE_FRAME,
64582
+ [BRIDGE_SERVE_FRAME]: BRIDGE_SERVE_FRAME,
64583
+ [ROOM_PING_FRAME]: ROOM_PING_FRAME,
64584
+ [ROOM_PONG_FRAME]: ROOM_PING_FRAME
64585
+ });
64586
+
64587
+ // node_modules/@fetchproxy/protocol/dist/mcp-id.js
64588
+ var ID_RE = /^([^:]+):([^:]+):([0-9a-f]{16})$/;
64589
+ function generateMcpId(serverName, version2) {
64590
+ if (serverName.includes(":")) {
64591
+ throw new Error(`serverName cannot contain ':': ${serverName}`);
64592
+ }
64593
+ if (version2.includes(":")) {
64594
+ throw new Error(`version cannot contain ':': ${version2}`);
64595
+ }
64596
+ const rand = randomHex(8);
64597
+ return `${serverName}:${version2}:${rand}`;
64598
+ }
64599
+ function isValidMcpId(id) {
64600
+ return typeof id === "string" && ID_RE.test(id);
64601
+ }
64602
+ function randomHex(bytes) {
64603
+ const arr = new Uint8Array(bytes);
64604
+ globalThis.crypto.getRandomValues(arr);
64605
+ return Array.from(arr, (b) => b.toString(16).padStart(2, "0")).join("");
64606
+ }
64607
+
64608
+ // node_modules/@fetchproxy/protocol/dist/account-attest.js
64609
+ var ACCOUNT_ATTEST_CONSENTS = Object.freeze([
64610
+ "silent",
64611
+ "confirm",
64612
+ "confirm-each"
64613
+ ]);
64614
+ var NO_SCOPE_DIGEST = "0".repeat(64);
64615
+ var ACCOUNT_ID_RE = /^[A-Za-z0-9_.\-]{1,128}$/;
64616
+ var HEX64_RE = /^[0-9a-f]{64}$/;
64617
+ var ACCOUNT_KID_RE = /^[0-9a-f]{16}$/;
64618
+ var enc2 = new TextEncoder();
64619
+
64620
+ // node_modules/@fetchproxy/protocol/dist/awaiting-approval.js
64621
+ var AWAITING_APPROVAL_REASON_PREFIX = "awaiting-approval:";
64622
+ function parseAwaitingApprovalReason(reason) {
64623
+ if (!reason.startsWith(AWAITING_APPROVAL_REASON_PREFIX))
64624
+ return null;
64625
+ return reason.slice(AWAITING_APPROVAL_REASON_PREFIX.length).trim();
64626
+ }
64485
64627
 
64486
64628
  // node_modules/@fetchproxy/protocol/dist/capability-availability.js
64487
64629
  var CAPABILITY_UNAVAILABLE_CODE = "capability_unavailable";
@@ -64507,27 +64649,6 @@ function parseUnsupportedCapabilityReason(reason) {
64507
64649
  return rest.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
64508
64650
  }
64509
64651
 
64510
- // node_modules/@fetchproxy/protocol/dist/mcp-id.js
64511
- var ID_RE = /^([^:]+):([^:]+):([0-9a-f]{16})$/;
64512
- function generateMcpId(serverName, version2) {
64513
- if (serverName.includes(":")) {
64514
- throw new Error(`serverName cannot contain ':': ${serverName}`);
64515
- }
64516
- if (version2.includes(":")) {
64517
- throw new Error(`version cannot contain ':': ${version2}`);
64518
- }
64519
- const rand = randomHex(8);
64520
- return `${serverName}:${version2}:${rand}`;
64521
- }
64522
- function isValidMcpId(id) {
64523
- return typeof id === "string" && ID_RE.test(id);
64524
- }
64525
- function randomHex(bytes) {
64526
- const arr = new Uint8Array(bytes);
64527
- globalThis.crypto.getRandomValues(arr);
64528
- return Array.from(arr, (b) => b.toString(16).padStart(2, "0")).join("");
64529
- }
64530
-
64531
64652
  // node_modules/@fetchproxy/protocol/dist/json-pointer.js
64532
64653
  function matchesDeclaredKey(requested, declared) {
64533
64654
  for (const decl of declared) {
@@ -65123,6 +65244,16 @@ function validateFrame(raw) {
65123
65244
  return { type: "extension-disconnected" };
65124
65245
  if (t === "peer-gone")
65125
65246
  return validatePeerGone(raw);
65247
+ if (t === ACCOUNT_KEY_FRAME)
65248
+ return validateAccountKey(raw);
65249
+ if (t === ACCOUNT_ATTEST_FRAME)
65250
+ return validateAccountAttest(raw);
65251
+ if (t === BRIDGE_ROLE_FRAME)
65252
+ return validateBridgeRole(raw);
65253
+ if (t === BRIDGE_SERVE_FRAME || t === ROOM_PING_FRAME || t === ROOM_PONG_FRAME) {
65254
+ assertExactFields(raw, TYPE_ONLY_FIELDS, t);
65255
+ return { type: t };
65256
+ }
65126
65257
  throw new ProtocolError3(`unknown frame type: ${String(t)}`);
65127
65258
  }
65128
65259
  function validateHello(raw) {
@@ -65326,6 +65457,165 @@ function validateHelloRejected(raw) {
65326
65457
  }
65327
65458
  return { type: "hello-rejected", mcpId: raw.mcpId, reason: raw.reason };
65328
65459
  }
65460
+ var DISPLAY_FORBIDDEN_RE = /[\u0000-\u001f\u007f-\u009f\u200e\u200f\u202a-\u202e\u2066-\u2069]/;
65461
+ function assertExactFields(raw, allowed, label) {
65462
+ for (const k of Object.keys(raw)) {
65463
+ if (!allowed.includes(k)) {
65464
+ throw new ProtocolError3(`${label}: unexpected field ${JSON.stringify(k)}`);
65465
+ }
65466
+ }
65467
+ }
65468
+ function assertMatch(x, re, label, what) {
65469
+ assertString(x, label);
65470
+ if (!re.test(x))
65471
+ throw new ProtocolError3(`${label}: must be ${what}`);
65472
+ }
65473
+ function assertPositiveSafeInt(x, label) {
65474
+ if (typeof x !== "number" || !Number.isSafeInteger(x) || x <= 0) {
65475
+ throw new ProtocolError3(`${label}: expected positive safe integer`);
65476
+ }
65477
+ }
65478
+ function assertDisplayString(x, label, min, max) {
65479
+ assertString(x, label);
65480
+ if (x.length < min || x.length > max) {
65481
+ throw new ProtocolError3(`${label}: must be ${min}-${max} characters`);
65482
+ }
65483
+ if (DISPLAY_FORBIDDEN_RE.test(x)) {
65484
+ throw new ProtocolError3(`${label}: control or bidi-override character`);
65485
+ }
65486
+ }
65487
+ var ACCOUNT_KEY_FIELDS = [
65488
+ "type",
65489
+ "accountId",
65490
+ "slug",
65491
+ "displayName",
65492
+ "confirmedBy",
65493
+ "tokenId",
65494
+ "kid",
65495
+ "publicKey",
65496
+ "generation",
65497
+ "bridgedRegistrations"
65498
+ ];
65499
+ function validateAccountKey(raw) {
65500
+ const L = "account-key";
65501
+ assertMatch(raw.accountId, ACCOUNT_ID_RE, `${L}.accountId`, "an account id");
65502
+ assertMatch(raw.slug, ACCOUNT_ID_RE, `${L}.slug`, "a slug");
65503
+ assertDisplayString(raw.displayName, `${L}.displayName`, 0, 128);
65504
+ assertDisplayString(raw.confirmedBy, `${L}.confirmedBy`, 1, 256);
65505
+ assertMatch(raw.tokenId, ACCOUNT_ID_RE, `${L}.tokenId`, "a token id");
65506
+ assertMatch(raw.kid, ACCOUNT_KID_RE, `${L}.kid`, "16 lowercase hex");
65507
+ assertBase64Bytes(raw.publicKey, `${L}.publicKey`, 32);
65508
+ assertPositiveSafeInt(raw.generation, `${L}.generation`);
65509
+ const n = raw.bridgedRegistrations;
65510
+ if (typeof n !== "number" || !Number.isSafeInteger(n) || n < 0) {
65511
+ throw new ProtocolError3(`${L}.bridgedRegistrations: expected non-negative safe integer`);
65512
+ }
65513
+ assertExactFields(raw, ACCOUNT_KEY_FIELDS, L);
65514
+ return {
65515
+ type: ACCOUNT_KEY_FRAME,
65516
+ accountId: raw.accountId,
65517
+ slug: raw.slug,
65518
+ displayName: raw.displayName,
65519
+ confirmedBy: raw.confirmedBy,
65520
+ tokenId: raw.tokenId,
65521
+ kid: raw.kid,
65522
+ publicKey: raw.publicKey,
65523
+ generation: raw.generation,
65524
+ bridgedRegistrations: n
65525
+ };
65526
+ }
65527
+ var ACCOUNT_ATTEST_FIELDS = [
65528
+ "type",
65529
+ "mcpId",
65530
+ "accountId",
65531
+ "generation",
65532
+ "tokenId",
65533
+ "kid",
65534
+ "registrationId",
65535
+ "slug",
65536
+ "identityHash",
65537
+ "identityEd25519Pub",
65538
+ "scopeDigest",
65539
+ "consent",
65540
+ "notAfter",
65541
+ "sig"
65542
+ ];
65543
+ function validateAccountAttest(raw) {
65544
+ const L = "account-attest";
65545
+ if (!isValidMcpId(raw.mcpId))
65546
+ throw new ProtocolError3(`${L}.mcpId: invalid format`);
65547
+ assertMatch(raw.accountId, ACCOUNT_ID_RE, `${L}.accountId`, "an account id");
65548
+ assertPositiveSafeInt(raw.generation, `${L}.generation`);
65549
+ assertMatch(raw.tokenId, ACCOUNT_ID_RE, `${L}.tokenId`, "a token id");
65550
+ assertMatch(raw.kid, ACCOUNT_KID_RE, `${L}.kid`, "16 lowercase hex");
65551
+ assertMatch(raw.registrationId, ACCOUNT_ID_RE, `${L}.registrationId`, "a registration id");
65552
+ assertMatch(raw.slug, ACCOUNT_ID_RE, `${L}.slug`, "a slug");
65553
+ assertMatch(raw.identityHash, HEX64_RE, `${L}.identityHash`, "64 lowercase hex");
65554
+ assertBase64Bytes(raw.identityEd25519Pub, `${L}.identityEd25519Pub`, 32);
65555
+ assertMatch(raw.scopeDigest, HEX64_RE, `${L}.scopeDigest`, "64 lowercase hex");
65556
+ assertString(raw.consent, `${L}.consent`);
65557
+ const consent = raw.consent;
65558
+ const known = ACCOUNT_ATTEST_CONSENTS.find((c) => c === consent);
65559
+ if (known === void 0) {
65560
+ throw new ProtocolError3(`${L}.consent: must be one of ${ACCOUNT_ATTEST_CONSENTS.join(", ")}`);
65561
+ }
65562
+ assertPositiveSafeInt(raw.notAfter, `${L}.notAfter`);
65563
+ assertBase64Bytes(raw.sig, `${L}.sig`, 64);
65564
+ assertExactFields(raw, ACCOUNT_ATTEST_FIELDS, L);
65565
+ return {
65566
+ type: ACCOUNT_ATTEST_FRAME,
65567
+ mcpId: raw.mcpId,
65568
+ accountId: raw.accountId,
65569
+ generation: raw.generation,
65570
+ tokenId: raw.tokenId,
65571
+ kid: raw.kid,
65572
+ registrationId: raw.registrationId,
65573
+ slug: raw.slug,
65574
+ identityHash: raw.identityHash,
65575
+ identityEd25519Pub: raw.identityEd25519Pub,
65576
+ scopeDigest: raw.scopeDigest,
65577
+ consent: known,
65578
+ notAfter: raw.notAfter,
65579
+ sig: raw.sig
65580
+ };
65581
+ }
65582
+ var TYPE_ONLY_FIELDS = ["type"];
65583
+ var BRIDGE_ROLE_FIELDS = ["type", "role", "canServe", "serving"];
65584
+ var BRIDGE_ROLE_SERVING_FIELDS = ["label", "since"];
65585
+ function validateBridgeRole(raw) {
65586
+ const L = BRIDGE_ROLE_FRAME;
65587
+ const role = raw.role;
65588
+ if (role !== "serving" && role !== "standby") {
65589
+ throw new ProtocolError3(`${L}.role: must be one of serving, standby`);
65590
+ }
65591
+ const canServe = raw.canServe;
65592
+ assertBoolean(canServe, `${L}.canServe`);
65593
+ assertExactFields(raw, BRIDGE_ROLE_FIELDS, L);
65594
+ const serving = raw.serving;
65595
+ if (role === "serving") {
65596
+ if (serving !== void 0) {
65597
+ throw new ProtocolError3(`${L}.serving: must be absent when role is serving`);
65598
+ }
65599
+ return { type: BRIDGE_ROLE_FRAME, role, canServe };
65600
+ }
65601
+ if (serving === void 0) {
65602
+ throw new ProtocolError3(`${L}.serving: required when role is standby`);
65603
+ }
65604
+ assertObject(serving, `${L}.serving`);
65605
+ const label = serving.label;
65606
+ assertDisplayString(label, `${L}.serving.label`, 1, BRIDGE_ROLE_LABEL_MAX);
65607
+ const since = serving.since;
65608
+ if (typeof since !== "number" || !Number.isSafeInteger(since) || since < 0) {
65609
+ throw new ProtocolError3(`${L}.serving.since: expected non-negative safe integer (Unix ms)`);
65610
+ }
65611
+ assertExactFields(serving, BRIDGE_ROLE_SERVING_FIELDS, `${L}.serving`);
65612
+ return {
65613
+ type: BRIDGE_ROLE_FRAME,
65614
+ role,
65615
+ canServe,
65616
+ serving: { label, since }
65617
+ };
65618
+ }
65329
65619
  function validateInnerFrame(raw) {
65330
65620
  assertObject(raw, "inner");
65331
65621
  const t = raw.type;
@@ -65793,10 +66083,10 @@ function validateInnerResponse(raw) {
65793
66083
  }
65794
66084
 
65795
66085
  // node_modules/@fetchproxy/protocol/dist/pair-code.js
65796
- var enc2 = new TextEncoder();
66086
+ var enc3 = new TextEncoder();
65797
66087
  var PAIR_LABEL = "fetchproxy/4/pair";
65798
66088
  async function pairTranscript(mcpIdentityPub, extIdentityPub, mcpNonce, extNonce, mcpSessionPub) {
65799
- const label = enc2.encode(PAIR_LABEL);
66089
+ const label = enc3.encode(PAIR_LABEL);
65800
66090
  const parts = [mcpIdentityPub, extIdentityPub, mcpNonce, extNonce, mcpSessionPub];
65801
66091
  const out = new Uint8Array(label.length + 1 + parts.reduce((n, p) => n + p.length, 0));
65802
66092
  out.set(label, 0);
@@ -65817,7 +66107,7 @@ function digits(hash2) {
65817
66107
  }
65818
66108
 
65819
66109
  // node_modules/@fetchproxy/protocol/dist/seal.js
65820
- var enc3 = new TextEncoder();
66110
+ var enc4 = new TextEncoder();
65821
66111
  var dec = new TextDecoder();
65822
66112
  function randomIv() {
65823
66113
  const iv = new Uint8Array(12);
@@ -65830,14 +66120,14 @@ function base64Length(byteLength) {
65830
66120
  return Math.ceil(byteLength / 3) * 4;
65831
66121
  }
65832
66122
  function encodeInnerFrame(inner) {
65833
- return enc3.encode(JSON.stringify(inner));
66123
+ return enc4.encode(JSON.stringify(inner));
65834
66124
  }
65835
66125
  function plaintextOf(inner) {
65836
66126
  return inner instanceof Uint8Array ? inner : encodeInnerFrame(inner);
65837
66127
  }
65838
66128
  function sealedFrameWireBytes(mcpId, seq, inner) {
65839
66129
  const plaintext = plaintextOf(inner).length;
65840
- const envelope = enc3.encode(JSON.stringify({ type: "frame", mcpId, seq, iv: "", ciphertext: "" })).length;
66130
+ const envelope = enc4.encode(JSON.stringify({ type: "frame", mcpId, seq, iv: "", ciphertext: "" })).length;
65841
66131
  return envelope + IV_B64_BYTES + base64Length(plaintext + AES_GCM_TAG_BYTES);
65842
66132
  }
65843
66133
  var MAX_FRAME_BYTES = 42 * 1024 * 1024;
@@ -66177,14 +66467,23 @@ var FetchproxyHelloRejectedError = class extends Error {
66177
66467
  platform;
66178
66468
  /**
66179
66469
  * #418: the remedy, when the reason is one this package knows how to
66180
- * explain — today only `unsupported-capability`, which blames the browser
66181
- * rather than the MCP. `null` otherwise.
66470
+ * explain — `unsupported-capability`, which blames the browser rather than
66471
+ * the MCP, and (D12) `awaiting-approval`, which says to approve in the
66472
+ * browser. `null` otherwise.
66182
66473
  */
66183
66474
  hint;
66475
+ /**
66476
+ * D12: `true` only for an `awaiting-approval:` refusal — the extension has
66477
+ * queued an approval card and nobody is at the browser yet, so the same call
66478
+ * succeeds once the person approves. Every other refusal is the extension's
66479
+ * final answer, and retrying unchanged is refused the same way.
66480
+ */
66481
+ retryable;
66184
66482
  constructor(info) {
66185
66483
  const unavailable = parseUnsupportedCapabilityReason(info.reason);
66484
+ const awaiting = parseAwaitingApprovalReason(info.reason);
66186
66485
  const platform = info.platform ?? null;
66187
- 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;
66486
+ 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;
66188
66487
  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.`));
66189
66488
  this.name = "FetchproxyHelloRejectedError";
66190
66489
  this.mcpId = info.mcpId;
@@ -66192,6 +66491,7 @@ var FetchproxyHelloRejectedError = class extends Error {
66192
66491
  this.unavailableCapabilities = unavailable ?? [];
66193
66492
  this.platform = platform;
66194
66493
  this.hint = hint;
66494
+ this.retryable = awaiting !== null;
66195
66495
  Object.setPrototypeOf(this, new.target.prototype);
66196
66496
  }
66197
66497
  };
@@ -66744,7 +67044,7 @@ function originVerdict(origin, allowLocal) {
66744
67044
  }
66745
67045
  var HANDSHAKE_TIMEOUT_MS = 15e3;
66746
67046
  var MAX_PAYLOAD_BYTES = MAX_FRAME_BYTES;
66747
- var enc4 = new TextEncoder();
67047
+ var enc5 = new TextEncoder();
66748
67048
  var OWN_DECRYPT_FAILURES_BEFORE_REHANDSHAKE = 3;
66749
67049
  async function startHost(opts) {
66750
67050
  const allowLocalOrigins = envAllowsLocalOrigins();
@@ -66815,10 +67115,20 @@ async function startHost(opts) {
66815
67115
  let resolveOwnSession;
66816
67116
  let rejectOwnSession;
66817
67117
  let ownSessionReady;
67118
+ let ownRetryableRefusal = false;
67119
+ let ownSessionSettled = false;
66818
67120
  function resetSessionPromise() {
67121
+ ownRetryableRefusal = false;
67122
+ ownSessionSettled = false;
66819
67123
  ownSessionReady = new Promise((resolve2, reject) => {
66820
- resolveOwnSession = resolve2;
66821
- rejectOwnSession = reject;
67124
+ resolveOwnSession = (st) => {
67125
+ ownSessionSettled = true;
67126
+ resolve2(st);
67127
+ };
67128
+ rejectOwnSession = (e) => {
67129
+ ownSessionSettled = true;
67130
+ reject(e);
67131
+ };
66822
67132
  });
66823
67133
  ownSessionReady.catch(() => {
66824
67134
  });
@@ -67085,13 +67395,15 @@ async function startHost(opts) {
67085
67395
  const extPub = fromB64(frame.extensionSessionPub);
67086
67396
  const shared = await ecdhX25519(held.priv, extPub);
67087
67397
  const salt = await transcriptHash(held.nonce, extNonce, held.pub, extPub);
67088
- const key = await hkdfSha256(shared, salt, enc4.encode(HKDF_SESSION_INFO), 32);
67398
+ const key = await hkdfSha256(shared, salt, enc5.encode(HKDF_SESSION_INFO), 32);
67089
67399
  if (extensionWs !== ws)
67090
67400
  return;
67091
67401
  ownSession = new SessionState(key);
67092
67402
  ownSessionInfo = sessionInfoFromHello(verifiedHello);
67093
67403
  ownDecryptFailures = 0;
67094
67404
  ownPendingPairCode = null;
67405
+ if (ownRetryableRefusal)
67406
+ resetSessionPromise();
67095
67407
  resolveOwnSession(ownSession);
67096
67408
  } else {
67097
67409
  const slot = peers.get(frame.mcpId);
@@ -67160,11 +67472,17 @@ async function startHost(opts) {
67160
67472
  }
67161
67473
  if (frame.type === "hello-rejected" && identified === "extension") {
67162
67474
  if (frame.mcpId === opts.ownMcpId) {
67163
- rejectOwnSession(new FetchproxyHelloRejectedError({
67475
+ const refusal = new FetchproxyHelloRejectedError({
67164
67476
  mcpId: frame.mcpId,
67165
67477
  reason: frame.reason,
67166
67478
  platform: extensionHello?.platform ?? null
67167
- }));
67479
+ });
67480
+ if (ownRetryableRefusal)
67481
+ resetSessionPromise();
67482
+ const settlesPending = !ownSessionSettled;
67483
+ rejectOwnSession(refusal);
67484
+ if (refusal.retryable && settlesPending)
67485
+ ownRetryableRefusal = true;
67168
67486
  } else {
67169
67487
  const slot = peers.get(frame.mcpId);
67170
67488
  if (slot?.helloFrame.accepts?.includes("hello-rejected")) {
@@ -67280,7 +67598,7 @@ async function startHost(opts) {
67280
67598
 
67281
67599
  // node_modules/@fetchproxy/server/dist/peer.js
67282
67600
  var PEER_DIAL_TIMEOUT_MS = 5e3;
67283
- var enc5 = new TextEncoder();
67601
+ var enc6 = new TextEncoder();
67284
67602
  function notSentError() {
67285
67603
  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.");
67286
67604
  }
@@ -67380,7 +67698,9 @@ async function startPeer(opts) {
67380
67698
  let rejectFirstReady;
67381
67699
  let sessionPromise;
67382
67700
  let sessionPromiseSettled = false;
67701
+ let retryableRefusal = false;
67383
67702
  function resetSessionPromise() {
67703
+ retryableRefusal = false;
67384
67704
  sessionPromiseSettled = false;
67385
67705
  sessionPromise = new Promise((resolve2, reject) => {
67386
67706
  resolveFirstReady = (st) => {
@@ -67477,6 +67797,8 @@ async function startPeer(opts) {
67477
67797
  raw = JSON.parse(data.toString());
67478
67798
  const frame = validateFrame(raw);
67479
67799
  if (frame.type === "hello" && frame.role === "extension") {
67800
+ if (retryableRefusal)
67801
+ resetSessionPromise();
67480
67802
  extensionHello = frame;
67481
67803
  const mintedKeypair = await generateSessionKeypair();
67482
67804
  const mintedHello = await buildServerHello({
@@ -67506,6 +67828,8 @@ async function startPeer(opts) {
67506
67828
  session = null;
67507
67829
  if (sessionPromiseSettled)
67508
67830
  resetSessionPromise();
67831
+ } else if (retryableRefusal) {
67832
+ resetSessionPromise();
67509
67833
  }
67510
67834
  extensionDisconnectListeners.forEach((cb) => cb());
67511
67835
  return;
@@ -67527,7 +67851,7 @@ async function startPeer(opts) {
67527
67851
  const extNonce = fromB64(authenticated.sessionNonce);
67528
67852
  const shared = await ecdhX25519(held.priv, extPub);
67529
67853
  const salt = await transcriptHash(held.nonce, extNonce, held.pub, extPub);
67530
- const sessionKey = await hkdfSha256(shared, salt, enc5.encode(HKDF_SESSION_INFO), 32);
67854
+ const sessionKey = await hkdfSha256(shared, salt, enc6.encode(HKDF_SESSION_INFO), 32);
67531
67855
  if (sessionEphemeral !== held) {
67532
67856
  console.warn(`[fetchproxy] ${opts.serverName}: dropping a session derived against an ephemeral that was superseded while it was being derived`);
67533
67857
  return;
@@ -67540,16 +67864,24 @@ async function startPeer(opts) {
67540
67864
  if (isRenegotiation) {
67541
67865
  renegotiateListeners.forEach((cb) => cb());
67542
67866
  } else {
67867
+ if (retryableRefusal)
67868
+ resetSessionPromise();
67543
67869
  resolveFirstReady(session);
67544
67870
  }
67545
67871
  return;
67546
67872
  }
67547
67873
  if (frame.type === "hello-rejected" && frame.mcpId === opts.mcpId) {
67548
- rejectFirstReady(new FetchproxyHelloRejectedError({
67874
+ const refusal = new FetchproxyHelloRejectedError({
67549
67875
  mcpId: frame.mcpId,
67550
67876
  reason: frame.reason,
67551
67877
  platform: extensionHello?.platform ?? null
67552
- }));
67878
+ });
67879
+ if (retryableRefusal)
67880
+ resetSessionPromise();
67881
+ const settlesPending = !sessionPromiseSettled;
67882
+ rejectFirstReady(refusal);
67883
+ if (refusal.retryable && settlesPending)
67884
+ retryableRefusal = true;
67553
67885
  }
67554
67886
  if (frame.type === "pair-pending" && frame.mcpId === opts.mcpId) {
67555
67887
  const hello2 = extensionHello;
@@ -67713,8 +68045,9 @@ function classifyFetchError(error62, code2) {
67713
68045
 
67714
68046
  // node_modules/@fetchproxy/server/dist/classify-bridge-error.js
67715
68047
  function classifyBridgeError(err) {
67716
- if (err instanceof FetchproxyHelloRejectedError)
67717
- return "hello_rejected";
68048
+ if (err instanceof FetchproxyHelloRejectedError) {
68049
+ return err.retryable ? "awaiting_approval" : "hello_rejected";
68050
+ }
67718
68051
  if (err instanceof FetchproxySessionNotReadyError)
67719
68052
  return "session_not_ready";
67720
68053
  if (err instanceof FetchproxyTimeoutError)
@@ -75921,7 +76254,7 @@ function registerVisitTools(server, client2, patients2) {
75921
76254
  }
75922
76255
 
75923
76256
  // src/version.ts
75924
- var VERSION = "1.3.3";
76257
+ var VERSION = "1.3.5";
75925
76258
 
75926
76259
  // src/index.ts
75927
76260
  var dotenvCandidates = [
@@ -76006,7 +76339,7 @@ await runMcp({
76006
76339
  });
76007
76340
  /*! Bundled license information:
76008
76341
 
76009
- @modelcontextprotocol/server/dist/src-BHSMhZ_W.mjs:
76342
+ @modelcontextprotocol/server/dist/src-Cqbh3MYc.mjs:
76010
76343
  (*!
76011
76344
  * content-type
76012
76345
  * Copyright(c) 2015 Douglas Christopher Wilson
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chrischall/myatriumhealth-mcp",
3
- "version": "1.3.3",
3
+ "version": "1.3.5",
4
4
  "description": "MCP server for MyAtriumHealth (Atrium Health's Epic MyChart patient portal) via the fetchproxy browser bridge.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -42,15 +42,15 @@
42
42
  "access": "public"
43
43
  },
44
44
  "dependencies": {
45
- "@chrischall/mcp-utils": "^2.13.0",
46
- "@fetchproxy/server": "^3.4.1",
47
- "@modelcontextprotocol/server": "^2.2.0",
45
+ "@chrischall/mcp-utils": "^2.15.0",
46
+ "@fetchproxy/server": "^3.6.0",
47
+ "@modelcontextprotocol/server": "^2.3.0",
48
48
  "dotenv": "^18.0.1",
49
49
  "node-html-parser": "^9.0.3",
50
50
  "zod": "^4.6.5"
51
51
  },
52
52
  "devDependencies": {
53
- "@modelcontextprotocol/client": "^2.2.0",
53
+ "@modelcontextprotocol/client": "^2.3.0",
54
54
  "@types/node": "^26.0.0",
55
55
  "@vitest/coverage-v8": "^5.0.0",
56
56
  "esbuild": "^0.28.1",
package/server.json CHANGED
@@ -6,12 +6,12 @@
6
6
  "url": "https://github.com/chrischall/myatriumhealth-mcp",
7
7
  "source": "github"
8
8
  },
9
- "version": "1.3.3",
9
+ "version": "1.3.5",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "@chrischall/myatriumhealth-mcp",
14
- "version": "1.3.3",
14
+ "version": "1.3.5",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  },