MCP server: One MCP server.

Docgen

@cyanheadsRun by its publisher: served from their own domain, namespace checked by the registry.MCP server · External

Render HTML/markdown to PDF, export rows to xlsx, and fill AcroForm PDFs.

PDF toolsv0.3.0Apache-2.0TypeScript
Freeno charge per call

Runs on your machine from its package; the steps are below.

What it does

  1. Reads
  2. Writes
  3. High impact

At most: creates or changes data: records, files, settings.

What it costs

Free

Odel charges nothing per call. The publisher may charge for its own service.

Runs locally over stdio

This server isn't hosted — your MCP client launches it from a package registry. Use one of the commands below, or drop the config into your client (e.g. Claude Desktop).

npm@cyanheads/docgen-mcp-serverv0.3.0

Run

npx -y @cyanheads/docgen-mcp-server

MCP client config

{
  "mcpServers": {
    "docgen": {
      "command": "npx",
      "args": [
        "-y",
        "@cyanheads/docgen-mcp-server"
      ]
    }
  }
}
npm@cyanheads/docgen-mcp-serverv0.3.0

Run

npx -y @cyanheads/docgen-mcp-server

MCP client config

{
  "mcpServers": {
    "docgen": {
      "command": "npx",
      "args": [
        "-y",
        "@cyanheads/docgen-mcp-server"
      ]
    }
  }
}
README9 min read

@cyanheads/docgen-mcp-server

Render HTML/markdown to PDF, export rows to xlsx, and fill AcroForm PDFs via MCP. STDIO or Streamable HTTP.

5 Tools • 1 Resource

Version License Docker MCP SDK npm TypeScript Bun

Install in Claude Desktop Install in Cursor Install in VS Code

Framework


Overview

Generate documents from an agent: render HTML, markdown, or template data to PDF, export rows to Excel, and fill PDF forms. Everything renders locally on a bundled stack (pdf-lib, exceljs, marked).

Tools

ToolDescription
docgen_render_pdfRender HTML, markdown, or a {{key}} template + data object to a downloadable PDF
docgen_export_spreadsheetRender one or more named worksheets of row objects to a downloadable .xlsx workbook
docgen_fill_formFill the AcroForm fields of a supplied PDF (base64 or https URL) and optionally flatten it
docgen_list_form_fieldsInspect exact AcroForm field names, types, required flags and choice options without changing the PDF
docgen_get_documentRe-fetch a previously rendered document by the id a render/export/fill tool returned

Resources

ResourceDescription
docgen://document/{documentId}A rendered document by id — raw bytes as a blob plus a JSON metadata block

All document data is also reachable via the tool surface — docgen_get_document is the tool-only twin of this resource.

Capability reference

docgen_render_pdf tool

  • Provide exactly one source: { html }, { markdown }, or { template, data } for {{key}} interpolation. pageOptions controls page size/orientation, CSS-length margins, headers/footers ({{page}}, {{total}}, {{date}}) and page numbers.
  • Returns a DocumentEnvelope with pageCount; degraded: true means CSS, images or scripts were dropped, or characters were replaced with ?. The lightweight engine renders headings, paragraphs, lists and tables.
  • Typed failures: invalid_source, template_render_failed, document_too_large, render_timeout.

docgen_export_spreadsheet tool

  • Supply at least one sheet with a unique name (1–31 characters) and scalar rows[]; empty rows yield a header-only sheet. Optional columns[] sets headers, type (string / number / date / boolean), width and Excel format.
  • Omitted columns use all row keys in first-seen order. Boolean columns accept true/1/yes/on and false/0/no/off, case-insensitive; other strings stay text.
  • Returns a DocumentEnvelope with sheetCount. Typed failures: empty_workbook, invalid_sheet_name, document_too_large, render_timeout.

docgen_fill_form tool

  • Provide exactly one sourcePdf: { base64 } or { url } (public https serving application/pdf), plus fields mapping exact, case-sensitive AcroForm field names to values. Flat or XFA-only PDFs return not_a_form.
  • Obtain exact names and choice options with docgen_list_form_fields before filling an unfamiliar PDF.
  • Returns a DocumentEnvelope with pageCount and unmatchedFields[] for names with no matching field.
  • flatten: true makes the filled values non-editable (default false).

docgen_list_form_fields tool

  • Accepts the same sourcePdf base64-or-URL source as docgen_fill_form. Returns { fields: [{ name, type, required, options? }] } without modifying the PDF or storing a document.
  • Types: text, checkbox, dropdown, optionList, radioGroup, signature, button. Only dropdown, optionList and radioGroup entries include options; signatures and buttons are discovery only.
  • Nested names retain their exact paths. PDFs without AcroForm fields return not_a_form.

docgen_get_document tool

  • Supply the documentId an earlier render/export/fill returned (doc_ plus 24 url-safe characters).
  • Re-fetches its DocumentEnvelope without rendering again; an expired or unknown id returns document_expired.

docgen://document/{documentId} resource

  • Use the documentId from a render/export/fill result (doc_ plus 24 url-safe characters).
  • Returns a base64 blob with the document's MIME type and JSON metadata (documentId, byteSize, pageCount/sheetCount, createdAt, ttlSecondsRemaining). Reads the same store as docgen_get_document; expired or unknown ids return document_expired.

Features

Built on @cyanheads/mcp-ts-core: stdio and Streamable HTTP transports, pluggable auth (none / jwt / oauth), swappable storage (in-memory, filesystem, Supabase, Cloudflare KV/R2/D1), structured logging with optional OpenTelemetry tracing.

docgen-specific:

  • No external API — a bundled rendering stack (pdf-lib, exceljs, marked), so renders are local and deterministic with no upstream to fail
  • One shared DocumentEnvelope across the four document delivery tools; field inspection returns its inventory directly
  • Bounded renders — a byte ceiling (DOCGEN_MAX_DOCUMENT_BYTES) and a timeout (DOCGEN_RENDER_TIMEOUT_MS) turn a runaway render into a typed error instead of a hang
  • Tenant-scoped, expiring storage — a document id resolves only for the tenant that created it, and documents expire rather than accumulate
  • SSRF-guarded form fetch — both form tools check every address, including each redirect hop, and block private, loopback and link-local destinations

Agent-friendly output:

  • Dual-surface delivery — every envelope field appears in both structuredContent and the markdown output, including the documentId, resource URI, size, and TTL
  • Inline by size — inlineBase64 is populated only at or under DOCGEN_INLINE_MAX_BYTES; larger documents are delivered through the resource URI
  • Partial-fill reporting — docgen_fill_form returns unmatchedFields[] so the agent sees which field names didn't match
  • Typed errors with recovery hints (invalid_source, template_render_failed, document_too_large, render_timeout, not_a_form, source_unfetchable, document_expired, …); semantic validation uses ValidationError (-32007) and malformed arguments InvalidParams (-32602)

Getting started

Add the following to your MCP client configuration file. No API keys are required.

json
{
  "mcpServers": {
    "docgen-mcp-server": {
      "type": "stdio",
      "command": "bunx",
      "args": ["@cyanheads/docgen-mcp-server@latest"],
      "env": {
        "MCP_TRANSPORT_TYPE": "stdio",
        "MCP_LOG_LEVEL": "info"
      }
    }
  }
}

Or with npx (no Bun required):

json
{
  "mcpServers": {
    "docgen-mcp-server": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "@cyanheads/docgen-mcp-server@latest"],
      "env": {
        "MCP_TRANSPORT_TYPE": "stdio",
        "MCP_LOG_LEVEL": "info"
      }
    }
  }
}

Or with Docker:

json
{
  "mcpServers": {
    "docgen-mcp-server": {
      "type": "stdio",
      "command": "docker",
      "args": [
        "run", "-i", "--rm",
        "-e", "MCP_TRANSPORT_TYPE=stdio",
        "ghcr.io/cyanheads/docgen-mcp-server:latest"
      ]
    }
  }
}

For Streamable HTTP, set the transport and start the server:

sh
MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 bun run start:http
# Server listens at http://localhost:3010/mcp

Documents are delivered by the docgen://document/{id} resource (and inline base64 when small enough) over every transport. The envelope's downloadUrl field is reserved for a future HTTP download route and is not emitted in this version.

Prerequisites

  • Bun v1.4.0 or higher (or Node.js v24+); development uses Bun v1.4.3.

Installation

  1. Clone the repository:
sh
git clone https://github.com/cyanheads/docgen-mcp-server.git
  1. Navigate into the directory:
sh
cd docgen-mcp-server
  1. Install dependencies:
sh
bun install
  1. Configure environment:
sh
cp .env.example .env
# edit .env to override any defaults

Configuration

All configuration is optional — docgen runs with no required environment variables.

VariableDescriptionDefault
DOCGEN_DOCUMENT_TTL_SECONDSHow long a rendered document is retrievable before it expires, in seconds.900
DOCGEN_MAX_DOCUMENT_BYTESHard ceiling on a single rendered artifact in bytes; exceeding it aborts the render.26214400
DOCGEN_RENDER_TIMEOUT_MSPer-render wall-clock budget in milliseconds; exceeding it aborts the render.30000
DOCGEN_INLINE_MAX_BYTESArtifacts at or under this byte size are returned inline as base64; larger ones omit it.5242880
DOCGEN_PDF_ENGINEPDF rendering engine. Only lightweight is implemented; chromium is reserved and rejected at startup.lightweight
MCP_TRANSPORT_TYPETransport: stdio or http.stdio
MCP_HTTP_PORTPort for the HTTP server.3010
MCP_SESSION_MODEHTTP session mode: auto, stateful, or stateless.stateless
MCP_AUTH_MODEAuth mode: none, jwt, or oauth.none
MCP_PUBLIC_URLPublic origin behind a TLS proxy.—
MCP_LOG_LEVELLog level (RFC 5424).info
LOG_TOOL_FAILURE_PAYLOADSLog failed arguments/results, redacted by key name and capped by LOG_TOOL_FAILURE_PAYLOAD_MAX_BYTES (default 16384). Free-form values may retain secrets.false
STORAGE_PROVIDER_TYPEStorage backend for document bytes + metadata.in-memory
OTEL_ENABLEDEnable OpenTelemetry instrumentation (spans, metrics, completion logs).false
OTEL_EXPORTER_OTLP_ENDPOINTBase OTLP URL; traces use /v1/traces, metrics use /v1/metrics. Signal-specific endpoints override it.—
OTEL_EXPORTER_OTLP_LOGS_ENDPOINTOpt-in log endpoint, used as-is; the base URL never enables log export.—

See .env.example for the full list of optional overrides.

The default in-memory storage drops every document on restart, and earlier ids then return document_expired. To keep documents across restarts, point STORAGE_PROVIDER_TYPE at a persistent backend.

Running the server

Local development

  • Build and run:

    sh
    # One-time build
    bun run rebuild
    
    # Run the built server
    bun run start:stdio
    # or
    bun run start:http
    
  • Run checks and tests:

    sh
    bun run devcheck   # Lint, format, typecheck, security, changelog sync
    bun run test       # Vitest test suite
    bun run lint:mcp   # Validate MCP definitions against spec
    

Docker

sh
docker build -t docgen-mcp-server .
docker run --rm -e MCP_TRANSPORT_TYPE=http -p 3010:3010 docgen-mcp-server

The Dockerfile defaults to HTTP transport, stateless session mode, and logs to /var/log/docgen-mcp-server. OpenTelemetry peer dependencies are installed by default — build with --build-arg OTEL_ENABLED=false to omit them.

Project structure

DirectoryPurpose
src/index.tscreateApp() entry point — registers the tools/resource and inits the render + storage services.
src/configServer-specific environment variable parsing and validation with Zod.
src/mcp-server/toolsTool definitions (*.tool.ts).
src/mcp-server/resourcesResource definitions (*.resource.ts).
src/services/documentThe rendering stack (RenderService) and artifact store (DocumentStore), shared types, and the SSRF fetch guard.
tests/Unit and integration tests mirroring src/.

Development guide

See CLAUDE.md/AGENTS.md for development guidelines and architectural rules. The short version:

  • Handlers throw, framework catches — no try/catch in tool logic
  • Use ctx.log for request-scoped logging, ctx.state for tenant-scoped storage
  • Register new tools and resources in src/index.ts's createApp() arrays
  • One shared DocumentEnvelope across all delivery tools — keep the writers and reader interchangeable

Contributing

Issues are welcome. Run checks and tests before submitting:

sh
bun run devcheck
bun run test

License

Apache-2.0 — see LICENSE for details.

Similar modules

Browse PDF tools