Skip to main content
Glama
daidahan

libreoffice-mcp-writer

by daidahan
README.md
# libreoffice-mcp-writer

Servidor [MCP](https://modelcontextprotocol.io) que conecta Claude con **LibreOffice Writer** mediante la API UNO. Permite crear, editar y dar formato a documentos `.odt` (texto, listas, tablas y estilos) desde Claude Code, con LibreOffice corriendo sin ventana y el acceso a archivos restringido a una sola carpeta.

> **English summary:** MCP server that lets Claude create, edit and format LibreOffice Writer `.odt` documents through the UNO API. LibreOffice runs headless and file access is sandboxed to a single root folder. Documentation is in Spanish.

---

## Cómo funciona

```
Claude Code  ──MCP (stdio)──▶  libreoffice_mcp_writer.py  ──UNO (socket :2002)──▶  LibreOffice (headless)  ──▶  .odt
```

El servidor nunca escribe archivos por su cuenta: le pide a LibreOffice que los abra y los guarde. Así los documentos se guardan siempre en el formato nativo, sin manipular el ZIP/XML interno del `.odt`.

## Modelo de seguridad

Un asistente con acceso a tus documentos puede dañarlos si improvisa. Este proyecto se diseñó para que eso no sea posible:

**En el servidor:**
- Solo acepta rutas dentro de la carpeta raíz (`LIBREOFFICE_MCP_ROOT`). Rechaza `../` y enlaces simbólicos que apunten fuera.
- Solo trabaja con archivos `.odt` y rechaza carpetas y archivos ocultos, así que no puede tocar `.mcp.json`, `.claude/` ni el propio script.
- `create_document` nunca sobrescribe un archivo existente.
- Los mensajes de error le indican al modelo que informe el problema en vez de buscar archivos por el disco o editarlos directamente.

**En Claude Code (muy recomendado):**
- Deniega `Bash`, `Edit` y `Write` en la carpeta de documentos (ver [`examples/settings.json`](examples/settings.json)). Sin eso, el modelo podría saltarse el MCP y modificar o borrar archivos directamente. Bloquear comandos específicos como `rm` no basta, porque siempre hay otro camino (`python -c`, `find -delete`…).

## Requisitos

- **LibreOffice** con los bindings de Python (`uno`):
  - Arch Linux: incluidos en `libreoffice-fresh` o `libreoffice-still`.
  - Debian / Ubuntu: `sudo apt install libreoffice-writer python3-uno`
- **Python 3.10 o superior**, el mismo intérprete contra el que se compiló `uno`.
- **Claude Code**.

Comprueba que Python puede cargar `uno`:

```bash
URE_BOOTSTRAP="vnd.sun.star.pathname:/usr/lib/libreoffice/program/fundamentalrc" \
LD_LIBRARY_PATH="/usr/lib/libreoffice/program" \
PYTHONPATH="/usr/lib/libreoffice/program" \
python3 -c "import uno; print('ok')"
```

## Instalación

```bash
git clone https://github.com/TU_USUARIO/libreoffice-mcp-writer.git
cd libreoffice-mcp-writer

python3 -m venv --system-site-packages ~/.venvs/libreoffice-mcp
~/.venvs/libreoffice-mcp/bin/pip install -r requirements.txt
```

`--system-site-packages` es necesario: `uno` lo instala LibreOffice en el Python del sistema y no está disponible en pip. Sin ese flag, el entorno virtual no lo ve.

## Configuración

Todo se configura en tu **carpeta de documentos**, que es desde donde inicias Claude Code.

**1. Registrar el servidor.** Copia `.mcp.json.example` como `.mcp.json` en tu carpeta de documentos y ajusta las rutas, o regístralo con la CLI:

```bash
cd ~/Documentos
claude mcp add --scope project libreoffice-writer \
  --env URE_BOOTSTRAP="vnd.sun.star.pathname:/usr/lib/libreoffice/program/fundamentalrc" \
  --env LD_LIBRARY_PATH="/usr/lib/libreoffice/program" \
  --env PYTHONPATH="/usr/lib/libreoffice/program" \
  --env LIBREOFFICE_MCP_ROOT="$HOME/Documentos" \
  -- ~/.venvs/libreoffice-mcp/bin/python3 /ruta/a/libreoffice-mcp-writer/libreoffice_mcp_writer.py
```

**2. Restringir permisos.** Copia `examples/settings.json` a `.claude/settings.json` en tu carpeta de documentos.

**3. Instrucciones para el modelo (opcional).** Copia `examples/CLAUDE.md` a tu carpeta de documentos. Ahorra tokens y evita que el modelo intente caminos que los permisos van a bloquear.

## Uso

```bash
./scripts/start-libreoffice.sh     # arranca LibreOffice headless (una sola vez por sesión)
cd ~/Documentos
claude --model sonnet
```

Dentro de Claude Code, `/mcp` debe mostrar `libreoffice-writer` conectado. Luego puedes pedir cosas como:

```
Crea informes/reunion.odt con el título "Acta de reunión" en estilo Heading 1
Agrega una tabla de 3x2 con los asistentes y sus cargos
Convierte los párrafos "Presupuesto", "Plazos" y "Riesgos" en una lista con viñetas
Pon "Total" en negrita y guarda el documento
```

Se recomienda Sonnet u Opus. Los modelos más pequeños tienden a ignorar las restricciones cuando una herramienta falla.

## Herramientas

| Herramienta | Qué hace |
|---|---|
| `open_document` | Abre un `.odt` existente dentro de la carpeta raíz |
| `create_document` | Crea un `.odt` nuevo y vacío; nunca sobrescribe |
| `list_documents` | Lista los documentos abiertos con su ruta |
| `save_document` | Guarda el documento en su archivo |
| `close_document` | Cierra un documento, opcionalmente guardándolo |
| `get_last_paragraphs` | Lee los últimos N párrafos |
| `get_document_text` | Lee el documento completo (costoso en tokens) |
| `find_and_replace` | Busca y reemplaza; con `replace=""` borra |
| `append_text` | Agrega texto al final |
| `delete_last_paragraph` | Elimina el último párrafo |
| `insert_paragraph_after` | Inserta un párrafo después del que contiene cierto texto |
| `set_char_format` | Negrita, cursiva o subrayado sobre un texto |
| `set_paragraph_style` | Aplica un estilo de párrafo |
| `set_list` | Convierte párrafos en lista real (viñetas o numerada) |
| `insert_table` | Inserta una tabla al final o después de un párrafo |
| `list_tables` | Lista las tablas del documento |
| `set_table_cell` / `get_table_cell` | Escribe o lee una celda |

LibreOffice corre sin ventana, así que no hay selección ni cursor visible: las herramientas de formato ubican el texto **buscándolo**.

## Solución de problemas

**`Segmentation fault` al hacer `import uno`.** Faltan las variables de entorno. `uno` carga el resto de LibreOffice en tiempo de ejecución y, sin `URE_BOOTSTRAP` y `LD_LIBRARY_PATH`, falla sin un mensaje claro. Deben estar en el bloque `env` de `.mcp.json`.

**`No module named 'mcp.server.fastmcp'`.** Se instaló mcp 2.x. Ejecuta `pip install "mcp<2"` dentro del entorno virtual.

**`No module named 'uno'` dentro del venv.** El entorno se creó sin `--system-site-packages`. Bórralo y créalo de nuevo con ese flag.

**Claude Code muestra `Failed to connect` o el error `-32000`.** El proceso del servidor terminó al arrancar. Ejecuta a mano el mismo comando y los mismos `args` de `.mcp.json`, con sus variables de entorno, para ver el error real. Revisa que la ruta del script sea exacta (sin sufijos como `~` de archivos de respaldo).

**LibreOffice se cierra apenas arranca, sin mensajes.** Quedó un archivo de bloqueo tras un cierre forzado. Bórralo con `rm ~/.config/libreoffice/4/.lock`, o usa `scripts/start-libreoffice.sh`, que lo hace automáticamente.

**`Can't open display` al arrancar LibreOffice.** Falta la opción `--headless`, o estás ejecutándolo con un usuario sin acceso a la sesión gráfica. El modo headless no necesita pantalla.

**Comprobar si LibreOffice está escuchando:** `ss -ltnp | grep 2002`

## Limitaciones

- Solo LibreOffice **Writer**. Calc e Impress no están soportados.
- No inserta imágenes.
- Las listas se aplican en un solo nivel (sin sublistas).
- Requiere el SDK de MCP 1.x.
- Probado en Linux.

## Licencia

[GNU General Public License v3.0](LICENSE) o posterior (GPL-3.0-or-later).

Puedes usar, modificar y redistribuir este proyecto. Si distribuyes una versión modificada, debe publicarse con su código fuente bajo esta misma licencia.