@drawpro/mcp 0.6.0 → 0.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/server.js +88 -17
  2. package/package.json +1 -1
package/dist/server.js CHANGED
@@ -1411,7 +1411,7 @@ function api() {
1411
1411
  return clientInstance;
1412
1412
  }
1413
1413
  var SESSION_ID = Math.random().toString(36).slice(2, 10);
1414
- var VERSION = "0.6.0";
1414
+ var VERSION = "0.6.3";
1415
1415
  var inFlight = [];
1416
1416
  var cachedUser = null;
1417
1417
  async function currentUser() {
@@ -1426,7 +1426,14 @@ async function unlockedKey() {
1426
1426
  const key = loadKey(user.email);
1427
1427
  if (!key) {
1428
1428
  return {
1429
- error: "This account is locked, so names and contents cannot be read. Ask the user to run `DRAWPRO_TOKEN=... npx -y @drawpro/mcp login` in a terminal. It prompts for their passcode and stores the derived key in the OS keychain. Never ask the user for their passcode here \u2014 it must not pass through this conversation."
1429
+ error: `This account (${user.email}) is locked, so names and contents cannot be read.
1430
+
1431
+ Ask the user to run this in a terminal:
1432
+ npx -y @drawpro/mcp login
1433
+
1434
+ It prompts for their passcode and stores the derived key in the OS keychain. They do NOT need to restart \u2014 once it completes, simply try the same tool again and it will work.
1435
+
1436
+ Never ask the user for their passcode here. It is the account's master secret and must not pass through this conversation.`
1430
1437
  };
1431
1438
  }
1432
1439
  return { key };
@@ -1615,11 +1622,11 @@ tool(
1615
1622
  );
1616
1623
  function buildOrExplain(spec) {
1617
1624
  const { scene, issues } = buildDiagram(spec);
1618
- const errors = issues.filter((i) => i.level === "error");
1619
- if (errors.length > 0) {
1625
+ const errors2 = issues.filter((i) => i.level === "error");
1626
+ if (errors2.length > 0) {
1620
1627
  return {
1621
1628
  ok: false,
1622
- error: "The diagram was not created. Fix the spec and try again:\n" + errors.map((i) => ` error: ${i.message}`).join("\n")
1629
+ error: "The diagram was not created. Fix the spec and try again:\n" + errors2.map((i) => ` error: ${i.message}`).join("\n")
1623
1630
  };
1624
1631
  }
1625
1632
  const warnings = issues.filter((i) => i.level === "warning");
@@ -1719,7 +1726,9 @@ tool(
1719
1726
  sheet_id: import_zod.z.string(),
1720
1727
  edits: import_zod.z.array(
1721
1728
  import_zod.z.object({
1722
- find: import_zod.z.string().describe("The element's exact current text, as read_sheet reports it"),
1729
+ find: import_zod.z.string().describe(
1730
+ "The element's current text, as read_sheet reports it. Whitespace is matched loosely, so the flattened single-line form read_sheet prints works even when the element itself contains line breaks."
1731
+ ),
1723
1732
  replace: import_zod.z.string()
1724
1733
  })
1725
1734
  ).describe("Applied all-or-nothing: if any find has no match, nothing is written.")
@@ -1729,13 +1738,14 @@ tool(
1729
1738
  if ("error" in unlocked) return text(unlocked.error);
1730
1739
  const scene = await api().readSheet(workspace_id, sheet_id, unlocked.key);
1731
1740
  const elements = scene.elements;
1741
+ const collapse = (t) => t.replace(/\s+/g, " ").trim();
1732
1742
  const counts = /* @__PURE__ */ new Map();
1733
1743
  const changedTextIds = /* @__PURE__ */ new Set();
1734
1744
  for (const el of elements) {
1735
1745
  if (el.type !== "text") continue;
1736
1746
  const current = (el.originalText ?? el.text)?.trim();
1737
1747
  if (current === void 0) continue;
1738
- const edit = edits.find((e) => e.find.trim() === current);
1748
+ const edit = edits.find((e) => e.find.trim() === current) ?? edits.find((e) => collapse(e.find) === collapse(current));
1739
1749
  if (!edit) continue;
1740
1750
  el.text = edit.replace;
1741
1751
  el.originalText = edit.replace;
@@ -1754,7 +1764,7 @@ tool(
1754
1764
  const missed = edits.filter((e) => !counts.has(e.find));
1755
1765
  if (missed.length > 0) {
1756
1766
  return text(
1757
- "Nothing was written. These strings matched no text element:\n" + missed.map((e) => ` ${JSON.stringify(e.find)}`).join("\n") + "\n\nRun read_sheet and copy the text exactly as it appears there."
1767
+ "Nothing was written. These strings matched no text element:\n" + missed.map((e) => ` ${JSON.stringify(e.find)}`).join("\n") + "\n\nRun read_sheet and copy the text as it appears there. Line breaks and repeated spaces do not need to match \u2014 only the words do."
1758
1768
  );
1759
1769
  }
1760
1770
  const user = await currentUser();
@@ -1854,6 +1864,44 @@ function summariseLog(file) {
1854
1864
  }))
1855
1865
  };
1856
1866
  }
1867
+ function errors(file) {
1868
+ let rows;
1869
+ try {
1870
+ rows = (0, import_node_fs3.readFileSync)(file, "utf8").split("\n").filter(Boolean).flatMap((line) => {
1871
+ try {
1872
+ return [JSON.parse(line)];
1873
+ } catch {
1874
+ return [];
1875
+ }
1876
+ });
1877
+ } catch {
1878
+ console.error(`Could not read ${file}`);
1879
+ process.exit(2);
1880
+ }
1881
+ const failures = rows.filter((r) => r.ok === false || r.refused);
1882
+ if (failures.length === 0) {
1883
+ console.log("No failures or refusals recorded.");
1884
+ return;
1885
+ }
1886
+ const grouped = /* @__PURE__ */ new Map();
1887
+ for (const r of failures) {
1888
+ const reason = String(r.error ?? "refused (the tool declined, see its message)").slice(0, 120);
1889
+ const acc = grouped.get(reason) ?? { n: 0, tools: /* @__PURE__ */ new Set(), last: "" };
1890
+ acc.n++;
1891
+ acc.tools.add(String(r.tool));
1892
+ acc.last = String(r.ts);
1893
+ grouped.set(reason, acc);
1894
+ }
1895
+ console.log(`${failures.length} of ${rows.length} calls failed or were refused
1896
+ `);
1897
+ for (const [reason, a] of [...grouped.entries()].sort((x, y) => y[1].n - x[1].n)) {
1898
+ console.log(` ${a.n}x ${[...a.tools].join(", ")}`);
1899
+ console.log(` ${reason}`);
1900
+ console.log(` last seen ${a.last}
1901
+ `);
1902
+ }
1903
+ console.log(" This reads your local log and sends nothing.");
1904
+ }
1857
1905
  function stats(path, asJson) {
1858
1906
  const file = path ?? logPath();
1859
1907
  if (!file) {
@@ -1885,7 +1933,7 @@ function stats(path, asJson) {
1885
1933
  console.log(
1886
1934
  "\n No ids, account details, or diagram content above \u2014 safe to paste into a bug report."
1887
1935
  );
1888
- console.log(" Add --json for a machine-readable copy.");
1936
+ console.log(" Add --json for a machine-readable copy, or --errors to see why calls failed.");
1889
1937
  }
1890
1938
  function buildReport() {
1891
1939
  const file = logPath();
@@ -1922,15 +1970,29 @@ async function telemetry(action) {
1922
1970
  if (action === "on") {
1923
1971
  writeConfig({ telemetry: "on" });
1924
1972
  console.log(`Telemetry on. Usage is recorded to ${logPath()}`);
1925
- console.log("Roughly once a day, an aggregate of it is sent:\n");
1926
- console.log(
1927
- report ? JSON.stringify(report, null, 2) : " (no calls recorded yet \u2014 the first report goes out once you have used the tools)"
1928
- );
1929
- console.log("\nTurn it off any time with: drawpro-mcp telemetry off");
1973
+ const built = buildReport();
1974
+ if (!built) {
1975
+ console.log("\nNothing recorded yet. The first report goes out once you have used the tools.");
1976
+ console.log("Turn it off any time with: drawpro-mcp telemetry off");
1977
+ return;
1978
+ }
1979
+ console.log("\nSending this now, and roughly once a day after:\n");
1980
+ console.log(JSON.stringify(built, null, 2));
1981
+ const { ok, detail } = await sendReport(built);
1982
+ if (ok) {
1983
+ writeConfig({ lastReportAt: (/* @__PURE__ */ new Date()).toISOString() });
1984
+ console.log("\nSent.");
1985
+ } else {
1986
+ console.log(`
1987
+ Could not send it (${detail}). Telemetry is still on and it will retry.`);
1988
+ }
1989
+ console.log("Turn it off any time with: drawpro-mcp telemetry off");
1930
1990
  return;
1931
1991
  }
1932
- console.log(`Telemetry is ${telemetryEnabled() ? "ON" : "OFF"}.
1933
- `);
1992
+ const last = readConfig().lastReportAt;
1993
+ console.log(`Telemetry is ${telemetryEnabled() ? "ON" : "OFF"}.`);
1994
+ console.log(last ? `Last report sent ${last}.
1995
+ ` : "No report has been sent yet.\n");
1934
1996
  console.log("If enabled, this is the entire payload \u2014 tool counts and timings,");
1935
1997
  console.log("no account, no token, no workspace or sheet ids, nothing drawn:\n");
1936
1998
  console.log(
@@ -2094,7 +2156,16 @@ async function main() {
2094
2156
  }
2095
2157
  if (command === "stats") {
2096
2158
  const rest = process.argv.slice(3);
2097
- stats(rest.find((a) => !a.startsWith("--")), rest.includes("--json"));
2159
+ const file = rest.find((a) => !a.startsWith("--")) ?? logPath();
2160
+ if (rest.includes("--errors")) {
2161
+ if (!file) {
2162
+ console.error("No usage is being recorded, so there is nothing to explain.");
2163
+ process.exit(2);
2164
+ }
2165
+ errors(file);
2166
+ return;
2167
+ }
2168
+ stats(file, rest.includes("--json"));
2098
2169
  return;
2099
2170
  }
2100
2171
  if (command && command !== "serve") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drawpro/mcp",
3
- "version": "0.6.0",
3
+ "version": "0.6.3",
4
4
  "description": "MCP server for DrawPro \u2014 read and create end-to-end encrypted Excalidraw diagrams from Claude",
5
5
  "license": "MIT",
6
6
  "repository": {