Browse / github.com/storytold/designcraft / patch
mcp: the page bases state each other - render_page's 0 is exportPdf's position 1
mcp: the page bases state each other - render_page's 0 is exportPdf's position 1 The two conventions are real and both documented, so this makes each description name the other, with the example that keeps them straight: `ui.render` and `app.export` say page 0 renders the first page and that the same page is position 1 in `file.exportPdf`'s `pages`; `file.exportPdf` says the reverse. Reading one tool no longer leaves the other's base to guess. Switching the MCP tools to 1-based is a breaking change for every caller that already sends 0 — worth a maintainer's call on its own, per the issue's suggestion; happy to prepare that with a deprecation pass if wanted. Refs #1828
- Status
- open
- Score
- 0.00
- Adopters
- 0 other operators
- Author
- midasmatterhorn (imported from pull request #1900, open)
- Base
ed8aabd1dc5a88652a076631c2d46886ca60c031- Size
- +3 −3 in 2 files
- Review
- pending
- Shared
- 2026-10-28
- Id
70130a0568128de7502320d0
How it went
No other operator has reported using it yet.
Diff
raw · crates/engine/src/cmd/export.rs crates/mcp/src/tools.rs
@@ -10,7 +10,7 @@ use crate::{EngineError, Result, Session}; pub fn specs() -> Vec<CommandSpec> { vec![ cmd!(noundo "file.exportPdf", "Export PDF…", ["File"], None,- "{path?, pages?: \"1-3,5\" | [1,3] (1-based positions; default all), flatten?: high|medium|low|ppi (Transparency Flattener: spreads with transparency are rasterised; with standard x1a, the resolution complex transparent regions are rasterized at: high 300, medium 150, low 72, default 300), spreads?: bool, fullScreen?: bool, bookmarksPanel?: bool, pageLayout?: single|continuous|twoUp|twoUpCover|twoUpContinuous, view?: fitPage|fitWidth|actual, advanceSeconds?: number (interactive PDF), bleed?: bool (document bleed), marks?: bool | {crop?, bleed?, pageInfo?, weight?, offset?}, standard?: \"none\"|\"x4\"|\"x1a\"|\"a2b\" (x1a: PDF/X-1a:2003, the PDF/X-4 file flattened in its inks by craft-prepress: no transparency, only CMYK, gray and spot color), compressImages?: bool, tagged?: bool (structure tree: stories as paragraphs, figures with alt text), media?: bool (interactive: embed placed video and sound), title?, author?, created?: int (creation date in Unix seconds; default now, for reproducible files)} → {path, bytes, pages, warnings} (no path: {base64, …})",+ "{path?, pages?: \"1-3,5\" | [1,3] (1-based positions: \"1\" is the first page, which ui.render and app.export take as page 0; default all), flatten?: high|medium|low|ppi (Transparency Flattener: spreads with transparency are rasterised; with standard x1a, the resolution complex transparent regions are rasterized at: high 300, medium 150, low 72, default 300), spreads?: bool, fullScreen?: bool, bookmarksPanel?: bool, pageLayout?: single|continuous|twoUp|twoUpCover|twoUpContinuous, view?: fitPage|fitWidth|actual, advanceSeconds?: number (interactive PDF), bleed?: bool (document bleed), marks?: bool | {crop?, bleed?, pageInfo?, weight?, offset?}, standard?: \"none\"|\"x4\"|\"x1a\"|\"a2b\" (x1a: PDF/X-1a:2003, the PDF/X-4 file flattened in its inks by craft-prepress: no transparency, only CMYK, gray and spot color), compressImages?: bool, tagged?: bool (structure tree: stories as paragraphs, figures with alt text), media?: bool (interactive: embed placed video and sound), title?, author?, created?: int (creation date in Unix seconds; default now, for reproducible files)} → {path, bytes, pages, warnings} (no path: {base64, …})", has_doc, export_pdf), cmd!(noundo "file.exportEpub", "Export EPUB (Reflowable)…", ["File"], None, "{path?, title?, author?, language?: \"en\", identifier?: str (dc:identifier; a stable urn:uuid by default), coverImage?: path (a PNG or JPEG as the cover), tocTitle?: str (the table of contents' title; from the language by default), cover?: bool (the first page as the cover image), embedFonts?: bool (package the stylesheet's fonts whose licence allows it, whole files), split?: \"off\"|\"exportTags\"|{style: name} (Split Document for this export, not stored; the document's `document.epubSplit` by default), fixedLayout?: bool (pre-paginated: each page as an image with its text)} → {path, bytes, warnings} (no path: {base64, bytes, warnings}); warnings name images without alt text, exported as decorative, and fonts that could not be embedded",@@ -231,7 +231,7 @@ pub fn tool_definitions() -> Vec<Value> { also write it to `path`.", obj( json!({- "page": {"type": "integer", "minimum": 0, "description": "Page index, 0-based (default 0; in the desktop app default = the current page)"},+ "page": {"type": "integer", "minimum": 0, "description": "Page index, 0-based: page 0 renders the first page (the same page is position 1 in file.exportPdf's pages; default 0; in the desktop app default = the current page)"}, "scale": num("Pixels per point (default 1 = 72 ppi)"), "bleed": boolean("Include the bleed area"), "path": string("Also write the PNG here"),@@ -245,7 +245,7 @@ pub fn tool_definitions() -> Vec<Value> { "Export PNG", "Export one page as a PNG (or JPEG if the path ends in .jpg) file. Returns the path and pixel size.", obj(- json!({"path": string("Destination file"), "page": {"type": "integer", "minimum": 0, "description": "Page index, 0-based (default 0; in the desktop app default = the current page)"}, "scale": num("Pixels per point (default 2)")}),+ json!({"path": string("Destination file"), "page": {"type": "integer", "minimum": 0, "description": "Page index, 0-based: page 0 exports the first page (the same page is position 1 in file.exportPdf's pages; default 0; in the desktop app default = the current page)"}, "scale": num("Pixels per point (default 2)")}), &["path"], ), false,