pdf-forge
Use this skill when an agent needs to operate pdf-forge through MCP instead of asking the user to perform actions in the UI.
This skill is intentionally split into:
- this file → the operational entrypoint for agents
- reference docs → the authoritative capability and contract references
Installation
npx skills add https://github.com/rendis/pdf-forge --skill pdf-forgeWhat This Skill Is For
Use this skill for tasks such as:
- discovering and calling
pdf-forgeAPIs through MCP - listing templates, versions, injectables, and render endpoints
- reading and updating
contentStructuresafely - understanding what the editor supports today without reverse-engineering the frontend
- distinguishing between:
- what the current UI exposes - what PortableDoc / contentStructure can represent - what the Typst renderer actually renders - what is safe for agent automation today
Operating Model for Agents
This is the rule that matters:
Do not assume that “TipTap supports it” means “pdf-forge supports it end-to-end.”
Always separate these layers:
- UI support — what the current body/header/footer editing surfaces expose
- PortableDoc support — what
contentStructurecan legally encode - Typst support — what the backend converter renders into PDF
- Agent-safe support — what is documented as safe for MCP automation today
If those layers disagree, be conservative.
Quick Decision Rules
| Situation | Default agent behavior |
|---|---|
| UI support = No and Agent-safe = Not documented as safe | Do not introduce it in new content. |
| PortableDoc/schema = Yes and Typst = Yes, but Agent-safe = Caution | Preserve existing usage if needed, but avoid inventing new usage casually. |
| UI / PortableDoc / Typst / Agent-safe all align | Safe default for routine MCP automation. |
MCP Proxy
This project uses mcp-openapi-proxy as the default MCP integration.
- Repository: https://github.com/rendis/mcp-openapi-proxy
- Install:
go install github.com/rendis/mcp-openapi-proxy/cmd/mcp-openapi-proxy@latest - Repo config:
.mcp.json(Claude Code) +.codex/config.toml(Codex) - Canonical MCP spec:
core/docs/openapi.yaml - Default server name:
pdf-forge - Default tool prefix:
pf
MCP Tool Contract
The proxy does not register one MCP tool per endpoint. It always exposes exactly 3 tools:
pf_list_endpointspf_describe_endpointpf_call_endpoint
Recommended workflow:
pf_list_endpoints→ discover candidate endpointspf_describe_endpoint→ inspect the exact contract for onetoolNamepf_call_endpoint→ execute the request withpath/query/headers/cookies/body
Common toolName examples:
pf_get_api_v1_content_templatespf_get_api_v1_content_templates_templateId_all_versionspf_get_api_v1_content_templates_templateId_versions_versionIdpf_put_api_v1_content_templates_templateId_versions_versionIdpf_post_api_v1_workspace_document_types_code_renderpf_post_api_v1_workspace_templates_versions_versionId_render
Multi-tenant Headers
pdf-forge is multi-tenant. MCP calls often need contextual headers.
Panel routes usually require:
X-Tenant-IDX-Workspace-ID
Render routes require:
X-Tenant-CodeX-Workspace-CodeX-Environment(devorprod)
Pass them:
- per request in
pf_call_endpoint.headers, or - globally with
MCP_EXTRA_HEADERS
Dummy auth mode skips JWT validation, but tenant/workspace headers are still required where the route expects them.
Agent-Safe Editing Rules
When editing a template version through MCP:
- Fetch the current version first.
- Treat
contentStructureas the canonical document contract. - Preserve unknown fields and untouched subtrees.
- Do read → modify → write, never blind overwrite.
- Validate body, header, and footer separately.
- Only use features documented as Supported or Partially supported / use with caution.
- If a feature is only known to exist in backend/schema but is not documented as agent-safe, do not introduce it casually.
- Preserve existing style attrs unless the task explicitly changes them. In particular, do not rewrite
textStyle.colorvalues just because they are not in your preferred format. - Inline injector placeholders can carry supported text marks/styles (for example bold, italic, strike, font family, font size, and color). Preserve those marks when editing injector nodes.
POST /api/v1/workspace/templates/versions/{versionId}/renderdoes not make DRAFT versions renderable. It only renders PUBLISHED versions, plus STAGING versions whenX-Environment: dev.- If you need MCP render validation for a version that is still DRAFT, stage it first, then render it in
dev. - Do not treat a successful draft save as proof of semantic correctness; render/publish validation is stronger than draft update validation.
Before Editing a Template Version
Minimum checklist:
- Read template + version details
- Inspect the current
contentStructure - Confirm whether the change affects:
- body content - header content - footer content - header/footer image/layout - variableIds - pageConfig
- Preserve document versioning and unknown metadata
- Update only the intended subtree/fields
- Save with the version update endpoint
- Render or preview to validate the result
Color Contract for Agents
When editing or reviewing contentStructure styles:
- prefer
#RRGGBB/#RGBwhen introducing a new color manually - preserve existing
textStyle.colorvalues if they already exist in live content - do not assume stored documents are hex-only
- expect persisted content to contain CSS color strings such as
rgb(...)orrgba(...), especially when rich-text styling came from the editor - if a change touches color-heavy content, render after saving instead of trusting the draft update alone
For the detailed boundary and document-contract guidance, read:
Recommended MCP Workflow
1) Discover and inspect endpoints
pf_list_endpoints→ find template/version/render operationspf_describe_endpoint→ inspect one contract before calling it
2) Read the document version
Typical read flow:
- list templates
- get template or all versions
- get version detail
- inspect
contentStructure
3) Modify contentStructure
- preserve the existing envelope
- update only the relevant body/header/footer/page config subtree
- keep
variableIdsconsistent with the content you introduce
4) Persist
- call the version update endpoint with the updated
contentStructure
5) Validate by rendering
Use either:
POST /api/v1/workspace/templates/versions/{versionId}/renderPOST /api/v1/workspace/document-types/{code}/render
Important nuance:
- render-by-version does not validate arbitrary saved drafts; it still enforces renderable statuses
- in practice that means PUBLISHED always, and STAGING only with
X-Environment: dev - if your edited version is still DRAFT, stage it first before claiming MCP render validation is available
Important Validation Boundary
Saving a draft version is a storage checkpoint, not a full semantic approval step.
- draft update success mainly means the server accepted the JSON payload
- stronger semantic confidence comes from render/publish-oriented validation
Agents should therefore save, then render or otherwise validate before claiming the document is correct.
Canonical References
Read these before making non-trivial document edits:
- editor-capability-matrix.md
- portable-document-contract.md
- typst-rendering-boundaries.md
- mcp-editor-workflows.md
- issue-routing.md
Also keep these existing references handy when relevant:
Issue Routing / Escalation
When a user reports a bug, asks for a missing behavior, or requests a new capability, do not route the issue by “bug vs feature”.
Route it by ownership of the change.
Suggest pdf-forge library repo when the change belongs to core reusable behavior
Typical library-owned areas:
- editor base behavior
- Typst rendering / layout
- PortableDoc /
contentStructure - public SDK or extension-point limitations
- generic API behavior
- docs / skill / MCP guidance
- bugs reproducible in a vanilla
pdf-forgesetup
Suggest the implementation repo when the change belongs to custom project behavior
Typical implementation-owned areas:
- domain-specific injectors
WorkspaceInjectableProviderRequestMapper- custom middleware or auth wiring
- external integrations
- business rules
- project-specific configuration or extension code
Mandatory routing flow
Before suggesting or creating an issue:
- verify whether the behavior already exists
- verify whether it is a real bug/gap vs expected custom extensibility
- classify ownership:
- core reusable - downstream custom - ambiguous
- suggest the target repo
- only offer issue creation when the destination is clear
Conservative defaults
- Missing domain injector by default → implementation repo
- Generic editor/render/API/PortableDoc bug by default →
pdf-forge - If the issue only appears with custom project code, assume implementation repo until proven otherwise
- If the downstream repo is not known, do not invent it
When the implementation repo is unknown, explain the probable owner and ask the user for the target repo/path before creating the issue.
Issue metadata to prepare
When offering issue creation, prepare at least:
- target repo
- issue type (
bug,feature,docs,enhancement) - short title
- observed behavior
- expected behavior
- why this belongs to that repo
- affected layer (
editor,render,PortableDoc,MCP/docs,custom injector/provider/mapper, etc.) - evidence (paths, endpoint/toolName, logs, screenshots when available)
Use issue-routing.md for detailed routing rules, examples, and ambiguous cases.
Capability Summary (Short Version)
Supported today for agent workflows
- body text content with headings 1-3
- bold / italic / strike
- text color, font family, font size
- line spacing presets
- text alignment
- bullet and ordered lists
- blockquote and horizontal rule
- body images
- conditional blocks
- editable tables
- table injectors and list injectors
- header/footer text + surface image layouts
- inline injector placeholders with supported text marks/styles
Use with caution
- page breaks
- image behaviors that depend on wrapping/layout details
- features supported by renderer/schema but not clearly exposed in the current toolbar/UI
Not documented as safe by default
- marks/nodes that exist in backend rendering but are not clearly exposed and validated as part of the current editing UX
- any new attrs/structures invented ad hoc by the agent
Portable Document Reminder
contentStructure is not “just editor HTML”. It is a PortableDoc envelope that includes:
- document
version pageConfigvariableIds- body
content - optional
header - optional
footer exportInfo
That envelope is what the backend validates and renders.
Editor Surface Reminder
The body, header, and footer are not equivalent surfaces.
- body supports richer structures such as conditionals and tables
- header and footer are intentionally constrained surfaces with dedicated image/layout behavior
- header renders on the first page; footer renders on the last page
Never assume a body-safe operation is automatically safe for header/footer surfaces.
Typst Reminder
The renderer supports more nodes/marks than the current toolbar obviously exposes. That does not automatically make them safe defaults for agents.
When in doubt:
- prefer the documented subset
- preserve existing structures
- render to verify
Spec Generation
mcp-openapi-proxy requires OpenAPI 3.x. This repo still generates Swagger 2.0 for Swagger UI, and make swagger converts it into core/docs/openapi.yaml for MCP.
make swaggerIf you are working with local, uncommitted API changes, regenerate the spec and temporarily point MCP_SPEC to ./core/docs/openapi.yaml. The committed default config intentionally uses the GitHub raw URL from main.