@drawpro/mcp 0.2.1 → 0.3.0

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/README.md CHANGED
@@ -76,6 +76,7 @@ passcode itself.
76
76
  | `create_diagram` | no | returns a link to the new sheet |
77
77
  | `update_diagram` | no | replaces the sheet wholesale — regenerates layout |
78
78
  | `edit_sheet_text` | yes | rewrites named text in place, preserving every other element |
79
+ | `import_sheet` | no | writes a local .excalidraw file into a sheet, coordinates verbatim |
79
80
 
80
81
  ### Editing a sheet a person drew
81
82
 
@@ -97,8 +98,16 @@ and nothing else moves — reflowing neighbours would be the wholesale rewrite
97
98
  this tool exists to avoid. Any box it grows is named in the result, so overlap
98
99
  with a neighbour is easy to spot and one drag to fix.
99
100
 
100
- It cannot add elements. A correction that needs a new box or a new annotation
101
- line is still a manual edit.
101
+ It cannot add elements or move anything. A correction that needs a new box, a
102
+ repositioned column, or a re-anchored arrow is a geometry change, and no spec or
103
+ text edit can express one.
104
+
105
+ `import_sheet` covers that case. Point it at a local `.excalidraw` file and it
106
+ writes the scene in verbatim — every coordinate exactly as in the file. Because
107
+ the server runs on your machine it can simply read the file, so a geometry pass
108
+ can be done with a real editor, or by a model editing the file directly, and
109
+ then pushed without a clipboard round trip. The file's contents go into the
110
+ encrypted blob, never into the model's context.
102
111
 
103
112
  `update_diagram` remains the right tool for sheets this package generated, where
104
113
  regenerating the layout is the point.
package/dist/server.js CHANGED
@@ -24,6 +24,8 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
24
24
  ));
25
25
 
26
26
  // src/server.ts
27
+ var import_node_fs2 = require("node:fs");
28
+ var import_node_path2 = require("node:path");
27
29
  var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
28
30
  var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
29
31
  var import_zod = require("zod");
@@ -338,7 +340,7 @@ function forgetKey(email) {
338
340
 
339
341
  // ../client/src/prompt.ts
340
342
  function askHidden(question) {
341
- return new Promise((resolve) => {
343
+ return new Promise((resolve2) => {
342
344
  const input = process.stdin;
343
345
  process.stdout.write(question);
344
346
  if (!input.isTTY) {
@@ -347,7 +349,7 @@ function askHidden(question) {
347
349
  input.on("data", (chunk) => {
348
350
  buffered += chunk;
349
351
  });
350
- input.on("end", () => resolve(buffered.split("\n")[0].trim()));
352
+ input.on("end", () => resolve2(buffered.split("\n")[0].trim()));
351
353
  return;
352
354
  }
353
355
  const wasRaw = input.isRaw;
@@ -363,7 +365,7 @@ function askHidden(question) {
363
365
  if (result === null) {
364
366
  process.exit(130);
365
367
  }
366
- resolve(result);
368
+ resolve2(result);
367
369
  };
368
370
  const onData = (chunk) => {
369
371
  for (const ch of chunk) {
@@ -1638,6 +1640,45 @@ ${sheetUrl(workspace_id, sheet_id)}`
1638
1640
  );
1639
1641
  }
1640
1642
  );
1643
+ server.tool(
1644
+ "import_sheet",
1645
+ "Write a local .excalidraw file into a sheet, exactly as it is. Use this when geometry has to change \u2014 repositioning, resizing, re-anchoring arrows \u2014 which no spec can express and edit_sheet_text will not touch. Every coordinate in the file is preserved verbatim.",
1646
+ {
1647
+ workspace_id: import_zod.z.string(),
1648
+ file_path: import_zod.z.string().describe("Path to a .excalidraw file on this machine"),
1649
+ name: import_zod.z.string().describe("Sheet name"),
1650
+ sheet_id: import_zod.z.string().optional().describe("Omit to create a new sheet; supply it to replace an existing one")
1651
+ },
1652
+ async ({ workspace_id, file_path, name, sheet_id }) => {
1653
+ let parsed;
1654
+ try {
1655
+ parsed = JSON.parse((0, import_node_fs2.readFileSync)((0, import_node_path2.resolve)(file_path), "utf8"));
1656
+ } catch (err) {
1657
+ return text(`Could not read ${file_path}: ${err.message}`);
1658
+ }
1659
+ const elements = parsed.elements;
1660
+ if (!Array.isArray(elements) || elements.length === 0) {
1661
+ return text(`${file_path} has no "elements" array \u2014 is it an .excalidraw file?`);
1662
+ }
1663
+ const issues = validateScene(elements);
1664
+ const noted = issues.length ? `
1665
+
1666
+ ${issues.length} thing(s) worth a look (imported anyway):
1667
+ ` + issues.slice(0, 5).map((i) => ` ${i.level}: ${i.message}`).join("\n") : "";
1668
+ const user = await currentUser();
1669
+ const payload = {
1670
+ name,
1671
+ elements,
1672
+ appState: parsed.appState ?? {}
1673
+ };
1674
+ const sheet = sheet_id ? await api().updateSheet(workspace_id, sheet_id, payload, user.publicKey) : await api().createSheet(workspace_id, payload, user.publicKey);
1675
+ const id = sheet_id ?? sheet.id;
1676
+ return text(
1677
+ `${sheet_id ? "Replaced" : "Created"} "${name}" from ${file_path} \u2014 ${elements.length} elements, every coordinate as in the file.
1678
+ ${sheetUrl(workspace_id, id)}${noted}`
1679
+ );
1680
+ }
1681
+ );
1641
1682
  async function main() {
1642
1683
  const command = process.argv[2];
1643
1684
  if (command === "login") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drawpro/mcp",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
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": {