Skip to main content

mcp-docgen

A Markdown-driven Model Context Protocol (MCP) server to create, read, edit and convert Word (.docx), Excel (.xlsx), PowerPoint (.pptx), PDF and HTML documents.

Built entirely on mature, permissively-licensed Python libraries (python-docx, python-pptx, openpyxl, reportlab, pypdf, markdown-it-py, markdownify, beautifulsoup4) — no proprietary dependencies. MIT licensed.

Part of the Touka project: giving AI agents the ability to produce, read and edit real Office documents using only open-source building blocks.

Why

LLMs are great at producing Markdown. mcp-docgen converts Markdown to polished Office documents — and reads them back to Markdown — so an MCP-capable assistant (Claude Desktop, Touka, …) can run a full read → edit → write loop on real .docx / .xlsx / .pptx / .pdf / .html files, and convert between any of them.

Install & run

uvx mcp-docgen          # once published to PyPI
# or, from a local checkout:
uv sync && uv run mcp-docgen

The server speaks MCP over stdio.

MCP client configuration

{
  "mcpServers": {
    "docgen": {
      "command": "uvx",
      "args": ["mcp-docgen"],
      "env": { "MCP_DOCGEN_OUTPUT_DIR": "/absolute/path/to/workdir" }
    }
  }
}

From a local checkout, swap the command for:

{ "command": "uv", "args": ["run", "--directory", "/path/to/mcp-docgen", "mcp-docgen"] }

Tools

Create (Markdown / structured data → file)

Tool Input → Output
create_docx(markdown, output_path, title?) Markdown → Word
create_pptx(markdown, output_path, title?) Markdown → PowerPoint
create_pdf(markdown, output_path, title?) Markdown → PDF
create_html(markdown, output_path, title?) Markdown → HTML
create_xlsx(sheets, output_path) structured rows → Excel

Markdown features: headings, bold / italic / inline code, bullet & numbered lists (nested), tables, block quotes, fenced code blocks, horizontal rules.

PowerPoint slide convention: # Heading starts a new slide (its title); content below becomes bullet points; --- forces a slide break; title adds a leading title slide.

Excel sheets: [{ "name": str, "rows": [[cell, …], …], "header"?: bool }]. Cells may be strings / numbers / booleans / null, or { "formula": "=SUM(A1:A2)" } for a live formula (a plain =… string is kept as inert text); the first row is a bold, frozen header unless "header": false.

Read (file → Markdown / structured data)

Tool Returns
read_docx(input_path, base_dir?) { "markdown": … }
read_pptx(input_path, base_dir?) { "markdown": … }
read_xlsx(input_path, base_dir?) { "sheets": [{ "name", "rows" }] } (round-trips with create_xlsx)
read_pdf(input_path, base_dir?) { "num_pages", "pages": […], "text" }
read_html(input_path, base_dir?) { "markdown": … }

Reading docx/pptx to Markdown enables editing without in-place tools: read → edit the Markdown → create_* to regenerate.

Edit (in-place, preserving the rest)

Tool Effect
edit_xlsx(input_path, output_path, edits) set cells / append rows / add sheets, keeping other sheets, formulas & formatting
append_docx(input_path, output_path, markdown) append Markdown content to the end
append_pptx(input_path, output_path, markdown) append Markdown-derived slides to the end

edits = { "set_cells": [{"sheet","cell","value"}], "append_rows": [{"sheet","rows"}], "add_sheet": [{"name","rows"}] }. Any cell value may be { "formula": "=…" } for a live formula.

PDF page operations

Tool Effect
pdf_merge(input_paths, output_path) concatenate PDFs in order
pdf_split(input_path, output_dir?) one file per page
pdf_extract(input_path, pages, output_path) extract a page subset (e.g. "1-3,5")

Note on PDF "editing": clean open-source PDF editing means page operations (merge / split / extract), not reflowing or replacing body text — PDFs are not designed for in-place text editing. To revise PDF content, regenerate with create_pdf.

Create/edit tools return {"path": <absolute path>}; pdf_split returns {"paths": […]}.

Convert (any format → any format)

Tool Effect
convert_document(input_path, output_path) convert between formats — both inferred from the file extensions

Conversions among html / docx / pptx read straight into a rich intermediate model, so colour, font, size, alignment, images and table cell spans survive — no lossy Markdown round-trip. xlsx → xlsx is lossless (styles, formulas, merges). Other pairings degrade gracefully. The result reports an honest fidelity plus a warnings list of what that particular pairing drops:

Fidelity When What it means
rich html · docx · pptx among themselves, and xlsx → xlsx styling preserved (colour / font / align / images / cell spans; formulas for xlsx)
clean md → a flow format structure preserved (Markdown carries no styling to begin with)
lossy any → pptx; xlsx ↔ documents slides split on # headings; only tables map to / from worksheets
text-only pdf as a source pypdf extracts plain text only; layout, styling and tables are lost

Honest limits: a PDF source is plain text (the open-source ceiling); Markdown can't carry styling, so any .md step is structure-only. A few write-side gaps remain (docx cell shading), and only inline-style HTML is read.

convert_document returns { "path", "source_format", "target_format", "fidelity", "warnings" }.

Directories & safety

  • Output files are written inside MCP_DOCGEN_OUTPUT_DIR (default ./out).
  • Input files (read / edit) are read from MCP_DOCGEN_INPUT_DIR (default = the output dir), so a read → edit → write loop shares one working directory.
  • Every tool accepts an optional base_dir to use as the root for that single call (it roots both reads and writes), e.g. a host that jails each call to one user's directory. When MCP_DOCGEN_INPUT_DIR is configured, a per-call base_dir may only narrow within it — one that escapes the configured boundary is rejected.
  • Every path is interpreted relative to its base; any path escaping it (via .. or an absolute path) is rejected, missing inputs and wrong suffixes raise errors.
  • When a per-call base_dir is supplied without MCP_DOCGEN_INPUT_DIR set, it is trusted as given — the host is responsible for validating it (the trust boundary is the host). A host that would rather fail closed can set MCP_DOCGEN_STRICT_BASE_DIR=1 to cap any base_dir at the output root even when no input root is configured.
  • The server makes no network calls and spawns no subprocesses.
  • Generated output is hardened against injection from untrusted source content: HTML text, inline styles and URLs are escaped / scheme-allowlisted, and spreadsheet text that looks like a formula (=…) is neutralized so it can't execute in Excel.
  • Process only trusted input files. Documents are parsed by third-party libraries (python-docx, openpyxl, python-pptx, pypdf); a hostile archive could attempt a decompression / XML-expansion bomb. Keep the pinned dependency versions current.

Examples

uv run python examples/generate_samples.py   # create report.docx / review.pptx / sales.xlsx
uv run python examples/roundtrip_demo.py      # create → read → edit → PDF → convert round-trip

Development

uv sync
uv run pytest
uv run ruff check .

License

MIT © 2026 Touka Project — see LICENSE.

Powered by python-docx, python-pptx, openpyxl, reportlab and pypdf; Markdown parsing by markdown-it-py; HTML reading by markdownify and BeautifulSoup. All MIT/BSD licensed.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mcp_docgen-0.8.0.tar.gz (37.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mcp_docgen-0.8.0-py3-none-any.whl (47.0 kB view details)

Uploaded Python 3

File details

Details for the file mcp_docgen-0.8.0.tar.gz.

File metadata

  • Download URL: mcp_docgen-0.8.0.tar.gz
  • Upload date:
  • Size: 37.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.5 {"installer":{"name":"uv","version":"0.10.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for mcp_docgen-0.8.0.tar.gz
Algorithm Hash digest
SHA256 ae1ad44904a9a7426a8603830ad1fe09603fba827a2480b3128e29350a05e55f
MD5 dc0bc79194465d30d0f7520ece57578e
BLAKE2b-256 fc4a245d9d5e7d7e26a3287d3eb372202e8820bfbecd1859d41aace298152075

See more details on using hashes here.

File details

Details for the file mcp_docgen-0.8.0-py3-none-any.whl.

File metadata

  • Download URL: mcp_docgen-0.8.0-py3-none-any.whl
  • Upload date:
  • Size: 47.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.5 {"installer":{"name":"uv","version":"0.10.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for mcp_docgen-0.8.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d450c65f812cd681a7b0f9a687321b6fb1acf0ebb060b5165a017e9f746bacc0
MD5 c53ead9ff5a42e30871f340f26044eca
BLAKE2b-256 f559463eef5059325c3f3912f2b6779bf71bb747fa6be43e08e73a6269799cf6

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.8.0 This release

2 files

0.7.0

2 files

0.6.0

2 files

0.4.0

2 files

0.2.0

2 files

0.1.0

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page