Skip to main content
Glama
README.md
# lo-mcp

A stdio MCP server for LibreOffice Writer. Minimal scope on purpose: create,
edit, and export Writer documents. No Calc/Impress yet — add them later if
Writer proves useful.

## Why this shape

LibreOffice's own bundled Python interpreter (`LibreOfficePython`, used by
`import uno` for document automation) carries a macOS Launch Constraint —
spawning it as a subprocess from Claude Code's process tree gets SIGKILLed
by macOS (`CODESIGNING 4 Launch Constraint Violation`). Plain `soffice` does
not carry this constraint, but driving Writer interactively needs the `uno`
Python bindings, which only load correctly under LibreOffice's own bundled
interpreter (cross-interpreter `import uno` from an unrelated Python is an
ABI mismatch — observed to hang the process in uninterruptible sleep, not
fail cleanly).

So: a small LibreOffice extension (`extension/`) runs *inside* the
already-running `soffice` process, using LibreOffice's own already-correct
interpreter — no subprocess, no cross-interpreter import. It exposes a
local HTTP server. The MCP server (`src/lo_mcp/`) is a thin, unconstrained
client that talks to it over plain HTTP — same pattern as `freecad-mcp`
connecting to the AICopilot addon running inside FreeCAD.

## Setup

1. Build and install the extension:
   ```bash
   extension/build.sh
   /Applications/LibreOffice.app/Contents/MacOS/unopkg add --force dist/lo-mcp.oxt
   ```
2. Launch (or restart) LibreOffice.
3. In LibreOffice: **lo-mcp menu > Start Server**. This opens
   `127.0.0.1:8794` — local-only, only listens while you've started it.
   No real authentication, but POSTs must carry an `X-Lo-Mcp-Client` header,
   which forces browsers to CORS-preflight cross-origin requests; since the
   server never answers with `Access-Control-Allow-Origin`, that preflight
   fails and a malicious web page can't drive it.
4. Register the MCP server:
   ```bash
   uv sync
   claude mcp add lo-mcp -- uv run --project /Volumes/Files/claude/lo-mcp lo-mcp
   ```

Call `check_connection` first in any session — if LibreOffice isn't running
or the server wasn't started, it says so.

## Tools

`check_connection`, `create_document`, `open_document`, `list_documents`,
`get_text`, `insert_text`, `find_and_replace`, `save_document`,
`export_document` (pdf/docx/odt/doc/rtf/txt/html), `close_document`.

## Testing

```bash
uv run pytest
```

Requires LibreOffice running with the server started; skips otherwise.

## Extending to Calc/Impress

Add operations to `extension/pythonpath/lo_mcp_extension.py` (new `*Ops`
class or methods, registered in `_OPS`) and matching thin `@mcp.tool()`
wrappers in `src/lo_mcp/server.py`. The HTTP/extension plumbing doesn't
change.

TDQS

A3.5/5.0

Scored across 15 tools

Disambiguation5/5

Each tool has a clearly distinct purpose, from connection checking to document lifecycle operations, content insertion, reading, and style management. No two tools could be easily confused.

Naming Consistency5/5

All tool names follow a consistent verb_noun pattern with underscores and lowercase, e.g., create_document, get_text, set_paragraph_style, find_and_replace. No mixing of conventions.

Tool Count5/5

15 tools cover document creation, opening, saving, closing, exporting, content insertion (text, table), reading (text, styles, table cells), search/replace, style listing, and document listing. This is well-scoped for a Writer manipulation server.

Completeness3/5

The tool set covers basic document operations and insertion, but lacks deletion capabilities (no tool to remove text, tables, or paragraphs) and modification of existing content (e.g., changing formatting after insertion, updating table cells). These gaps are notable for a full document editing workflow.

Maintenance

ActivityStale
ResponsivenessNo issues