MCP server#
scenet mcp serves the compiler over the Model Context Protocol,
targeting revision 2026-07-28. It is
the one way of driving Scenet from a model that closes the loop: the model writes a document,
validates it, reads findings that name the rule, the line and the fix, corrects its own output,
and renders it — with no person copying error text from a terminal into a chat window.
Starting it, transports and exit status are in the command-line reference; connecting a client is in driving Scenet from a model. This page is the tools.
Tools#
Five, deliberately. Each extra tool is more for a model to read before it can choose one.
Tool |
Takes |
Returns |
|---|---|---|
|
|
The spec pack, one part at a time, as Markdown |
|
— |
Every character, with each pose and expression it declares |
|
|
|
|
|
Each panel’s notes and Panel Core |
|
|
A summary, then one SVG per panel |
Every tool is annotated read-only, idempotent and closed-world: none writes a file, reaches the network, or answers differently on a second call. A client can run them without asking.
Arguments#
sourceThe whole document, as text. Not a path: a remote client shares no file system with the server, and one calling convention for both transports is one less thing to get wrong.
syntaxyaml(default) for a*.panel.yamlor*.scene.yamldocument — a top-levelpanels:key is what makes it a scene, so there is no third value — orscriptfor a comic script.sectionOne of
preamble,language,shot-types,comic-script,characters,diagnostics,schema,gallery. Omitted,get_specreturns the preamble — what Scenet is, the check-and-fix loop, the mistakes generators make most — and a list of the other parts.deepAlso run the solver, as
scenet check --deepdoes, to catchlayoutandballoon-placementfailures the cheap pass cannot see.live_textEmit selectable
<text>instead of glyph outlines, asscenet build --live-textdoes. About a third the size, which matters inside a model’s context, but it depends on the viewer having a matching font.
Findings#
validate returns every finding, in source order:
Field |
|
|---|---|
|
Stable rule id, such as |
|
What is wrong, naming the offending construct |
|
What to do about it |
|
Path to the offending value, such as |
|
1-based |
compile and render refuse a document that does not compile with a tool error, whose
text lists the same findings with the same fixes. A solver failure is located as well as a
syntax one: only a failure pays for the deep check that locates it, so a document that compiles
is compiled once.
The findings travel in two forms: as structured content for clients that read it, and as text for the many hosts that show a model only a result’s text.
Rendering#
render returns a short text summary — how many panels, and the compiler’s notes — then one
embedded resource per panel, scenet://panels/NAME.svg, of type image/svg+xml. The SVG is
byte-identical to what scenet build writes for the same document.
It is never sent as an image content block. Hosts forward image blocks to the model, and model APIs that accept images do not accept SVG, so an image block would fail the whole turn instead of showing a picture. There is no PNG either: rasterising would put a renderer in the runtime dependencies for one tool.
Instructions#
On connecting, the server sends instructions that many hosts hand straight to the model. They
say what to do rather than what the server is: read get_spec first, cast only what
list_puppets lists, validate until clean, and pass text rather than paths.
Keeping stdout clean#
On the stdio transport, stdout is the protocol stream, and one stray line corrupts it. Nothing
in the server goes through the code scenet build uses to print wrote and note: lines; the
compiler’s notes travel inside tool results instead. The SDK additionally points file descriptor
1 at stderr while it serves, which catches a stray print from a dependency. The test suite
drives a real stdio session against the installed command, so neither can regress quietly.
From Python#
The tools are plain functions, importable without starting a server:
from scenet.mcp import list_puppets, validate
report = validate("""
cast:
alice: {reference: alice}
script:
- say: {by: bpb, text: Hello}
""")
assert not report.valid
finding = report.findings[0]
assert finding.rule == "scenet/unknown-actor"
assert finding.where == "script.0.by"
assert (finding.line, finding.column) == (5, 5)
assert [puppet.name for puppet in list_puppets().puppets] == ["alice", "bob"]
build_server() returns the configured server without running it, for a host that wants to
connect in process or mount it inside a server of its own.
Publishing#
server.json at the repository root
describes the server for the official MCP registry,
which holds metadata only and points at the package on PyPI. Ownership of the
io.github.creatoan/scenet namespace is proved by the mcp-name marker in the README, which
becomes the PyPI project description. Listing a release is done by a workflow; see
releasing.