@chrischall/myatriumhealth-mcp 0.4.3 → 0.4.4

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 Angi — find home-service pros by trade and city, read ratings, reviews and pro profiles. Angi serves its pages only to a real browser, so requests route through the user's own angi.com tab via the fetchproxy browser extension, reusing their existing session. The trade/city taxonomy is read from Angi's public sitemaps directly.",
10
- "version": "0.4.3"
10
+ "version": "0.4.4"
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 fetchproxy bridge.",
18
- "version": "0.4.3",
18
+ "version": "0.4.4",
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": "0.4.3",
4
+ "version": "0.4.4",
5
5
  "description": "MyAtriumHealth (Epic MyChart) access for Claude — test results, medications, allergies, immunizations, visits",
6
6
  "author": {
7
7
  "name": "Chris Hall",
package/dist/bundle.js CHANGED
@@ -9763,7 +9763,7 @@ var require_websocket = __commonJS({
9763
9763
  var http = __require("http");
9764
9764
  var net = __require("net");
9765
9765
  var tls = __require("tls");
9766
- var { randomBytes: randomBytes2, createHash } = __require("crypto");
9766
+ var { randomBytes: randomBytes3, createHash } = __require("crypto");
9767
9767
  var { Duplex, Readable } = __require("stream");
9768
9768
  var { URL: URL2 } = __require("url");
9769
9769
  var PerMessageDeflate2 = require_permessage_deflate();
@@ -10301,7 +10301,7 @@ var require_websocket = __commonJS({
10301
10301
  }
10302
10302
  }
10303
10303
  const defaultPort = isSecure ? 443 : 80;
10304
- const key = randomBytes2(16).toString("base64");
10304
+ const key = randomBytes3(16).toString("base64");
10305
10305
  const request = isSecure ? https.request : http.request;
10306
10306
  const protocolSet = /* @__PURE__ */ new Set();
10307
10307
  let perMessageDeflate;
@@ -40115,7 +40115,7 @@ function parseChallengeContext(html) {
40115
40115
  if (flag("AllowSMS", tfs)) channels.push("sms");
40116
40116
  if (flag("AllowEmail", tfs)) channels.push("email");
40117
40117
  if (flag("AllowTotp", tfs)) channels.push("totp");
40118
- const rm = /RememberMeSettings\s*:\s*\{([\s\S]{0,300}?)\}/.exec(html)?.[1] ?? "";
40118
+ const rm2 = /RememberMeSettings\s*:\s*\{([\s\S]{0,300}?)\}/.exec(html)?.[1] ?? "";
40119
40119
  const workflow = /Workflow\s*:\s*(\d+)/.exec(tfs)?.[1];
40120
40120
  const email3 = str("DisplayEmail");
40121
40121
  const phone = str("DisplayPhone");
@@ -40124,8 +40124,8 @@ function parseChallengeContext(html) {
40124
40124
  ...email3 !== void 0 ? { displayEmail: email3 } : {},
40125
40125
  ...phone !== void 0 ? { displayPhone: phone } : {},
40126
40126
  ...workflow !== void 0 ? { workflow: Number(workflow) } : {},
40127
- rememberMeEnabled: flag("Enabled", rm),
40128
- enrollDeviceTracking: flag("EnrollDeviceTracking", rm)
40127
+ rememberMeEnabled: flag("Enabled", rm2),
40128
+ enrollDeviceTracking: flag("EnrollDeviceTracking", rm2)
40129
40129
  };
40130
40130
  }
40131
40131
  var b64 = (s) => Buffer.from(s, "utf8").toString("base64");
@@ -42534,9 +42534,29 @@ var SessionState = class {
42534
42534
  outboundSeq = 0;
42535
42535
  lastInboundSeq = 0;
42536
42536
  inflightInbound = /* @__PURE__ */ new Set();
42537
+ saturationWarned = false;
42537
42538
  constructor(sessionKey) {
42538
42539
  this.sessionKey = sessionKey;
42539
42540
  }
42541
+ /**
42542
+ * Whether the caller holding this claim verdict should log saturation now.
42543
+ * True only on the TRANSITION into it: a line per dropped frame turned a
42544
+ * flood — or a set that never drains — into a log flood amplifying the very
42545
+ * condition it reported (#376). An `'ok'` claim re-arms the latch, since it
42546
+ * is the one signal every caller sees that the set has drained below the
42547
+ * bound; a replay needs no capacity and leaves it alone. Latched per
42548
+ * session, so a renegotiated one starts armed. A set hovering at the bound
42549
+ * can still alternate ok/saturated, but that logs at the rate frames finish
42550
+ * opening, not the rate they arrive.
42551
+ */
42552
+ saturationWarningDue(claim2) {
42553
+ if (claim2 === "ok")
42554
+ this.saturationWarned = false;
42555
+ if (claim2 !== "saturated" || this.saturationWarned)
42556
+ return false;
42557
+ this.saturationWarned = true;
42558
+ return true;
42559
+ }
42540
42560
  nextOutboundSeq() {
42541
42561
  this.outboundSeq += 1;
42542
42562
  return this.outboundSeq;
@@ -42547,19 +42567,21 @@ var SessionState = class {
42547
42567
  * the first `await` of the receive path, or a duplicate frame read in the
42548
42568
  * same pass will be judged against a counter neither frame has moved yet.
42549
42569
  *
42550
- * Answering false means "not this frame's to process" — replayed, or a
42551
- * duplicate of one still in flight. Every true MUST be answered by exactly
42552
- * one {@link commitInboundSeq} or {@link releaseInboundSeq}.
42570
+ * Anything but `'ok'` means "not this frame's to process": `'replay'` for
42571
+ * a spent seq or a duplicate of one still in flight, `'saturated'` when the
42572
+ * in-flight bound is full. Replay is checked first, since refusing a stale
42573
+ * seq needs no capacity. Every `'ok'` MUST be answered by exactly one
42574
+ * {@link commitInboundSeq} or {@link releaseInboundSeq}.
42553
42575
  */
42554
42576
  claimInboundSeq(seq) {
42555
42577
  if (seq <= this.lastInboundSeq)
42556
- return false;
42578
+ return "replay";
42557
42579
  if (this.inflightInbound.has(seq))
42558
- return false;
42580
+ return "replay";
42559
42581
  if (this.inflightInbound.size >= MAX_INFLIGHT_INBOUND_SEQS)
42560
- return false;
42582
+ return "saturated";
42561
42583
  this.inflightInbound.add(seq);
42562
- return true;
42584
+ return "ok";
42563
42585
  }
42564
42586
  /**
42565
42587
  * Give a claim back without spending the seq — for a frame that did not
@@ -42654,11 +42676,12 @@ async function awaitSessionReady(ready, opts) {
42654
42676
  }
42655
42677
 
42656
42678
  // node_modules/@fetchproxy/server/dist/extension-trust.js
42657
- import { readFile as readFile2, writeFile as writeFile2, rename, unlink, mkdir as mkdir2, chmod as chmod2 } from "node:fs/promises";
42679
+ import { readFile as readFile2, writeFile as writeFile2, rename as rename2, unlink as unlink2, mkdir as mkdir2, chmod as chmod2 } from "node:fs/promises";
42658
42680
  import { isAbsolute as isAbsolute2, join as join3 } from "node:path";
42659
42681
 
42660
42682
  // node_modules/@fetchproxy/server/dist/identity.js
42661
- import { readFile, writeFile, mkdir, chmod } from "node:fs/promises";
42683
+ import { readFile, readdir, writeFile, mkdir, chmod, rename, rm, unlink } from "node:fs/promises";
42684
+ import { randomBytes as randomBytes2 } from "node:crypto";
42662
42685
  import { isAbsolute, join as join2 } from "node:path";
42663
42686
  import { homedir as homedir2 } from "node:os";
42664
42687
  var SAFE_PLAIN = /^[A-Za-z0-9._-]+$/;
@@ -42714,16 +42737,46 @@ function parseIdentity(text2) {
42714
42737
  createdAt: j.createdAt
42715
42738
  };
42716
42739
  }
42740
+ async function openIdentityDir(dir) {
42741
+ await mkdir(dir, { recursive: true, mode: 448 });
42742
+ try {
42743
+ await chmod(dir, 448);
42744
+ } catch (e) {
42745
+ const code = e.code;
42746
+ if (code !== "EROFS" && code !== "EPERM")
42747
+ throw e;
42748
+ }
42749
+ }
42750
+ var STAGING_BYTES = 6;
42751
+ async function sweepStagingFiles(dir, serverName) {
42752
+ const base = safeIdentityFileBase(serverName).replace(/[.]/g, "\\.");
42753
+ const staging = new RegExp(`^${base}\\.json\\.[0-9a-f]{${STAGING_BYTES * 2}}\\.tmp$`);
42754
+ let names;
42755
+ try {
42756
+ names = await readdir(dir);
42757
+ } catch {
42758
+ return;
42759
+ }
42760
+ await Promise.all(names.filter((name) => staging.test(name)).map((name) => unlink(join2(dir, name)).catch(() => void 0)));
42761
+ }
42717
42762
  async function writeIdentityFile(dir, serverName, id) {
42718
42763
  const path = identityFilePath(dir, serverName);
42719
- await mkdir(dir, { recursive: true, mode: 448 });
42720
- await writeFile(path, serializeIdentity(id), { mode: 384 });
42721
- await chmod(path, 384);
42764
+ await openIdentityDir(dir);
42765
+ await sweepStagingFiles(dir, serverName);
42766
+ const tmp = `${path}.${randomBytes2(STAGING_BYTES).toString("hex")}.tmp`;
42767
+ try {
42768
+ await writeFile(tmp, serializeIdentity(id), { mode: 384, flag: "wx" });
42769
+ await chmod(tmp, 384);
42770
+ await rename(tmp, path);
42771
+ } catch (e) {
42772
+ await rm(tmp, { force: true });
42773
+ throw e;
42774
+ }
42722
42775
  return path;
42723
42776
  }
42724
42777
  async function loadOrCreateIdentity(serverName, dir = defaultIdentityDir()) {
42725
42778
  const path = identityFilePath(dir, serverName);
42726
- await mkdir(dir, { recursive: true, mode: 448 });
42779
+ await openIdentityDir(dir);
42727
42780
  try {
42728
42781
  return parseIdentity(await readFile(path, "utf8"));
42729
42782
  } catch (e) {
@@ -42836,7 +42889,7 @@ async function writeExtensionPin(serverName, pin, dir = defaultTrustDir()) {
42836
42889
  const tmp = `${path}.tmp`;
42837
42890
  await writeFile2(tmp, JSON.stringify(pin, null, 2), { mode: 384 });
42838
42891
  await chmod2(tmp, 384);
42839
- await rename(tmp, path);
42892
+ await rename2(tmp, path);
42840
42893
  }
42841
42894
 
42842
42895
  // node_modules/@fetchproxy/server/dist/host.js
@@ -43191,7 +43244,11 @@ async function startHost(opts) {
43191
43244
  const session = ownSession;
43192
43245
  if (!session)
43193
43246
  return;
43194
- if (!session.claimInboundSeq(frame.seq))
43247
+ const claim2 = session.claimInboundSeq(frame.seq);
43248
+ if (session.saturationWarningDue(claim2)) {
43249
+ console.warn(`[fetchproxy] ${opts.ownServerName}: dropped an inbound frame (seq ${frame.seq}) unread \u2014 too many frames from the extension are still being opened (inbound claims saturated). Not a replay. Further drops are not logged until the in-flight set drains.`);
43250
+ }
43251
+ if (claim2 !== "ok")
43195
43252
  return;
43196
43253
  let inner;
43197
43254
  try {
@@ -43320,7 +43377,9 @@ async function startHost(opts) {
43320
43377
  // node_modules/@fetchproxy/server/dist/peer.js
43321
43378
  var enc5 = new TextEncoder();
43322
43379
  async function startPeer(opts) {
43323
- const ws = new import_websocket.default(`ws://${opts.host}:${opts.port}`);
43380
+ const ws = new import_websocket.default(`ws://${opts.host}:${opts.port}`, {
43381
+ maxPayload: opts.maxPayloadBytes ?? MAX_PAYLOAD_BYTES
43382
+ });
43324
43383
  await new Promise((resolve2, reject) => {
43325
43384
  const onOpen = () => {
43326
43385
  cleanup();
@@ -43558,7 +43617,11 @@ async function startPeer(opts) {
43558
43617
  const inboundSession = session;
43559
43618
  if (!inboundSession)
43560
43619
  return;
43561
- if (!inboundSession.claimInboundSeq(frame.seq))
43620
+ const claim2 = inboundSession.claimInboundSeq(frame.seq);
43621
+ if (inboundSession.saturationWarningDue(claim2)) {
43622
+ console.warn(`[fetchproxy] ${opts.serverName}: dropped an inbound frame (seq ${frame.seq}) unread \u2014 too many frames from the extension are still being opened (inbound claims saturated). Not a replay. Further drops are not logged until the in-flight set drains.`);
43623
+ }
43624
+ if (claim2 !== "ok")
43562
43625
  return;
43563
43626
  let result;
43564
43627
  try {
@@ -51138,7 +51201,7 @@ function registerVisitTools(server, client2, patients2) {
51138
51201
  }
51139
51202
 
51140
51203
  // src/version.ts
51141
- var VERSION = "0.4.3";
51204
+ var VERSION = "0.4.4";
51142
51205
 
51143
51206
  // src/index.ts
51144
51207
  var dotenvCandidates = [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chrischall/myatriumhealth-mcp",
3
- "version": "0.4.3",
3
+ "version": "0.4.4",
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": {
@@ -43,7 +43,7 @@
43
43
  },
44
44
  "dependencies": {
45
45
  "@chrischall/mcp-utils": "^0.26.1",
46
- "@fetchproxy/server": "^3.0.0",
46
+ "@fetchproxy/server": "^3.0.1",
47
47
  "@modelcontextprotocol/sdk": "^1.30.0",
48
48
  "dotenv": "^17.4.2",
49
49
  "node-html-parser": "^9.0.3",
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": "0.4.3",
9
+ "version": "0.4.4",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "@chrischall/myatriumhealth-mcp",
14
- "version": "0.4.3",
14
+ "version": "0.4.4",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  },