Skip to main content

MCP server wrapping OfficeCLI for OpenWebUI: handle-based HTTP file layer + streamable-HTTP/stdio

Project description

officecli-mcp

An MCP server that wraps OfficeCLI so it can be used from OpenWebUI (and any streamable-HTTP MCP client). It solves OfficeCLI's core limitation for remote clients: OfficeCLI's built-in MCP mode only accepts local file paths, which doesn't work when the LLM client runs in a different container/pod and never holds the file bytes.

officecli-mcp adds a handle-based file layer: an HTTP upload endpoint accepts office documents and returns a file_id; MCP tools then operate on that file_id. OfficeCLI's built-in rendering returns HTML (as text) and screenshots (as base64 images) directly to the LLM, closing the render → look → fix loop.

Quick start (3 steps)

You need three things talking to each other: the officecli-mcp container, the officecli_file native tool auto-synced into OpenWebUI, and the model attached to it. Do them in order:

1. Run the server.

docker compose up -d   # http://localhost:8765, auto-pulls officecli on first start

The one env you MUST set when OpenWebUI is in a separate container is OFFICECLI_MCP_ALLOWED_HOSTS - the MCP SDK's DNS-rebinding guard returns 421 "Invalid Host header" to any Host it isn't told about. The compose file already sets it to officecli-mcp:8765,localhost:8765,127.0.0.1:8765; if your service name or port differs, edit it (or set OFFICECLI_MCP_DNS_REBINDING_PROTECTION=0 to disable the guard).

2. Set the auto-sync env vars (or paste the tool manually).

Add these env vars to your docker-compose or env file (the compose file has commented placeholders):

Var Example What it is
OFFICECLI_MCP_OWUI_URL http://open-webui:8080 OpenWebUI internal base (for self-sync)
OFFICECLI_MCP_OWUI_API_KEY sk-... OpenWebUI admin API key (for self-sync -- keep secret, use an env file or secrets, never commit)

The server auto-creates/updates the officecli native tool in OpenWebUI on boot when both vars are set. If you'd rather paste it manually, use examples/openwebui_officecli_file.py in Workspace -> Tools (set owui_sync=0 to disable auto-sync).

3. In OpenWebUI: attach the tool and chat.

Find the auto-created officecli tool (display name OfficeCLI) in Workspace -> Tools -> make it Public -> attach it to a model. No MCP connection is required -- the tool drives officecli-mcp over plain HTTP (/tools/call). The MCP streamable-HTTP endpoint (/mcp) stays available for debugging but is no longer the primary path.

In a chat with that model: attach a .docx/.xlsx/.pptx and ask it to edit; the model calls officecli_file(action="upload") to get a file_id, edits via the officecli_* tools over HTTP, then officecli_file(action="download") to hand back a downloadable file chip.

Admin API key

The OFFICECLI_MCP_OWUI_API_KEY is sensitive -- it grants tool create/update access to your OpenWebUI. Use an env file or a secrets manager; never commit it to version control. The compose file has the var commented out for safety.


How it works

OpenWebUI (pod A)                         officecli-mcp (pod B)
┌──────────────────────────────┐          ┌─────────────────────────────────────────────────┐
│ LLM ──► officecli_file       │  HTTP    │ HTTP /tools/call (dispatch)     │
│         (native tool) ───────┼─────────►  POST {"name","arguments"}     │
│                                │          │   → same handlers as /mcp     │
│ Native Tool "officecli_file"  │          │                                 │
│   reads __files__, fetches    │  HTTP    │ HTTP /files  (upload → file_id)│
│   bytes, POSTs ──────────────┼─────────► /files/{id} (download)        │
│   returns file_id to LLM      │          │                                 │
│                                │          │                                 │
│ LLM ──► MCP client            │          │ FastMCP (streamable-HTTP)       │
│         (debugging only) ──────┼─────────►  tools: create, view_html…    │
└──────────────────────────────┘          │ officecli binary (auto-pulled) │
                                          └─────────────────────────────────────────────────┘
  • The LLM never sees raw bytes — only a short file_id handle.
  • Bytes move server-to-server (OpenWebUI REST → our /files), never through the model context.
  • officecli is downloaded on first start (latest release for the host platform); the image stays small and decoupled from OfficeCLI version churn.

Transport

  • Primary: streamable-HTTP (OpenWebUI native MCP, v0.6.31+, is streamable-HTTP-only — connect directly, no mcpo needed).
  • Fallback: stdio (wrap with mcpo for OpenAPI/OpenWebUI if needed).

Runtime dependency: .NET globalization

officecli is a self-contained .NET app. On a slim image without libicu it fails fast (Couldn't find a valid ICU package). Rather than bundle ICU, we run .NET in globalization-invariant mode (DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1, set in the Dockerfile and injected by the runner subprocess env). This only affects locale-aware culture data (dates/numbers use invariant culture) and is fine for office document manipulation. If you ever need full locale support, apt-get install libicu in your image and unset that variable.

Status

✅ Implemented and verified end-to-end against the real officecli binary (v1.0.136). See docs/ for the design spec and implementation plan.

Local dev

python3 -m pip install -e ".[dev]"
python3 -m pytest                      # unit tests (no binary needed)
officecli-mcp --transport http --port 8765

E2E against the real binary:

curl -L https://github.com/iOfficeAI/OfficeCLI/releases/latest/download/officecli-linux-x64 -o /tmp/officecli && chmod +x /tmp/officecli
OFFICECLI_BIN=/tmp/officecli python3 -m pytest tests/test_e2e_real.py -v

Tools

All MCP tools are prefixed officecli_ and take a file_id handle (returned by POST /files or the officecli_file tool (action="upload")):

Tool Purpose
officecli_create create a blank doc/xlsx/pptx -> new file_id
officecli_view_html render to HTML (returned as text)
officecli_view_screenshot render a page to PNG (base64 image)
officecli_view_text / _annotated / _outline / _stats / _issues various text views
officecli_get / _set / _add / _remove / _move / _swap / _edit DOM edits (add supports prop list for pictures: ["src=<asset>","width=200"])
officecli_import CSV/TSV -> Excel via staged source filename
officecli_validate OpenXML schema validation
officecli_batch multi-command batch
officecli_file(action="stage") drop an image/CSV into a doc's workdir (returns asset name for src= or source= in other tools)

Configuration (env)

Var Default Meaning
OFFICECLI_MCP_TRANSPORT http http (streamable-HTTP) or stdio
OFFICECLI_MCP_PORT 8765 HTTP port
OFFICECLI_MCP_DATA_DIR /data where the officecli binary lives
OFFICECLI_MCP_WORK_DIR /work per-file_id workdirs
OFFICECLI_MCP_WORK_TTL_SECONDS 172800 (48h) idle workdir cleanup (doc + staged assets); swept lazily on each upload/stage, mtime refreshed on read
OFFICECLI_MCP_VIEW_HTML_MODE 2 (compact) officecli_view_html output: 0=disabled (error, use screenshot/annotated), 1=full HTML, 2=compact (strip styles/scripts, base64 images -> [IMG], keep text structure), 3=truncate to VIEW_HTML_MAX_CHARS. Compact is the default because officecli's full HTML is a large interactive page that blows the model context
OFFICECLI_MCP_VIEW_HTML_MAX_CHARS 8000 truncation limit when VIEW_HTML_MODE=3
OFFICECLI_MCP_MAX_UPLOAD_MB 50 upload size cap
OFFICECLI_VERSION latest pin a release tag
OFFICECLI_SHA256 (none) verify binary integrity
OFFICECLI_MCP_API_KEY (none) if set, require Bearer on HTTP surface
OFFICECLI_MCP_ALLOWED_HOSTS 127.0.0.1:*,localhost:*,[::1]:* comma-separated Host headers the /mcp endpoint is reachable by (use host:* for any port). OpenWebUI calls http://officecli-mcp:8765/mcp across the docker network, so the docker service name must be listed or clients get 421 Invalid Host header. The compose file sets this to officecli-mcp:8765,localhost:8765,127.0.0.1:8765.
OFFICECLI_MCP_DNS_REBINDING_PROTECTION 1 the MCP SDK DNS-rebinding / Host-header guard; set 0 to disable it entirely
OFFICECLI_MCP_SCREENSHOT_MAX_EDGE 1024 screenshot downscale longest edge (px); 0=off
OFFICECLI_MCP_OWUI_SYNC 1 push the officecli tool into OpenWebUI on boot
OFFICECLI_MCP_OWUI_URL "" OpenWebUI internal base (for self-sync)
OFFICECLI_MCP_OWUI_API_KEY "" OpenWebUI admin API key (for self-sync; keep secret)
OFFICECLI_MCP_OWUI_TOOL_ID officecli tool id to create/update

officecli_file actions

The native tool (installed in Quick start step 3) takes these action values:

  • action="upload" - push chat-attached office docs into officecli-mcp; returns a file_id to pass to the officecli_* MCP tools.
  • action="download" - pull a finished doc back out into OpenWebUI storage and return a browser-reachable download URL. Also emits a files event so OpenWebUI shows a downloadable file chip on the assistant message (no need to copy the URL out of the tool call).
  • action="stage" - drop a generated/uploaded image or CSV into a document's workdir; returns an asset filename for officecli_add type=picture (src=<asset>) or officecli_import (source=<asset>). Pictures must be staged first - officecli's SSRF guard blocks passing a URL as src=.
  • action="run" - call ANY document tool by name: pass tool=<name> and arguments=<JSON object as a STRING>. The action dispatches to the same FastMCP handlers as the /mcp endpoint, so validation and error handling are identical.
  • action="tools" - fetch the live tool manifest from /tools. Use this as a fallback if the pasted shim file is stale; returns the current tool list, signatures, and descriptions so the model can construct correct calls.

License

Apache-2.0 (same as OfficeCLI).

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

officecli_mcp-0.3.0.tar.gz (57.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

officecli_mcp-0.3.0-py3-none-any.whl (35.0 kB view details)

Uploaded Python 3

File details

Details for the file officecli_mcp-0.3.0.tar.gz.

File metadata

  • Download URL: officecli_mcp-0.3.0.tar.gz
  • Upload date:
  • Size: 57.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for officecli_mcp-0.3.0.tar.gz
Algorithm Hash digest
SHA256 c1da6e0ca813a0ddd15c42fe10dd0d418e5cf0daab0b7304262a0b2f1f7649f1
MD5 06f84d23fd82181415ae260791cf417e
BLAKE2b-256 d9c519fe572a8811384e01ec80d7e9c8bff2f900d7d79d0ebab883dabdaedc20

See more details on using hashes here.

Provenance

The following attestation bundles were made for officecli_mcp-0.3.0.tar.gz:

Publisher: publish.yml on xyonium/officecli-mcp

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file officecli_mcp-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: officecli_mcp-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 35.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for officecli_mcp-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 a593cefea829aaf77850b205db66244c6858b35b89d8a02769d1f422fb48e9e0
MD5 cee4aaf9ee175677424064147fce8992
BLAKE2b-256 3039a4149b2b55ff6b49fe7c119ffa71c4225c2c60a9c04710e208d87e3f529b

See more details on using hashes here.

Provenance

The following attestation bundles were made for officecli_mcp-0.3.0-py3-none-any.whl:

Publisher: publish.yml on xyonium/officecli-mcp

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page