office-mcp
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@office-mcpOpen my budget.xlsx and update cell B2 to 1500"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
office-mcp
MCP server for managing Word, Excel, and PowerPoint documents via stdio JSON-RPC.
office-mcp is a Model Context Protocol
server written in Python that lets an MCP-compatible agent (Claude
Desktop, MCP Inspector, a custom agent) read, write, edit, format,
and export modern Microsoft Office files (.docx, .xlsx,
.pptx) over the standard stdio JSON-RPC transport.
The server is stateless: every tools/call opens the file,
performs the operation, saves, and returns. There is no in-memory
cache, no background thread, and no database.
47 tools across four modules (4 unprefixed general tools
14 Word + 15 Excel + 14 PowerPoint tools).
PDF / HTML / CSV export for every supported format.
docx → htmlis pure-Python (viamammoth); everything else uses LibreOffice headless.Cross-platform — Windows, macOS, Linux. The codebase never shells out; LibreOffice is invoked as a subprocess with argument lists.
UTF-8 end-to-end with explicit
PYTHONUTF8=1/PYTHONIOENCODING=utf-8so non-ASCII content survives every tool call.
Table of contents
Related MCP server: docx-mcp
Install
1. Requirements
Python 3.10+ (tested on 3.12). The pre-built virtual environment in
.venv/already targets 3.12.LibreOffice 7+ for
*.pdfand.xlsx/.pptx → .htmlexport. The classic install paths are auto-detected; on Windows the installer placessoffice.exeatC:\Program Files\LibreOffice\program\soffice.exe. Theheadlessmode used byoffice-mcpdoes not require a display or a running LibreOffice instance.All Python dependencies are listed in
pyproject.tomland pre-installed in.venv/:mcp[cli]>=1.27,<2,python-docx,openpyxl,python-pptx,xlsxwriter,mammoth,defusedxml,pydantic. The optionalPillowis used for sample image generation only.
2. One-time setup
init.sh is idempotent: it sets the right environment variables,
adds LibreOffice to PATH, activates the venv, and verifies every
required Python package. Run it once from the project root
in a Bash-compatible shell (Git Bash, WSL, or bash on
macOS / Linux):
bash init.shWhat it does:
export PYTHONUTF8=1andexport PYTHONIOENCODING=utf-8so Unicode survives every subprocess boundary.Prepends the LibreOffice program directory to
PATH(no effect if it is already there).Activates
.venv/(Scripts/activateon Windows,bin/activateelsewhere). If.venv/is missing it bootstraps a fresh one.Imports every required package and prints a confirmation line.
Prints
soffice --version(or a warning if LibreOffice is missing — export tools will then raiseERR_LIBREOFFICE_MISSINGat call time, not at server start).
If you do not want to source init.sh, set the same variables
manually before launching the server:
export PYTHONUTF8=1
export PYTHONIOENCODING=utf-8
export PATH="/c/Program Files/LibreOffice/program:$PATH" # Windows Git Bash
.venv/Scripts/python.exe server.py3. Re-install / re-provision the venv (optional)
# from the project root
.venv/Scripts/python.exe -m pip install -e .The .[dev] extra pulls in pytest, pytest-asyncio, and
pyflakes.
Quick start
# 1. Activate venv + set encoding + add LibreOffice to PATH
bash init.sh
# 2. Launch the server (talks JSON-RPC on stdio)
python server.pyThe server prints nothing on success — it owns stdin/stdout for
the JSON-RPC stream. All diagnostic output goes to stderr. To
see the negotiated session, run the server under the MCP
Inspector:
.venv/Scripts/python.exe -m mcp.cli inspector server.pyTo verify the server from a clean shell without an agent, use the manual smoke test:
# Spawns the server over stdio, runs `initialize` + `tools/list`,
# prints the registered tool count, and exits.
.venv/Scripts/python.exe smoke_server.pyExpected last line: OK initialize -> tools/list (47 tools).
Claude Desktop registration
Add the following to your claude_desktop_config.json. On
Windows the file lives at
%APPDATA%\Claude\claude_desktop_config.json; on macOS at
~/Library/Application Support/Claude/claude_desktop_config.json.
{
"mcpServers": {
"office-mcp": {
"command": "python",
"args": ["E:/PROJECT FILE/Dengan Hati/PROJECT IDEA/office-mcp/server.py"],
"env": {
"PYTHONUTF8": "1",
"PYTHONIOENCODING": "utf-8",
"OFFICE_MCP_DEFAULT_FOLDER": "E:/Documents"
}
}
}
}Notes:
args[0]must be the absolute path toserver.pyfor your checkout. The forward slashes in the example work on Windows; use backslashes only if you escape them.OFFICE_MCP_DEFAULT_FOLDERis the directory the server will resolve relative file paths against. Any tool that accepts afolderargument overrides this default for that call.If you prefer the venv's Python, set
commandto the full path (e.g.E:/PROJECT FILE/Dengan Hati/PROJECT IDEA/office-mcp/.venv/Scripts/python.exe) — both work because the venv already has the server's dependencies on itssys.path.Restart Claude Desktop after editing the config so it re-reads the file.
The 47 tools
architecture.md §3 is the authoritative description; the table
below is the index. Format-specific tools are prefixed
(word_*, excel_*, pptx_*); the four general tools have no
prefix.
General (4)
Tool | Purpose |
| Non-recursive folder scan; returns one entry per |
| Auto-detect format by extension; dispatch to the format-specific |
| Cross-format text search with locations |
| Dispatch to the right exporter based on source extension and |
Word (15)
Tool | Purpose |
| Create a new |
| Counts of paragraphs / sections / tables / images + core properties. |
| All paragraphs with index, style, text, runs. |
| One paragraph by index. |
| Append a paragraph (optional style). |
| Append a heading at a level (1-9). |
| Find and replace text (case-sensitive toggle). |
| Update a run's bold / italic / size / font / color. |
| Append a table (rows × cols, optional data + style). |
| Embed an image (optional width_inches). |
| Set the text of a section's header. |
| Set the text of a section's footer. |
| Configure a section's orientation + page size. |
| Convert a |
| Convert a |
Excel (14)
Tool | Purpose |
| Create a new |
| Sheet count + names + per-sheet dimensions. |
| Names + indices + dimensions of all sheets. |
| Read a sheet (or an |
| Write a single cell (string, number, or |
| Write a 2-D list of values starting at a cell. |
| Append a new sheet. |
| Remove a sheet (refuse to delete the last). |
| Rename a sheet (refuse duplicates). |
| Apply font / fill / border / number-format to a range. |
| Add a |
| Export a sheet to CSV (pure Python). |
| Export a workbook to PDF via LibreOffice. |
| Export a workbook to HTML via LibreOffice. |
PowerPoint (14)
Tool | Purpose |
| Create a new |
| Slide count + layouts + slide dimensions. |
| All slides with index, title, layout, shape count. |
| One slide's title + shapes. |
| Add a slide (layout index, optional title). |
| Remove a slide (refuse to delete the last). |
| Move a slide to a new index (move, not swap). |
| Add a text box (x / y / w / h in inches, optional font). |
| Embed an image (x / y / w / h). |
| Add an MSO shape (type, x / y / w / h, optional text). |
| Add a table (rows / cols / data, x / y / w / h). |
| Add a chart (type, data dict, x / y / w / h). |
| Export to PDF via LibreOffice. |
| Export to HTML via LibreOffice. |
Usage examples
Each example assumes the server is registered with your MCP
agent (see Claude Desktop registration)
or that you are calling the tool functions directly from Python
(via mcp._tool_manager._tools["..."].fn).
General tools
A typical agent workflow starts with a folder scan: ask the
agent to "list the Office files in ~/Documents". The
list_documents tool returns one entry per file with its type
(word / excel / pptx), its on-disk size, and an ISO-8601
modified timestamp. The agent can then dispatch per-file
operations. get_document_info is the next step: it returns
format-specific metadata (paragraphs / sections for Word,
sheet count and dimensions for Excel, slide count and layouts
for PowerPoint) so the agent can plan a sequence of edits
without opening the file. search_text is the cross-format
text search — give it a substring and it returns every
paragraph, cell, or shape that contains the match, with a
small surrounding context window. convert_document is the
end-of-pipeline tool: pass the source path and
target_format ("pdf", "html", or "csv" for Excel only)
and an optional output path; the output lands next to the
source if you omit output.
Word tools
word_create_document is the typical entry point. Pass a
path (absolute or relative to folder / the default folder)
and an optional title; the tool refuses to overwrite an
existing file (ERR_INVALID_PARAMS) so it is safe to re-call
on a fresh scratch path. Once a document exists the agent can
chain word_add_heading, word_add_paragraph, and
word_add_table to build the body; word_format_run then
applies bold / italic / font / size / color to a specific run
in a specific paragraph (the contract is that an all-None
call is a true no-op — the file SHA256 is preserved).
word_find_replace is the bulk-edit hammer: pass find,
replace, and case_sensitive; the tool returns the total
replacement count and rewrites the file in place.
word_add_image accepts PNG / JPEG / GIF / TIFF and either
embeds the picture at its native size or scales it to
width_inches (aspect ratio is preserved automatically).
word_add_header / word_add_footer / word_set_section
configure the section-level metadata; word_set_section
takes "portrait" or "landscape" and a page size
("A4", "Letter", "Legal", "A5", "Tabloid", "B5").
Finally, word_export_pdf and word_export_html produce the
deliverable: the first uses LibreOffice, the second uses the
pure-Python mammoth library so it works even on a machine
without LibreOffice installed.
Excel tools
excel_create_workbook returns the absolute path of a new
.xlsx with a single default sheet ("Sheet1") — pass
sheet_name to use something more descriptive from the start.
excel_write_cell is the small-scale writer: it accepts
strings, numbers, and "=..." formulas; the cell reference
is parsed by the helper that also powers excel_write_range
so "A1", "B2", "AA10" all work, and malformed refs
raise ERR_CELL_PARSE (-32008). For bulk writes use
excel_write_range with start_cell and a 2-D data list
in row-major order. excel_create_sheet / excel_delete_sheet
/ excel_rename_sheet manage the workbook's sheet list;
excel_delete_sheet refuses to remove the only remaining
sheet. excel_format_cells applies font / fill / border /
number-format to a range (the range argument accepts the
same "A1:C3" syntax as excel_read_sheet).
excel_add_chart writes an openpyxl chart of type bar,
line, pie, area, or scatter anchored at target_cell
— a typical call looks like
excel_add_chart(path, sheet, "bar", "A1:B5", "D2", "Sales").
excel_export_csv is pure-Python (no LibreOffice needed) and
exports a single sheet; excel_export_pdf and
excel_export_html go through LibreOffice for fidelity with
what the user sees in Excel.
PowerPoint tools
pptx_create_presentation lays down a .pptx with one
title slide (configurable via title / subtitle). Add
slides with pptx_add_slide(layout_index=..., title=...);
the default layouts are 0 (Title Slide), 1 (Title and
Content), 5 (Title Only), 6 (Blank). pptx_delete_slide
and pptx_reorder_slides let the agent reshape the deck
(pptx_reorder_slides is a move, not a swap).
pptx_add_text_box and pptx_add_shape are the simple
shape writers — both take x / y / w / h in inches. For
data, pptx_add_table writes a rows × cols table at a
given position, and pptx_add_chart writes a chart with a
Python dict payload (categories + series). All
pptx_export_* tools delegate to LibreOffice.
Architecture overview
The full design is in architecture.md. The 30-second version:
+--------------------+ stdio JSON-RPC +--------------------+
| MCP agent | <-----------------------> | office-mcp |
| (Claude Desktop) | initialize / tools/list | (Python process) |
| or custom client | tools/call {name, args} | |
+--------------------+ | FastMCP("office- |
| mcp") + 47 tools |
+---------+----------+
|
subprocess (PDF / HTML / CSV)
|
+---------v----------+
| LibreOffice 7+ |
| (soffice) |
+--------------------+Module layout:
office-mcp/
├── server.py # FastMCP entry; mcp singleton; tool registration
└── office_mcp/
├── config.py # OFFICE_MCP_DEFAULT_FOLDER + find_libreoffice()
├── paths.py # resolve_path(file, folder) -> absolute Path
├── errors.py # OfficeMCPError + 9 error codes (-32001..-32009)
├── general_tools.py # list_documents, get_document_info, search_text, convert_document
├── word_tools.py # 14 word_* tools
├── excel_tools.py # 15 excel_* tools
├── pptx_tools.py # 14 pptx_* tools
└── exporters.py # export_to_pdf / export_to_html / export_to_csvCritical idioms (also enforced by the test suite):
Singleton via
from server import mcp. Every tool function is decorated with@mcp.tool()frommcp.server.fastmcp, imported asfrom server import mcp. This works becauseserver.pyregisters itself under both__main__andserverinsys.modulesviasys.modules.setdefault("server", sys.modules[__name__])at module top. Do not remove that line.Side-effect imports in
server.pyuseimportlib.import_module. This avoidspyflakesF401 false positives on the tool module side-effect imports.No
print()to stdout. All logging goes tosys.stderr(configured at the top ofserver.py).Office files are ZIPs — open in binary mode (
"rb"/"wb"). Text files (HTML, CSV, README) withencoding="utf-8".*_create_*tools refuse to overwrite. All other tools raiseERR_FILE_NOT_FOUND(-32001) if the target does not exist.Error model. Every tool raises
OfficeMCPError(code, message, details). FastMCP serialises the exception to aCallToolResultwithisError=Trueand astructuredContentdict carrying the error code. The JSON-RPC error code stays clean.
Configuration
Env var | Default | Effect |
| server CWD | Base folder for relative paths in tool calls. |
| auto-detected | Override the LibreOffice executable path. |
| unset | Set to |
| unset | Set to |
Set these in claude_desktop_config.json (see
Claude Desktop registration) or
in the shell before launching the server.
Troubleshooting
"LibreOffice (soffice) is not installed or not on PATH"
ERR_LIBREOFFICE_MISSING (-32006) is raised on any export that
needs LibreOffice — every *_export_pdf, every
excel_export_html, and every pptx_export_html call. Word's
word_export_html does not need LibreOffice (it uses
mammoth).
Install LibreOffice. On Windows, use the official MSI (
soffice.exelands inC:\Program Files\LibreOffice\program). On macOS, drag the.dmgto/Applications. On Debian / Ubuntu:sudo apt install libreoffice.Check the executable.
init.shprintssoffice: LibreOffice ...when the binary is found. If it printsWARNING: soffice not on PATH, either re-runinit.shor setOFFICE_MCP_SOFFICE=/full/path/to/soffice(.exe)in the agent config.Watch out for the Windows
.COMshim.shutil.whichon Windows can returnsoffice.COM(a 16-bit DOS wrapper) when the LibreOfficeprogramdirectory is onPATHbut not in the auto-detect candidate list.find_libreoffice()inoffice_mcp/config.pyexplicitly prefers the.execandidates and rejects.COMfiles. If you see the server hang for the full 180s timeout before raisingERR_EXPORT_FAILED, you are probably hitting this — setOFFICE_MCP_SOFFICEto the absolutesoffice.exepath.Concurrent calls. Each
*_export_*call uses a unique-env:UserInstallation=file:///<tempdir>so two simultaneous exports do not fight over the default profile. You can runword_export_pdfin parallel from two different agent sessions without locking.
Encoding issues (mojibake, UnicodeDecodeError)
Always launch the server with
PYTHONUTF8=1andPYTHONIOENCODING=utf-8in the env.init.shdoes this for the local shell; the Claude Desktop config snippet above sets them in theenvblock.Office files are ZIPs — the tools always open them in binary mode, so non-ASCII content is preserved end-to-end.
If you see mojibake only in tool output (not on disk), the most likely cause is the agent or its console not running in UTF-8. Set
PYTHONIOENCODING=utf-8in the server'senv.If you see mojibake on disk, the source file was probably written by an older tool that did not declare UTF-8. The server cannot retroactively fix it.
"File is locked" / "Permission denied" on save
ERR_FILE_LOCKED(-32002) means the file is open in Word / Excel / PowerPoint (or another process holds an exclusive handle). Close the file in the Office app and re-call the tool. Office writes lock files (.~lock.<name>#) next to the document; if the previous run crashed you can delete the lock file by hand.If you are running the server in a sandboxed agent, make sure the agent's filesystem permissions include write access to the file and its containing directory.
Antivirus software occasionally holds a write lock for a few seconds after Word closes. If the failure is intermittent, wait a second and retry.
Server starts but the agent sees 0 tools
This means the sys.modules.setdefault("server", sys.modules[__name__]) line in server.py is missing, or
server.py was launched in a way that bypasses the FastMCP
singleton. Do not edit that line. Verify it is present and
re-launch. The same fix applies if you ever see the second
FastMCP instance warning in the logs.
Server hangs without producing output
Confirm the agent is talking JSON-RPC and not raw bytes — the server owns stdin/stdout and will not echo any prompt.
Confirm
PYTHONIOENCODING=utf-8is set. Without it, the interpreter may try to re-encode the JSON-RPC stream and fail silently.Check the server's
stderr(the agent usually surfaces this). A clean start produces a single INFO line for the session negotiation and nothing else.
Excel "file is corrupt" / openpyxl warnings
openpyxlwrites a warning when a workbook contains features it does not preserve (some pivot tables, some VBA macros). The server still saves the file but the warning is instderr. Reopening the file in Excel succeeds because Excel ignores the missing optional features; the warning is informational only.
Development commands
All commands assume the project root as the current directory
and the venv on PATH (or the explicit .venv/Scripts/python.exe
prefix shown). The single source of truth is services.yaml.
# Run the full pytest suite (≈100 tests, sequential, no -n).
.venv/Scripts/python.exe -m pytest -q
# Run tests for a single module.
.venv/Scripts/python.exe -m pytest tests/test_word_core.py -q
.venv/Scripts/python.exe -m pytest tests/test_excel_advanced.py -q
# Run the cross-format integration test (spawns the server
# over stdio and exercises the agent round-trip).
.venv/Scripts/python.exe -m pytest tests/test_integration.py -v
# Smoke check: import the singleton and report the tool count.
.venv/Scripts/python.exe -c "from server import mcp; print(mcp.name, len(mcp._tool_manager._tools))"
# Type check (syntax + import resolution for every .py file).
.venv/Scripts/python.exe -m py_compile server.py office_mcp/*.py
# Lint with pyflakes (suppress the import side-effect noise).
.venv/Scripts/python.exe -m pyflakes office_mcp server.py
# Manually launch the server from a clean shell (talks
# JSON-RPC on stdio; press Ctrl+C to exit).
python server.py
# Manually launch via the MCP Inspector for an interactive UI.
.venv/Scripts/python.exe -m mcp.cli inspector server.pyThe services.yaml file at the project root pins the exact
command strings (Windows PowerShell) used by the per-milestone
scrutiny validator; if you add a new test entry point, add it
there too.
License
MIT. See pyproject.toml for the canonical metadata.
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/gawirable/office-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server