@excaliwow/mcp 0.1.1 → 0.2.1
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 +90 -21
- package/dist/bin.js +98 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,7 +7,25 @@ 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 — that
|
|
12
|
+
is everything the server's tools need (see [Security notes](#security-notes)).
|
|
13
|
+
Pass it as `EXCALIWOW_TOKEN`.
|
|
14
|
+
|
|
15
|
+
### Claude Code (CLI)
|
|
16
|
+
|
|
17
|
+
One command. `--scope local` stores the server in your own settings, so the
|
|
18
|
+
token never lands in a file you might commit:
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
claude mcp add excaliwow --scope local \
|
|
22
|
+
--env EXCALIWOW_TOKEN=excw_pat_… \
|
|
23
|
+
-- npx -y @excaliwow/mcp
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Claude Desktop (and other JSON-config clients)
|
|
27
|
+
|
|
28
|
+
Add it to your client's config file (e.g. Claude Desktop's
|
|
11
29
|
`claude_desktop_config.json`, or your client's MCP settings — see your client's
|
|
12
30
|
MCP setup docs). **Pin the version** — `npx -y` otherwise always pulls the newest
|
|
13
31
|
release, which is an avoidable supply-chain surface for a process that holds a
|
|
@@ -18,7 +36,7 @@ token to your account:
|
|
|
18
36
|
"mcpServers": {
|
|
19
37
|
"excaliwow": {
|
|
20
38
|
"command": "npx",
|
|
21
|
-
"args": ["-y", "@excaliwow/mcp@0.
|
|
39
|
+
"args": ["-y", "@excaliwow/mcp@0.2.1"],
|
|
22
40
|
"env": {
|
|
23
41
|
"EXCALIWOW_TOKEN": "excw_pat_…"
|
|
24
42
|
}
|
|
@@ -27,25 +45,69 @@ token to your account:
|
|
|
27
45
|
}
|
|
28
46
|
```
|
|
29
47
|
|
|
30
|
-
`EXCALIWOW_TOKEN`
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
48
|
+
The server reads `EXCALIWOW_TOKEN` per call from the environment (or, if you also
|
|
49
|
+
use `@excaliwow/cli` and have run `excaliwow auth login`, that stored login) and
|
|
50
|
+
never writes the token to disk itself. Bump the pinned version deliberately when
|
|
51
|
+
you've reviewed a new release.
|
|
52
|
+
|
|
53
|
+
## Troubleshooting
|
|
54
|
+
|
|
55
|
+
**`npx -y @excaliwow/mcp@0.2.1` fails with `ENOENT … /@excaliwow/mcp@0.2.1/package.json`.**
|
|
56
|
+
On some npm/Node versions, `npx` misreads a scoped package + `@version` spec as a
|
|
57
|
+
local directory. It's an upstream npm bug (it reproduces with other scoped
|
|
58
|
+
packages, e.g. `@modelcontextprotocol/server-filesystem@1.0.0`), not an Excaliwow one. Either
|
|
59
|
+
use the unversioned spec `npx -y @excaliwow/mcp` (as the Claude Code command
|
|
60
|
+
above does), or pin safely by installing once and pointing the client at the
|
|
61
|
+
binary:
|
|
62
|
+
|
|
63
|
+
```sh
|
|
64
|
+
npm i -g @excaliwow/mcp@0.2.1
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"mcpServers": {
|
|
70
|
+
"excaliwow": {
|
|
71
|
+
"command": "excaliwow-mcp",
|
|
72
|
+
"env": { "EXCALIWOW_TOKEN": "excw_pat_…" }
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
With Claude Code: `claude mcp add excaliwow --scope local --env EXCALIWOW_TOKEN=… -- excaliwow-mcp`.
|
|
79
|
+
|
|
80
|
+
**"Not authenticated" / 401 / the agent's tool calls fail.** The server starts
|
|
81
|
+
even without a token (so it can list its tools), so a missing or invalid
|
|
82
|
+
`EXCALIWOW_TOKEN` only surfaces when the agent first calls a tool. Starting with
|
|
83
|
+
no token prints a one-line `EXCALIWOW_TOKEN is not set` warning to **stderr**. To
|
|
84
|
+
check a token directly, run the health probe — it makes one authenticated read
|
|
85
|
+
and prints a clear verdict (`ok`, `401 — token is invalid or expired`, or
|
|
86
|
+
unreachable):
|
|
87
|
+
|
|
88
|
+
```sh
|
|
89
|
+
npx -y @excaliwow/mcp --health
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### CLI flags
|
|
93
|
+
|
|
94
|
+
| Flag | Effect |
|
|
95
|
+
| ----------- | ------------------------------------------------------------------ |
|
|
96
|
+
| `--health` | Check the token + API reachability, then exit (0 ok, 1 bad token). |
|
|
97
|
+
| `--version` | Print the installed version and exit. |
|
|
98
|
+
| `--help` | Print usage (flags + env vars) and exit. |
|
|
37
99
|
|
|
38
100
|
## Tools
|
|
39
101
|
|
|
40
102
|
Five tools, scoped to safe agent use:
|
|
41
103
|
|
|
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). |
|
|
104
|
+
| Tool | Capability | What it does |
|
|
105
|
+
| ------------------ | ---------- | --------------------------------------------------------------------------- |
|
|
106
|
+
| `generate_diagram` | `write` | Create a diagram from the high-level node/edge DSL; returns the editor URL. |
|
|
107
|
+
| `read_diagram` | `read` | Compact summary (title + per-type element counts) **plus** a rendered PNG. |
|
|
108
|
+
| `list_diagrams` | `read` | Page through your diagrams. |
|
|
109
|
+
| `move_diagram` | `write` | Move a diagram to a folder (or to root). |
|
|
110
|
+
| `edit_diagram` | `write` | Additively merge a DSL fragment (add nodes/edges, update node style/label). |
|
|
49
111
|
|
|
50
112
|
`read_diagram` returns a summary + image, **never** the raw scene JSON, to keep
|
|
51
113
|
context small. Making a diagram publicly shareable is deliberately **not** an
|
|
@@ -67,14 +129,21 @@ resource at **`excaliwow://dsl/reference`**.
|
|
|
67
129
|
|
|
68
130
|
## Security notes
|
|
69
131
|
|
|
70
|
-
- **
|
|
71
|
-
|
|
132
|
+
- **Keep the token out of anything you commit.** A project-scoped config that
|
|
133
|
+
lives in the repo (a committed `.mcp.json`, or `claude mcp add --scope
|
|
134
|
+
project`) puts the token into git history. Use a user- or local-scoped config
|
|
135
|
+
(`--scope local`), or reference an environment variable instead of pasting the
|
|
136
|
+
literal token.
|
|
137
|
+
- **Mint with only `read` + `write`.** Those are the only capabilities the five
|
|
138
|
+
tools use (see the table above); none publish or delete. So a `read` + `write`
|
|
139
|
+
PAT can neither expose your diagrams publicly nor delete them, even if the
|
|
140
|
+
agent is misled. Pick those two capabilities specifically — the coarse
|
|
141
|
+
`read-write` preset additionally grants `publish` and `delete`.
|
|
142
|
+
- **Pin the version** in your client config rather than floating on `@latest`,
|
|
143
|
+
and review release notes before bumping.
|
|
72
144
|
- The token is a credential to your account. It lives only in your MCP client
|
|
73
145
|
config / environment; this server does not persist it. Treat that config the
|
|
74
146
|
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
147
|
|
|
79
148
|
## License
|
|
80
149
|
|
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";
|
|
@@ -100,7 +96,12 @@ async function request(opts) {
|
|
|
100
96
|
if (res.status === 204) return null;
|
|
101
97
|
if (contentType.includes("image/")) {
|
|
102
98
|
const buf = new Uint8Array(await res.arrayBuffer());
|
|
103
|
-
return {
|
|
99
|
+
return {
|
|
100
|
+
bytes: buf,
|
|
101
|
+
contentType,
|
|
102
|
+
quality: res.headers.get("x-render-quality") ?? void 0,
|
|
103
|
+
note: res.headers.get("x-render-note") ?? void 0
|
|
104
|
+
};
|
|
104
105
|
}
|
|
105
106
|
const text = await res.text();
|
|
106
107
|
if (text === "") return null;
|
|
@@ -177,7 +178,9 @@ function renderDiagram(ctx, id, opts) {
|
|
|
177
178
|
path: `${PREFIX}/diagrams/${encodeURIComponent(id)}/render`,
|
|
178
179
|
token: ctx.token,
|
|
179
180
|
baseUrl: ctx.baseUrl,
|
|
180
|
-
|
|
181
|
+
// `quality` (#220) — omitted ⇒ the server default (faithful). An older
|
|
182
|
+
// server ignores it and serves fast; the response's quality signal tells.
|
|
183
|
+
query: { format: opts.format, quality: opts.quality },
|
|
181
184
|
accept,
|
|
182
185
|
fetchImpl: ctx.fetchImpl
|
|
183
186
|
});
|
|
@@ -203,6 +206,10 @@ function moveDiagram(ctx, id, folderId) {
|
|
|
203
206
|
});
|
|
204
207
|
}
|
|
205
208
|
|
|
209
|
+
// src/server.ts
|
|
210
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
211
|
+
import { z as z2 } from "zod";
|
|
212
|
+
|
|
206
213
|
// src/bootstrap.ts
|
|
207
214
|
var DSL_BOOTSTRAP_EXAMPLE = {
|
|
208
215
|
version: "1",
|
|
@@ -419,6 +426,9 @@ var EditFragmentZ = z.object({
|
|
|
419
426
|
updateNodes: z.array(UpdateNodePatchZ).optional()
|
|
420
427
|
});
|
|
421
428
|
|
|
429
|
+
// src/version.ts
|
|
430
|
+
var VERSION = "0.2.1";
|
|
431
|
+
|
|
422
432
|
// src/server.ts
|
|
423
433
|
function textResult(text) {
|
|
424
434
|
return { content: [{ type: "text", text }] };
|
|
@@ -460,7 +470,7 @@ function elementBreakdown(detail) {
|
|
|
460
470
|
return { total: elements.length, byType };
|
|
461
471
|
}
|
|
462
472
|
function createServer(deps) {
|
|
463
|
-
const server2 = new McpServer({ name: "excaliwow", version:
|
|
473
|
+
const server2 = new McpServer({ name: "excaliwow", version: VERSION });
|
|
464
474
|
server2.registerTool(
|
|
465
475
|
"generate_diagram",
|
|
466
476
|
{
|
|
@@ -516,9 +526,11 @@ function createServer(deps) {
|
|
|
516
526
|
try {
|
|
517
527
|
const png = await renderDiagram(ctx, id, { format: "png" });
|
|
518
528
|
if (png && png.bytes.length > 0) {
|
|
529
|
+
const text = png.quality && png.quality !== "faithful" ? `${summary}
|
|
530
|
+
preview fidelity: ${png.quality} \u2014 faithful renderer unavailable; layout/fonts approximate` : summary;
|
|
519
531
|
return {
|
|
520
532
|
content: [
|
|
521
|
-
{ type: "text", text
|
|
533
|
+
{ type: "text", text },
|
|
522
534
|
{
|
|
523
535
|
type: "image",
|
|
524
536
|
data: Buffer.from(png.bytes).toString("base64"),
|
|
@@ -631,5 +643,83 @@ function createServer(deps) {
|
|
|
631
643
|
}
|
|
632
644
|
|
|
633
645
|
// src/bin.ts
|
|
646
|
+
var argv = process.argv.slice(2);
|
|
647
|
+
var has = (...flags) => argv.some((a) => flags.includes(a));
|
|
648
|
+
if (has("--version", "-v")) {
|
|
649
|
+
process.stdout.write(`${VERSION}
|
|
650
|
+
`);
|
|
651
|
+
process.exit(0);
|
|
652
|
+
}
|
|
653
|
+
if (has("--help", "-h")) {
|
|
654
|
+
process.stdout.write(
|
|
655
|
+
[
|
|
656
|
+
`excaliwow-mcp ${VERSION} \u2014 Excaliwow Model Context Protocol server (stdio).`,
|
|
657
|
+
"",
|
|
658
|
+
"An MCP client (Claude Desktop, Claude Code, \u2026) launches this on demand; you",
|
|
659
|
+
"do not normally run it by hand. The flags below are for setup/debugging.",
|
|
660
|
+
"",
|
|
661
|
+
"Usage:",
|
|
662
|
+
" excaliwow-mcp Start the MCP server on stdio (what a client runs).",
|
|
663
|
+
" excaliwow-mcp --health Check the token + API reachability, then exit.",
|
|
664
|
+
" excaliwow-mcp --version Print the version and exit.",
|
|
665
|
+
" excaliwow-mcp --help Print this help and exit.",
|
|
666
|
+
"",
|
|
667
|
+
"Environment:",
|
|
668
|
+
" EXCALIWOW_TOKEN Personal Access Token with read + write. Required.",
|
|
669
|
+
" Mint one at https://excaliwow.com/app/settings.",
|
|
670
|
+
" EXCALIWOW_API_URL API origin. Defaults to https://excaliwow.com.",
|
|
671
|
+
""
|
|
672
|
+
].join("\n")
|
|
673
|
+
);
|
|
674
|
+
process.exit(0);
|
|
675
|
+
}
|
|
676
|
+
if (has("--health")) {
|
|
677
|
+
process.exit(await health());
|
|
678
|
+
}
|
|
679
|
+
if (!resolveToken()) {
|
|
680
|
+
process.stderr.write(
|
|
681
|
+
"[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"
|
|
682
|
+
);
|
|
683
|
+
}
|
|
634
684
|
var server = createServer();
|
|
635
685
|
await server.connect(new StdioServerTransport());
|
|
686
|
+
async function health() {
|
|
687
|
+
const token = resolveToken();
|
|
688
|
+
const baseUrl = resolveBaseUrl({});
|
|
689
|
+
if (!token) {
|
|
690
|
+
process.stderr.write(
|
|
691
|
+
"[excaliwow-mcp] health: EXCALIWOW_TOKEN is not set \u2014 no token to check.\n"
|
|
692
|
+
);
|
|
693
|
+
return 1;
|
|
694
|
+
}
|
|
695
|
+
try {
|
|
696
|
+
await listDiagrams({ token, baseUrl }, { limit: 1 });
|
|
697
|
+
process.stderr.write(`[excaliwow-mcp] health: ok \u2014 token authenticates against ${baseUrl}.
|
|
698
|
+
`);
|
|
699
|
+
return 0;
|
|
700
|
+
} catch (err) {
|
|
701
|
+
if (err instanceof ApiError && err.status === 401) {
|
|
702
|
+
process.stderr.write(
|
|
703
|
+
`[excaliwow-mcp] health: 401 \u2014 token is invalid or expired (${baseUrl}).
|
|
704
|
+
`
|
|
705
|
+
);
|
|
706
|
+
return 1;
|
|
707
|
+
}
|
|
708
|
+
if (err instanceof ApiError && err.status === 403) {
|
|
709
|
+
process.stderr.write(
|
|
710
|
+
`[excaliwow-mcp] health: token authenticates against ${baseUrl}, but lacks the \`read\` capability. Mint a token with read + write.
|
|
711
|
+
`
|
|
712
|
+
);
|
|
713
|
+
return 0;
|
|
714
|
+
}
|
|
715
|
+
if (err instanceof NotAuthenticatedError) {
|
|
716
|
+
process.stderr.write(`[excaliwow-mcp] health: ${err.message}
|
|
717
|
+
`);
|
|
718
|
+
return 1;
|
|
719
|
+
}
|
|
720
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
721
|
+
process.stderr.write(`[excaliwow-mcp] health: could not reach ${baseUrl} \u2014 ${msg}
|
|
722
|
+
`);
|
|
723
|
+
return 2;
|
|
724
|
+
}
|
|
725
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@excaliwow/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.1",
|
|
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": {
|