figdown 0.3.0 → 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/.claude-plugin/plugin.json +2 -2
- package/dist/figdown.js +613 -43
- package/dist/figdown.mjs +613 -43
- package/examples/evpn-fabric.svg +1 -1
- package/examples/showcase/arp-resolution.svg +1 -1
- package/examples/showcase/ethernet-frame.svg +1 -1
- package/examples/showcase/l2-forwarding-logic.svg +1 -1
- package/examples/showcase/tcp-handshake.svg +1 -1
- package/examples/showcase/tcp-header.svg +1 -1
- package/examples/showcase/tcp-state-machine.svg +1 -1
- package/guide/expressing.md +2 -2
- package/guide/layout.md +21 -13
- package/integrations/mcp-server/README.md +174 -0
- package/integrations/mcp-server/server.js +593 -0
- package/package.json +8 -3
- package/skill/figdown/SKILL.md +14 -4
- package/skill/figdown/figdown.html +611 -41
package/guide/layout.md
CHANGED
|
@@ -37,23 +37,31 @@ expresses the identical structure and relationships. No knowledge lives in the
|
|
|
37
37
|
layout zone. If removing a `pin` changes what an AI reader can learn, the
|
|
38
38
|
position was encoding meaning that should live in text instead.
|
|
39
39
|
|
|
40
|
-
**The layout
|
|
40
|
+
**The layout NAMESPACE is DEFAULT-IGNORED (`GENRE-NAMESPACE`, strengthening `CONTENT-LAYOUT-ZONE-SPLIT`; restated
|
|
41
|
+
over the namespace by `GENRE-NAMESPACE`).** It exists
|
|
41
42
|
ONLY to stabilise the rendered `.svg`. Anything that is content, logic or
|
|
42
43
|
concept MUST be expressible in the content zone, so a reading agent's default
|
|
43
|
-
behaviour is to
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
44
|
+
behaviour is to ignore **every member of the layout namespace, wherever in the
|
|
45
|
+
document it appears** — not merely that it may. **Membership decides, never
|
|
46
|
+
position.** Write for that: every `pin` you write is a line an AI reader will
|
|
47
|
+
not see, whether you put it after `layout` or before.
|
|
48
|
+
There is no keyword to opt in or out. (The earlier form of this promise said
|
|
49
|
+
"skip everything from the `layout` line down", and that was literally true and
|
|
50
|
+
practically empty: `pin` is legal before `layout` too, and about half the pins
|
|
51
|
+
in this repository's own corpus are written there, so an agent keeping that
|
|
52
|
+
promise still met half the layout information. `GENRE-NAMESPACE` moved the promise onto the
|
|
53
|
+
namespace, where the container model already was.
|
|
54
|
+
**the layout zone is a namespace of its own** (`LAYOUT-ZONE-NAMESPACE`, clause
|
|
55
|
+
`LAYOUT-ZONE-NAMESPACE`), and every
|
|
50
56
|
member of it is genre-independent. No genre may
|
|
51
57
|
define, redefine or extend a keyword inside the zone; `GENRE-VOCABULARY-OBLIGATION`, "a genre owns its
|
|
52
|
-
words", does not reach in. That is what makes the
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
58
|
+
words", does not reach in. That is what makes the default safe *for ever* and
|
|
59
|
+
what makes it usable: ONE enumeration of the members is correct under every
|
|
60
|
+
genre, so a reader may apply it without even resolving the header's genre
|
|
61
|
+
token. That enumeration is **core §10 (a′)**, it is NORMATIVE, and it has exactly **one** member — `pin`, NORMATIVE. `layout` is not a
|
|
62
|
+
member: it is the zone's OPENER and lives in the universal core of three
|
|
63
|
+
(§10 (a)) alongside `figdown` and `title`. The withdrawal of `path` and
|
|
64
|
+
`routing` left `LAYOUT-ZONE-NAMESPACE` whole and took its only experimental members with it.) If a
|
|
57
65
|
figure needs
|
|
58
66
|
"arrangement carries meaning", the language owes it a content-zone construct
|
|
59
67
|
(`MEANINGFUL-ARRANGEMENT`); until that lands, say the arrangement in prose in the content zone
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# figdown-mcp — FigDown over the Model Context Protocol
|
|
2
|
+
|
|
3
|
+
A dependency-free MCP server that gives an AI agent the three things it needs
|
|
4
|
+
to work with FigDown figures: **build** a `.fd` into an SVG, find out **why**
|
|
5
|
+
one does not parse, and **read** a figure's meaning out of the model rather
|
|
6
|
+
than out of the picture.
|
|
7
|
+
|
|
8
|
+
FigDown exists so an agent can read a figure's *meaning* instead of OCR-ing a
|
|
9
|
+
drawing. MCP is how agents reach tools. This is that channel — and it is the
|
|
10
|
+
only one that exposes the parsed semantic model (spec core §12), which is what
|
|
11
|
+
a *reading* agent actually wants and what neither the SVG nor the CLI hands
|
|
12
|
+
over.
|
|
13
|
+
|
|
14
|
+
## No network, no service, no API key
|
|
15
|
+
|
|
16
|
+
Nothing here opens a socket, resolves a name, or reads a credential. There is
|
|
17
|
+
no account, no rate limit and no telemetry. The server has **zero
|
|
18
|
+
dependencies** — including no MCP SDK: the stdio transport is newline-delimited
|
|
19
|
+
JSON-RPC 2.0, which is about eighty lines, and taking a dependency to save them
|
|
20
|
+
would have been the first dependency in the whole project.
|
|
21
|
+
|
|
22
|
+
Its only I/O is stdin/stdout plus, on explicit request, reading a `.fd`/`.svg`
|
|
23
|
+
and writing that source's sidecar. **There is no arbitrary output path:** the
|
|
24
|
+
only file this server ever writes is `X.svg` beside the `X.fd` it was given,
|
|
25
|
+
and only when the call passes `write: true`.
|
|
26
|
+
|
|
27
|
+
## It does not contain a copy of the engine
|
|
28
|
+
|
|
29
|
+
This repository has shipped seven four-copy-drift incidents. There are four
|
|
30
|
+
engine copies already — `editor/figdown.html` (hand-edited) plus the three
|
|
31
|
+
generated from it (`dist/figdown.js`, `dist/figdown.mjs`,
|
|
32
|
+
`skill/figdown/figdown.html`). This server adds **no fifth copy**: it
|
|
33
|
+
`require`s `dist/figdown.js`, exactly as the other two integrations do.
|
|
34
|
+
|
|
35
|
+
`dist/figdown.js` is the right consumption point rather than merely an
|
|
36
|
+
available one. It is the package's own `main` — what `require('figdown')`
|
|
37
|
+
returns — it is the only artifact with a module API, and `npm run gate:dist`
|
|
38
|
+
already holds it to the reference engine *behaviourally*: regenerating must be
|
|
39
|
+
a byte-level no-op, both builds must render identically, and every published
|
|
40
|
+
`.fd` must parse through it with **the same error set** as
|
|
41
|
+
`editor/figdown.html`. That is agreement on behaviour rather than on a version
|
|
42
|
+
string, and this server inherits it for free.
|
|
43
|
+
|
|
44
|
+
The same rule applies one level down. The genre router — *which reference file
|
|
45
|
+
does genre G need* — is **parsed out of `skill/figdown/SKILL.md`** at call
|
|
46
|
+
time, by the same marker comment `tools/skill-coverage.js` gates, rather than
|
|
47
|
+
restated here. A restated router would let a renamed reference file turn into
|
|
48
|
+
an agent silently receiving nothing.
|
|
49
|
+
|
|
50
|
+
## Run it
|
|
51
|
+
|
|
52
|
+
```sh
|
|
53
|
+
node tools/make-lib.js # regenerate dist/ first, if you edited the engine
|
|
54
|
+
node integrations/mcp-server/server.js # speaks MCP on stdin/stdout
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Configure an MCP client with either the published executable or the path:
|
|
58
|
+
|
|
59
|
+
```json
|
|
60
|
+
{
|
|
61
|
+
"mcpServers": {
|
|
62
|
+
"figdown": { "command": "npx", "args": ["-y", "figdown", "figdown-mcp"] }
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"mcpServers": {
|
|
70
|
+
"figdown": { "command": "node", "args": ["/path/to/figdown/integrations/mcp-server/server.js"] }
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Two environment overrides, in the style of `tools/build-svg.js`: `FIGDOWN_LIB`
|
|
76
|
+
(path to `figdown.js`) and `FIGDOWN_SKILL` (path to the `skill/figdown/`
|
|
77
|
+
directory holding `SKILL.md` and `reference/`).
|
|
78
|
+
|
|
79
|
+
## The tools
|
|
80
|
+
|
|
81
|
+
Four, matching the three verbs `skill/figdown/SKILL.md` already teaches —
|
|
82
|
+
author, build, read — plus the diagnostics loop that connects them. A tool
|
|
83
|
+
nobody calls is a maintenance cost with no reader, so there are no others: in
|
|
84
|
+
particular there is no layout-lint tool and no "render to PNG".
|
|
85
|
+
|
|
86
|
+
| Tool | Why an agent needs it |
|
|
87
|
+
|---|---|
|
|
88
|
+
| `figdown_build` | Turn one `.fd` into the deterministic, self-carrying SVG. The artifact embeds its own source, that source's SHA-256 and the engine version (core §7), so it round-trips back to text. |
|
|
89
|
+
| `figdown_check` | Parse one document *or walk a whole tree*, render nothing, write nothing. This is the write → validate → fix loop and the corpus sweep; it exists separately from `build` so that iterating on a broken file costs no render and puts no SVG in the agent's context. |
|
|
90
|
+
| `figdown_read` | The semantic model — participants, relationships and direction, containment, and the **stated meaning** of every class — shipped with the reading contract. Accepts a `.svg` too, recovering the source from its metadata and reporting the artifact stale if the `.fd` has moved on. |
|
|
91
|
+
| `figdown_reference` | The per-genre reference, so an agent knows what a genre may and may not say. The grammar is **closed** and the parser **never warns about portability**, so this is the only place the frozen / EXPERIMENTAL split is visible. |
|
|
92
|
+
|
|
93
|
+
### `figdown_build`
|
|
94
|
+
|
|
95
|
+
`source` or `path` (not both); `write`, `with_title`, `return_svg`,
|
|
96
|
+
`include_model`.
|
|
97
|
+
|
|
98
|
+
**What it returns, decided rather than defaulted.** The SVG comes back inline
|
|
99
|
+
*unless* it was just written to disk, in which case the path comes back and the
|
|
100
|
+
caller is spared 5–50 KB of markup it can already open. `return_svg` overrides
|
|
101
|
+
in both directions. The parsed model is **opt-in** here (`include_model`) and
|
|
102
|
+
the default in `figdown_read`: an author who just wrote the source does not
|
|
103
|
+
need it read back, and a reader should not have to render to get it.
|
|
104
|
+
|
|
105
|
+
### `figdown_check`
|
|
106
|
+
|
|
107
|
+
`source`, or `path` to a file **or a directory** (walked recursively — a check
|
|
108
|
+
that does not recurse is a check that lies). It states how many documents it
|
|
109
|
+
looked at, so the number can be compared against `find <dir> -name '*.fd' | wc -l`.
|
|
110
|
+
|
|
111
|
+
### `figdown_read`
|
|
112
|
+
|
|
113
|
+
`source` or `path`, `.fd` or `.svg`. Returns the section shape, the reading
|
|
114
|
+
contract in short form, and the full model as JSON with every element's source
|
|
115
|
+
line. Handed an SVG that FigDown did not produce it refuses and says so, rather
|
|
116
|
+
than guessing at the picture.
|
|
117
|
+
|
|
118
|
+
### `figdown_reference`
|
|
119
|
+
|
|
120
|
+
`name` — a genre (`block`, `bitfield`, `table`, `topology`, `flowchart`,
|
|
121
|
+
`statechart`, `timing`), a task (`reading`, `transcribe`), or `skill`. Omit it
|
|
122
|
+
for the index. `experimental: true` adds the genre's EXPERIMENTAL files, which sit
|
|
123
|
+
outside the v0.1 conformance surface and its compatibility promise.
|
|
124
|
+
|
|
125
|
+
## A parse error is a result, not an exception
|
|
126
|
+
|
|
127
|
+
The diagnostics are the product: each one carries a 1-based line number, the
|
|
128
|
+
reason, and — for a retired spelling — what to write instead. They are what
|
|
129
|
+
makes an automated author → validate → fix loop possible, so they must reach
|
|
130
|
+
the caller intact. Three channels are kept distinct:
|
|
131
|
+
|
|
132
|
+
| Channel | Used for |
|
|
133
|
+
|---|---|
|
|
134
|
+
| JSON-RPC `error` | Protocol faults only: unparseable JSON, unknown method. |
|
|
135
|
+
| `isError: true` | The tool could not run at all — no such file, `source` *and* `path` given, an SVG with no FigDown metadata. The request was malformed; there was nothing to read. |
|
|
136
|
+
| a normal result | **Including a parse failure.** The tool did exactly its job: it reported why the document does not parse. The first line reads `PARSE FAILED — N diagnostic(s)`, the engine's text follows verbatim, and no client can hide it as an error string. |
|
|
137
|
+
|
|
138
|
+
No partial render is ever emitted: `svg` is null whenever there are errors —
|
|
139
|
+
determinism over convenience.
|
|
140
|
+
|
|
141
|
+
## Where MCP fits awkwardly, stated plainly
|
|
142
|
+
|
|
143
|
+
- **A tool that wants to return two things.** A build is an SVG *and* its
|
|
144
|
+
metadata; a read is a model *and* the contract for using it. MCP's result is
|
|
145
|
+
an ordered list of content blocks, which carries them — but the blocks are
|
|
146
|
+
untyped text, so "this block is the SVG and that one is the model" can only
|
|
147
|
+
be said in prose inside the blocks. Structured output (`structuredContent` +
|
|
148
|
+
`outputSchema`) would type it, at the cost of client compatibility across
|
|
149
|
+
protocol revisions; this server puts machine-readable payloads in the text
|
|
150
|
+
blocks as JSON instead, which every revision carries identically.
|
|
151
|
+
- **The diagnostic does not fit the error shape.** MCP has one error flag and
|
|
152
|
+
two meanings to spend it on ("your request was malformed" and "your document
|
|
153
|
+
is malformed"). Those need opposite handling — the first is the caller's bug,
|
|
154
|
+
the second is the caller's *work product* and the whole reason the tool
|
|
155
|
+
exists — so `isError` is reserved for the first and the second is a normal
|
|
156
|
+
result. Anyone reading a transcript should know that a clean-looking result
|
|
157
|
+
can still say `PARSE FAILED`.
|
|
158
|
+
- **Protocol version.** Every payload here is plain text content blocks, which
|
|
159
|
+
every revision carries identically, so there is nothing to negotiate: the
|
|
160
|
+
server echoes the revision the client asked for.
|
|
161
|
+
|
|
162
|
+
## Test
|
|
163
|
+
|
|
164
|
+
```sh
|
|
165
|
+
node integrations/mcp-server/test.js # npm run gate:mcp
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
It speaks real JSON-RPC to a spawned subprocess (framing is the half that was
|
|
169
|
+
hand-rolled, and an in-process test cannot catch a framing bug), then exercises
|
|
170
|
+
every tool against real figures from `examples/` — a multi-section topology, an
|
|
171
|
+
experimental `figdown 0.2 statechart`, a published artifact read back through
|
|
172
|
+
its own metadata, and a deliberately broken document. Two of its assertions are
|
|
173
|
+
drift guards rather than feature tests: every genre in SKILL.md's router must
|
|
174
|
+
still resolve to files that exist, and this server must contain no engine copy.
|