@excaliwow/mcp 0.2.0 → 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 +104 -25
- package/dist/bin.js +138 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,7 +7,26 @@ client (Claude Desktop, Claude Code, etc.) can launch it with `npx`.
|
|
|
7
7
|
|
|
8
8
|
## Install
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
First mint a Personal Access Token at https://excaliwow.com/app/settings
|
|
11
|
+
(Settings → Developer / API tokens) with **`read` + `write`** capabilities —
|
|
12
|
+
enough for five of the seven tools. Add **`delete`** only if you want the agent
|
|
13
|
+
to trash and restore diagrams (see [Security notes](#security-notes)). Pass it as
|
|
14
|
+
`EXCALIWOW_TOKEN`.
|
|
15
|
+
|
|
16
|
+
### Claude Code (CLI)
|
|
17
|
+
|
|
18
|
+
One command. `--scope local` stores the server in your own settings, so the
|
|
19
|
+
token never lands in a file you might commit:
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
claude mcp add excaliwow --scope local \
|
|
23
|
+
--env EXCALIWOW_TOKEN=excw_pat_… \
|
|
24
|
+
-- npx -y @excaliwow/mcp
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Claude Desktop (and other JSON-config clients)
|
|
28
|
+
|
|
29
|
+
Add it to your client's config file (e.g. Claude Desktop's
|
|
11
30
|
`claude_desktop_config.json`, or your client's MCP settings — see your client's
|
|
12
31
|
MCP setup docs). **Pin the version** — `npx -y` otherwise always pulls the newest
|
|
13
32
|
release, which is an avoidable supply-chain surface for a process that holds a
|
|
@@ -18,7 +37,7 @@ token to your account:
|
|
|
18
37
|
"mcpServers": {
|
|
19
38
|
"excaliwow": {
|
|
20
39
|
"command": "npx",
|
|
21
|
-
"args": ["-y", "@excaliwow/mcp@0.
|
|
40
|
+
"args": ["-y", "@excaliwow/mcp@0.3.0"],
|
|
22
41
|
"env": {
|
|
23
42
|
"EXCALIWOW_TOKEN": "excw_pat_…"
|
|
24
43
|
}
|
|
@@ -27,30 +46,80 @@ token to your account:
|
|
|
27
46
|
}
|
|
28
47
|
```
|
|
29
48
|
|
|
30
|
-
`EXCALIWOW_TOKEN`
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
49
|
+
The server reads `EXCALIWOW_TOKEN` per call from the environment (or, if you also
|
|
50
|
+
use `@excaliwow/cli` and have run `excaliwow auth login`, that stored login) and
|
|
51
|
+
never writes the token to disk itself. Bump the pinned version deliberately when
|
|
52
|
+
you've reviewed a new release.
|
|
53
|
+
|
|
54
|
+
## Troubleshooting
|
|
55
|
+
|
|
56
|
+
**`npx -y @excaliwow/mcp@0.3.0` fails with `ENOENT … /@excaliwow/mcp@0.3.0/package.json`.**
|
|
57
|
+
On some npm/Node versions, `npx` misreads a scoped package + `@version` spec as a
|
|
58
|
+
local directory. It's an upstream npm bug (it reproduces with other scoped
|
|
59
|
+
packages, e.g. `@modelcontextprotocol/server-filesystem@1.0.0`), not an Excaliwow one. Either
|
|
60
|
+
use the unversioned spec `npx -y @excaliwow/mcp` (as the Claude Code command
|
|
61
|
+
above does), or pin safely by installing once and pointing the client at the
|
|
62
|
+
binary:
|
|
63
|
+
|
|
64
|
+
```sh
|
|
65
|
+
npm i -g @excaliwow/mcp@0.3.0
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
```json
|
|
69
|
+
{
|
|
70
|
+
"mcpServers": {
|
|
71
|
+
"excaliwow": {
|
|
72
|
+
"command": "excaliwow-mcp",
|
|
73
|
+
"env": { "EXCALIWOW_TOKEN": "excw_pat_…" }
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
With Claude Code: `claude mcp add excaliwow --scope local --env EXCALIWOW_TOKEN=… -- excaliwow-mcp`.
|
|
80
|
+
|
|
81
|
+
**"Not authenticated" / 401 / the agent's tool calls fail.** The server starts
|
|
82
|
+
even without a token (so it can list its tools), so a missing or invalid
|
|
83
|
+
`EXCALIWOW_TOKEN` only surfaces when the agent first calls a tool. Starting with
|
|
84
|
+
no token prints a one-line `EXCALIWOW_TOKEN is not set` warning to **stderr**. To
|
|
85
|
+
check a token directly, run the health probe — it makes one authenticated read
|
|
86
|
+
and prints a clear verdict (`ok`, `401 — token is invalid or expired`, or
|
|
87
|
+
unreachable):
|
|
88
|
+
|
|
89
|
+
```sh
|
|
90
|
+
npx -y @excaliwow/mcp --health
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### CLI flags
|
|
94
|
+
|
|
95
|
+
| Flag | Effect |
|
|
96
|
+
| ----------- | ------------------------------------------------------------------ |
|
|
97
|
+
| `--health` | Check the token + API reachability, then exit (0 ok, 1 bad token). |
|
|
98
|
+
| `--version` | Print the installed version and exit. |
|
|
99
|
+
| `--help` | Print usage (flags + env vars) and exit. |
|
|
37
100
|
|
|
38
101
|
## Tools
|
|
39
102
|
|
|
40
|
-
|
|
103
|
+
Seven tools, scoped to safe agent use:
|
|
41
104
|
|
|
42
|
-
| Tool | What it does |
|
|
43
|
-
| ------------------ | --------------------------------------------------------------------------- |
|
|
44
|
-
| `generate_diagram` | Create a diagram from the high-level node/edge DSL; returns the editor URL. |
|
|
45
|
-
| `read_diagram` | Compact summary (title + per-type element counts) **plus** a rendered PNG. |
|
|
46
|
-
| `list_diagrams` | Page through your diagrams.
|
|
47
|
-
| `move_diagram` | Move a diagram to a folder (or to root). |
|
|
48
|
-
| `edit_diagram` | Additively merge a DSL fragment (add nodes/edges, update node style/label). |
|
|
105
|
+
| Tool | Capability | What it does |
|
|
106
|
+
| ------------------ | ---------- | --------------------------------------------------------------------------- |
|
|
107
|
+
| `generate_diagram` | `write` | Create a diagram from the high-level node/edge DSL; returns the editor URL. |
|
|
108
|
+
| `read_diagram` | `read` | Compact summary (title + per-type element counts) **plus** a rendered PNG. |
|
|
109
|
+
| `list_diagrams` | `read` | Page through your diagrams (`filter: active \| trash`). |
|
|
110
|
+
| `move_diagram` | `write` | Move a diagram to a folder (or to root). |
|
|
111
|
+
| `edit_diagram` | `write` | Additively merge a DSL fragment (add nodes/edges, update node style/label). |
|
|
112
|
+
| `trash_diagram` | `delete` | Soft-delete a diagram to trash. **Reversible** (see `restore_diagram`). |
|
|
113
|
+
| `restore_diagram` | `delete` | Restore a trashed diagram, reopening it at its original id and URL. |
|
|
49
114
|
|
|
50
115
|
`read_diagram` returns a summary + image, **never** the raw scene JSON, to keep
|
|
51
|
-
context small.
|
|
52
|
-
|
|
53
|
-
|
|
116
|
+
context small. `trash_diagram` / `restore_diagram` are a **reversible** pair
|
|
117
|
+
gated on the `delete` capability — registered always, they return a clean
|
|
118
|
+
`insufficient_scope` error (changing nothing) unless the token carries `delete`,
|
|
119
|
+
so a `read` + `write` token can't trash anything. Hard-delete/purge and making a
|
|
120
|
+
diagram publicly shareable are deliberately **not** agent tools — those are
|
|
121
|
+
irreversible, so a misled agent can't destroy or expose your diagram. Purge or
|
|
122
|
+
publish from the dashboard or the CLI.
|
|
54
123
|
|
|
55
124
|
### DSL discovery
|
|
56
125
|
|
|
@@ -67,14 +136,24 @@ resource at **`excaliwow://dsl/reference`**.
|
|
|
67
136
|
|
|
68
137
|
## Security notes
|
|
69
138
|
|
|
70
|
-
- **
|
|
71
|
-
|
|
139
|
+
- **Keep the token out of anything you commit.** A project-scoped config that
|
|
140
|
+
lives in the repo (a committed `.mcp.json`, or `claude mcp add --scope
|
|
141
|
+
project`) puts the token into git history. Use a user- or local-scoped config
|
|
142
|
+
(`--scope local`), or reference an environment variable instead of pasting the
|
|
143
|
+
literal token.
|
|
144
|
+
- **Mint with `read` + `write` (add `delete` only if you want trash/restore).**
|
|
145
|
+
Five of the seven tools need just `read` + `write`; a `read` + `write` PAT can
|
|
146
|
+
neither expose your diagrams publicly nor delete them, even if the agent is
|
|
147
|
+
misled (`trash_diagram` / `restore_diagram` simply return `insufficient_scope`
|
|
148
|
+
and do nothing). Add the `delete` capability only if you want the agent to be
|
|
149
|
+
able to soft-delete and restore — it's reversible, but it's still a capability
|
|
150
|
+
to grant deliberately. Pick capabilities specifically rather than the coarse
|
|
151
|
+
`read-write` preset, which additionally grants `publish` (and `delete`).
|
|
152
|
+
- **Pin the version** in your client config rather than floating on `@latest`,
|
|
153
|
+
and review release notes before bumping.
|
|
72
154
|
- The token is a credential to your account. It lives only in your MCP client
|
|
73
155
|
config / environment; this server does not persist it. Treat that config the
|
|
74
156
|
way you'd treat any secrets file.
|
|
75
|
-
- The token authorizes the same operations as a `read-write` PAT used by the
|
|
76
|
-
REST API and CLI — there is no separate per-capability scope, so treat it as
|
|
77
|
-
full read-write access to your account.
|
|
78
157
|
|
|
79
158
|
## License
|
|
80
159
|
|
package/dist/bin.js
CHANGED
|
@@ -3,10 +3,6 @@
|
|
|
3
3
|
// src/bin.ts
|
|
4
4
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
5
|
|
|
6
|
-
// src/server.ts
|
|
7
|
-
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
8
|
-
import { z as z2 } from "zod";
|
|
9
|
-
|
|
10
6
|
// ../core/src/config.ts
|
|
11
7
|
import { chmodSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from "fs";
|
|
12
8
|
import { homedir } from "os";
|
|
@@ -209,6 +205,28 @@ function moveDiagram(ctx, id, folderId) {
|
|
|
209
205
|
fetchImpl: ctx.fetchImpl
|
|
210
206
|
});
|
|
211
207
|
}
|
|
208
|
+
function deleteDiagram(ctx, id) {
|
|
209
|
+
return request({
|
|
210
|
+
method: "DELETE",
|
|
211
|
+
path: `${PREFIX}/diagrams/${encodeURIComponent(id)}`,
|
|
212
|
+
token: ctx.token,
|
|
213
|
+
baseUrl: ctx.baseUrl,
|
|
214
|
+
fetchImpl: ctx.fetchImpl
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
function restoreDiagram(ctx, id) {
|
|
218
|
+
return request({
|
|
219
|
+
method: "POST",
|
|
220
|
+
path: `${PREFIX}/diagrams/${encodeURIComponent(id)}/restore`,
|
|
221
|
+
token: ctx.token,
|
|
222
|
+
baseUrl: ctx.baseUrl,
|
|
223
|
+
fetchImpl: ctx.fetchImpl
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// src/server.ts
|
|
228
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
229
|
+
import { z as z2 } from "zod";
|
|
212
230
|
|
|
213
231
|
// src/bootstrap.ts
|
|
214
232
|
var DSL_BOOTSTRAP_EXAMPLE = {
|
|
@@ -426,6 +444,9 @@ var EditFragmentZ = z.object({
|
|
|
426
444
|
updateNodes: z.array(UpdateNodePatchZ).optional()
|
|
427
445
|
});
|
|
428
446
|
|
|
447
|
+
// src/version.ts
|
|
448
|
+
var VERSION = "0.3.0";
|
|
449
|
+
|
|
429
450
|
// src/server.ts
|
|
430
451
|
function textResult(text) {
|
|
431
452
|
return { content: [{ type: "text", text }] };
|
|
@@ -467,7 +488,7 @@ function elementBreakdown(detail) {
|
|
|
467
488
|
return { total: elements.length, byType };
|
|
468
489
|
}
|
|
469
490
|
function createServer(deps) {
|
|
470
|
-
const server2 = new McpServer({ name: "excaliwow", version:
|
|
491
|
+
const server2 = new McpServer({ name: "excaliwow", version: VERSION });
|
|
471
492
|
server2.registerTool(
|
|
472
493
|
"generate_diagram",
|
|
473
494
|
{
|
|
@@ -622,6 +643,40 @@ preview fidelity: ${png.quality} \u2014 faithful renderer unavailable; layout/fo
|
|
|
622
643
|
}
|
|
623
644
|
}
|
|
624
645
|
);
|
|
646
|
+
server2.registerTool(
|
|
647
|
+
"trash_diagram",
|
|
648
|
+
{
|
|
649
|
+
title: "Trash a diagram (soft delete)",
|
|
650
|
+
description: "Soft-delete a diagram to trash. REVERSIBLE \u2014 restore_diagram brings it back, and it stays visible under list_diagrams with filter='trash'. Use this to clean up diagrams you no longer want (e.g. earlier drafts from iterating). Requires a Personal Access Token with the `delete` capability; without it the call returns a clean 'insufficient_scope' error and changes nothing. Idempotent on an already-trashed id.",
|
|
651
|
+
inputSchema: { id: z2.string() }
|
|
652
|
+
},
|
|
653
|
+
async ({ id }) => {
|
|
654
|
+
try {
|
|
655
|
+
const ctx = buildCtx(deps);
|
|
656
|
+
await deleteDiagram(ctx, id);
|
|
657
|
+
return textResult(JSON.stringify({ id, trashed: true }));
|
|
658
|
+
} catch (err) {
|
|
659
|
+
return toErrorResult(err);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
);
|
|
663
|
+
server2.registerTool(
|
|
664
|
+
"restore_diagram",
|
|
665
|
+
{
|
|
666
|
+
title: "Restore a diagram from trash",
|
|
667
|
+
description: "Restore a soft-deleted diagram from trash, reopening it in the editor at its original id and url. The inverse of trash_diagram. Requires a Personal Access Token with the `delete` capability; without it the call returns a clean 'insufficient_scope' error and changes nothing. Idempotent on an already-active id.",
|
|
668
|
+
inputSchema: { id: z2.string() }
|
|
669
|
+
},
|
|
670
|
+
async ({ id }) => {
|
|
671
|
+
try {
|
|
672
|
+
const ctx = buildCtx(deps);
|
|
673
|
+
await restoreDiagram(ctx, id);
|
|
674
|
+
return textResult(JSON.stringify({ id, restored: true }));
|
|
675
|
+
} catch (err) {
|
|
676
|
+
return toErrorResult(err);
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
);
|
|
625
680
|
server2.registerResource(
|
|
626
681
|
"dsl-reference",
|
|
627
682
|
DSL_REFERENCE_URI,
|
|
@@ -640,5 +695,83 @@ preview fidelity: ${png.quality} \u2014 faithful renderer unavailable; layout/fo
|
|
|
640
695
|
}
|
|
641
696
|
|
|
642
697
|
// src/bin.ts
|
|
698
|
+
var argv = process.argv.slice(2);
|
|
699
|
+
var has = (...flags) => argv.some((a) => flags.includes(a));
|
|
700
|
+
if (has("--version", "-v")) {
|
|
701
|
+
process.stdout.write(`${VERSION}
|
|
702
|
+
`);
|
|
703
|
+
process.exit(0);
|
|
704
|
+
}
|
|
705
|
+
if (has("--help", "-h")) {
|
|
706
|
+
process.stdout.write(
|
|
707
|
+
[
|
|
708
|
+
`excaliwow-mcp ${VERSION} \u2014 Excaliwow Model Context Protocol server (stdio).`,
|
|
709
|
+
"",
|
|
710
|
+
"An MCP client (Claude Desktop, Claude Code, \u2026) launches this on demand; you",
|
|
711
|
+
"do not normally run it by hand. The flags below are for setup/debugging.",
|
|
712
|
+
"",
|
|
713
|
+
"Usage:",
|
|
714
|
+
" excaliwow-mcp Start the MCP server on stdio (what a client runs).",
|
|
715
|
+
" excaliwow-mcp --health Check the token + API reachability, then exit.",
|
|
716
|
+
" excaliwow-mcp --version Print the version and exit.",
|
|
717
|
+
" excaliwow-mcp --help Print this help and exit.",
|
|
718
|
+
"",
|
|
719
|
+
"Environment:",
|
|
720
|
+
" EXCALIWOW_TOKEN Personal Access Token with read + write. Required.",
|
|
721
|
+
" Mint one at https://excaliwow.com/app/settings.",
|
|
722
|
+
" EXCALIWOW_API_URL API origin. Defaults to https://excaliwow.com.",
|
|
723
|
+
""
|
|
724
|
+
].join("\n")
|
|
725
|
+
);
|
|
726
|
+
process.exit(0);
|
|
727
|
+
}
|
|
728
|
+
if (has("--health")) {
|
|
729
|
+
process.exit(await health());
|
|
730
|
+
}
|
|
731
|
+
if (!resolveToken()) {
|
|
732
|
+
process.stderr.write(
|
|
733
|
+
"[excaliwow-mcp] warning: EXCALIWOW_TOKEN is not set (and no `excaliwow auth login` config was found). The server will start, but every tool call will fail with an auth error until you set a Personal Access Token. See https://excaliwow.com/docs/mcp\n"
|
|
734
|
+
);
|
|
735
|
+
}
|
|
643
736
|
var server = createServer();
|
|
644
737
|
await server.connect(new StdioServerTransport());
|
|
738
|
+
async function health() {
|
|
739
|
+
const token = resolveToken();
|
|
740
|
+
const baseUrl = resolveBaseUrl({});
|
|
741
|
+
if (!token) {
|
|
742
|
+
process.stderr.write(
|
|
743
|
+
"[excaliwow-mcp] health: EXCALIWOW_TOKEN is not set \u2014 no token to check.\n"
|
|
744
|
+
);
|
|
745
|
+
return 1;
|
|
746
|
+
}
|
|
747
|
+
try {
|
|
748
|
+
await listDiagrams({ token, baseUrl }, { limit: 1 });
|
|
749
|
+
process.stderr.write(`[excaliwow-mcp] health: ok \u2014 token authenticates against ${baseUrl}.
|
|
750
|
+
`);
|
|
751
|
+
return 0;
|
|
752
|
+
} catch (err) {
|
|
753
|
+
if (err instanceof ApiError && err.status === 401) {
|
|
754
|
+
process.stderr.write(
|
|
755
|
+
`[excaliwow-mcp] health: 401 \u2014 token is invalid or expired (${baseUrl}).
|
|
756
|
+
`
|
|
757
|
+
);
|
|
758
|
+
return 1;
|
|
759
|
+
}
|
|
760
|
+
if (err instanceof ApiError && err.status === 403) {
|
|
761
|
+
process.stderr.write(
|
|
762
|
+
`[excaliwow-mcp] health: token authenticates against ${baseUrl}, but lacks the \`read\` capability. Mint a token with read + write.
|
|
763
|
+
`
|
|
764
|
+
);
|
|
765
|
+
return 0;
|
|
766
|
+
}
|
|
767
|
+
if (err instanceof NotAuthenticatedError) {
|
|
768
|
+
process.stderr.write(`[excaliwow-mcp] health: ${err.message}
|
|
769
|
+
`);
|
|
770
|
+
return 1;
|
|
771
|
+
}
|
|
772
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
773
|
+
process.stderr.write(`[excaliwow-mcp] health: could not reach ${baseUrl} \u2014 ${msg}
|
|
774
|
+
`);
|
|
775
|
+
return 2;
|
|
776
|
+
}
|
|
777
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@excaliwow/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Excaliwow Model Context Protocol (MCP) server — lets AI agents create, read, render, and edit Excaliwow diagrams over the public REST API, via stdio.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"publishConfig": {
|