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
| Ferramenta | Propósito |
|---|---|
get_contents_from_docx | Ler em lote blocos de conteúdo (parágrafos e tabelas) |
write_contents_to_docx | Escrever blocos de conteúdo; cria arquivo se ausente |
get_styles_from_docx | Ler em lote catálogo de estilos de parágrafo |
write_styles_to_docx | Unir 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
| Camada | Pacote | Pode importar de | Não deve importar |
|---|---|---|---|
| MCP | server.py | services/, errors | adapters/, docx |
| Serviço | services/ | adapters/, domain/, errors | docx, mcp |
| Adaptador | adapters/ | domain/, errors, docx | services/, mcp |
| Domínio | domain/ | somente stdlib | todo 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
- python-docx —
.docxE/S - MCP Python SDK (
mcp>=1.12.0) — servidor FastMCP - uv — gerenciador de pacotes e executor
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
| Runtime | file_path nas ferramentas |
|---|---|
Nativo (uv) | Caminho do host como passado pelo agente, ex.: /home/user/docs/report.docx |
| Docker | Caminho 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;
--rmremove 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âmetro | Tipo | Padrão | Descrição |
|---|---|---|---|
file_path | str | obrigatório | Caminho para o arquivo .docx |
offset | int | 0 | Índice inicial na lista de blocos |
limit | int | 10 | Má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âmetro | Tipo | Padrão | Descrição |
|---|---|---|---|
file_path | str | obrigatório | Caminho para o arquivo .docx |
offset | int | 0 | Índice inicial na lista de estilos |
limit | int | 25 | Má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âmetro | Tipo | Padrão | Descrição |
|---|---|---|---|
file_path | str | obrigatório | Caminho de saída |
contents | list[dict] | obrigatório | Blocos 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âmetro | Tipo | Padrão | Descrição |
|---|---|---|---|
file_path | str | obrigatório | Arquivo de destino (deve existir) |
styles | dict | obrigató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.docxpara corresponder acompany_template.docx. Salvar comoreport_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
-
Ler conteúdo — paginar
get_contents_from_docx(draft, offset, limit)até quehas_moreseja falso. Coletar todos ositems. -
Ler estilos — paginar
get_styles_from_docx(template, offset, limit)até quehas_moreseja falso. Mesclar todos osparagraph_styles; mantersectiondo primeiro lote (offset=0). -
Escrever conteúdo —
write_contents_to_docx(output, contents)com os blocos coletados. -
Unir estilos —
write_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
| Ordem | Ferramenta | Arquivo deve existir |
|---|---|---|
| 1 | get_contents_from_docx | Sim (origem) |
| 2 | get_styles_from_docx | Sim (modelo) |
| 3 | write_contents_to_docx | Não — cria saída |
| 4 | write_styles_to_docx | Sim — saída do passo 3 |
Regras de união de estilos
Aplicado por write_styles_to_docx via StyleProfile.union_with(incoming, master="other"):
| Caso | Resultado |
|---|---|
| Estilo apenas no recebido (modelo) | Adicionado ao destino |
| Estilo apenas no arquivo existente | Mantido |
| Mesmo nome, definição diferente | O recebido vence — sobrescreve o destino |
| Configuração de seção no recebido | Aplicado 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):
- Correspondência exata de nome nos estilos do modelo
- Entrada no opcional
custom_map - Fallback de título mais próximo (
Heading N→Heading min(N, available)) - 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;
ParagraphAlignercobre 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
| Subplano | Tópico |
|---|---|
| SP-08 | Exemplos de prompt de agente e integração com Cursor |
| SP-09 | Cache de análise de documento entre chamadas de lote |
| SP-10 | Formatação de nível de execução quando existe estilo nomeado |
| SP-11 | Extração e escrita de cabeçalhos/rodapés |
| SP-12 | Imagens, caixas de texto, notas de rodapé, numeração |
| SP-13 | Transporte HTTP / streamable-http |
Licença
Consulte o arquivo de licença do repositório.