Labsco
gossipauthorxpm logo

docs-mcp

from gossipauthorxpm

MCP for work with docx files. Make copy format from docx files.

๐Ÿ”ฅ๐Ÿ”ฅ๐Ÿ”ฅโœ“ VerifiedFreeQuick setup

docs-mcp

MCP server for reading and writing .docx files. Exposes four paginated tools so agents can batch-read document content and styles, write content, and union style definitions โ€” without a monolithic reformat tool.

Requirements: Python 3.11+

Features

ToolPurpose
get_contents_from_docxBatch-read content blocks (paragraphs and tables)
write_contents_to_docxWrite content blocks; creates file if missing
get_styles_from_docxBatch-read paragraph style catalog
write_styles_to_docxUnion style definitions onto an existing file (incoming wins on conflict)

Primary use case: reformat a draft document using a template's styles โ€” the agent orchestrates four tool calls with pagination.

Architecture

Layered design: MCP tools delegate to services, services use adapters, adapters translate to/from domain models.

flowchart TB
  subgraph mcpLayer [MCP Layer]
    Server[FastMCP Server]
    Tools["4 Tools: get/write contents & styles"]
  end

  subgraph serviceLayer [Service Layer]
    ReadSvc[ReadService]
    WriteSvc[WriteService]
  end

  subgraph adapterLayer [Adapter Layer]
    DocxAdapter[DocxAdapter]
    ContentWriter[ContentWriter]
    StyleMigrator[StyleMigrator]
    ContentExtractor[ContentExtractor]
    StyleExtractor[StyleExtractor]
  end

  subgraph domainLayer [Domain Layer]
    DocModel[DocumentModel]
    StyleProfile[StyleProfile]
    BlockModel[ParagraphBlock / TableBlock]
  end

  Agent[Cursor Agent] -->|batch tool calls| Server
  Server --> Tools
  Tools --> ReadSvc
  Tools --> WriteSvc
  ReadSvc --> DocxAdapter
  WriteSvc --> DocxAdapter
  DocxAdapter --> ContentExtractor
  DocxAdapter --> StyleExtractor
  DocxAdapter --> ContentWriter
  DocxAdapter --> StyleMigrator
  ReadSvc --> domainLayer
  WriteSvc --> domainLayer

Layer rules

LayerPackageMay import fromMust not import
MCPserver.pyservices/, errorsadapters/, docx
Serviceservices/adapters/, domain/, errorsdocx, mcp
Adapteradapters/domain/, errors, docxservices/, mcp
Domaindomain/stdlib onlyeverything else

Dependency direction is always downward: MCP โ†’ Service โ†’ Adapter โ†’ Domain.

See AGENTS.md for contributor guidelines.

Tech stack

Tools reference

All tools return JSON-serializable dicts. On failure, the response contains structured error fields instead of raising an unhandled exception:

{
  "code": "FILE_NOT_FOUND",
  "message": "File not found: /path/missing.docx",
  "details": { "path": "/path/missing.docx" }
}

Error codes: FILE_NOT_FOUND, FILE_NOT_READABLE, FILE_NOT_WRITABLE, INVALID_PATH, PARSE_ERROR, STYLE_NOT_FOUND, REFORMAT_ERROR, INTERNAL_ERROR.


get_contents_from_docx

Return a paginated batch of document content blocks.

ParameterTypeDefaultDescription
file_pathstrrequiredPath to .docx file
offsetint0Start index in block list
limitint10Max blocks per batch (max 200)

Example response:

{
  "items": [
    {
      "block_type": "paragraph",
      "runs": [
        {
          "text": "ะ›ะะ‘ะžะ ะะขะžะ ะะะฏ ะ ะะ‘ะžะขะ โ„–3 (Java)",
          "bold": null,
          "italic": null,
          "font_name": null,
          "font_size_pt": null
        }
      ],
      "style": {
        "name": "Heading 1",
        "style_type": "paragraph"
      }
    }
  ],
  "total": 48,
  "offset": 0,
  "limit": 10,
  "has_more": true,
  "source_path": "/path/plain.docx"
}

Blocks carry a style name reference (StyleHint), not full style definitions. See .agents/skills/docx-mcp/references/blocks for the full schema.


get_styles_from_docx

Return a paginated batch of paragraph styles from a .docx file.

ParameterTypeDefaultDescription
file_pathstrrequiredPath to .docx file
offsetint0Start index in style list
limitint25Max styles per batch (max 200)

Example response (first batch, offset=0):

{
  "paragraph_styles": [
    {
      "name": "Heading 1",
      "base_style": "Normal",
      "font_name": null,
      "font_size_pt": null,
      "font_color": "000000",
      "bold": null,
      "italic": null,
      "alignment": null,
      "line_spacing": 1.0,
      "space_before_pt": 18.0,
      "space_after_pt": 12.0,
      "left_indent_cm": null,
      "right_indent_cm": null,
      "first_line_indent_cm": null
    }
  ],
  "section": {
    "page_width_cm": 21.0,
    "page_height_cm": 29.7,
    "left_margin_cm": 2.5,
    "right_margin_cm": 1.0,
    "top_margin_cm": 1.5,
    "bottom_margin_cm": 1.5
  },
  "total": 33,
  "offset": 0,
  "limit": 25,
  "has_more": true,
  "source_path": "/path/format.docx"
}

section is included only when offset == 0; later batches omit it. Merge paragraph_styles client-side across batches.


write_contents_to_docx

Write content blocks to a .docx file. Creates a new file if the path does not exist; replaces the document body if it exists.

ParameterTypeDefaultDescription
file_pathstrrequiredOutput path
contentslist[dict]requiredContent blocks from get_contents_from_docx

Example response:

{
  "file_path": "/path/output.docx",
  "blocks_written": 48,
  "created": true
}

write_styles_to_docx

Union style definitions onto an existing .docx file. Incoming styles win on name conflict.

ParameterTypeDefaultDescription
file_pathstrrequiredTarget file (must exist)
stylesdictrequired{ "paragraph_styles": [...], "section": {...} }

Example response:

{
  "file_path": "/path/output.docx",
  "styles_added": 5,
  "styles_updated": 12,
  "styles_unchanged": 8
}

Returns FILE_NOT_FOUND if the target file does not exist โ€” call write_contents_to_docx first.

User story: Reformat by template

Prompt example:

Reformat report_draft.docx to match company_template.docx. Save as report_final.docx.

Agent workflow:

report_draft.docx                    company_template.docx
        โ”‚                                      โ”‚
        โ”œโ”€ get_contents_from_docx (batches)    โ”œโ”€ get_styles_from_docx (batches)
        โ”‚                                      โ”‚
        โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                           โ–ผ
              write_contents_to_docx(report_final.docx)   โ† creates file
                           โ–ผ
              write_styles_to_docx(report_final.docx)     โ† union; template wins
                           โ–ผ
                    formatted output

Step-by-step

  1. Read content โ€” paginate get_contents_from_docx(draft, offset, limit) until has_more is false. Collect all items.

  2. Read styles โ€” paginate get_styles_from_docx(template, offset, limit) until has_more is false. Merge all paragraph_styles; keep section from the first batch (offset=0).

  3. Write content โ€” write_contents_to_docx(output, contents) with the collected blocks.

  4. Union styles โ€” write_styles_to_docx(output, styles) with the merged style profile.

Pagination pattern

# Contents
items = []
offset = 0
while True:
    batch = get_contents_from_docx(path, offset=offset, limit=50)
    items.extend(batch["items"])
    if not batch["has_more"]:
        break
    offset += batch["limit"]

# Styles
paragraph_styles = []
section = None
offset = 0
while True:
    batch = get_styles_from_docx(path, offset=offset, limit=50)
    if offset == 0:
        section = batch.get("section")
    paragraph_styles.extend(batch["paragraph_styles"])
    if not batch["has_more"]:
        break
    offset += batch["limit"]
styles = {"paragraph_styles": paragraph_styles, "section": section}

Tool order

OrderToolFile must exist
1get_contents_from_docxYes (source)
2get_styles_from_docxYes (template)
3write_contents_to_docxNo โ€” creates output
4write_styles_to_docxYes โ€” output from step 3

Style union rules

Applied by write_styles_to_docx via StyleProfile.union_with(incoming, master="other"):

CaseResult
Style only in incoming (template)Added to target
Style only in existing fileKept
Same name, different definitionIncoming wins โ€” overwrites target
Section setup in incomingApplied from incoming profile

Styles with null field values inherit from base_style at write time (StyleProfile.resolve_inherited()). For the run-level overrides bold, italic, and font_color, a resolved null is an explicit reset: the corresponding override is cleared in the target style so draft theme artifacts (e.g. blue, bold headings) do not survive a reformat.

StyleMapper (adapter helper)

When mapping source style names to a template catalog (used internally during reformat):

  1. Exact name match in template styles
  2. Entry in optional custom_map
  3. Nearest heading fallback (Heading N โ†’ Heading min(N, available))
  4. Fallback to Normal, or first available template style

Unmapped styles are tracked in unmapped_styles.

Known limitations (v1)

Not supported in the current release:

  • Headers and footers (content)
  • Floating images
  • Text boxes
  • Footnotes and endnotes
  • Numbering restart / list numbering preservation
  • Run-level formatting when a named paragraph style exists (deferred โ€” styles applied in step 4 override inline hints)
  • Paragraph-level direct formatting (e.g. a centered title set on the paragraph, not in the style) โ€” not carried by content blocks; ParagraphAligner covers only the title/conclusions heuristic used in the reformat tests
  • Document parse caching โ€” each batch call re-reads the file from disk

Development

uv sync --extra dev
uv run pytest
uv run docx-mcp

Project layout

docs-mcp/
โ”œโ”€โ”€ README.md
โ”œโ”€โ”€ AGENTS.md
โ”œโ”€โ”€ Dockerfile
โ”œโ”€โ”€ pyproject.toml
โ”œโ”€โ”€ src/docx_mcp/
โ”‚   โ”œโ”€โ”€ server.py          # MCP tools (thin handlers)
โ”‚   โ”œโ”€โ”€ errors.py
โ”‚   โ”œโ”€โ”€ domain/            # DocumentModel, StyleProfile, blocks
โ”‚   โ”œโ”€โ”€ adapters/          # python-docx isolation
โ”‚   โ””โ”€โ”€ services/          # ReadService, WriteService
โ”œโ”€โ”€ tests/
โ”‚   โ””โ”€โ”€ assets/            # plain.docx, format.docx fixtures
โ””โ”€โ”€ .agents/skills/docx-mcp/  # Agent skill for MCP workflow

Test fixtures for manual exploration:

  • tests/assets/plain.docx โ€” sample content (draft)
  • tests/assets/format.docx โ€” sample styles (template)

End-to-end pipeline test: tests/test_reformat_pipeline.py.

Roadmap

SubplanTopic
SP-08Agent prompt examples and Cursor onboarding
SP-09Document parse caching across batch calls
SP-10Run-level formatting when named style exists
SP-11Headers/footers extraction and write
SP-12Images, text boxes, footnotes, numbering
SP-13HTTP / streamable-http transport