Skip to content

The MCP server

@json-to-office/mcp-server puts the whole authoring loop behind the Model Context Protocol, so an agent inside Claude Code, Claude Desktop, Cursor or any other MCP host can discover the component model, author JSON, validate it, look at rendered pages, and produce a real .docx or .pptx — without you wiring any of it up.

It is local and stdio-only: no auth, no hosted endpoint, no network beyond the asset and font services you configure. The agent gets the same generation packages the CLI uses, and the same rule holds — the JSON is the artifact, the Office file is a build product of it.

Install

Nothing to install. Configure your client to run it and the host will fetch it on first use.

bash
claude mcp add json-to-office -- npx -y @json-to-office/mcp-server
json
// ~/Library/Application Support/Claude/claude_desktop_config.json  (macOS)
// %APPDATA%\Claude\claude_desktop_config.json                      (Windows)
{
  "mcpServers": {
    "json-to-office": {
      "command": "npx",
      "args": ["-y", "@json-to-office/mcp-server"]
    }
  }
}
json
// ~/.cursor/mcp.json, or .cursor/mcp.json in one project
{
  "mcpServers": {
    "json-to-office": {
      "command": "npx",
      "args": ["-y", "@json-to-office/mcp-server"]
    }
  }
}

Any other stdio host takes the same three facts: transport stdio, command npx, args ["-y", "@json-to-office/mcp-server"]. Substitute pnpm / ["dlx", "@json-to-office/mcp-server"] if you would rather not go through npm. Pin a version — @json-to-office/mcp-server@1.0.0 — for anything that has to reproduce later, since the renderer version is part of what a document renders to.

Restart the client after editing a config file. claude mcp list will tell you whether Claude Code can reach it.

Where files go

The server writes everywhere it writes into exactly one directory, and nowhere else — a file name that is absolute, contains .., or slips out through a symlink is refused before anything touches the disk.

SettingEffect
--output-dir <path>The output root. Highest precedence.
JTO_MCP_OUTPUT_DIRThe same, when the flag is absent.
(neither)A per-connection directory under the system temp dir.

Set one. The default is fine for previews you only look at, but a report you wanted to keep should not land somewhere the OS reaps:

bash
claude mcp add json-to-office -s user \
  -e JTO_MCP_OUTPUT_DIR=$HOME/Documents/jto-out \
  -- npx -y @json-to-office/mcp-server

In Claude Desktop and Cursor the equivalent is an "env" object beside "command".

Workspaces that survive a lost session

A workspace holds a document server-side so the agent patches it instead of resending it. By default it lives in memory and belongs to one connection: whatever ends the connection — a client restart, a host session reset, a crash — takes the open documents with it, however many revisions of authoring they held.

Give the server a workspace directory and every committed revision is mirrored there instead:

bash
claude mcp add json-to-office -s user \
  -e JTO_MCP_OUTPUT_DIR=$HOME/Documents/jto-out \
  -e JTO_MCP_WORKSPACE_DIR=$HOME/Documents/jto-workspaces \
  -- npx -y @json-to-office/mcp-server
SettingEffect
--workspace-dir <path>Workspace revisions are mirrored here. Highest precedence.
JTO_MCP_WORKSPACE_DIRThe same, when the flag is absent.
(neither)Memory-only handles, ending with the connection. The default.

After a reconnect the agent calls jto_workspace_list and gets its handles back — including ones opened by the connection that died — then reads or patches them as usual. Memory stays the fast path; the disk copy only loads when a handle is actually used. Closing a workspace still destroys it, on disk as well, and a revision that could not be written comes back as a W_WORKSPACE_NOT_PERSISTED warning with the edit applied. jto_info.workspaces.persistent says which mode a connection is in.

The directory holds document JSON in the clear, so point it somewhere private and check its permissions yourself: the server creates a new root 0o700 and writes files 0o600, but it leaves an existing directory's permissions alone, and Windows does not enforce those bits. Give each client its own root: two connections sharing one share its handles, and baseRevision guards a write against the connection that made it, not against another one editing the same handle at the same time.

What the agent gets

Fifteen tools, three prompts and jto:// resources: twelve for discovery, plus a document and a thumbnail for each bundled template. The package README documents every input and output field; the shape of the loop is:

Discover. jto_info reports versions, formats, renderer ids, the output root and whether preview can run here. jto_discover lists components, renderer profiles, themes — each with its visual voice and when to use it — and starter documents; jto_describe_component returns one component's exact schema, with nested components collapsed to names so nothing pulls a megabyte of schema through the model. jto://guide/design/<format> is the design guide: every theme with its extended values, every quality profile and rule, the block catalogue and the blueprints, rendered to one page from the same registries jto_validate enforces, so the guidance cannot drift from what is checked. Its first lesson is the boundary the system keeps: a theme paints, a profile requires.

It also lists the template gallery: eleven designed documents bundled with the package, each with an archetype, a measured page count, a component and slot inventory, and a sentence on when to use it. jto://templates/<name> returns the document and jto://templates/<name>/thumbnail returns every page tiled into one low-DPI image — worth a look before copying several hundred kilobytes of JSON. Bundled rather than fetched, so the cold path sees a designed document with no network at all. The photographs are deliberately not shipped; each manifest lists the image paths its template expects, so an agent knows to supply its own rather than send someone else's.

Scaffold. For a report the first move is jto_scaffold: a blueprint id, a theme and the facts of the brief open a draft workspace with every section and block in place, the archetype's quality profile on the root, and a fill map listing each slot still owed — its JSON pointer, kind, budget and guidance. A markdown outline fills the section openers and body text on the way in. On a deck the outline also decides the slides, in exactly three ways: a section whose bullets all read Label: figure becomes rows of measurements, at most four to a slide; a section with more bullets than a slide's list holds becomes as many slides as it needs, the later ones titled "(cont.)"; and a markdown table fills the evidence column of a two-column slide, its numeric columns right-aligned. Where the variant draws no slide of the shape a section asks for, the section is filled as the variant's own and the answer says so — nothing is invented and nothing is reordered. The draft validates with advisory marker findings and generationReady: false; jto_generate refuses it by pointer until every marker is patched. jto://blueprints carries the plans in full.

Author and repair. jto_validate returns path-addressed diagnostics — RFC 6901 pointers into the document you sent, usable directly as patch targets. Beside structural errors it reports design-quality findings (W_QUALITY_*) with category, certainty, and evidence. They advise by default; pass quality.policy.gate to make the selected severity block ok. It also checks what the document points at, the way generation will: a props.theme that names no theme (W_UNKNOWN_THEME, listing the ones that exist) and an image file that cannot be read (E_ASSET_UNREADABLE, relative paths resolved against the baseDir you pass, as jto_generate resolves them). Optional workspaces (jto_workspace_create, _inspect, _patch, _snapshot, _list, _close) hold a document server-side so an agent can send an RFC 6902 patch instead of resending the whole tree; with a workspace directory configured they outlive the connection.

Look. jto_preview renders selected pages to PNG and hands them back as image blocks. This is the part that has no CLI equivalent worth the name: a model reasoning about whether a table overflowed is guessing, and a model looking at the page is not.

Pass contactSheet: true and it answers with one labelled image tiling every selected page instead. Cross-page questions — does every section opener look like the others, does the rhythm hold, is the footer on all of them — are questions about the set, and asking them one page at a time costs twenty images and answers none of them. The sheet renders at 72 DPI, inlines when it fits one image block, and is written to the output root when it does not: forty pages tile into a sheet too large to survive a client's downscale with its thumbnails still readable, so it is delivered at full size as a file instead.

Measure. Pass renderedFindings: true and the same PDF is read a second way: poppler's pdftotext gives the box of every word LibreOffice set, pdffonts the faces it embedded, and the rendered pass turns them into findings the static rules can only estimate — text cut off past a frame, a box or the page edge, a framed paragraph drawn taller than its box, words drawn over each other, authored text that never rendered at all, a requested family the PDF does not carry, a page with nothing on it, a heading stranded at a page foot, a paragraph split into a lone line. They come back in diagnostics as quality findings with certainty: "rendered", and rendered summarises the pass.

Every finding says how it reached its pointer. The document's text inventory — every string a paragraph, heading, list, table, statistic, caption, header or footer paints, read off the same prepared document jto_validate analyses, so a string a block compiled reports at its authored slot — is matched against the PDF's words in reading order, with running heads and footers matched first so their page numbers cannot interleave a paragraph that breaks across pages. Reading order comes from geometry, so a table's cells are matched as cells — parted at any gap the row's own word spacing cannot account for, read column by column whenever a run of rows resolves into more than one column, and never mistaken for a page number because they happened to fall inside a page band. context.mapping is mapped when the finding's words belong to one authored string, ambiguous when only one side of a pair did, and unmapped when nothing authored owns them; an unmapped finding sits at the document root rather than being dropped, and rendered.findings.unmapped counts them so a mapping gap is visible, not silent. A missing host font is reported as information — Calibri on a Mac without Office is the preview's substitution, not the document's defect — and becomes a warning only when the document declared a source for the family.

The pass judges by the same quality profile and policy jto_validate takes — pass them to jto_preview alongside renderedFindings — and otherwise by the profile the document declares or the format default. A profile can switch a rendered rule off or move its severity, a policy can suppress one at a pointer, and a gate marks findings blocking so the two tools agree on one policy; rendered.suppressed, rendered.blocked, rendered.truncated and rendered.profileId report what applied. Generation never runs the pass, so a gated rendered finding does not stop jto_generate.

The pass is advisory: nothing it finds blocks jto_generate, and the fidelity caveat applies to it as much as to the pixels. It needs pdftotext; without it the pages still render and a warning names what was missing. Geometry is cached beside the page count, so a re-preview of an unchanged document answers without launching a converter.

Judge. Everything above is deterministic: a rule fires or it does not. Whether a document is worth sending is not that kind of question, and the only judge in the room is the model already in the conversation. jto_critique inspect renders the exact revision, puts the evidence in front of it — a contact sheet of the whole document, full-resolution pages where the rendered pass found something — together with the rubric as data and a run id; jto_critique record files the verdict that produced.

The separation is the point. Only record creates a round, so inspecting twice is not a second opinion and a retried response after a dropped one is the round already filed, not a new one. A verdict belongs to the revision it was formed against: patch between the looking and the recording and the record is refused, because a round about a document nobody saw is not evidence of anything. Three recorded iterate rounds is the limit — past three, subjective polish stops converging — and the third answers with a recommendation to ship or change the structure. It is advice, not a gate: nothing here refuses a later patch or jto_generate.

A ship verdict has to answer for the integrity findings the inspection showed: text clipped, spilled, overlapping or missing on the rendered page. The matcher can be wrong about a document, so the verdict stays the model's to give — a finding the rendered page shows is wrong is accepted in accept, with the reason, in the selectors a quality policy suppression takes, and filed with the round — but a ship that leaves one unanswered is refused, and spends no round.

Ship. jto_generate writes the real file. jto_docx_diff produces a Word redline with native tracked changes between two versions of a document.

Document defects always come back as structured diagnostics with ok: false — never as protocol errors, so an agent can read and repair them instead of retrying blind.

Three prompts into the loop

Clients that offer prompts get three entry points: design-brief writes the six lines a document is designed against and picks the archetype and theme; report-from-notes restructures rough notes into an outline and scaffolds a report from it; deck-from-outline scaffolds a deck and names the three transformations the outline itself decides. They are a convenience, not a second home for the workflow — each one lands on jto_scaffold and walks the same path, and each names the blueprints and themes the cores actually ship rather than a copy of them. None of them restates a design rule: that is what jto://guide/design/<format> and the diagnostics are for.

Preview needs two host binaries

jto_preview converts with LibreOffice and rasterizes with poppler:

bash
brew install --cask libreoffice && brew install poppler      # macOS
sudo apt-get install libreoffice poppler-utils               # Debian/Ubuntu
winget install TheDocumentFoundation.LibreOffice; winget install oschwartz10612.Poppler  # Windows

Everything else — discovery, validation, generation, diff, workspaces — works on a host with neither, because generation writes OOXML directly and never launches an office suite. Without them jto_preview returns a structured error naming what is missing and how to install it, and jto_info.previewDependencies answers the question before the agent spends a call on it.

Claude Desktop does not inherit your shell's PATH. If the binaries live somewhere unusual — or even if they don't — name them with LIBREOFFICE_PATH and PDFTOPPM_PATH in the client's env block; the Claude Desktop guide has the whole entry, including the output and workspace directories and the chart export server.

Preview pixels come from LibreOffice, not Microsoft Office: line breaks, pagination, font substitution and chart rasterization can differ from Word or PowerPoint on the recipient's machine. Treat a preview as a strong indication of layout, not as the final document.

Document-local JSON blocks

Use jto://blocks to inspect example definitions extracted from complete playground templates, including slot schemas and source pointers, for both formats. Copy chosen definitions into the document’s props.blocks; the catalog never registers runtime names. The client-report playground template demonstrates all four migrated report compositions and an adaptive metric row; the consulting-deck playground template carries the action-chart slide block on the house theme.

jto_workspace_inspect with includeBlocks: true returns that revision’s definitions, derived slot schemas and invocation fill pointers. jto_validate with includeCompiled: true returns expanded primitives and authored source maps. See JSON blocks for the contract and breaking changes.

Released under the MIT License.