MCP: prompts, resource templates, completions and logging

The server answered tools/* and two fixed resources and nothing else, and
resources/templates/list returned an empty array. This adds the rest of the surface a
client can actually use, against 2025-06-18.

- prompts/list and prompts/get: five workflows (poster, icon-set, recolor,
  trace-and-style, export-set) with templated arguments. A missing required argument
  is -32602 and names itself.
- resources/templates/list, and resources/read for the templates: object/{id},
  command/{id}, effect/{id}, swatch/{name}. Reading one thing at a time instead of
  vectorcraft://document/json, which on a real document is thousands of lines an agent
  pays for again on every change.
- completion/complete for prompt arguments and template variables, ranked prefix
  first, capped at 100 with total and hasMore. The values are read live, so a suggested
  format, effect id, command id, trace preset, swatch or object id is one the engine
  has.
- logging/setLevel and notifications/message. Nothing installed a log::Log before, so
  the engine's log::debug! calls went nowhere; this is the sink, silent until asked.
  Installing it is vectorcraft-cli's job rather than the library's, so an embedder
  that already has a logger keeps it.

docs/mcp.md gains a Protocol section with the capability table and a Not implemented
section: subscriptions, progress, cancellation, sampling, elicitation and Streamable
HTTP all need the transport to interleave reads, which this one does not — handle_line
answers a line with a reply, so it neither reads an incoming cancellation nor can write
progress before the reply it belongs to.

62 tests (50 unit, 12 property/fuzz). protocol_props.rs gained five: prompts survive
junk arguments, completion/complete never panics and always answers at most 100
string values with a consistent hasMore, every template reads or fails cleanly, and
prompts/completions/templates are swept with junk params while asserting the document
comes back byte-identical.

ROADMAP: one bullet under Automation in Shipped so far and the changelog line. The
parity table and the gap list are untouched — MCP is agent automation, so feature
parity does not move.
This commit is contained in:
Fahri Meral
2026-10-07 07:25:02 +07:00
committed by bflatastic
parent 597b651bb6
commit e80b7e383b
10 changed files with 1665 additions and 26 deletions
+97
View File
@@ -32,6 +32,103 @@ Other clients use the same command in their JSON config:
For a live session, start the app first: `cargo run --release -p vectorcraft -- --control 7979`.
## Protocol
Newline-delimited JSON-RPC 2.0 on stdio. The revision is **`2025-06-18`**; `2025-03-26` and `2024-11-05` are
accepted too, and `initialize` answers with whichever of those the client asked for. The specs' own
`resultType` fields and the `2026-07-28` revision are not implemented — see
[Not implemented](#not-implemented).
`initialize` advertises:
| Capability | What it covers |
|---|---|
| `tools` | The 25 tools below |
| `resources` | Two fixed documents and four templates |
| `prompts` | Five ready-made workflows |
| `completions` | `completion/complete` for prompt arguments and template variables |
| `logging` | `logging/setLevel` and `notifications/message` |
### Prompts
`prompts/list` offers five workflows; `prompts/get` returns one templated user message with its
arguments filled in. A missing required argument is `-32602`.
| Prompt | Arguments (required first) |
|---|---|
| `poster` | `brief`, `palette`, `text` |
| `icon-set` | `subject`, `count`, `detail` |
| `recolor` | `palette`, `method` |
| `trace-and-style` | `path`, `preset`, `effect` |
| `export-set` | `formats`, `directory`, `scale` |
```sh
echo '{"jsonrpc":"2.0","id":1,"method":"prompts/get","params":{"name":"poster","arguments":{"brief":"a jazz festival"}}}' \
| vectorcraft-cli mcp --headless
```
### Resource templates
`vectorcraft://document` (summary) and `vectorcraft://document/json` (the whole model) are the fixed
resources. The templates read one thing at a time, which matters on a real document: the full model
is thousands of lines an agent pays for again on every change.
| `uriTemplate` | Reads |
|---|---|
| `vectorcraft://object/{id}` | One layer or object with its children, bounds and paint |
| `vectorcraft://command/{id}` | One command: label, menu path, shortcut, parameter description, enablement |
| `vectorcraft://effect/{id}` | One live effect with its parameters and defaults |
| `vectorcraft://swatch/{name}` | One swatch, colour, gradient or pattern swatch (percent-encode spaces) |
An unknown URI is `-32002`; a template with no value, or a value that names nothing, is `-32602`.
### Completions
`completion/complete` answers for a prompt argument (`ref/prompt`) or a template variable
(`ref/resource`), ranked by prefix and then by substring, capped at 100 values with `total` and
`hasMore`. The values are read from the live catalogues, so they follow the app: `formats` suggests
what the engine writes, `preset` the Image Trace presets, `effect` the effect ids, `{id}` the layer
and object ids in the document, `{name}` its swatch names. An unknown reference comes back empty
rather than as an error, since a completion is asked for mid-typing.
### Logging
`logging/setLevel` accepts any RFC 5424 severity — `debug`, `info`, `notice`, `warning`, `error`,
`critical`, `alert`, `emergency` — and an unknown one is `-32602` with the previous level left in
place. From then on the engine's own `log` records arrive as `notifications/message` with `level`,
`logger` and `data`. A `null` level turns it back off. Nothing is sent before the client asks, and
records queued at the old level are dropped rather than replayed at the new one.
Installing the logger that receives those records is `vectorcraft-cli`'s job, not the library's: a
crate that installed one would override whatever logger an embedder had already set up. Embedding
`vectorcraft-mcp` directly means calling `vectorcraft_mcp::logging::install()` yourself if you want
log records to reach your client.
### Not implemented
Everything below needs the same thing first: the server reads one line at a time and answers it with
one reply, so **while a `tools/call` is running it neither reads an incoming line nor writes an
outgoing one.** Progress and cancellation are therefore both meaningless as they stand, and a
subscription could only ever be checked between calls — which is not what a client asking to be told
about a change expects.
- **`resources/subscribe` / `notifications/resources/updated`.** It would also have to be honest about
*whose* edits it sees: comparing the undo history's length misses edits made in the app window,
which is the main reason to run against a live document. `document.inspect`'s `revision` is the
right signal — it is monotonic and already bumped when a document is replaced — but noticing an
edit still means someone asking.
- **`notifications/progress`.** It would have to be written *before* the reply it belongs to, since a
progress token stops meaning anything once the request has completed.
- **`notifications/cancelled`.** It would have to be read *during* the call it cancels.
- **`sampling/createMessage` and `elicitation/create`.** Both are server→client requests, blocked by
the same one-line-one-reply loop. Supporting them means being able to suspend a tool call
mid-flight, which is what the `2026-07-28` revision's input-requests mechanism exists for.
- **Streamable HTTP / SSE.** stdio only.
`completion/complete` covers the "what could I pass here" half of the same problem, which is why it
is here and subscriptions are not. The fix for the rest is a reader thread (or an event loop) around
the transport — a design change rather than a feature, so it is not in this crate yet.
## Tools
Coordinates are points in document space: y points down, the origin is the first artboard's top-left, and a new