docs-mcp
MCP para trabajar con archivos docx. Realiza copia de formato desde archivos docx.
Documentación
docs-mcp
Servidor MCP para leer y escribir archivos .docx. Expone cuatro herramientas paginadas para que los agentes puedan leer por lotes el contenido y los estilos de documentos, escribir contenido y unir definiciones de estilos, sin necesidad de una herramienta monolítica de reformateo.
Requisitos: Python 3.11+
Características
| Herramienta | Propósito |
|---|---|
get_contents_from_docx | Leer por lotes bloques de contenido (párrafos y tablas) |
write_contents_to_docx | Escribir bloques de contenido; crea el archivo si no existe |
get_styles_from_docx | Leer por lotes el catálogo de estilos de párrafo |
write_styles_to_docx | Unir definiciones de estilos a un archivo existente (el entrante gana en caso de conflicto) |
Caso de uso principal: reformatear un borrador de documento usando los estilos de una plantilla — el agente orquesta cuatro llamadas a herramientas con paginación.
Arquitectura
Diseño en capas: las herramientas MCP delegan en servicios, los servicios usan adaptadores, y los adaptadores traducen hacia/desde modelos de dominio.
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
Reglas de capas
| Capa | Paquete | Puede importar desde | No debe importar |
|---|---|---|---|
| MCP | server.py | services/, errors | adapters/, docx |
| Servicio | services/ | adapters/, domain/, errors | docx, mcp |
| Adaptador | adapters/ | domain/, errors, docx | services/, mcp |
| Dominio | domain/ | solo stdlib | todo lo demás |
La dirección de dependencias siempre es descendente: MCP → Servicio → Adaptador → Dominio.
Consulta AGENTS.md para las pautas de contribución.
Stack tecnológico
- python-docx — E/S de
.docx - MCP Python SDK (
mcp>=1.12.0) — servidor FastMCP - uv — gestor de paquetes y ejecutor
Inicio rápido
1. Clonar e instalar
git clone <repo-url> docs-mcp
cd docs-mcp
uv sync --extra dev
2. Ejecutar pruebas
uv run pytest
3. Prueba de humo del servidor MCP
uv run docx-mcp
El proceso escucha en stdio (JSON-RPC). Pulsa Ctrl+C para detenerlo.
4. Añadir a Cursor
Reemplaza /absolute/path/to/docs-mcp con la ruta de tu clon. La configuración MCP de Cursor requiere rutas absolutas.
Nativo (uv):
{
"mcpServers": {
"docs-mcp": {
"command": "uv",
"args": [
"run",
"--directory",
"/absolute/path/to/docs-mcp",
"docx-mcp"
]
}
}
}
Docker (sesión efímera):
Compila una vez desde la raíz del repositorio (sin rutas de archivos en la imagen ni en el comando de compilación):
cd docs-mcp
docker build -t docs-mcp .
Configuración MCP — solo indica cómo iniciar el proceso del servidor. Qué archivos leer/escribir no se configura aquí; cada herramienta recibe file_path del cliente MCP (agente/usuario) en el momento de la llamada:
{
"mcpServers": {
"docs-mcp": {
"command": "docker",
"args": ["run", "--rm", "-i", "docs-mcp"]
}
}
}
Rutas de archivos en las llamadas a herramientas
| Entorno de ejecución | file_path en las herramientas |
|---|---|
Nativo (uv) | Ruta del host tal como la pasa el agente, p. ej. /home/user/docs/report.docx |
| Docker | Ruta dentro del sistema de archivos del contenedor |
Con Docker, la configuración predeterminada anterior no tiene montajes de enlace (bind mounts) — las rutas de las herramientas deben existir dentro del contenedor a menos que extiendas args. Para leer/escribir archivos del host, añade un montaje de volumen que coincida con las rutas que pasas en las herramientas, por ejemplo:
"args": ["run", "--rm", "-i", "-v", "/home/user/docs:/home/user/docs", "docs-mcp"]
Entonces el agente llama a get_contents_from_docx(file_path="/home/user/docs/report.docx") — la misma cadena de ruta en el host y en el contenedor.
Un contenedor se ejecuta durante toda la sesión MCP (no por cada llamada a herramienta). El host inicia el proceso al conectar y lo detiene al desconectar;
--rmelimina el contenedor automáticamente.
Referencia de herramientas
Todas las herramientas devuelven diccionarios serializables a JSON. En caso de fallo, la respuesta contiene campos de error estructurados en lugar de lanzar una excepción no controlada:
{
"code": "FILE_NOT_FOUND",
"message": "File not found: /path/missing.docx",
"details": { "path": "/path/missing.docx" }
}
Códigos de error: FILE_NOT_FOUND, FILE_NOT_READABLE, FILE_NOT_WRITABLE, INVALID_PATH, PARSE_ERROR, STYLE_NOT_FOUND, REFORMAT_ERROR, INTERNAL_ERROR.
get_contents_from_docx
Devuelve un lote paginado de bloques de contenido del documento.
| Parámetro | Tipo | Predeterminado | Descripción |
|---|---|---|---|
file_path | str | obligatorio | Ruta al archivo .docx |
offset | int | 0 | Índice inicial en la lista de bloques |
limit | int | 10 | Máximo de bloques por lote (máx. 200) |
Ejemplo de respuesta:
{
"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"
}
Los bloques llevan una referencia de nombre de estilo (StyleHint), no definiciones completas de estilo. Consulta .agents/skills/docx-mcp/references/blocks para el esquema completo.
get_styles_from_docx
Devuelve un lote paginado de estilos de párrafo de un archivo .docx.
| Parámetro | Tipo | Predeterminado | Descripción |
|---|---|---|---|
file_path | str | obligatorio | Ruta al archivo .docx |
offset | int | 0 | Índice inicial en la lista de estilos |
limit | int | 25 | Máximo de estilos por lote (máx. 200) |
Ejemplo de respuesta (primer 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 se incluye solo cuando offset == 0; los lotes posteriores lo omiten. Combina paragraph_styles en el lado del cliente entre lotes.
write_contents_to_docx
Escribe bloques de contenido en un archivo .docx. Crea un archivo nuevo si la ruta no existe; reemplaza el cuerpo del documento si ya existe.
| Parámetro | Tipo | Predeterminado | Descripción |
|---|---|---|---|
file_path | str | obligatorio | Ruta de salida |
contents | list[dict] | obligatorio | Bloques de contenido de get_contents_from_docx |
Ejemplo de respuesta:
{
"file_path": "/path/output.docx",
"blocks_written": 48,
"created": true
}
write_styles_to_docx
Une definiciones de estilos a un archivo .docx existente. Los estilos entrantes ganan en caso de conflicto de nombre.
| Parámetro | Tipo | Predeterminado | Descripción |
|---|---|---|---|
file_path | str | obligatorio | Archivo de destino (debe existir) |
styles | dict | obligatorio | { "paragraph_styles": [...], "section": {...} } |
Ejemplo de respuesta:
{
"file_path": "/path/output.docx",
"styles_added": 5,
"styles_updated": 12,
"styles_unchanged": 8
}
Devuelve FILE_NOT_FOUND si el archivo de destino no existe — llama a write_contents_to_docx primero.
Historia de usuario: Reformatear con plantilla
Ejemplo de prompt:
Reformatea
report_draft.docxpara que coincida concompany_template.docx. Guárdalo comoreport_final.docx.
Flujo de trabajo del 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
Paso a paso
-
Leer contenido — pagina
get_contents_from_docx(draft, offset, limit)hasta quehas_moresea falso. Recopila todos lositems. -
Leer estilos — pagina
get_styles_from_docx(template, offset, limit)hasta quehas_moresea falso. Combina todos losparagraph_styles; conservasectiondel primer lote (offset=0). -
Escribir contenido —
write_contents_to_docx(output, contents)con los bloques recopilados. -
Unir estilos —
write_styles_to_docx(output, styles)con el perfil de estilos combinado.
Patrón de paginación
# 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}
Orden de herramientas
| Orden | Herramienta | El archivo debe existir |
|---|---|---|
| 1 | get_contents_from_docx | Sí (origen) |
| 2 | get_styles_from_docx | Sí (plantilla) |
| 3 | write_contents_to_docx | No — crea la salida |
| 4 | write_styles_to_docx | Sí — salida del paso 3 |
Reglas de unión de estilos
Aplicadas por write_styles_to_docx mediante StyleProfile.union_with(incoming, master="other"):
| Caso | Resultado |
|---|---|
| Estilo solo en el entrante (plantilla) | Se añade al destino |
| Estilo solo en el archivo existente | Se conserva |
| Mismo nombre, definición diferente | El entrante gana — sobrescribe el destino |
| Configuración de sección en el entrante | Se aplica desde el perfil entrante |
Los estilos con valores de campo null heredan de base_style en el momento de la escritura (StyleProfile.resolve_inherited()). Para las anulaciones a nivel de ejecución bold, italic y font_color, un null resuelto es un restablecimiento explícito: la anulación correspondiente se elimina en el estilo de destino para que los artefactos de tema del borrador (p. ej. encabezados azules, en negrita) no sobrevivan a un reformateo.
StyleMapper (ayudante de adaptador)
Al asignar nombres de estilos de origen a un catálogo de plantilla (usado internamente durante el reformateo):
- Coincidencia exacta de nombre en los estilos de la plantilla
- Entrada en
custom_mapopcional - Respaldo al encabezado más cercano (
Heading N→Heading min(N, available)) - Respaldo a
Normal, o al primer estilo disponible de la plantilla
Los estilos no asignados se registran en unmapped_styles.
Limitaciones conocidas (v1)
No compatible en la versión actual:
- Encabezados y pies de página (contenido)
- Imágenes flotantes
- Cuadros de texto
- Notas al pie y notas finales
- Reinicio de numeración / conservación de numeración de listas
- Formato a nivel de ejecución cuando existe un estilo de párrafo con nombre (diferido — los estilos aplicados en el paso 4 anulan las indicaciones en línea)
- Formato directo a nivel de párrafo (p. ej. un título centrado definido en el párrafo, no en el estilo) — no lo transportan los bloques de contenido;
ParagraphAlignercubre solo la heurística de título/conclusiones usada en las pruebas de reformateo - Caché de análisis de documentos — cada llamada de lote vuelve a leer el archivo desde el disco
Desarrollo
uv sync --extra dev
uv run pytest
uv run docx-mcp
Estructura del proyecto
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
Accesorios de prueba para exploración manual:
tests/assets/plain.docx— contenido de muestra (borrador)tests/assets/format.docx— estilos de muestra (plantilla)
Prueba de pipeline de extremo a extremo: tests/test_reformat_pipeline.py.
Hoja de ruta
| Subplan | Tema |
|---|---|
| SP-08 | Ejemplos de prompts de agente e incorporación en Cursor |
| SP-09 | Caché de análisis de documentos entre llamadas de lote |
| SP-10 | Formato a nivel de ejecución cuando existe un estilo con nombre |
| SP-11 | Extracción y escritura de encabezados/pies de página |
| SP-12 | Imágenes, cuadros de texto, notas al pie, numeración |
| SP-13 | Transporte HTTP / streamable-http |
Licencia
Consulta el archivo de licencia del repositorio.