Skip to content
Conduit

mcp server

edit2docs

AI-agent-native document engine: generate & chat-edit DOCX, XLSX, PPTX — Python library / agent tools / MCP server / hosted service. Native editable OOXML incl. real charts. English-first, first-class

CocoRoF/edit2docscommunitylistedunreviewed

Set up

Runs on your computer. Pick your agent and operating system.

Before you start

  • uv (`curl -LsSf https://astral.sh/uv/install.sh | sh`)
~/Library/Application Support/Claude/claude_desktop_config.json
{
  "mcpServers": {
    "edit2docs": {
      "command": "uvx",
      "args": [
        "edit2docs"
      ]
    }
  }
}

same format Cursor, GitHub Copilot CLI, Junie, JetBrains AI Assistant, Augment (Auggie CLI), Factory Droid, Kiro, Devin CLI +12

Values in angle brackets are placeholders. Restart Claude Desktop after editing its config.Claude Desktop docs

README

synced Oct 21, 2026

edit2docs

AI-agent-native document engine: generate and chat-edit DOCX, XLSX and PPTX as a Python library, agent tool set, MCP server or hosted service. English-first, with first-class Korean support.

PyPI Python License: Apache-2.0

한국어 README

What it is

LLM agents that "edit a Word/Excel/PowerPoint file" usually either regenerate the whole file or flatten it into pictures, destroying charts, tables, styles and formulas on the way. edit2docs gives an agent (or a script) a small set of format-dispatched verbs that work on real OOXML:

  • Generate a complete document from a one-line intent (LLM) or from a spec you write (deterministic, no key).
  • Edit an existing file by chat (LLM) or by exact addresses (deterministic) — untouched package parts stay byte-identical, so a chart, sparkline, image or cached formula is never collateral damage.
  • Inspect and render — an addressable outline, page PNG/PDF/SVG, markdown.

The output is always natively editable (real paragraphs, cells, charts). One engine, four surfaces: Python library, agent tools (function calling), a local stdio MCP server, and a hosted FastAPI service.

Install

Requires Python 3.12+.

pip install edit2docs              # library + agent tools + local MCP server
pip install "edit2docs[server]"    # + the hosted multi-tenant service

Optional extras: images (Gemini/OpenAI image backends), svg-fallback (cairosvg), dev (tests/lint, includes server). The repo's main branch is ahead of PyPI at times (see Version history); to get it: pip install "edit2docs @ git+https://github.com/CocoRoF/edit2docs".

Quick start

Deterministic verbs need no API key. This runs as-is:

from edit2docs import build_doc, analyze_doc, set_doc_text, render_doc

build_doc({"slides": [
    {"layout": "title", "title": "Q3 Review"},
    {"layout": "content", "title": "Highlights", "bullets": ["Revenue +12%", "Churn down"]},
]}, "deck.pptx")

info = analyze_doc("deck.pptx")      # outline with exact addresses
title = next(t for t in info["slides"][1]["texts"] if t["text"] == "Highlights")
# title == {"para": 0, "text": "Highlights", "shape_id": 2}

r = set_doc_text("deck.pptx", [
    {"slide": 1, "shape_id": title["shape_id"], "para": title["para"], "new_text": "Key results"},
])
print(r.path, r.applied)             # deck_edited.pptx 1  (the input is not overwritten)

render_doc("deck_edited.pptx", to="png", out_dir="pages")   # page PNGs, no LibreOffice

Generative verbs need an Anthropic key (api_key= or ANTHROPIC_API_KEY):

from edit2docs import generate_doc, edit_doc

generate_doc("Executive briefing on Q3 sales", output="deck.pptx")
r = edit_doc("deck.pptx", "Make slide 3's title more assertive")
print(r.reply)        # the editor explains what it changed
print(r.path)         # deck_edited.pptx (pass output=... to choose)

The verbs

The file extension picks the engine. All of these exist as library functions in edit2docs; [det] verbs are deterministic and keyless, [LLM] verbs are BYOK.

verbwhat it does
build_doc(spec, output)render a document from a spec you write: markdown (docx), {"sheets": [...]} (xlsx), {"slides": [...], "theme": {...}} (pptx, incl. themed decks)det
generate_doc(intent, *, output, ...)intent (+ optional sources, PPTX template) → designed documentLLM
edit_doc(doc, instruction, ...)one natural-language edit turn; untouched content stays byte-identicalLLM
analyze_doc(doc)structure outline with the exact addresses the edit verbs take, plus a charts listdet
set_doc_text(doc, edits, *, output=None)targeted edits: docx paragraphs/table cells, xlsx cells/rows/sheets, pptx shape paragraphs/table cells, and native chart title/data (edits with a chart index)det
edit_chart(doc, edits) / list_charts(doc)edit a native chart's data or title (chart XML and embedded workbook stay in sync), docx/xlsx/pptxdet
arrange_doc(doc, ops, *, output=None)structure: duplicate / move / delete whole slides (pptx) and sheets (xlsx), rename sheetsdet
list_doc_parts(doc) / get_doc_xml(doc, part) / set_doc_xml(doc, part, ...)read the package part map or one part's XML; patch, create or delete a part (colors, fonts, geometry, anything)det
preview_doc(doc, *, out_dir=None)pptx → per-slide SVG, docx/xlsx → markdowndet
render_doc(doc, *, to="png", out_dir=None, dpi=144.0)to = png / pdf / svg / md; resvg + PyMuPDF, no LibreOfficedet
doc_guide(topic=None)progressive-disclosure guide for agents (topics: build, generate, edit, edit.text, edit.chart, arrange, edit.xml, recipes.slides, recipes.colors, render)det

Async variants: async_generate_doc, async_edit_doc. PPTX-specific aliases (generate_pptx, edit_pptx, preview_pptx, set_pptx_text, analyze_pptx) also exist.

Result objects: GenerateResult(path, page_count, design_spec, warnings), EditResult (has .reply, .operations, .path), TextEditsResult(path, applied, results) (per-edit status: applied | stale | not_found | invalid), ArrangeResult, RenderResult(paths, page_count, format, to). Verbs that write a derived file default to <stem>_edited.<ext> (_chart, _arranged for those verbs); pass output= to choose.

Generate

generate_doc("Q3 performance report", output="report.docx")
generate_doc("Quarterly sales summary", output="sales.xlsx", sources=["raw.pdf"])
generate_doc("Executive briefing on Q3 sales", output="deck.pptx",
             template="brand.pptx",          # optional user PPTX template
             deck_mode="template_restyle",   # "new" | "template_restyle" | "template_extend"
             pages=(8, 12))                  # target page range (pptx)
generate_doc("3분기 실적 보고서", output="report.docx", lang="ko-KR")

sources accepts PDF / DOCX / DOC / PPTX / XLSX / HTML / EPUB / IPYNB paths; each is converted to markdown and given to the writer as reference material. Full signature: generate_doc(intent, *, output, api_key=None, sources=None, template=None, deck_mode="new", pages=(8, 12), lang="en-US", model=...).

Edit by chat

r = edit_doc("report.docx", "Add a 'deployment complete' item to the progress section")
print(r.reply, r.operations)

r = edit_doc("deck.pptx", "이 문서 내용을 반영해서 3번 슬라이드를 고쳐줘",
             sources=["notes.pdf"], lang="ko-KR",
             chat_history=[{"role": "user", "content": "..."},
                           {"role": "assistant", "content": "..."}])

The planner sees a numbered outline of the document, plans the minimal operations, and a deterministic engine applies them. If planning fails the reply says so instead of silently doing nothing. For decks, targeted edits run as in-place, byte-preserving operations on the real shapes (25 ops, e.g. set_text, set_runs, set_table_cell, insert_row, set_chart_data, add_slide, set_notes, set_hyperlink, set_theme_color); new-slide and full-redesign turns that need it fall back to regenerating the slide as SVG. The op catalog is introspectable (see below).

Edit deterministically

set_doc_text("report.docx", [
    {"action": "replace", "para": 0, "new_text": "Q3 Final Report"},
    {"action": "insert_after", "para": 0, "markdown": "New paragraph"},
    {"action": "replace", "table": 0, "row": 1, "col": 2, "new_text": "142"},
    {"action": "delete", "para": 5},
])
set_doc_text("sales.xlsx", [
    {"action": "set_cell", "sheet": "Sales", "cell": "B3", "value": 142},
    {"action": "append_rows", "sheet": "Sales", "rows": [["Q4", 160]]},
    {"action": "add_sheet", "sheet": "Notes", "headers": ["a"], "rows": [["x"]]},
])
set_doc_text("deck.pptx", [{"slide": 1, "shape_id": 5, "para": 0, "new_text": "New title"}])

edit_chart("deck.pptx", [
    {"chart": 0, "title": "Q3 Sales"},
    {"chart": 0, "categories": ["Q1", "Q2", "Q3"],
     "series": [{"name": "Sales", "values": [120, 135, 150]}]},
])

arrange_doc("deck.pptx", [{"op": "duplicate", "target": 0}, {"op": "move", "target": 2, "to": 0}])

Optional old_text / old_value guards reject stale edits. Take addresses from analyze_doc. Run doc_guide("edit.text") for the full shapes.

The PPTX surgical engine is also directly callable:

from edit2docs.documents.pptx_engine import apply_pptx_edits, describe_ops, find_shapes

describe_ops()                                    # machine-readable op catalog (address + payload per op)
find_shapes(pptx_bytes, text="Revenue")           # query shapes by text/kind/name/slide
new_bytes, results = apply_pptx_edits(pptx_bytes, edits, dry_run=True)   # validate without writing
# atomic=True -> all-or-nothing

Agent tools (function calling)

The verbs doc_guide, analyze_doc, render_doc, set_doc_text, arrange_doc, read_doc_xml, set_doc_xml, build_doc, generate_doc and edit_doc are exposed as tool schemas plus a dispatcher:

import anthropic
from edit2docs.agent_tools import ANTHROPIC_TOOLS, run_tool

client = anthropic.Anthropic()
msg = client.messages.create(
    model="claude-sonnet-5",
    max_tokens=2048,
    tools=ANTHROPIC_TOOLS,
    messages=[{"role": "user", "content": "Fix the title of slide 3 in deck.pptx"}],
)
for block in msg.content:
    if block.type == "tool_use":
        result = run_tool(block.name, block.input)   # run_tool_async also exists

OPENAI_TOOLS is the function-calling shape for OpenAI-style APIs. tool_specs(provider, extension="xlsx") returns only the verbs (with format-specialized descriptions) relevant to that file type; the unscoped default returns the full set. Chart editing goes through set_doc_text (edits with a chart index) on this surface.

Local MCP server (zero infra)

pip install edit2docs installs an edit2docs-mcp stdio server exposing the same ten verbs over local files.

// Claude Desktop / Claude Code / Cursor
{
  "mcpServers": {
    "edit2docs": {
      "command": "edit2docs-mcp",
      "env": { "ANTHROPIC_API_KEY": "sk-ant-..." }   // only generative tools need it
    }
  }
}

Without installing: uvx --from edit2docs edit2docs-mcp. See docs/mcp-clients.md for client setups (note: that page describes the hosted server's tool list, which differs from the local one).

Hosted service

pip install "edit2docs[server]"
edit2docs serve [--host H] [--port P] [--reload]    # FastAPI, default port 8000

Standalone mode needs no external infrastructure: SQLite + local-filesystem storage + an inline job queue under EDIT2DOCS_DATA_DIR. Postgres, Redis (arq worker queue) and S3-compatible storage are enabled by env vars.

endpointpurpose
POST /v1/assets, GET/DELETE /v1/assets/{id}upload / fetch / delete documents (200 MB default cap)
POST /v1/jobs/generate-deckqueue a generation job; output_format = pptx / docx / xlsx
POST /v1/jobs/edit-deckqueue a chat-edit job
GET /v1/jobs/{id}, GET /v1/jobs/{id}/eventsstatus; SSE progress stream (stages plus per-operation live-edit events with addressable targets)
POST /v1/previewpptx → per-slide SVGs, docx/xlsx → addressable HTML (data-e2d-*)
POST /v1/text-editsdeterministic targeted edits
GET /v1/modelslive model list (proxies the Anthropic Models API with the caller's key; curated fallback)
GET /healthliveness and mode report
/mcp, /mcp-sseMCP over Streamable HTTP / SSE (deck-job oriented tools: generate_deck, edit_deck, upload_source, request_upload_url, get_asset, download_url, list_templates, list_voices, hello)

Anthropic keys are BYOK per request (X-Anthropic-API-Key header). Errors are bilingual: message follows Accept-Language, with English and Korean variants available.

Configuration

Environment variables, prefix EDIT2DOCS_ (see src/edit2docs/config.py):

vardefaultnotes
EDIT2DOCS_DEFAULT_LANGen-USko-KR makes a deployment Korean-by-default
EDIT2DOCS_HOST / EDIT2DOCS_PORT0.0.0.0 / 8000overridable by serve flags
EDIT2DOCS_DATA_DIR/data/edit2docsSQLite + file storage root
EDIT2DOCS_DATABASE_URLSQLite under data dire.g. postgresql+asyncpg://...
EDIT2DOCS_REDIS_URLunset (inline queue)enables the arq worker queue
EDIT2DOCS_S3_ENDPOINT_URL + EDIT2DOCS_S3_BUCKETunset (local fs)with S3_REGION, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_PUBLIC_BASE_URL
EDIT2DOCS_RAW_SIGNING_KEYrandom per startset for production so signed local URLs survive restarts
EDIT2DOCS_AUTH_DEV_API_KEYunset (anonymous)single bearer token for small deployments
EDIT2DOCS_MAX_UPLOAD_SIZE_BYTES200 MBmatch your reverse proxy
EDIT2DOCS_MODEL_{PLANNER,WRITER,STRATEGIST,EXECUTOR}request modelper-role model override
EDIT2DOCS_STRATEGIST_SOURCE_CHAR_CAP60000per-source cap fed to the deck strategist (0 = uncapped)

Image generation backends (images extra) read provider variables such as IMAGE_BACKEND, OPENAI_API_KEY, GEMINI_API_KEY; see .env.example.

Web studio

edit2docs-web is the official frontend: upload, generation with staged SSE progress, and a chat-edit studio whose canvas highlights the exact paragraph / cell / slide each operation touches. Point it at the engine with EDIT2DOCS_SERVER_INTERNAL_URL and EDIT2DOCS_SERVER_API_KEY.

How each format works

  • DOCX — the writer LLM emits constrained markdown; a deterministic python-docx renderer produces the file. Edits are paragraph/table-cell addressed. Preview is native addressable HTML (data-e2d-para, data-e2d-table, data-e2d-cell).
  • XLSX — the designer LLM emits a YAML sheet spec; openpyxl renders it. Edits are set_cell / append_rows / add_sheet with staleness guards. Preview is a grid with data-e2d-cell="B3" addresses and cached formula values.
  • PPTX — strategist → per-page SVG → native DrawingML, user-template restyle/extend, optional Edge-TTS narration. Chat edits use the surgical in-place engine described above.

Lossless editing is built on contextifier's raw OOXML layer (contextifier>=0.8.0): only the parts an edit touches are rewritten.

Native charts and tables (PPTX)

SVG groups marked data-pptx-native="chart|table" export as real PowerPoint charts (chart part plus embedded workbook) or native <a:tbl> tables:

<g id="sales_chart" data-pptx-native="chart">
  <metadata data-pptx-native="chart">
    { "name": "sales_chart", "x": 125, "y": 141, "width": 1000, "height": 440,
      "type": "bar", "categories": ["Q1", "Q2", "Q3"],
      "series": [{ "name": "Sales", "values": [120, 135, 150] }] }
  </metadata>
  <!-- fallback shapes, used when native export is off -->
</g>

Opt in with ExportRequest(native_objects=True) (tools/export.py).

Languages

English is the default (lang="en-US"). Korean is first-class: Hangul-aware text widths, per-run OOXML lang from the actual script, Korean font stacks, a complete message catalog and localized replies. Switch per call (lang="ko-KR"), per request (Accept-Language), or per deployment (EDIT2DOCS_DEFAULT_LANG=ko-KR). zh-CN / zh-TW / ja-JP get the same script detection and font-stack handling.

Ecosystem

RepoWhat it is
edit2docs-webWeb studio for the hosted service (Next.js)
contextifierRaw OOXML layer used for lossless edits
ppt-masterUpstream (MIT) the PPTX core under src/edit2docs/core/ is derived from
edit2pptSister project; the deck pipeline originates there

Project layout

src/edit2docs/: simple.py (library verbs) · agent_tools.py, agent_guide.py, tool_matrix.py (agent surface) · documents/ (docx/xlsx/pptx engines, arrange, chart and XML editing) · tools/ (LLM pipelines: generate, edit, preview, export) · core/ (ppt-master-derived deck engine) · mcp/ (local stdio + hosted servers) · api/ (FastAPI) · render/, i18n/, llm/, db/, storage/, workers/, services/. Design notes live in docs/ and PPTX_EDITING_UPGRADE_PLAN.md.

Development

git clone https://github.com/CocoRoF/edit2docs && cd edit2docs
uv venv .venv && uv pip install -e ".[server,dev]"
.venv/bin/python -m pytest tests/          # 949 passed, 1 skipped at 0.19.0
.venv/bin/python -m ruff check src/edit2docs

scripts/lint_ascii_paths.py enforces ASCII-only paths (run by the tests).

Version history

Current version in the repo: 0.19.0. As of 2026-10-01 PyPI has 0.16.1; 0.17.0-0.19.0 are on main only.

versionhighlights
0.19.0native, layout-aware add_slide; ops set_notes, set_bullet, set_hyperlink, set_z_order, set_legend, set_series_color, set_theme_color, set_theme_font (25 PPTX ops); needs contextifier>=0.8.0
0.18.0self-describing PPTX engine: OP_CATALOG / describe_ops(), dry_run, atomic, find_shapes; ops set_runs, add_textbox, delete_shape, duplicate_shape, insert_column, delete_column, merge_cells
0.17.xstructural PPTX editing: addressable, byte-preserving in-place edits instead of whole-slide SVG regeneration for targeted turns
0.16.xextension-scoped tools (tool_specs(extension=...), tool_matrix); GET /v1/models; hardening of slide-edit retries and failed-job handling
0.15.1arrange_doc (0.15.0 shipped without its implementation; use 0.15.1+)
0.14.1cap mcp<2.0 (2.0 removed mcp.server.fastmcp)
0.10-0.14build_doc, read_doc_xml / set_doc_xml, hierarchical doc_guide, themed decks
0.9.0token optimization: prompt-cache restructuring, input caps, per-role model tiering
0.8.0lossless editing on contextifier; edit_chart
0.7.xupstream sync with ppt-master v3.1, native chart/table export, English-first, Apache-2.0 relicense (0.7.1)
0.1-0.6multi-format engine, hosted API, live edit streaming, addressable previews, render_doc

License

Apache License 2.0. The PPTX core under src/edit2docs/core/ is derived from ppt-master (MIT, Copyright (c) Hugo He); its original MIT terms are preserved in NOTICE and LICENSE.ppt-master.MIT.

↑↓ navigate openesc close