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

HerramientaPropósito
get_contents_from_docxLeer por lotes bloques de contenido (párrafos y tablas)
write_contents_to_docxEscribir bloques de contenido; crea el archivo si no existe
get_styles_from_docxLeer por lotes el catálogo de estilos de párrafo
write_styles_to_docxUnir 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

CapaPaquetePuede importar desdeNo debe importar
MCPserver.pyservices/, errorsadapters/, docx
Servicioservices/adapters/, domain/, errorsdocx, mcp
Adaptadoradapters/domain/, errors, docxservices/, mcp
Dominiodomain/solo stdlibtodo 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

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ónfile_path en las herramientas
Nativo (uv)Ruta del host tal como la pasa el agente, p. ej. /home/user/docs/report.docx
DockerRuta 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; --rm elimina 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ámetroTipoPredeterminadoDescripción
file_pathstrobligatorioRuta al archivo .docx
offsetint0Índice inicial en la lista de bloques
limitint10Má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ámetroTipoPredeterminadoDescripción
file_pathstrobligatorioRuta al archivo .docx
offsetint0Índice inicial en la lista de estilos
limitint25Má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ámetroTipoPredeterminadoDescripción
file_pathstrobligatorioRuta de salida
contentslist[dict]obligatorioBloques 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ámetroTipoPredeterminadoDescripción
file_pathstrobligatorioArchivo de destino (debe existir)
stylesdictobligatorio{ "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.docx para que coincida con company_template.docx. Guárdalo como report_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

  1. Leer contenido — pagina get_contents_from_docx(draft, offset, limit) hasta que has_more sea falso. Recopila todos los items.

  2. Leer estilos — pagina get_styles_from_docx(template, offset, limit) hasta que has_more sea falso. Combina todos los paragraph_styles; conserva section del primer lote (offset=0).

  3. Escribir contenidowrite_contents_to_docx(output, contents) con los bloques recopilados.

  4. Unir estiloswrite_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

OrdenHerramientaEl archivo debe existir
1get_contents_from_docxSí (origen)
2get_styles_from_docxSí (plantilla)
3write_contents_to_docxNo — crea la salida
4write_styles_to_docxSí — salida del paso 3

Reglas de unión de estilos

Aplicadas por write_styles_to_docx mediante StyleProfile.union_with(incoming, master="other"):

CasoResultado
Estilo solo en el entrante (plantilla)Se añade al destino
Estilo solo en el archivo existenteSe conserva
Mismo nombre, definición diferenteEl entrante gana — sobrescribe el destino
Configuración de sección en el entranteSe 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):

  1. Coincidencia exacta de nombre en los estilos de la plantilla
  2. Entrada en custom_map opcional
  3. Respaldo al encabezado más cercano (Heading NHeading min(N, available))
  4. 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; ParagraphAligner cubre 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

SubplanTema
SP-08Ejemplos de prompts de agente e incorporación en Cursor
SP-09Caché de análisis de documentos entre llamadas de lote
SP-10Formato a nivel de ejecución cuando existe un estilo con nombre
SP-11Extracción y escritura de encabezados/pies de página
SP-12Imágenes, cuadros de texto, notas al pie, numeración
SP-13Transporte HTTP / streamable-http

Licencia

Consulta el archivo de licencia del repositorio.