trackmcp
Back to directory

docs-mcp

0 stars PythonOthers Updated Jun 13, 2026

Documentation

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_docx`Batch-read content blocks (paragraphs and tables)
`write_contents_to_docx`Write content blocks; creates file if missing
`get_styles_from_docx`Batch-read paragraph style catalog
`write_styles_to_docx`Union 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.

mermaid
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
MCP`server.py``services/`, `errors``adapters/`, `docx`
Service`services/``adapters/`, `domain/`, `errors``docx`, `mcp`
Adapter`adapters/``domain/`, `errors`, `docx``services/`, `mcp`
Domain`domain/`stdlib onlyeverything else

Dependency direction is always downward: MCP → Service → Adapter → Domain.

See AGENTS.md for contributor guidelines.

Tech stack

Quick start

1. Clone and install

bash
git clone  docs-mcp
cd docs-mcp
uv sync --extra dev

2. Run tests

bash
uv run pytest

3. Smoke test the MCP server

bash
uv run docx-mcp

The process listens on stdio (JSON-RPC). Press Ctrl+C to stop.

4. Add to Cursor

Replace `/absolute/path/to/docs-mcp` with your clone path. Cursor MCP config requires absolute paths.

Native (uv):

json
{
  "mcpServers": {
    "docs-mcp": {
      "command": "uv",
      "args": [
        "run",
        "--directory",
        "/absolute/path/to/docs-mcp",
        "docx-mcp"
      ]
    }
  }
}

Docker (ephemeral session):

Build once from the repo root (no file paths in the image or build command):

bash
cd docs-mcp
docker build -t docs-mcp .

MCP config — only how to start the server process. Which files to read/write is not configured here; every tool receives `file_path` from the MCP client (agent/user) at call time:

json
{
  "mcpServers": {
    "docs-mcp": {
      "command": "docker",
      "args": ["run", "--rm", "-i", "docs-mcp"]
    }
  }
}

File paths in tool calls

Runtime`file_path` in tools
Native (`uv`)Host path as passed by the agent, e.g. `/home/user/docs/report.docx`
DockerPath inside the container filesystem

With Docker, the default config above has no bind mounts — tool paths must exist inside the container unless you extend `args`. To read/write host files, add a volume mount that matches the paths you pass in tools, for example:

json
"args": ["run", "--rm", "-i", "-v", "/home/user/docs:/home/user/docs", "docs-mcp"]

Then the agent calls `get_contents_from_docx(file_path="/home/user/docs/report.docx")` — same path string on host and in the container.

> One container runs for the entire MCP session (not per tool call). The host spawns the process on connect and tears it down on disconnect; `--rm` removes the container automatically.

Tools reference

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

json
{
  "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_path``str`requiredPath to `.docx` file
`offset``int``0`Start index in block list
`limit``int``10`Max blocks per batch (max 200)

Example response:

json
{
  "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_path``str`requiredPath to `.docx` file
`offset``int``0`Start index in style list
`limit``int``25`Max styles per batch (max 200)

Example response (first batch, `offset=0`):

json
{
  "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_path``str`requiredOutput path
`contents``list[dict]`requiredContent blocks from `get_contents_from_docx`

Example response:

json
{
  "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_path``str`requiredTarget file (must exist)
`styles``dict`required`{ "paragraph_styles": [...], "section": {...} }`

Example response:

json
{
  "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:

code
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

python
# 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
1`get_contents_from_docx`Yes (source)
2`get_styles_from_docx`Yes (template)
3`write_contents_to_docx`No — creates output
4`write_styles_to_docx`Yes — 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

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

Project layout

code
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

License

See repository license file.

Frequently asked questions

What is docs-mcp?

docs-mcp is docs-mcp

How do I install docs-mcp?

Open the GitHub repository and follow its README. Most MCP servers are added to your client's MCP config, then called by your agent.

Is docs-mcp open source?

Yes — it is hosted on GitHub at https://github.com/gossipauthorxpm/docs-mcp.

Related MCP tools

Run your own MCP server? See who uses it and what to fix.

Measure it with TrackMCP