zuiku-mcp 0.3.1 → 0.3.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/README.md CHANGED
@@ -6,6 +6,7 @@
6
6
 
7
7
  ## What it exposes
8
8
 
9
+ - `zuiku.version`: report the running package/server version and update hints
9
10
  - `zuiku.open`: open a markdown file, canonical Zuiku Markdown, or Mermaid-only content as a localhost editor session
10
11
  - `zuiku.read`: read current Markdown and hash before updating
11
12
  - `zuiku.apply`: write Mermaid-only or canonical Markdown back as normalized Zuiku Markdown with hash-based conflict protection
@@ -18,6 +19,32 @@
18
19
  npx -y zuiku-mcp
19
20
  ```
20
21
 
22
+ ## Version and update
23
+
24
+ Check the currently installed package version:
25
+
26
+ ```bash
27
+ npx -y zuiku-mcp --version
28
+ ```
29
+
30
+ Check the latest published npm version:
31
+
32
+ ```bash
33
+ npm view zuiku-mcp version
34
+ ```
35
+
36
+ Run the latest package one-shot:
37
+
38
+ ```bash
39
+ npx -y zuiku-mcp@latest
40
+ ```
41
+
42
+ Update a global install:
43
+
44
+ ```bash
45
+ npm install -g zuiku-mcp@latest
46
+ ```
47
+
21
48
  ## Typical usage
22
49
 
23
50
  - Codex CLI / Claude Code / Gemini CLI: register it as an MCP server command
@@ -35,9 +62,11 @@ npx -y zuiku-mcp
35
62
  ## Notes
36
63
 
37
64
  - Requires Node.js 20+
65
+ - `zuiku.version` can be called from MCP clients to confirm the running build and show update hints
38
66
  - For chat clients, `ZUIKU_MCP_OPEN_BROWSER=0` is usually the better default
39
67
  - `zuiku.apply(openAfterApply=true)` can request opening or reusing the local editor session after save
40
68
  - Discovery guides are exposed over MCP resources/prompts so AI clients can avoid external lookup
69
+ - After updating, restart the MCP client or IDE session so the new package is launched
41
70
  - During preview, CLI and manual MCP setup are the source of truth; optional IDE extension listings can be added on top later
42
71
  - Install guide: `https://zuiku.dev/install`
43
72
  - Docs: `https://zuiku.dev/docs`
package/bin/zuiku-mcp.mjs CHANGED
@@ -1,4 +1,14 @@
1
1
  #!/usr/bin/env node
2
+ import { createRequire } from "node:module";
3
+
4
+ const require = createRequire(import.meta.url);
5
+ const packageJson = require("../package.json");
6
+
7
+ if (process.argv.includes("--version") || process.argv.includes("-v") || process.argv[2] === "version") {
8
+ process.stdout.write(`${packageJson.version}\n`);
9
+ process.exit(0);
10
+ }
11
+
2
12
  const entryPath = new URL("../dist/zuiku-mcp-server.mjs", import.meta.url);
3
13
 
4
14
  try {
@@ -5,6 +5,16 @@ import path4 from "node:path";
5
5
 
6
6
  // ../../apps/web/src/lib/mcpContract.ts
7
7
  var MCP_TOOL_DEFINITIONS = [
8
+ {
9
+ name: "zuiku.version",
10
+ description: "Return the current zuiku-mcp package/server version and update hints. Use this to confirm which MCP build is currently running.",
11
+ inputSchema: {
12
+ type: "object",
13
+ properties: {},
14
+ required: [],
15
+ additionalProperties: false
16
+ }
17
+ },
8
18
  {
9
19
  name: "zuiku.open",
10
20
  description: "Open a markdown file, canonical Zuiku Markdown, or Mermaid-only text in a local editor session. Mermaid-only input is normalized on save, and the result includes editorUrl.",
@@ -99,6 +109,18 @@ function validateMcpToolRequest(input) {
99
109
  return invalidRequest("args must be an object");
100
110
  }
101
111
  const args = input.args;
112
+ if (tool === "zuiku.version") {
113
+ if (!hasOnlyKeys(args, [])) {
114
+ return invalidRequest("zuiku.version.args contains unknown keys");
115
+ }
116
+ return {
117
+ ok: true,
118
+ value: {
119
+ tool,
120
+ args: {}
121
+ }
122
+ };
123
+ }
102
124
  if (tool === "zuiku.open") {
103
125
  if (!hasOnlyKeys(args, ["path", "markdown", "title", "openInBrowser"])) {
104
126
  return invalidRequest("zuiku.open.args contains unknown keys");
@@ -5689,6 +5711,56 @@ function parseBearerToken(value) {
5689
5711
  return match?.[1]?.trim();
5690
5712
  }
5691
5713
 
5714
+ // package.json
5715
+ var package_default = {
5716
+ name: "zuiku-mcp",
5717
+ version: "0.3.2",
5718
+ description: "Round-trip Diagram Editor MCP server for Zuiku",
5719
+ type: "module",
5720
+ license: "UNLICENSED",
5721
+ homepage: "https://zuiku.dev/docs",
5722
+ bugs: {
5723
+ url: "https://github.com/kamotami/zuiku/issues"
5724
+ },
5725
+ repository: {
5726
+ type: "git",
5727
+ url: "git+https://github.com/kamotami/zuiku.git",
5728
+ directory: "packages/zuiku-mcp"
5729
+ },
5730
+ keywords: [
5731
+ "zuiku",
5732
+ "mcp",
5733
+ "diagram",
5734
+ "mermaid",
5735
+ "markdown",
5736
+ "round-trip"
5737
+ ],
5738
+ engines: {
5739
+ node: ">=20.0.0"
5740
+ },
5741
+ bin: {
5742
+ "zuiku-mcp": "bin/zuiku-mcp.mjs"
5743
+ },
5744
+ scripts: {
5745
+ build: "node ./scripts/build.mjs",
5746
+ prepack: "npm run build"
5747
+ },
5748
+ files: [
5749
+ "bin",
5750
+ "dist",
5751
+ "README.md"
5752
+ ],
5753
+ publishConfig: {
5754
+ access: "public"
5755
+ }
5756
+ };
5757
+
5758
+ // ../../apps/web/src/lib/mcpServerMeta.ts
5759
+ var ZUIKU_MCP_PACKAGE_NAME = package_default.name;
5760
+ var ZUIKU_MCP_PACKAGE_VERSION = package_default.version;
5761
+ var ZUIKU_MCP_SERVER_NAME = "zuiku-mcp-min";
5762
+ var ZUIKU_MCP_PROTOCOL_VERSION = "2024-11-05";
5763
+
5692
5764
  // ../../apps/web/src/lib/mcpStdioRuntime.ts
5693
5765
  var McpToolExecutionError = class extends Error {
5694
5766
  code;
@@ -5800,6 +5872,26 @@ function warnHookFailure(tool, hookName, error) {
5800
5872
  }
5801
5873
  async function executeValidatedToolCall(request, ownerId, context) {
5802
5874
  switch (request.tool) {
5875
+ case "zuiku.version": {
5876
+ return {
5877
+ packageName: ZUIKU_MCP_PACKAGE_NAME,
5878
+ packageVersion: ZUIKU_MCP_PACKAGE_VERSION,
5879
+ serverName: ZUIKU_MCP_SERVER_NAME,
5880
+ serverVersion: ZUIKU_MCP_PACKAGE_VERSION,
5881
+ protocolVersion: ZUIKU_MCP_PROTOCOL_VERSION,
5882
+ entryCommand: "npx -y zuiku-mcp",
5883
+ versionCommands: {
5884
+ cli: "npx -y zuiku-mcp --version",
5885
+ npm: "npm view zuiku-mcp version"
5886
+ },
5887
+ updateCommands: {
5888
+ oneShotLatest: "npx -y zuiku-mcp@latest",
5889
+ globalInstallLatest: "npm install -g zuiku-mcp@latest"
5890
+ },
5891
+ restartRequiredAfterUpdate: true,
5892
+ message: "Restart your MCP client or IDE session after updating so the new package is launched."
5893
+ };
5894
+ }
5803
5895
  case "zuiku.open": {
5804
5896
  const source = request.args.path ? "path" : "markdown";
5805
5897
  if (source === "path") {
@@ -5813,13 +5905,18 @@ async function executeValidatedToolCall(request, ownerId, context) {
5813
5905
  }
5814
5906
  const markdown2 = await readFile(filePath, "utf8");
5815
5907
  if (context.localEditorHost) {
5816
- const opened = await context.localEditorHost.openSession({
5817
- path: filePath,
5818
- openInBrowser: request.args.openInBrowser ?? context.defaultOpenInBrowser
5819
- });
5908
+ const opened = await openLocalEditorSessionOrThrow(
5909
+ context.localEditorHost,
5910
+ {
5911
+ path: filePath,
5912
+ openInBrowser: request.args.openInBrowser ?? context.defaultOpenInBrowser
5913
+ },
5914
+ "failed to open Zuiku editor session"
5915
+ );
5820
5916
  return {
5821
5917
  ...opened,
5822
- modifiedAt: fileStat.mtime.toISOString()
5918
+ modifiedAt: fileStat.mtime.toISOString(),
5919
+ message: buildEditorSessionMessage(opened)
5823
5920
  };
5824
5921
  }
5825
5922
  return {
@@ -5844,12 +5941,19 @@ async function executeValidatedToolCall(request, ownerId, context) {
5844
5941
  throw new McpToolExecutionError("limit_exceeded", shapeError);
5845
5942
  }
5846
5943
  if (context.localEditorHost) {
5847
- const opened = await context.localEditorHost.openSession({
5848
- markdown: normalizedMarkdown,
5849
- title: request.args.title,
5850
- openInBrowser: request.args.openInBrowser ?? context.defaultOpenInBrowser
5851
- });
5852
- return opened;
5944
+ const opened = await openLocalEditorSessionOrThrow(
5945
+ context.localEditorHost,
5946
+ {
5947
+ markdown: normalizedMarkdown,
5948
+ title: request.args.title,
5949
+ openInBrowser: request.args.openInBrowser ?? context.defaultOpenInBrowser
5950
+ },
5951
+ "failed to open Zuiku editor session"
5952
+ );
5953
+ return {
5954
+ ...opened,
5955
+ message: buildEditorSessionMessage(opened)
5956
+ };
5853
5957
  }
5854
5958
  return {
5855
5959
  source,
@@ -5972,10 +6076,14 @@ async function handleApply(request, ownerId, context) {
5972
6076
  let sessionId;
5973
6077
  let editorUrl;
5974
6078
  if (request.args.openAfterApply && context.localEditorHost) {
5975
- const opened = await context.localEditorHost.openSession({
5976
- path: filePath,
5977
- openInBrowser: true
5978
- });
6079
+ const opened = await openLocalEditorSessionOrThrow(
6080
+ context.localEditorHost,
6081
+ {
6082
+ path: filePath,
6083
+ openInBrowser: true
6084
+ },
6085
+ "saved markdown but failed to open the Zuiku editor session"
6086
+ );
5979
6087
  sessionId = opened.sessionId;
5980
6088
  editorUrl = opened.editorUrl;
5981
6089
  openedInEditor = opened.openedInEditor;
@@ -5990,7 +6098,13 @@ async function handleApply(request, ownerId, context) {
5990
6098
  ...sessionId ? { sessionId } : {},
5991
6099
  ...editorUrl ? { editorUrl } : {},
5992
6100
  openedInEditor,
5993
- reusedSession
6101
+ reusedSession,
6102
+ message: buildApplyResultMessage({
6103
+ openAfterApply: request.args.openAfterApply ?? false,
6104
+ editorUrl,
6105
+ openedInEditor,
6106
+ reusedSession
6107
+ })
5994
6108
  };
5995
6109
  }
5996
6110
  async function buildMarkdownForApplyMode(request, filePath) {
@@ -6138,6 +6252,58 @@ async function resolvePathWithinAllowedRootsOrThrow(rawPath, allowedRoots2, opti
6138
6252
  }
6139
6253
  return resolved;
6140
6254
  }
6255
+ function openLocalEditorSessionOrThrow(host, request, fallbackMessage) {
6256
+ return host.openSession(request).catch((error) => {
6257
+ throw normalizeLocalEditorHostError(error, fallbackMessage);
6258
+ });
6259
+ }
6260
+ function normalizeLocalEditorHostError(error, fallbackMessage) {
6261
+ if (error instanceof McpToolExecutionError) {
6262
+ return error;
6263
+ }
6264
+ if (isStructuredToolError(error)) {
6265
+ return new McpToolExecutionError(error.code, error.message, error.data);
6266
+ }
6267
+ if (error instanceof Error) {
6268
+ return new McpToolExecutionError("invalid_request", error.message);
6269
+ }
6270
+ return new McpToolExecutionError("invalid_request", fallbackMessage);
6271
+ }
6272
+ function isStructuredToolError(value) {
6273
+ if (typeof value !== "object" || value === null) {
6274
+ return false;
6275
+ }
6276
+ const candidate = value;
6277
+ return typeof candidate.message === "string" && isMcpErrorCode(candidate.code) && (candidate.data === void 0 || typeof candidate.data === "object" && candidate.data !== null && !Array.isArray(candidate.data));
6278
+ }
6279
+ function isMcpErrorCode(value) {
6280
+ return value === "invalid_request" || value === "parse_error" || value === "conflict" || value === "limit_exceeded" || value === "not_found";
6281
+ }
6282
+ function buildEditorSessionMessage(opened) {
6283
+ if (opened.openedInEditor && opened.reusedSession) {
6284
+ return "Reused the existing Zuiku editor session and requested it to open.";
6285
+ }
6286
+ if (opened.openedInEditor) {
6287
+ return "Requested the Zuiku editor session to open.";
6288
+ }
6289
+ if (opened.reusedSession) {
6290
+ return "Reused the existing Zuiku editor session. If your client did not open it automatically, use the editorUrl.";
6291
+ }
6292
+ return "Zuiku editor session is ready. If your client did not open it automatically, use the editorUrl.";
6293
+ }
6294
+ function buildApplyResultMessage(options) {
6295
+ if (!options.openAfterApply) {
6296
+ return "Saved canonical Zuiku Markdown.";
6297
+ }
6298
+ if (!options.editorUrl) {
6299
+ return "Saved canonical Zuiku Markdown. The editor session was not opened.";
6300
+ }
6301
+ return `Saved canonical Zuiku Markdown. ${buildEditorSessionMessage({
6302
+ editorUrl: options.editorUrl,
6303
+ openedInEditor: options.openedInEditor,
6304
+ reusedSession: options.reusedSession
6305
+ })}`;
6306
+ }
6141
6307
  function hashText(value) {
6142
6308
  return `sha256:${createHash("sha256").update(value).digest("hex")}`;
6143
6309
  }
@@ -6311,7 +6477,7 @@ function createMcpJsonRpcDispatcher(options) {
6311
6477
  jsonrpc: "2.0",
6312
6478
  id,
6313
6479
  result: {
6314
- content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
6480
+ content: [{ type: "text", text: formatToolSuccessText(toolName, payload) }],
6315
6481
  structuredContent: payload
6316
6482
  }
6317
6483
  };
@@ -6348,7 +6514,7 @@ function createMcpJsonRpcDispatcher(options) {
6348
6514
  function toolError(code, message, data) {
6349
6515
  return {
6350
6516
  isError: true,
6351
- content: [{ type: "text", text: `${code}: ${message}` }],
6517
+ content: [{ type: "text", text: formatToolErrorText(code, message, data) }],
6352
6518
  structuredContent: {
6353
6519
  code,
6354
6520
  message,
@@ -6356,6 +6522,44 @@ function toolError(code, message, data) {
6356
6522
  }
6357
6523
  };
6358
6524
  }
6525
+ function formatToolSuccessText(toolName, payload) {
6526
+ if (toolName === "zuiku.version" && isObjectRecord(payload)) {
6527
+ const packageVersion = asString4(payload.packageVersion);
6528
+ const cliCommand = asString4(asObject(payload.versionCommands)?.cli);
6529
+ if (packageVersion && cliCommand) {
6530
+ return `zuiku-mcp ${packageVersion}
6531
+ check: ${cliCommand}`;
6532
+ }
6533
+ }
6534
+ if ((toolName === "zuiku.open" || toolName === "zuiku.apply") && isObjectRecord(payload)) {
6535
+ const message = asString4(payload.message);
6536
+ const editorUrl = asString4(payload.editorUrl);
6537
+ const lines = [
6538
+ ...message ? [message] : [],
6539
+ ...editorUrl ? ["Open this URL if needed:", editorUrl] : []
6540
+ ];
6541
+ if (lines.length > 0) {
6542
+ return lines.join("\n");
6543
+ }
6544
+ }
6545
+ return JSON.stringify(payload, null, 2);
6546
+ }
6547
+ function formatToolErrorText(code, message, data) {
6548
+ const lines = [`${code}: ${message}`];
6549
+ const suggestion = asString4(data?.suggestion);
6550
+ const editorUrl = asString4(data?.editorUrl);
6551
+ if (suggestion) {
6552
+ lines.push(`hint: ${suggestion}`);
6553
+ }
6554
+ if (editorUrl) {
6555
+ lines.push("Open this URL if needed:");
6556
+ lines.push(editorUrl);
6557
+ }
6558
+ return lines.join("\n");
6559
+ }
6560
+ function isObjectRecord(value) {
6561
+ return typeof value === "object" && value !== null && !Array.isArray(value);
6562
+ }
6359
6563
  function asObject(value) {
6360
6564
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
6361
6565
  }
@@ -6401,12 +6605,14 @@ var LocalEditorHostImpl = class {
6401
6605
  sessionIdleTimeoutMs;
6402
6606
  tempFileTtlMs;
6403
6607
  cleanupIntervalMs;
6608
+ registryFilePath;
6404
6609
  sessions = /* @__PURE__ */ new Map();
6405
6610
  pathSessionIds = /* @__PURE__ */ new Map();
6406
6611
  server;
6407
6612
  listeningPort;
6408
6613
  tempDir;
6409
6614
  cleanupTimer;
6615
+ attachedBaseUrl;
6410
6616
  constructor(options) {
6411
6617
  this.host = options.host?.trim() || DEFAULT_HOST;
6412
6618
  this.port = options.port ?? DEFAULT_PORT;
@@ -6426,12 +6632,13 @@ var LocalEditorHostImpl = class {
6426
6632
  options.cleanupIntervalMs,
6427
6633
  DEFAULT_CLEANUP_INTERVAL_MS
6428
6634
  );
6635
+ this.registryFilePath = path3.join(tmpdir(), `zuiku-mcp-host-${slug2(`${this.host}-${this.port}`)}.json`);
6429
6636
  }
6430
6637
  getBaseUrl() {
6431
- return `http://${this.host}:${this.listeningPort}`;
6638
+ return this.attachedBaseUrl ?? `http://${this.host}:${this.listeningPort}`;
6432
6639
  }
6433
6640
  async ensureStarted() {
6434
- if (this.server?.listening) {
6641
+ if (this.server?.listening || this.attachedBaseUrl) {
6435
6642
  return;
6436
6643
  }
6437
6644
  const server = createServer((request, response) => {
@@ -6443,21 +6650,37 @@ var LocalEditorHostImpl = class {
6443
6650
  });
6444
6651
  });
6445
6652
  });
6446
- await new Promise((resolve, reject) => {
6447
- server.once("error", reject);
6448
- server.listen(this.port, this.host, () => {
6449
- server.off("error", reject);
6450
- resolve();
6653
+ try {
6654
+ await new Promise((resolve, reject) => {
6655
+ server.once("error", reject);
6656
+ server.listen(this.port, this.host, () => {
6657
+ server.off("error", reject);
6658
+ resolve();
6659
+ });
6451
6660
  });
6452
- });
6661
+ } catch (error) {
6662
+ if (await this.tryAttachToRunningHost(error)) {
6663
+ return;
6664
+ }
6665
+ if (isAddressInUseError(error)) {
6666
+ throw buildPortInUseJsonError(this.host, this.port, this.editorUrl);
6667
+ }
6668
+ throw error;
6669
+ }
6453
6670
  const address = server.address();
6454
6671
  if (address && typeof address === "object") {
6455
6672
  this.listeningPort = address.port;
6456
6673
  }
6457
6674
  this.server = server;
6675
+ this.attachedBaseUrl = void 0;
6676
+ await this.writeRegistryFile();
6458
6677
  this.startCleanupTimer();
6459
6678
  }
6460
6679
  async stop() {
6680
+ if (this.attachedBaseUrl && !this.server) {
6681
+ this.attachedBaseUrl = void 0;
6682
+ return;
6683
+ }
6461
6684
  this.stopCleanupTimer();
6462
6685
  if (this.server) {
6463
6686
  await new Promise((resolve, reject) => {
@@ -6471,6 +6694,8 @@ var LocalEditorHostImpl = class {
6471
6694
  }).catch(() => void 0);
6472
6695
  this.server = void 0;
6473
6696
  }
6697
+ this.attachedBaseUrl = void 0;
6698
+ await this.removeRegistryFileIfOwned().catch(() => void 0);
6474
6699
  const tempDir = this.tempDir;
6475
6700
  const records = [...this.sessions.values()];
6476
6701
  this.sessions.clear();
@@ -6487,6 +6712,9 @@ var LocalEditorHostImpl = class {
6487
6712
  }
6488
6713
  async openSession(request) {
6489
6714
  await this.ensureStarted();
6715
+ if (this.attachedBaseUrl && !this.server) {
6716
+ return this.openSessionViaAttachedHost(request);
6717
+ }
6490
6718
  const nowIso = (/* @__PURE__ */ new Date()).toISOString();
6491
6719
  const nowMs = Date.now();
6492
6720
  if (request.path && request.markdown) {
@@ -6595,6 +6823,108 @@ var LocalEditorHostImpl = class {
6595
6823
  reusedSession
6596
6824
  };
6597
6825
  }
6826
+ async tryAttachToRunningHost(error) {
6827
+ if (!isAddressInUseError(error)) {
6828
+ return false;
6829
+ }
6830
+ const registry = await this.readRegistryFile();
6831
+ if (!registry) {
6832
+ return false;
6833
+ }
6834
+ if (registry.baseUrl !== `http://${this.host}:${this.port}`) {
6835
+ return false;
6836
+ }
6837
+ const response = await fetch(`${registry.baseUrl}/health`).catch(() => void 0);
6838
+ if (!response?.ok) {
6839
+ await rm(this.registryFilePath, { force: true }).catch(() => void 0);
6840
+ return false;
6841
+ }
6842
+ const health = await response.json().catch(() => void 0);
6843
+ if (!health || health.ok !== true || health.host !== registry.baseUrl) {
6844
+ await rm(this.registryFilePath, { force: true }).catch(() => void 0);
6845
+ return false;
6846
+ }
6847
+ this.attachedBaseUrl = registry.baseUrl;
6848
+ this.sessionToken = registry.sessionToken;
6849
+ this.listeningPort = this.port;
6850
+ return true;
6851
+ }
6852
+ async openSessionViaAttachedHost(request) {
6853
+ const response = await fetch(`${this.getBaseUrl()}/sessions?mcpToken=${encodeURIComponent(this.sessionToken)}`, {
6854
+ method: "POST",
6855
+ headers: {
6856
+ "Content-Type": "application/json"
6857
+ },
6858
+ body: JSON.stringify({
6859
+ ...request.path ? { path: request.path } : {},
6860
+ ...typeof request.markdown === "string" ? { markdown: request.markdown } : {},
6861
+ ...request.title ? { title: request.title } : {},
6862
+ ...typeof request.openInBrowser === "boolean" ? { openInBrowser: request.openInBrowser } : {}
6863
+ })
6864
+ }).catch((error) => {
6865
+ const message = error instanceof Error ? error.message : "failed to reach running editor host";
6866
+ throw jsonError(503, "invalid_request", message);
6867
+ });
6868
+ const payload = await response.json().catch(() => ({}));
6869
+ if (!response.ok) {
6870
+ throw jsonError(
6871
+ response.status,
6872
+ payload.code ?? "invalid_request",
6873
+ payload.message ?? "failed to open session through running editor host"
6874
+ );
6875
+ }
6876
+ if (typeof payload.sessionId !== "string" || typeof payload.path !== "string" || typeof payload.hash !== "string" || typeof payload.bytes !== "number" || typeof payload.createdAt !== "string" || typeof payload.updatedAt !== "string" || typeof payload.editorUrl !== "string" || typeof payload.openedInEditor !== "boolean" || typeof payload.reusedSession !== "boolean" || payload.source !== "path" && payload.source !== "markdown") {
6877
+ throw jsonError(502, "invalid_request", "running editor host returned invalid session payload");
6878
+ }
6879
+ return {
6880
+ sessionId: payload.sessionId,
6881
+ source: payload.source,
6882
+ path: payload.path,
6883
+ hash: payload.hash,
6884
+ bytes: payload.bytes,
6885
+ createdAt: payload.createdAt,
6886
+ updatedAt: payload.updatedAt,
6887
+ editorUrl: payload.editorUrl,
6888
+ openedInEditor: payload.openedInEditor,
6889
+ reusedSession: payload.reusedSession
6890
+ };
6891
+ }
6892
+ async readRegistryFile() {
6893
+ const raw = await readFile2(this.registryFilePath, "utf8").catch(() => void 0);
6894
+ if (!raw) {
6895
+ return void 0;
6896
+ }
6897
+ try {
6898
+ const parsed = JSON.parse(raw);
6899
+ if (typeof parsed.baseUrl !== "string" || typeof parsed.sessionToken !== "string" || typeof parsed.editorUrl !== "string" || typeof parsed.ownerPid !== "number") {
6900
+ return void 0;
6901
+ }
6902
+ return {
6903
+ baseUrl: parsed.baseUrl,
6904
+ sessionToken: parsed.sessionToken,
6905
+ editorUrl: parsed.editorUrl,
6906
+ ownerPid: parsed.ownerPid
6907
+ };
6908
+ } catch {
6909
+ return void 0;
6910
+ }
6911
+ }
6912
+ async writeRegistryFile() {
6913
+ const payload = {
6914
+ baseUrl: this.getBaseUrl(),
6915
+ sessionToken: this.sessionToken,
6916
+ editorUrl: this.editorUrl,
6917
+ ownerPid: process.pid
6918
+ };
6919
+ await writeFile2(this.registryFilePath, JSON.stringify(payload), "utf8");
6920
+ }
6921
+ async removeRegistryFileIfOwned() {
6922
+ const registry = await this.readRegistryFile();
6923
+ if (!registry || registry.ownerPid !== process.pid) {
6924
+ return;
6925
+ }
6926
+ await rm(this.registryFilePath, { force: true }).catch(() => void 0);
6927
+ }
6598
6928
  async handleRequest(request, response) {
6599
6929
  try {
6600
6930
  this.ensureLoopbackClient(request);
@@ -7021,6 +7351,22 @@ function asString5(value) {
7021
7351
  function asBoolean2(value) {
7022
7352
  return typeof value === "boolean" ? value : void 0;
7023
7353
  }
7354
+ function isAddressInUseError(error) {
7355
+ return typeof error === "object" && error !== null && "code" in error && error.code === "EADDRINUSE";
7356
+ }
7357
+ function buildPortInUseJsonError(host, port, editorUrl) {
7358
+ return jsonError(
7359
+ 409,
7360
+ "conflict",
7361
+ `editor host port ${host}:${port} is already in use by another process`,
7362
+ {
7363
+ host,
7364
+ port,
7365
+ editorUrl,
7366
+ suggestion: "Stop the other process or set ZUIKU_MCP_EDITOR_PORT to a different free port."
7367
+ }
7368
+ );
7369
+ }
7024
7370
  function isJsonError(value) {
7025
7371
  return typeof value === "object" && value !== null && "status" in value && "code" in value;
7026
7372
  }
@@ -7102,9 +7448,9 @@ function isMainModule(currentModuleUrl) {
7102
7448
  }
7103
7449
 
7104
7450
  // ../../scripts/zuiku-mcp-server.ts
7105
- var SERVER_NAME = "zuiku-mcp-min";
7106
- var SERVER_VERSION = "0.3.0";
7107
- var PROTOCOL_VERSION = "2024-11-05";
7451
+ var SERVER_NAME = ZUIKU_MCP_SERVER_NAME;
7452
+ var SERVER_VERSION = ZUIKU_MCP_PACKAGE_VERSION;
7453
+ var PROTOCOL_VERSION = ZUIKU_MCP_PROTOCOL_VERSION;
7108
7454
  var allowedRoots = resolveAllowedRoots();
7109
7455
  var editorHostEnabled = parseBooleanEnv2(process.env.ZUIKU_MCP_ENABLE_EDITOR_HOST, true);
7110
7456
  var localEditorHost = editorHostEnabled ? createLocalEditorHost({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zuiku-mcp",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "Round-trip Diagram Editor MCP server for Zuiku",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",