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,
XlsxWriter,
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; 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"}] }.
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
.mdstep is structure-only. A few write-side gaps remain (docx clickable hyperlinks and cell shading), and only inline-styleHTML 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_dirto 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. WhenMCP_DOCGEN_INPUT_DIRis configured, a per-callbase_dirmay 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. - The server makes no network calls and spawns no subprocesses.
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, XlsxWriter, 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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file mcp_docgen-0.6.0.tar.gz.
File metadata
- Download URL: mcp_docgen-0.6.0.tar.gz
- Upload date:
- Size: 33.8 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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b2d1ae4826a13fe891bc97371eb6ccffbf60d012f975ccd5b46196c89bb31069
|
|
| MD5 |
bfbc62613bc1f49dab47fbf6201046b7
|
|
| BLAKE2b-256 |
c439914981ffd1b3ec867cfcd8b702052310925f06ddb7e62ba75caee3afa9dc
|
File details
Details for the file mcp_docgen-0.6.0-py3-none-any.whl.
File metadata
- Download URL: mcp_docgen-0.6.0-py3-none-any.whl
- Upload date:
- Size: 42.2 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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fdf37a1beb0b23cf58bfd477896fd19e9c7e94bf79e39775277d319f46ee197c
|
|
| MD5 |
a3778ded1e635eca2252ddc362da3f16
|
|
| BLAKE2b-256 |
2cc5e5784a55277515f725c8f62c31edacce982ec91a3c6086f081279d74bd65
|