docs-mcp

MCP para trabalhar com arquivos docx. Faz cópia de formato de arquivos docx.

Documentação

docs-mcp

Servidor MCP para leitura e escrita de arquivos .docx. Expõe quatro ferramentas paginadas para que agentes possam ler em lote conteúdo e estilos de documentos, escrever conteúdo e unir definições de estilo — sem uma ferramenta monolítica de reformatação.

Requisitos: Python 3.11+

Recursos

FerramentaPropósito
get_contents_from_docxLer em lote blocos de conteúdo (parágrafos e tabelas)
write_contents_to_docxEscrever blocos de conteúdo; cria arquivo se ausente
get_styles_from_docxLer em lote catálogo de estilos de parágrafo
write_styles_to_docxUnir definições de estilo a um arquivo existente (o recebido vence em conflito)

Caso de uso principal: reformatar um documento de rascunho usando os estilos de um modelo — o agente orquestra quatro chamadas de ferramenta com paginação.

Arquitetura

Design em camadas: ferramentas MCP delegam para serviços, serviços usam adaptadores, adaptadores traduzem para/de modelos de domínio.

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

Regras de camada

CamadaPacotePode importar deNão deve importar
MCPserver.pyservices/, errorsadapters/, docx
Serviçoservices/adapters/, domain/, errorsdocx, mcp
Adaptadoradapters/domain/, errors, docxservices/, mcp
Domíniodomain/somente stdlibtodo o resto

A direção da dependência é sempre para baixo: MCP → Serviço → Adaptador → Domínio.

Consulte AGENTS.md para diretrizes de contribuição.

Pilha de tecnologia

Início rápido

1. Clone e instale

git clone <repo-url> docs-mcp
cd docs-mcp
uv sync --extra dev

2. Execute os testes

uv run pytest

3. Teste de fumaça do servidor MCP

uv run docx-mcp

O processo escuta em stdio (JSON-RPC). Pressione Ctrl+C para parar.

4. Adicione ao Cursor

Substitua /absolute/path/to/docs-mcp pelo caminho do seu clone. A configuração MCP do Cursor exige caminhos absolutos.

Nativo (uv):

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

Docker (sessão efêmera):

Compile uma vez a partir da raiz do repositório (sem caminhos de arquivo na imagem ou no comando de build):

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

Configuração MCP — apenas como iniciar o processo do servidor. Quais arquivos ler/escrever não é configurado aqui; cada ferramenta recebe file_path do cliente MCP (agente/usuário) no momento da chamada:

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

Caminhos de arquivo nas chamadas de ferramenta

Runtimefile_path nas ferramentas
Nativo (uv)Caminho do host como passado pelo agente, ex.: /home/user/docs/report.docx
DockerCaminho dentro do sistema de arquivos do contêiner

Com Docker, a configuração padrão acima não tem bind mounts — os caminhos das ferramentas devem existir dentro do contêiner, a menos que você estenda args. Para ler/escrever arquivos do host, adicione um volume mount que corresponda aos caminhos que você passa nas ferramentas, por exemplo:

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

Então o agente chama get_contents_from_docx(file_path="/home/user/docs/report.docx") — a mesma string de caminho no host e no contêiner.

Um contêiner é executado durante toda a sessão MCP (não por chamada de ferramenta). O host inicia o processo na conexão e o encerra na desconexão; --rm remove o contêiner automaticamente.

Referência de ferramentas

Todas as ferramentas retornam dicionários serializáveis em JSON. Em caso de falha, a resposta contém campos de erro estruturados em vez de lançar uma exceção não tratada:

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

Códigos de erro: FILE_NOT_FOUND, FILE_NOT_READABLE, FILE_NOT_WRITABLE, INVALID_PATH, PARSE_ERROR, STYLE_NOT_FOUND, REFORMAT_ERROR, INTERNAL_ERROR.


get_contents_from_docx

Retorna um lote paginado de blocos de conteúdo do documento.

ParâmetroTipoPadrãoDescrição
file_pathstrobrigatórioCaminho para o arquivo .docx
offsetint0Índice inicial na lista de blocos
limitint10Máximo de blocos por lote (máx. 200)

Exemplo de resposta:

{
  "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"
}

Os blocos carregam uma referência de nome de estilo (StyleHint), não definições completas de estilo. Consulte .agents/skills/docx-mcp/references/blocks para o esquema completo.


get_styles_from_docx

Retorna um lote paginado de estilos de parágrafo de um arquivo .docx.

ParâmetroTipoPadrãoDescrição
file_pathstrobrigatórioCaminho para o arquivo .docx
offsetint0Índice inicial na lista de estilos
limitint25Máximo de estilos por lote (máx. 200)

Exemplo de resposta (primeiro lote, 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 é incluído apenas quando offset == 0; lotes posteriores o omitem. Mescle paragraph_styles no lado do cliente entre os lotes.


write_contents_to_docx

Escreve blocos de conteúdo em um arquivo .docx. Cria um novo arquivo se o caminho não existir; substitui o corpo do documento se existir.

ParâmetroTipoPadrãoDescrição
file_pathstrobrigatórioCaminho de saída
contentslist[dict]obrigatórioBlocos de conteúdo de get_contents_from_docx

Exemplo de resposta:

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

write_styles_to_docx

Une definições de estilo a um arquivo .docx existente. Estilos recebidos vencem em conflito de nome.

ParâmetroTipoPadrãoDescrição
file_pathstrobrigatórioArquivo de destino (deve existir)
stylesdictobrigatório{ "paragraph_styles": [...], "section": {...} }

Exemplo de resposta:

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

Retorna FILE_NOT_FOUND se o arquivo de destino não existir — chame write_contents_to_docx primeiro.

História de usuário: Reformatar por modelo

Exemplo de prompt:

Reformatar report_draft.docx para corresponder a company_template.docx. Salvar como report_final.docx.

Fluxo de trabalho do agente:

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

Passo a passo

  1. Ler conteúdo — paginar get_contents_from_docx(draft, offset, limit) até que has_more seja falso. Coletar todos os items.

  2. Ler estilos — paginar get_styles_from_docx(template, offset, limit) até que has_more seja falso. Mesclar todos os paragraph_styles; manter section do primeiro lote (offset=0).

  3. Escrever conteúdowrite_contents_to_docx(output, contents) com os blocos coletados.

  4. Unir estiloswrite_styles_to_docx(output, styles) com o perfil de estilos mesclado.

Padrão de paginação

# 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}

Ordem das ferramentas

OrdemFerramentaArquivo deve existir
1get_contents_from_docxSim (origem)
2get_styles_from_docxSim (modelo)
3write_contents_to_docxNão — cria saída
4write_styles_to_docxSim — saída do passo 3

Regras de união de estilos

Aplicado por write_styles_to_docx via StyleProfile.union_with(incoming, master="other"):

CasoResultado
Estilo apenas no recebido (modelo)Adicionado ao destino
Estilo apenas no arquivo existenteMantido
Mesmo nome, definição diferenteO recebido vence — sobrescreve o destino
Configuração de seção no recebidoAplicado a partir do perfil recebido

Estilos com valores de campo null herdam de base_style no momento da escrita (StyleProfile.resolve_inherited()). Para as substituições de nível de execução bold, italic e font_color, um null resolvido é uma redefinição explícita: a substituição correspondente é limpa no estilo de destino para que artefatos do tema do rascunho (ex.: títulos azuis, em negrito) não sobrevivam a uma reformatação.

StyleMapper (auxiliar de adaptador)

Ao mapear nomes de estilos de origem para um catálogo de modelo (usado internamente durante a reformatação):

  1. Correspondência exata de nome nos estilos do modelo
  2. Entrada no opcional custom_map
  3. Fallback de título mais próximo (Heading NHeading min(N, available))
  4. Fallback para Normal, ou primeiro estilo de modelo disponível

Estilos não mapeados são rastreados em unmapped_styles.

Limitações conhecidas (v1)

Não suportado na versão atual:

  • Cabeçalhos e rodapés (conteúdo)
  • Imagens flutuantes
  • Caixas de texto
  • Notas de rodapé e notas finais
  • Reinício de numeração / preservação de numeração de lista
  • Formatação de nível de execução quando existe um estilo de parágrafo nomeado (adiado — estilos aplicados no passo 4 sobrescrevem dicas inline)
  • Formatação direta de nível de parágrafo (ex.: um título centralizado definido no parágrafo, não no estilo) — não transportada por blocos de conteúdo; ParagraphAligner cobre apenas a heurística de título/conclusões usada nos testes de reformatação
  • Cache de análise de documento — cada chamada de lote relê o arquivo do disco

Desenvolvimento

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

Estrutura do projeto

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

Fixtures de teste para exploração manual:

  • tests/assets/plain.docx — conteúdo de exemplo (rascunho)
  • tests/assets/format.docx — estilos de exemplo (modelo)

Teste de pipeline de ponta a ponta: tests/test_reformat_pipeline.py.

Roteiro

SubplanoTópico
SP-08Exemplos de prompt de agente e integração com Cursor
SP-09Cache de análise de documento entre chamadas de lote
SP-10Formatação de nível de execução quando existe estilo nomeado
SP-11Extração e escrita de cabeçalhos/rodapés
SP-12Imagens, caixas de texto, notas de rodapé, numeração
SP-13Transporte HTTP / streamable-http

Licença

Consulte o arquivo de licença do repositório.