mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-24 10:08:00 -06:00
With only --automation-read-root / --automation-write-root set, a path went straight to std::fs::canonicalize, so a UNC, WebDAV or device path (`\host\share\x.pdf`, `\.\pipe\x`) was resolved, contacting the host, before it was refused. `--root` mode already refuses those by name (`foreign_share`). `foreign_share` moves into file_access, and `AutomationRoots::check_path` applies it to the path made absolute (by name, nothing read) before following links, with the same refusal as any other path outside the root. A root on a share still accepts that share. `resolve` is split into `absolute` (empty path, device names, made absolute) and `resolve` (links followed), so `out\NUL` keeps its own refusal. Part of #4003 (the first of its three points).
161 lines
9.1 KiB
Markdown
161 lines
9.1 KiB
Markdown
# MCP conventions
|
||
|
||
Start explicitly with `pdfcraft-cli mcp [--root DIR] [--compact] [--automation-read-root DIR] [--automation-write-root DIR]`. The server uses JSON lines on
|
||
stdin/stdout, opens no port, and stops on EOF. `--root` confines all reads and writes, including
|
||
commands invoked through the common tools. Logs stay on stderr.
|
||
|
||
Supply `--root` at most once, followed by a nonempty directory path. Missing operands,
|
||
repeated roots and another long option in place of the directory are refused before any
|
||
script is read or server is started. Prefix a directory name beginning with `--` with `./`.
|
||
|
||
## Install and register
|
||
|
||
From the repository root, with Rust 1.95+ and a native linker installed
|
||
(see [development](development.md) for build and environment notes):
|
||
|
||
```sh
|
||
cargo install --path apps/pdfcraft-cli --locked
|
||
# POSIX shells, for Cargo's default install location:
|
||
export PATH="${CARGO_HOME:-$HOME/.cargo}/bin:$PATH"
|
||
pdfcraft-cli --help
|
||
pdfcraft-cli mcp --help
|
||
```
|
||
|
||
The default features include MCP; do not pass `--no-default-features` for this use.
|
||
Cargo installs a binary, not a symlink into `target/`, so it survives `cargo clean`.
|
||
Re-run the install command after updating the checkout.
|
||
By default the executable goes in `$CARGO_HOME/bin` (`~/.cargo/bin` when unset), or
|
||
`%USERPROFILE%\.cargo\bin` on Windows. If you set `--root`, `CARGO_INSTALL_ROOT`, or
|
||
Cargo's `install.root`, use that installation root's `bin` directory instead. Add it
|
||
to the `PATH` inherited by the MCP client, not just an interactive shell, and restart
|
||
the client after changing its environment.
|
||
|
||
Register the installed command explicitly (replace `/work/project` with an existing folder):
|
||
|
||
```sh
|
||
claude mcp add pdfcraft -- pdfcraft-cli mcp --root /work/project
|
||
```
|
||
|
||
Other MCP clients can use this bare-name configuration:
|
||
|
||
```json
|
||
{"mcpServers":{"pdfcraft":{"command":"pdfcraft-cli","args":["mcp","--root","/work/project"]}}}
|
||
```
|
||
|
||
Linux `.deb` and `.rpm` packages already install `/usr/bin/pdfcraft-cli`; those users
|
||
can skip the source install. The [downloads list](../README.md#downloads) also includes
|
||
standalone Linux CLI tarballs and a macOS CLI zip; unpack and copy the executable into
|
||
a directory on the client's `PATH`. Installing does not start or register a server:
|
||
MCP remains opt-in and uses stdio only.
|
||
|
||
If a client reports `ENOENT` or "Executable not found in $PATH", verify the install with
|
||
`command -v pdfcraft-cli` (PowerShell: `Get-Command pdfcraft-cli`) in the client's
|
||
environment. If that environment cannot be changed, set JSON `command` to the absolute
|
||
installed binary path, not a literal `~` or `$HOME` expression. Avoid symlinks into
|
||
`target/release`: cleaning the build directory breaks them. A source build alone does
|
||
not put the CLI on `PATH`.
|
||
|
||
## Confining file access
|
||
|
||
```sh
|
||
pdfcraft-cli mcp --automation-read-root /work/project --automation-write-root /work/project/out
|
||
```
|
||
|
||
```json
|
||
{"mcpServers": {"pdfcraft": {"command": "pdfcraft-cli",
|
||
"args": ["mcp", "--automation-read-root", "/work/project", "--automation-write-root", "/work/project/out"]}}}
|
||
```
|
||
|
||
The two flags (VectorCraft, PhotoCraft and the other Craft apps take the same ones, so one client
|
||
config works across the suite) keep the agent to the folders you give it:
|
||
|
||
- Every file a tool reads must lie inside the **read root**: `doc_open`, `page_insert_file`,
|
||
`page_replace`, `doc_combine` and `doc_create_multiple` inputs, `doc_create` images, stamp,
|
||
picture, attachment, signature and form-image files, `doc_watermark` and `doc_background` files,
|
||
`doc_import_data`, `form_merge_data`, the files `action_run` and `ocr_recognize_files` process,
|
||
digital ID, graphic and certificate files for signing and `sign_trust`, `text_font_import`, and
|
||
the folders `text_font_files` searches. Without `directories`, `text_font_files` searches the read
|
||
root instead of the installed fonts.
|
||
- Every file a tool writes must lie inside the **write root**: `doc_save` (also saving in place),
|
||
`page_extract`, `doc_combine`, `doc_create_multiple`, `doc_split`, `doc_reduce`, `doc_optimize`,
|
||
`doc_print` to a file, the `doc_export_*` tools, `image_save`, `measure_export`,
|
||
`comments_summarize`, the accessibility report and review record, `doc_compare_report`,
|
||
`form_merge_data`, the output folders of `action_run` and `ocr_recognize_files`, `sign_id_create`
|
||
and `sign_document`.
|
||
- Read and write access are separate. Give the same folder twice for both. A root left out grants
|
||
none of its access: with only `--automation-read-root`, nothing can be written to disk, though
|
||
tools that give their result back instead of a path still do. Without either flag nothing changes.
|
||
- A path is made absolute against the server's working directory, then its links are followed: a file
|
||
(or folder) link that leads out of a root is outside it. For a file that doesn't exist yet, its
|
||
nearest existing folder's links are followed and the rest must be plain names, so `..` below a
|
||
folder that doesn't exist is refused, as is a link that leads nowhere (writing through it would
|
||
create its target). The result is compared with the root by whole folder names: `/work2/x` is not
|
||
inside `/work`. On Windows case is ignored, device names (`CON`, `NUL`, `COM1`...) are refused,
|
||
and a path on another network share or in a device namespace than the root's (`\\host\share`,
|
||
`\\?\UNC\...`, `\\.\...`) is refused by name, before it is resolved, so the host is never
|
||
contacted. A root on a share accepts the paths on that share.
|
||
- A refused path is an ordinary tool error the agent can read and correct, for example
|
||
`automation path rejected: outside the write root /work/project/out: /etc/x.pdf` or
|
||
`automation filesystem access is not granted: write authority is absent: /work/project/out/x.pdf`.
|
||
Tools that handle several files report a refused one in its own entry.
|
||
- The roots must be existing folders; a missing one stops the server with an error. Each flag is
|
||
given once (`--flag <dir>` or `--flag=<dir>`).
|
||
- `--root DIR` still works and can be combined with these flags. Both then apply: a path must be
|
||
inside `--root` and inside the matching automation root.
|
||
|
||
`pdfcraft-cli mcp` always runs the session in its own process (there is no flag to connect it to a
|
||
running app), so the roots cover every server it starts. The desktop app is not confined by them.
|
||
|
||
What the roots do not cover: tools that take no path still read and change the open documents, and
|
||
`text_extract` and the other inspection tools return their content. Keychain and Windows
|
||
certificate-store identities for `sign_document` are not files and are not checked. Sending a job
|
||
to a printer is not a file write. Every path a tool reads or writes goes through
|
||
`Automation::resolve`, and a unit test keeps the tool modules from touching the file system
|
||
without it.
|
||
|
||
A path is checked, then opened: another program that swaps a link in between can still win that
|
||
race, so the roots keep an agent to the files it was given rather than guarding against other
|
||
software on the computer.
|
||
|
||
## Tools
|
||
|
||
| Tool | Arguments |
|
||
|---|---|
|
||
| `command_list` | `doc?`, `filter?`, `enabled_only?`; returns `{commands: [...]}` with mapped tool schemas |
|
||
| `command_run` | `id`, `params?` (the mapped task tool's arguments, including `doc` when required) |
|
||
| `command_batch` | `steps: [{id, params?}]`, `stop_on_error?` (default true) |
|
||
| `doc_inspect` | `doc?`; one document's info or `{documents: [...]}` for the session |
|
||
| `render_preview` | `doc?`, `page?` (1-based, default 1), `max_side?` (1–4096, default 1024) |
|
||
|
||
All existing task tools remain available. Compact mode lists these five tools alongside its
|
||
existing core set and `tool_search` / `tool_call`. There are no legacy-name aliases. The full
|
||
[automation guide](../crates/automation/README.md) describes the tools, resources, coordinate
|
||
system, root confinement, and errors.
|
||
|
||
```text
|
||
command_run {"id":"file.open","params":{"path":"input.pdf"}}
|
||
→ {"doc":1,...}
|
||
command_list {"doc":1,"filter":"rotate"}
|
||
command_run {"id":"page.rotate","params":{"doc":1,"degrees":90}}
|
||
render_preview {"doc":1,"page":1,"max_side":512}
|
||
```
|
||
|
||
Every tool has a title and the four MCP annotation hints. Unknown top-level argument keys return
|
||
JSON-RPC `-32602`; command parameter keys that do not belong to the mapped task tool are ignored
|
||
with warnings. Task failures and escaped tool panics return `isError: true`, and the server keeps
|
||
serving. A failed batch includes per-step errors and is marked `isError: true`.
|
||
|
||
Exports complete synchronously. A call's `progressToken` and cancellation notifications are
|
||
harmlessly ignored; the server does not provide background export progress or cancellation.
|
||
|
||
## Resources and protocol versions
|
||
|
||
`pdfcraft://document` returns the session's document information (as `doc_inspect` without
|
||
arguments), including an empty `documents` array before any PDF is opened. `pdfcraft://commands`
|
||
returns the command catalog (as `command_list`). Existing per-document info, text and page-image
|
||
resources and their templates are preserved.
|
||
|
||
Unknown top-level argument keys are refused with JSON-RPC `-32602` (invalid params) instead of
|
||
being ignored, so a misspelled argument is reported rather than silently dropped. Clients that
|
||
sent extra keys before get an error now.
|