@json-to-office/mcp-server 1.2.0

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/LICENSE ADDED
@@ -0,0 +1,18 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Wiseair srl
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
6
+ associated documentation files (the "Software"), to deal in the Software without restriction, including
7
+ without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
9
+ following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all copies or substantial
12
+ portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
15
+ LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
16
+ EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
18
+ USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,340 @@
1
+ # @json-to-office/mcp-server
2
+
3
+ A local [Model Context Protocol](https://modelcontextprotocol.io) server for [json-to-office](https://github.com/Wiseair-srl/json-to-office). It lets an agent author, inspect, validate, preview, diff and generate `.docx` and `.pptx` — with the document JSON as the artifact and the Office file as a build product of it.
4
+
5
+ stdio only, no auth, no network beyond the asset and font services you configure.
6
+
7
+ ## Run it
8
+
9
+ ```bash
10
+ pnpm dlx @json-to-office/mcp-server # or: npx -y @json-to-office/mcp-server
11
+ ```
12
+
13
+ It speaks MCP over stdin/stdout, so in practice a client starts it rather than you — see the configuration below. Run it by hand only to check that it starts; `--help` and `--version` are the only things it ever prints to stdout that are not protocol frames.
14
+
15
+ ## Client configuration
16
+
17
+ ### Claude Code
18
+
19
+ ```bash
20
+ claude mcp add json-to-office -- npx -y @json-to-office/mcp-server
21
+ ```
22
+
23
+ `-s user` makes it available in every project instead of just this one; `-s project` writes a `.mcp.json` your team can commit. Environment goes through `-e`, before the `--`:
24
+
25
+ ```bash
26
+ claude mcp add json-to-office -s user \
27
+ -e JTO_MCP_OUTPUT_DIR=$HOME/Documents/jto-out \
28
+ -- npx -y @json-to-office/mcp-server
29
+ ```
30
+
31
+ Check it with `claude mcp list`, or `claude mcp get json-to-office`.
32
+
33
+ ### Claude Desktop
34
+
35
+ Edit `claude_desktop_config.json` — `~/Library/Application Support/Claude/` on macOS, `%APPDATA%\Claude\` on Windows — and restart the app:
36
+
37
+ ```json
38
+ {
39
+ "mcpServers": {
40
+ "json-to-office": {
41
+ "command": "npx",
42
+ "args": ["-y", "@json-to-office/mcp-server"],
43
+ "env": {
44
+ "JTO_MCP_OUTPUT_DIR": "/Users/you/Documents/jto-out"
45
+ }
46
+ }
47
+ }
48
+ }
49
+ ```
50
+
51
+ Claude Desktop does not inherit your shell's `PATH`, so if LibreOffice or poppler live somewhere unusual, name them explicitly with `LIBREOFFICE_PATH` and `PDFTOPPM_PATH` in the same `env` block. Without an output directory the server writes to a temp directory that the OS eventually reaps — fine for previews, less fine for a report you wanted to keep.
52
+
53
+ ### Cursor
54
+
55
+ `~/.cursor/mcp.json` for every project, or `.cursor/mcp.json` inside one:
56
+
57
+ ```json
58
+ {
59
+ "mcpServers": {
60
+ "json-to-office": {
61
+ "command": "npx",
62
+ "args": ["-y", "@json-to-office/mcp-server"]
63
+ }
64
+ }
65
+ }
66
+ ```
67
+
68
+ ### Any other stdio host
69
+
70
+ The server is an ordinary stdio MCP server with no auth and no arguments of its own, so the generic shape works everywhere:
71
+
72
+ | Field | Value |
73
+ | --------- | -------------------------------------------------------------- |
74
+ | transport | `stdio` |
75
+ | command | `npx` (or `pnpm`) |
76
+ | args | `["-y", "@json-to-office/mcp-server"]` (`["dlx", …]` for pnpm) |
77
+ | env | optional; see the table below |
78
+
79
+ Pin a version by asking for one — `npx -y @json-to-office/mcp-server@1.0.0` — which is worth doing for anything reproducible, since the renderer version is part of what a document renders to.
80
+
81
+ ## Options
82
+
83
+ | Flag / variable | Meaning |
84
+ | --------------------- | ---------------------------------------------------------------- |
85
+ | `--output-dir <path>` | Where generated files are written. Highest precedence. |
86
+ | `JTO_MCP_OUTPUT_DIR` | Same, when the flag is absent. |
87
+ | _(neither)_ | A per-connection directory under the system temp dir. |
88
+ | `LIBREOFFICE_PATH` | LibreOffice binary, for preview and the docx `visual` component. |
89
+ | `PDFTOPPM_PATH` | poppler `pdftoppm` binary, for preview. |
90
+
91
+ ## Contracts
92
+
93
+ **Output root.** Every file the server writes lands under one directory, resolved from the table above. A requested file name is rejected — structurally, before anything touches the filesystem — if it is absolute, carries a drive letter, contains `..`, or resolves outside the root through a symlink. There is no way to ask for a write elsewhere. Unconfigured connections get an unpredictable owner-only temp root; new artifacts are owner-only too. A configured root is never deleted.
94
+
95
+ **Artifacts.** Generated bytes come back as a path by default: `{ mode: "path", path, relative, bytes, filename, mimeType }`. Ask for `outputMode: "base64"` and you get `{ mode: "base64", base64, bytes, filename, mimeType }` instead, but only under 4 MiB (`jto_info.output.maxInlineArtifactBytes`); over that the call returns `E_ARTIFACT_TOO_LARGE` rather than silently writing a file you were not expecting.
96
+
97
+ **Diagnostics.** A defective document is a normal result, never a protocol error. Every tool answers `{ ok, diagnostics, … }`, where each diagnostic carries a stable `code`, a `message`, a `severity` of `error` / `warning` / `info` and — when the defect has a location — an RFC 6901 JSON Pointer in `path`, pointing into the document you sent. JSON-RPC errors are reserved for transport and server failures. An input that violates a tool's own input schema comes back as an `isError` result from the SDK, also not a protocol error.
98
+
99
+ **Documents.** Every document-taking tool accepts either inline JSON (`document`) or a workspace reference (`handle`, optionally `revision`), never both, and answers identically apart from the `source` field that says which you used. Inline is the portable baseline and needs no prior state.
100
+
101
+ **stdout.** Protocol frames only; the transport's own failures go to stderr. Warnings the renderer emits mid-run — unresolved fonts, unknown themes, an unreadable theme file — are collected for the duration of the call that caused them and come back in that call's `diagnostics` as `W_HOST_NOTE`, rather than on a stream no client reads.
102
+
103
+ ## The loop this server expects
104
+
105
+ 1. **`jto_info`** — what this host can actually do: versions, formats and their renderer ids, the output root, whether workspaces are on, and whether preview has its binaries.
106
+ 2. **`jto_discover`** (or the `jto://catalog` resource) — components per format, renderer profiles, built-in themes, and starter documents to copy. **`jto_describe_component`** for the exact schema of the one component you are about to write.
107
+ 3. **Author** — start from a starter, or `jto_workspace_create` and patch content in.
108
+ 4. **`jto_validate`** after each edit, not once at the end. Diagnostics are path-addressed, so a pointer is a patch target.
109
+ 5. **Repair** — `jto_workspace_patch` at the pointers the diagnostics named, or fix the inline JSON.
110
+ 6. **`jto_preview`** whenever the question is visual: did the table overflow, did the title wrap, is the slide crowded. It is an iteration step, not a final check.
111
+ 7. **`jto_generate`** for the real file. `jto_docx_diff` when someone needs to see what changed.
112
+
113
+ `jto_workspace_snapshot` before a restructuring you could not cleanly undo.
114
+
115
+ ## Tools
116
+
117
+ Thirteen tools, in the order `tools/list` reports them.
118
+
119
+ | Tool | Purpose |
120
+ | ------------------------ | ---------------------------------------------------------------------------------------------------- |
121
+ | `jto_info` | Versions, formats and renderer ids, output root and limits, preview dependency status. |
122
+ | `jto_discover` | Components, categories and allowed children per format, plus renderer profiles, themes and starters. |
123
+ | `jto_describe_component` | One component's exact schema, children, parents and renderer support. |
124
+ | `jto_validate` | Path-addressed diagnostics for a document, mirroring the gate generation applies. |
125
+ | `jto_generate` | Renders to a real `.docx`/`.pptx`, with render warnings alongside the artifact. |
126
+ | `jto_preview` | Renders selected pages to PNG, inline as images or written under the output root. |
127
+ | `jto_docx_diff` | A redline `.docx` with native Word tracked changes, plus a summary of what could not be tracked. |
128
+ | `jto_workspace_create` | Hold a document server-side; returns a handle and revision 1. |
129
+ | `jto_workspace_inspect` | Read a whole document, or only the JSON Pointers you name. |
130
+ | `jto_workspace_patch` | Apply an RFC 6902 patch atomically; bumps the revision. |
131
+ | `jto_workspace_snapshot` | Export the JSON and pin the revision it was taken at. |
132
+ | `jto_workspace_list` | Every document open on this connection, with revisions, sizes and the budget. |
133
+ | `jto_workspace_close` | Release a handle and its pinned snapshots. |
134
+
135
+ Every output below also carries the envelope: `ok` (boolean) and `diagnostics` (array, always present, possibly empty). Where a tool takes a document, `document` / `handle` / `revision` are the [document source](#contracts); where it renders, `renderer` / `theme` / `themePath` / `deterministic` / `generatedAt` / `baseDir` are the shared render options. `themePath` accepts data-only `.json` themes and resolves relative paths against `baseDir`; executable theme modules are rejected.
136
+
137
+ ### `jto_info`
138
+
139
+ Call this first.
140
+
141
+ **In** — `includePreviewDependencies` (boolean, default true): probe the filesystem for LibreOffice and poppler.
142
+
143
+ **Out** — `server` `{name, package, version, protocolTransport}`; `runtime` `{node, platform, arch}`; `packages` (installed versions of `jto-ops`, `shared*`, `core-docx`, `core-pptx` — a render is a function of the document plus these); `formats[]` `{name, extension, label, rendererIds}` with defaults first; `workspaces` `{available, open}`; `output` `{root, ephemeral, maxInlineArtifactBytes}`; `previewDependencies` `{libreoffice, pdftoppm}`, each `{available, path?, envVar, searched[]}`, absent when not probed.
144
+
145
+ The probe is a PATH walk, not a `--version` spawn: `jto_info` is a per-connection discovery call and a cold `soffice --version` costs about a second. It therefore reports a binary that exists, not a binary that works; a real preview settles the rest.
146
+
147
+ ### `jto_discover`
148
+
149
+ Compact by design: names, not schemas.
150
+
151
+ **In** — `format` (`docx` | `pptx`, optional — omit for both); `includeStarters` (boolean, default true).
152
+
153
+ **Out** — `formats[]`, each `{name, extension, label, rootComponent, defaultRenderer, renderers[], components[], themes[], starters[]}`. A renderer is `{id, default, components[], unsupported[]}`, where `unsupported` names what another renderer of the same format accepts and this one does not. A component is `{name, category, description, hasChildren, root, renderers[], allowedChildren?, allowedParents[], stability?}`. A starter is a small valid document with `{id, format, title, description, document}`.
154
+
155
+ Today: `docx` roots at `docx` and renders with `docxjs` (default) or `office-open`, themes `apex`, `corporate`, `devportal`, `minimal`, `modern`; `pptx` roots at `pptx` and renders with `pptxgenjs` (default) or `office-open`, themes `default`, `dark`, `minimal`. Read them from the server rather than from here — that list is what your installed version supports.
156
+
157
+ ### `jto_describe_component`
158
+
159
+ **In** — `format` and `name` (required); `renderer` (which profile to describe it under; profiles differ wherever a backend cannot draw something); `expandProps[]` (props to return in full, named by a previous call's `elided`).
160
+
161
+ **Out** — `component` `{format, name, category, description, hasChildren, root, stability}`; `renderer` (the profile `schema` came from); `renderers[]` `{id, default, supported}`; `schema` (the branch the validator dispatches on, with nested component unions collapsed to names); `definitions` (targets of the `$ref`s left in `schema`, so it resolves standalone); `elided[]` `{pointer, prop, bytes, hint}`; `allowedChildren[]` (absent when the component takes none); `allowedParents[]`.
162
+
163
+ Nested components collapse to their names on purpose — describe those separately instead of pulling one megabyte-scale schema through a model.
164
+
165
+ ### `jto_validate`
166
+
167
+ **In** — `format` (required); document source; `renderer` (validate against this profile instead of the document's own, for this check only); `maxDiagnostics` (1–1000, default 100 — errors are kept ahead of warnings when the cap bites).
168
+
169
+ **Out** — `valid`, `format`, `renderer` (when one was requested), `source` `{origin, handle?, revision?}`, `counts` `{error, warning, info}` (before any cap), `truncated`.
170
+
171
+ `ok` mirrors the gate generation applies: schema and semantic errors block it, renderer-profile findings (`W_UNSUPPORTED_RENDERER_FEATURE`) come back as warnings, because the renderer has the last word on those.
172
+
173
+ The gate, not `jto://schema/{format}/document`. The two agree except on component nodes whose props are all optional, where the generated schema asks for a `props: {}` the validator and both renderers treat as omissible; validate follows the renderer, because that is what `jto_generate` runs.
174
+
175
+ ### `jto_generate`
176
+
177
+ **In** — `format` (required); document source; render options; `outputMode` (`path` | `base64`, default `path`); `filename` (relative to the output root, default `document.docx` / `presentation.pptx`); `fonts` `{strict, mode: "custom" | "substitute", substitution, baseDir, googleFonts: {enabled, fetchTimeoutMs}}`; `validation` `{allowUnknownFields}`.
178
+
179
+ **Out** — `format`, `renderer` (when requested), `theme` (what the render actually settled on, absent when the document's own `props.theme` decided), `artifact`, `source`.
180
+
181
+ Warnings the render emitted arrive as warning-severity diagnostics in the same envelope as a successful artifact — one place to look whether generation refused or merely compromised. The tool reports progress against a three-step total when the client sends a progress token, and checks for cancellation before the write so an abandoned request leaves no file behind.
182
+
183
+ ### `jto_preview`
184
+
185
+ **In** — `format` (required); document source; render options; `pages` (printer syntax, 1-based and inclusive: `"all"` (default), `"3"`, `"2-5"`, `"4-"`, `"-3"`, `"1-3,7"`; at most 50 pages per call); `dpi` (36–600, default 150); `outputMode` (`auto` | `images` | `path`, default `auto`); `filenamePrefix` (each page becomes `<prefix>-pNNN.png` under the output root).
186
+
187
+ **Out** — `format`, `source`, `totalPages`, `selection` (canonical spelling of what was rendered), `dpi`, `delivery` (`images` | `paths`), `pages[]` `{page, width, height, bytes, cached, delivery, artifact?}`, `renderer` `{engine, libreoffice, pdftoppm, fidelity}`, `cache` `{key, documentKey, hits, misses, enabled}`, `timings` `{generateMs, convertMs, rasterizeMs}`.
188
+
189
+ Inlined pages ride in image content blocks, not in the structured output — they follow the text block in page order and correspond to the `pages[]` entries whose `delivery` is `image`. `auto` inlines when the payload fits the client-safe budget (at most 10 pages, 2 MB per page, 8 MB total) and writes files otherwise; `images` refuses instead of falling back; `path` always writes.
190
+
191
+ The pixels come from LibreOffice, not from Microsoft Office. Line breaks, pagination, font substitution and chart rasterization can differ from what Word or PowerPoint does on the recipient's machine — a strong indication of layout, not the final document.
192
+
193
+ ### `jto_docx_diff`
194
+
195
+ DOCX only.
196
+
197
+ **In** — `before` and `after` (required), each a nested document source `{document?, handle?, revision?}`; `author` (shown in Word); `date` (ISO 8601 — omit for a deterministic redline); `dryRun`; `includeRedlineDocument`; render options; `outputMode` / `filename`.
198
+
199
+ **Out** — `summary` `{tracked: {modified, inserted, deleted}, untracked[], unchangedBlocks, notes[]}`; `artifact` (unless `dryRun`); `redline` (the redline document JSON, only when asked for); `dryRun`; `before` and `after` source summaries.
200
+
201
+ Both sides are validated before the walk, and their diagnostics are tagged with `context.side`. Read `summary.untracked`: changes with no native Word revision — tables, images, charts — are invisible as revisions inside the file itself.
202
+
203
+ ### Workspaces
204
+
205
+ Optional, and an optimization rather than a second source of truth: the JSON stays authoritative, and every tool that takes a `handle` also takes inline `document`. The point is the round trip you do not make — open a document once, patch the two paths a diagnostic pointed at, validate or preview by handle, without putting the whole tree back through the model. Handles are memory-only and scoped to one stdio connection.
206
+
207
+ Default budget per connection: 16 open documents, 16 MiB per document, 64 MiB in total, 8 pinned revisions each, and a 30-minute idle TTL that any use resets. Capacity overflow refuses with `E_WORKSPACE_LIMIT`; it never evicts another live workspace or an older pin. `jto_workspace_list` reports the live figures.
208
+
209
+ **`jto_workspace_create`** — in: `format` (required), `document` (omit to open an empty skeleton and patch into it), `title` (your own label). Out: `workspace`. Nothing is validated here.
210
+
211
+ **`jto_workspace_inspect`** — in: `handle` (required), `revision` (the current one, or one pinned by a snapshot; anything else fails rather than quietly returning newer JSON), `paths[]` (project only these pointers), `includeDocument` (default false when `paths` is given, true otherwise). Out: `workspace`, `document`, `projection` (pointer → value), `missingPaths[]` — so "absent" stays distinguishable from "present and null".
212
+
213
+ **`jto_workspace_patch`** — in: `handle` and `operations[]` (required; RFC 6902 `add` / `remove` / `replace` / `move` / `copy` / `test`, applied in order), `baseRevision` (makes the write conditional; a mismatch fails with `E_STALE_REVISION`). Out: `workspace`. Atomic: the whole patch is checked, applied to a copy, and committed only if every operation lands — a failure leaves the document exactly as it was and does not burn a revision. Invalid intermediate states are allowed on purpose; validate when you are ready.
214
+
215
+ **`jto_workspace_snapshot`** — in: `handle` (required), `filename` (write the JSON under the output root instead of returning it inline). Out: `workspace`, `document` or `artifact`. Pins the revision so `jto_workspace_inspect` can still read that exact tree after later patches; a full pin budget downgrades to an export with a `W_SNAPSHOT_NOT_PINNED` warning.
216
+
217
+ **`jto_workspace_list`** — no input. Out: `workspaces[]`, `available`, `limits`, `usage` `{workspaces, bytes}`. The cheapest way back after losing track of a handle.
218
+
219
+ **`jto_workspace_close`** — in: `handle` (required). Out: `handle`, `closed`. Idempotent; closing something already gone reports `closed: false` rather than failing. Not recoverable — snapshot first.
220
+
221
+ A `workspace` record is `{handle, format, revision, bytes, createdAt, updatedAt, title?, pinnedRevisions[]}`.
222
+
223
+ ## Resources
224
+
225
+ The same catalogues, for clients that read resources. URIs are stable.
226
+
227
+ | URI | Contents |
228
+ | ---------------------------- | ------------------------------------------------------------------------- |
229
+ | `jto://catalog` | The resource form of `jto_discover`: every format, in full. |
230
+ | `jto://renderers` | Renderer ids per format, which is default, what each profile can draw. |
231
+ | `jto://themes` | Built-in theme names per format. |
232
+ | `jto://templates` | Every starter document. |
233
+ | `jto://schema/docx/document` | Generated JSON Schema for a complete `.docx` document, by renderer. |
234
+ | `jto://schema/pptx/document` | The same for `.pptx`. |
235
+ | `jto://schema/docx/theme` | Generated JSON Schema for a `.docx` theme file, as passed to `themePath`. |
236
+ | `jto://schema/pptx/theme` | The same for `.pptx`. |
237
+
238
+ All `application/json`. The document schemas are megabytes — prefer `jto_describe_component` unless you genuinely need the whole thing. Tools and resources are generated from the same registries, and a drift test fails the build if they disagree.
239
+
240
+ ## Preview requirements
241
+
242
+ `jto_preview` shells out to two host binaries: **LibreOffice** (`soffice`, to convert the generated file to PDF) and **poppler** (`pdftoppm`, to rasterize that PDF to PNG). Neither is bundled — they are tens of megabytes and platform-specific.
243
+
244
+ ```bash
245
+ # macOS
246
+ brew install --cask libreoffice && brew install poppler
247
+ # Debian / Ubuntu
248
+ sudo apt-get install libreoffice poppler-utils
249
+ # Windows
250
+ winget install TheDocumentFoundation.LibreOffice; winget install oschwartz10612.Poppler
251
+ ```
252
+
253
+ They are looked for in `LIBREOFFICE_PATH` / `PDFTOPPM_PATH` first, then in the usual install locations, then on `PATH`.
254
+
255
+ **What degrades without them:** only preview. Discovery, validation, generation, diff and every workspace tool work on a host with neither, because generation writes OOXML directly and never launches an office suite. `jto_preview` returns `E_DEPENDENCY_MISSING` naming what is missing, where it looked, which environment variable overrides the search and the install line for the platform — a structured refusal, never a crash. `jto_info.previewDependencies` answers the same question before you spend a call on it.
256
+
257
+ The one other feature that wants LibreOffice is the DOCX `visual` component, which rasterizes a slide into a Word document.
258
+
259
+ ## Diagnostic codes
260
+
261
+ Stable: agents branch on them, so they are added freely and never renamed. `E_` blocks, `W_` does not. The validators underneath speak dialects of their own — TypeBox reports a defect as an enum ordinal, the cores use snake_case names — and every one of them is mapped into this table at the boundary; the original spelling rides along in `context.validatorCode` for anyone debugging a validator rather than a document.
262
+
263
+ The cores name their generation warnings in a dialect of their own too — bare `FONT_UNRESOLVED`, `CHART_NO_DATA`, lower-case `theme_not_found` — and those are prefixed into `W_` and upper-cased rather than passed through, so that reading the first two characters is a total test: every diagnostic this server emits starts `E_` or `W_`, and nothing has to be special-cased to find out whether it blocks. The core's own spelling stays on `context.code`, which is the string `jto-cli` prints if you are comparing the two surfaces.
264
+
265
+ | Code | Raised when |
266
+ | ---------------------------------- | ----------------------------------------------------------------------------------------------------------- |
267
+ | `E_REQUIRED_PROPERTY` | A property the component requires is absent. |
268
+ | `E_UNEXPECTED_PROPERTY` | A property the component does not declare. |
269
+ | `E_TYPE_MISMATCH` | A value of the wrong JSON type. |
270
+ | `E_UNION_MISMATCH` | No branch of a union accepted the value. |
271
+ | `E_VALUE_CONSTRAINT` | Right type, outside the schema's bounds, length, pattern or format. |
272
+ | `E_INVALID_VALUE` | Right type and shape, but not a value this position accepts. |
273
+ | `E_UNKNOWN_COMPONENT` | `name` is not a component of this format, or not one allowed here. |
274
+ | `E_MUTUALLY_EXCLUSIVE` | Two props that exclude each other were both set. |
275
+ | `E_THEME_NOT_FOUND` | A theme the document names does not exist. |
276
+ | `E_EMPTY_DOCUMENT` | The document has no content. |
277
+ | `E_INVALID_DOCUMENT` | The document fails a rule with no more specific code. |
278
+ | `E_INVALID_JSON` | A document string does not parse. |
279
+ | `E_DOC_SOURCE_MISSING` | Neither `document` nor `handle` was given. |
280
+ | `E_DOC_SOURCE_AMBIGUOUS` | Both were. |
281
+ | `E_UNKNOWN_HANDLE` | The handle is not open on this connection. |
282
+ | `E_STALE_REVISION` | `revision` / `baseRevision` no longer matches. |
283
+ | `E_WORKSPACES_UNAVAILABLE` | Workspaces are switched off for this connection. |
284
+ | `E_WORKSPACE_EVICTED` | The handle was released by the idle TTL. |
285
+ | `E_WORKSPACE_LIMIT` | The connection's workspace or byte budget is full. |
286
+ | `E_DOCUMENT_TOO_LARGE` | One document is over `maxDocumentBytes`. |
287
+ | `E_PATCH_SYNTAX` | An operation is malformed. |
288
+ | `E_INVALID_POINTER` | A JSON Pointer is not RFC 6901. |
289
+ | `E_PATCH_FAILED` | An operation could not be applied. |
290
+ | `E_PATCH_TEST_FAILED` | A `test` operation did not hold. |
291
+ | `E_OUTPUT_ROOT_ESCAPE` | A file name would land outside the output root. |
292
+ | `E_ARTIFACT_TOO_LARGE` | `outputMode: "base64"` was asked for above the inline limit. |
293
+ | `E_UNKNOWN_RENDERER` | The renderer id does not exist for this format. |
294
+ | `E_UNSUPPORTED_FORMAT` | The tool does not support that format (`jto_docx_diff` is DOCX-only). |
295
+ | `E_INVALID_DATE` | A date option is not ISO 8601. |
296
+ | `E_INVALID_THEME_PATH` | `themePath` is not a data-only `.json` theme path. |
297
+ | `E_INVALID_PAGE_SPEC` | `pages` is malformed, or selects pages the document does not have. |
298
+ | `E_PREVIEW_TOO_LARGE` | Inline images were demanded above the client-safe budget. |
299
+ | `E_PREVIEW_RENDER_FAILED` | A preview stage (build, convert, rasterize) failed. |
300
+ | `E_PREVIEW_PAGE_COUNT_UNAVAILABLE` | The PDF produced no readable page count. |
301
+ | `E_DEPENDENCY_MISSING` | A host binary preview needs, or an optional renderer backend, is absent. |
302
+ | `E_CANCELLED` | The client cancelled the request. |
303
+ | `E_INTERNAL` | A bug here. Everything else is about your document or your host. |
304
+ | `W_UNSUPPORTED_RENDERER_FEATURE` | The renderer cannot draw one feature of an otherwise valid document. |
305
+ | `W_HOST_NOTE` | A note the render emitted mid-run — unknown theme, unreadable theme file, staged font. |
306
+ | `W_UNKNOWN_THEME` | A requested theme name matched nothing; generation continued with a fallback. |
307
+ | `W_BLANK_DOCUMENT` | A workspace was opened on an empty skeleton, with no content yet. |
308
+ | `W_PATH_NOT_FOUND` | A pointer a read asked for does not resolve in that revision. |
309
+ | `W_SNAPSHOT_NOT_PINNED` | A snapshot was exported but not pinned: the workspace budget is full. |
310
+ | `W_GENERATION` | A generation warning with no code of its own. |
311
+ | `W_<CORE_CODE>` | A generation warning the core named: `W_FONT_UNRESOLVED`, `W_CHART_NO_DATA`, `W_UNKNOWN_SHAPE` and friends. |
312
+
313
+ ## Budget
314
+
315
+ Measured on Node 24.15, macOS arm64, median of five runs. Cold start is spawn to `initialized`; the first `jto_info` additionally loads both generator cores to read their renderer registries and probes the host binaries.
316
+
317
+ | Step | Median |
318
+ | ---------------------------------------- | ------ |
319
+ | spawn → `initialized` | 217 ms |
320
+ | first `jto_info` (cores + binary probe) | 93 ms |
321
+ | subsequent `jto_info` (probe suppressed) | 2 ms |
322
+
323
+ The published tarball is ~300 kB (~1.2 MB unpacked, two thirds of which is source maps). The runtime dependency closure is ~118 MB on disk across 63 packages, dominated by things that are not this server: `date-fns` (36 MB, via `core-docx`), `koffi` (28 MB, the FFI the Windows font stager needs), the MCP SDK with its `zod` (13 MB) and TypeBox (5 MB). Nothing terminal-facing — no ink, react, commander or chalk — is in the graph.
324
+
325
+ ## Embedding
326
+
327
+ The package also exports the server itself, for a host that owns its own transport:
328
+
329
+ ```ts
330
+ import { createServer, createToolDeps } from '@json-to-office/mcp-server';
331
+
332
+ const server = createServer(createToolDeps({ outputDir: './out' }));
333
+ await server.connect(myTransport);
334
+ ```
335
+
336
+ `setWorkspaceStore` before `createServer` installs your own store — with tighter limits, or `unavailableWorkspaceStore` to switch workspaces off entirely.
337
+
338
+ ## License
339
+
340
+ MIT
package/dist/cli.d.ts ADDED
@@ -0,0 +1,20 @@
1
+ /**
2
+ * `jto-mcp` — the stdio entry point.
3
+ *
4
+ * stdout is the protocol. Nothing here writes to it except `--version` and
5
+ * `--help`, both of which exit before a transport exists; the transport
6
+ * failures the SDK hands us go to stderr, and everything a document or a
7
+ * render has to say goes back in the tool result that asked. A single stray
8
+ * `console.log` anywhere in this process desynchronizes the client's framing,
9
+ * which is why the argument parser below is fifteen lines of hand-rolled code
10
+ * rather than commander: fewer things in the graph, fewer things that print.
11
+ */
12
+ interface ParsedArgs {
13
+ outputDir?: string;
14
+ version: boolean;
15
+ help: boolean;
16
+ unknown: string[];
17
+ }
18
+ declare function parseArgs(argv: readonly string[]): ParsedArgs;
19
+
20
+ export { parseArgs };