office-mcp
Click on "Deploy 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-mcp Open 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-server
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.
Available Tools
47 toolsconvert_documentA
Convert a .docx/.xlsx/.pptx file to pdf/html/csv.
Dispatches to the format-specific exporter based on the source
file's extension and the requested target_format. When
output is omitted, the output is written next to the source
with the new extension (VAL-GEN-024).
Args:
path: Path to an existing Office file.
target_format: One of "pdf", "html", "csv". The
match is case-insensitive and a leading dot is tolerated.
output: Optional target path for the converted file. When
None (default), a sibling of the input is used.
Relative paths are resolved against folder (or the
default folder).
folder: Optional base folder for relative paths.
Returns:
{"output_path": "<absolute path>", "format": <target>}
where format is the lower-cased, dot-stripped target
format (e.g. "pdf").
Raises:
OfficeMCPError: ERR_INVALID_PARAMS for unknown
target_format, ERR_FILE_NOT_FOUND if the source
is missing, ERR_UNSUPPORTED_FMT for an unsupported
(source, target) combination (e.g. .docx → .csv,
or any non-Office source), or any error propagated from
:mod:office_mcp.exporters (ERR_LIBREOFFICE_MISSING,
ERR_EXPORT_FAILED).
Inputs: path (required), target_format (required), output, folder. Returns: object
excel_add_chartA
Add a chart to a sheet, anchored at target_cell.
Supported chart_type values: "bar", "line",
"pie", "area", "scatter". Any other value raises
:class:OfficeMCPError with ERR_INVALID_PARAMS
(VAL-EXCEL-056) and the workbook is not modified.
The chart's data is read from data_range (e.g. "A1:B5");
the first column is treated as categories and subsequent columns
as series. The chart is anchored at target_cell (e.g.
"D2") so its top-left corner coincides with that cell
(VAL-EXCEL-052).
Args:
path: Path to an existing .xlsx.
sheet: Name of the sheet that will own the chart.
chart_type: One of "bar", "line", "pie",
"area", "scatter".
data_range: A range reference like "A1:B5".
target_cell: A single cell reference like "D2". The
chart's top-left anchor is set to this cell.
title: Optional chart title. When supplied, it is set via
openpyxl's title API and survives a reload.
folder: Optional base folder for relative paths.
Returns:
{"ok": True}.
Raises:
OfficeMCPError: ERR_FILE_NOT_FOUND if the file is
missing, ERR_SHEET_NOT_FOUND for an unknown sheet,
ERR_INVALID_PARAMS for an unknown chart_type or
malformed data_range / target_cell,
ERR_CELL_PARSE for a malformed range,
ERR_UNSUPPORTED_FMT for non-.xlsx extensions.
Inputs: path (required), sheet (required), chart_type (required), data_range (required), +3 more. Returns: object
excel_create_sheetA
Append a new sheet to the workbook.
Args:
path: Path to an existing .xlsx.
name: Name of the new sheet. Must be non-empty and free of
Excel-forbidden characters (: / \ ? * [ ]).
folder: Optional base folder for relative paths.
Returns:
{"index": <n>} where <n> is the position of the new
sheet in wb.sheetnames (0-based).
Raises:
OfficeMCPError: ERR_FILE_NOT_FOUND if the file is
missing, ERR_INVALID_PARAMS for a duplicate or
malformed name, ERR_UNSUPPORTED_FMT for
non-.xlsx extensions.
Inputs: path (required), name (required), folder. Returns: object
excel_create_workbookA
Create a new .xlsx file at path.
Args:
path: Target .xlsx path. May be absolute or relative to
folder (or to the default folder when folder is
None).
sheet_name: Optional name for the initial sheet. When
None (the default), the openpyxl default "Sheet1"
is used.
folder: Optional base folder for relative paths.
Returns:
{"path": "<absolute path>"}.
Raises:
OfficeMCPError: ERR_INVALID_PARAMS if path is empty
or a file already exists at the target (or the requested
sheet name is malformed). ERR_UNSUPPORTED_FMT if
path does not end in .xlsx.
Inputs: path (required), sheet_name, folder. Returns: object
excel_delete_sheetA
Remove a sheet from the workbook.
Refuses to delete the last remaining sheet
(VAL-EXCEL-036 / VAL-EXCEL-037): the workbook must
always have at least one sheet.
Args:
path: Path to an existing .xlsx.
name: Name of the sheet to delete.
folder: Optional base folder for relative paths.
Returns:
{"ok": True}.
Raises:
OfficeMCPError: ERR_FILE_NOT_FOUND if the file is
missing, ERR_SHEET_NOT_FOUND for an unknown sheet,
ERR_INVALID_PARAMS when the target is the only
remaining sheet, ERR_UNSUPPORTED_FMT for non-.xlsx
extensions.
Inputs: path (required), name (required), folder. Returns: object
excel_export_csvA
Export a sheet to CSV via :mod:openpyxl and the stdlib :mod:csv.
Delegates to :func:office_mcp.exporters.export_to_csv. The
output is written as UTF-8 (without a BOM) and uses the
standard Excel-friendly CSV dialect.
Args:
path: Path to an existing .xlsx file.
output: Target path for the produced .csv. The parent
directory is created if it does not exist. If a relative
path is given, it is resolved against folder (or the
default folder).
sheet: Optional sheet name. When None (the default), the
workbook's first sheet is exported.
folder: Optional base folder for relative paths.
Returns:
{"output_path": "<absolute path of the produced CSV>"}.
Raises:
OfficeMCPError: ERR_FILE_NOT_FOUND if the source is
missing, ERR_UNSUPPORTED_FMT for non-.xlsx
sources, ERR_SHEET_NOT_FOUND for an unknown sheet.
Inputs: path (required), output (required), sheet, folder. Returns: object
excel_export_htmlA
Convert a .xlsx file to HTML via LibreOffice headless.
Delegates to :func:office_mcp.exporters.export_to_html. Unlike
the Word variant this path always goes through LibreOffice
(openpyxl does not produce standalone HTML).
Args:
path: Path to an existing .xlsx file.
output: Target path for the produced HTML. The parent
directory is created if it does not exist. If a relative
path is given, it is resolved against folder (or the
default folder).
folder: Optional base folder for relative paths.
Returns:
{"output_path": "<absolute path of the produced HTML>"}.
Raises:
OfficeMCPError: ERR_FILE_NOT_FOUND if the source is
missing, ERR_UNSUPPORTED_FMT for non-.xlsx
sources, ERR_LIBREOFFICE_MISSING when soffice is
not on PATH, ERR_EXPORT_FAILED for any other failure.
Inputs: path (required), output (required), folder. Returns: object
excel_export_pdfA
Convert a .xlsx file to PDF via LibreOffice headless.
Delegates to :func:office_mcp.exporters.export_to_pdf. A
unique -env:UserInstallation is used per call so multiple
exports can run concurrently.
Args:
path: Path to an existing .xlsx file.
output: Target path for the produced PDF. The parent
directory is created if it does not exist. If a relative
path is given, it is resolved against folder (or the
default folder).
folder: Optional base folder for relative paths.
Returns:
{"output_path": "<absolute path of the produced PDF>"}.
Raises:
OfficeMCPError: ERR_FILE_NOT_FOUND if the source is
missing, ERR_UNSUPPORTED_FMT for non-.xlsx
sources, ERR_LIBREOFFICE_MISSING when soffice is
not on PATH, ERR_EXPORT_FAILED for any other failure.
Inputs: path (required), output (required), folder. Returns: object
excel_format_cellsA
Apply font, fill, border, alignment, and number-format to a range.
Each formatting argument is None by default and is treated as
"do not change". When every formatting argument is None,
the call is a true no-op: the file is not re-saved, so its
SHA256 and mtime are preserved (VAL-EXCEL-049).
Args:
path: Path to an existing .xlsx.
sheet: Name of the sheet to format.
cell_range: A cell reference ("A1") or a range
("A1:C3"). Column letters may be upper or lower case.
bold: True / False to set font.bold, or None
to leave it alone.
italic: Same semantics as bold for font.italic.
font_name: A font family name (e.g. "Arial").
font_size: Point size (e.g. 14).
font_color: Hex RGB string ("FF0000").
fill_color: Hex RGB string for the cell background.
border: Either a single style name applied to all four sides
("thin", "medium", "thick", "dashed",
"dotted", "double", "hair") or a dict mapping
"left" / "right" / "top" / "bottom" to
a style name.
alignment: "left" / "center" / "right" /
"justify" / "general" for Alignment.horizontal.
number_format: An Excel number format code (e.g. "0.00%",
"#,##0.00", "yyyy-mm-dd").
folder: Optional base folder for relative paths.
Returns:
{"ok": True}.
Raises:
OfficeMCPError: ERR_FILE_NOT_FOUND if the file is
missing, ERR_SHEET_NOT_FOUND for an unknown sheet,
ERR_CELL_PARSE for a malformed range,
ERR_INVALID_PARAMS for an unknown border style or
alignment, ERR_UNSUPPORTED_FMT for non-.xlsx
extensions.
Inputs: path (required), sheet (required), cell_range (required), bold, +9 more. Returns: object
excel_get_infoA
Return a summary of the workbook's sheets and sizes.
The returned dict contains:
path— absolute path of the file on disk.sheets— list of one dict per sheet, in insertion order. Each entry hasname(str),index(int, 0-based),rows(int, openpyxl'smax_row), andcols(int, openpyxl'smax_column).
Args:
path: Path to an existing .xlsx.
folder: Optional base folder for relative paths.
Raises:
OfficeMCPError: ERR_FILE_NOT_FOUND if the file is
missing, ERR_UNSUPPORTED_FMT for non-.xlsx
extensions.
Inputs: path (required), folder. Returns: object
excel_list_sheetsA
Return one dict per sheet in the workbook (insertion order).
Each entry is identical to those in :func:excel_get_info['sheets'].
Args:
path: Path to an existing .xlsx.
folder: Optional base folder for relative paths.
Raises:
OfficeMCPError: ERR_FILE_NOT_FOUND if the file is
missing, ERR_UNSUPPORTED_FMT for non-.xlsx
extensions.
Inputs: path (required), folder. Returns: result
excel_read_sheetA
Read cell values from a sheet, optionally limited to a range.
Args:
path: Path to an existing .xlsx.
sheet: Name of the sheet to read.
range: Optional cell or range reference. Accepts:
* ``None`` — read the entire used range
(``ws.max_row`` × ``ws.max_column``).
* ``"A1"`` — read a single cell; the result is a 1x1 list
``[[value]]``.
* ``"A1:C3"`` — read a rectangular range. Reversed ranges
(``"C3:A1"``) are rejected.
Column letters may be lower-case or upper-case.
folder: Optional base folder for relative paths.Returns:
A 2D list of values. Empty cells (None) are normalised
to the empty string "" so the result is JSON-friendly.
Raises:
OfficeMCPError: ERR_FILE_NOT_FOUND if the file is
missing, ERR_SHEET_NOT_FOUND if sheet is not in
the workbook, ERR_CELL_PARSE for malformed
range, ERR_UNSUPPORTED_FMT for non-.xlsx
extensions.
Inputs: path (required), sheet (required), range, folder. Returns: result
excel_rename_sheetA
Rename a sheet in the workbook.
Cell contents are preserved across the rename
(VAL-EXCEL-042). Renaming a sheet to its own name is a
no-op (the file is still saved, but the sheet names are
unchanged — VAL-EXCEL-041).
Args:
path: Path to an existing .xlsx.
old_name: Current name of the sheet to rename.
new_name: Desired new name. Must be non-empty and free of
Excel-forbidden characters.
folder: Optional base folder for relative paths.
Returns:
{"ok": True}.
Raises:
OfficeMCPError: ERR_FILE_NOT_FOUND if the file is
missing, ERR_SHEET_NOT_FOUND for an unknown
old_name, ERR_INVALID_PARAMS when new_name
is empty, contains forbidden characters, or already
exists as another sheet, ERR_UNSUPPORTED_FMT for
non-.xlsx extensions.
Inputs: path (required), old_name (required), new_name (required), folder. Returns: object
excel_write_cellA
Write a single value to one cell.
Strings starting with = are persisted as formulas
(VAL-EXCEL-022). Other types (int, float, bool,
None) are written verbatim via :meth:openpyxl.cell.Cell.value.
Args:
path: Path to an existing .xlsx.
sheet: Name of the sheet.
cell: A single cell reference like "A1".
value: Value to write.
folder: Optional base folder for relative paths.
Returns:
{"ok": True}.
Raises:
OfficeMCPError: ERR_FILE_NOT_FOUND if the file is
missing, ERR_SHEET_NOT_FOUND for an unknown sheet,
ERR_CELL_PARSE for a malformed cell,
ERR_UNSUPPORTED_FMT for non-.xlsx extensions.
Inputs: path (required), sheet (required), cell (required), value (required), +1 more. Returns: object
excel_write_rangeA
Write a 2D list of values starting at start_cell.
The data is interpreted in row-major order: data[0][0] goes
to start_cell, data[0][1] to the cell one column to the
right, data[1][0] to the cell one row below, and so on.
Writing extends the sheet's used range as needed
(VAL-EXCEL-027).
Args:
path: Path to an existing .xlsx.
sheet: Name of the sheet.
start_cell: Top-left cell of the write region (e.g. "A1").
data: 2D list of values. The empty list [] is accepted
and is a no-op (the file is still saved; the workbook
remains valid).
folder: Optional base folder for relative paths.
Returns:
{"ok": True}.
Raises:
OfficeMCPError: ERR_FILE_NOT_FOUND if the file is
missing, ERR_SHEET_NOT_FOUND for an unknown sheet,
ERR_CELL_PARSE for a malformed start_cell,
ERR_INVALID_PARAMS if data is not a list,
ERR_UNSUPPORTED_FMT for non-.xlsx extensions.
Inputs: path (required), sheet (required), start_cell (required), data (required), +1 more. Returns: object
get_document_infoA
Return metadata for a single Office file.
Detects the format by extension and dispatches to the
format-specific *_get_info tool. The returned dict is a
superset of the underlying tool's dict, augmented with type
("word" / "excel" / "pptx") and size_bytes.
Args:
path: Path to an existing Office file (abs or relative to
folder/default folder).
folder: Optional base folder for relative paths.
Returns: A dict with:
* ``type`` — one of ``"word"``, ``"excel"``, ``"pptx"``.
* ``size_bytes`` — file size in bytes.
* Format-specific fields: for ``.docx`` →
``paragraphs``, ``sections``, ``tables``, ``images``,
``properties``; for ``.xlsx`` → ``sheets`` (list),
``sheet_count``, ``sheet_names``; for ``.pptx`` →
``slide_count``, ``layouts``, ``dimensions_inches`` (with
``width_inches`` / ``height_inches``).Raises:
OfficeMCPError: ERR_FILE_NOT_FOUND if the file is missing
(VAL-GEN-010), ERR_UNSUPPORTED_FMT if the
extension is not one of .docx / .xlsx / .pptx.
Inputs: path (required), folder. Returns: object
list_documentsA
List all Office files in a folder (non-recursive).
Scans the immediate children of folder and returns one entry
per file with extension .docx/.xlsx/.pptx. Non-Office
files (and subdirectories) are filtered out. The scan does not
descend into subdirectories.
Args:
folder: Folder to scan. When None (default), the default
folder from :func:office_mcp.config.get_default_folder
is used. Relative paths are joined with the default folder.
Returns: A list of dicts, each with:
* ``path`` — absolute, canonical path to the file.
* ``name`` — file basename (e.g. ``"report.docx"``).
* ``type`` — one of ``"word"``, ``"excel"``, ``"pptx"``.
* ``size_bytes`` — file size on disk (positive int).
* ``modified`` — ISO-8601 timestamp of last modification
(``datetime.fromisoformat``-parseable, with timezone).
An empty folder returns ``[]`` (``VAL-GEN-002``).Raises:
OfficeMCPError: ERR_INVALID_PARAMS if folder exists
but is not a directory.
Inputs: folder. Returns: result
pptx_add_chartA
Add a chart to a slide.
The data argument must follow the structure documented in
:func:_build_chart_data: a dict with "categories" (list of
x-axis labels) and "series" (list of {"name", "values"}
dicts).
Args:
path: Path to an existing .pptx.
chart_type: One of column, bar, line, pie,
etc. — see :data:_CHART_TYPE_MAP. (VAL-PPTX-051,
VAL-PPTX-052.)
data: Chart data structure with categories and series
keys (VAL-PPTX-051).
x: Left edge in inches.
y: Top edge in inches.
w: Width in inches.
h: Height in inches.
slide: 0-based slide index (VAL-PPTX-055).
folder: Optional base folder for relative paths.
Returns:
{"shape_index": <int>} — 0-based index of the new chart
within the slide's shape list.
Raises:
OfficeMCPError: ERR_INVALID_PARAMS for unknown
chart_type (VAL-PPTX-053), empty series
(VAL-PPTX-054), out-of-range slide
(VAL-PPTX-055), or non-numeric geometry;
ERR_FILE_NOT_FOUND if the file is missing;
ERR_UNSUPPORTED_FMT for non-.pptx extensions.
Inputs: path (required), chart_type (required), data (required), x (required), +5 more. Returns: object
pptx_add_imageA
Add an image to a slide at the given inch coordinates.
The w / h arguments control the rendered size:
Both given — the picture is stretched to exactly those dimensions (
VAL-PPTX-036).Only
wgiven —his computed from the image's intrinsic aspect ratio (VAL-PPTX-038). Aspect ratio preserved within 1% tolerance.Only
hgiven —wis computed from the image's intrinsic aspect ratio (VAL-PPTX-039).Neither given — the picture is rendered at its natural pixel size at 96 DPI (
VAL-PPTX-037).
Args:
path: Path to an existing .pptx.
image_path: Path to the image to embed (any format python-pptx
can read: PNG, JPEG, GIF, BMP, TIFF).
x: Left edge in inches.
y: Top edge in inches.
w: Optional width in inches. None = compute from aspect
ratio (when h is given) or use natural size.
h: Optional height in inches. None = compute from aspect
ratio (when w is given) or use natural size.
slide: 0-based slide index.
folder: Optional base folder for relative paths. Used for
both the deck and the image path.
Returns:
{"shape_index": <int>} — 0-based index of the new picture
within the slide's shape list.
Raises:
OfficeMCPError: ERR_FILE_NOT_FOUND if either the deck
or the image file is missing (VAL-PPTX-040);
ERR_INVALID_PARAMS for out-of-range slide
(VAL-PPTX-041) or non-numeric geometry; ERR_UNSUPPORTED_FMT
for non-.pptx extensions.
Inputs: path (required), image_path (required), x (required), y (required), +4 more. Returns: object
pptx_add_shapeA
Add a shape (rectangle, oval, etc.) to a slide.
shape_type is one of the case-insensitive names mapped by
:data:_SHAPE_TYPE_MAP — the supported set covers the most
common MSO_SHAPE members (VAL-PPTX-042 for rectangle,
VAL-PPTX-043 for oval and rounded rectangle). An unknown
type returns ERR_INVALID_PARAMS and the deck is left
unchanged (VAL-PPTX-044).
Args:
path: Path to an existing .pptx.
shape_type: Shape type (e.g. "rectangle", "oval",
"rounded_rectangle"). See :data:_SHAPE_TYPE_MAP.
x: Left edge in inches.
y: Top edge in inches.
w: Width in inches.
h: Height in inches.
slide: 0-based slide index (VAL-PPTX-045).
folder: Optional base folder for relative paths.
text: Optional text to put inside the shape's text frame.
Returns:
{"shape_index": <int>} — 0-based index of the new shape
within the slide's shape list.
Raises:
OfficeMCPError: ERR_INVALID_PARAMS for an unknown shape
type, out-of-range slide, or non-numeric geometry;
ERR_FILE_NOT_FOUND if the file is missing;
ERR_UNSUPPORTED_FMT for non-.pptx extensions.
Inputs: path (required), shape_type (required), x (required), y (required), +5 more. Returns: object
pptx_add_slideA
Append a new slide to the deck.
The slide is always appended to the end
(VAL-PPTX-018 — the returned index equals the previous
slide count). When title is given, the slide's title
placeholder is populated (VAL-PPTX-021).
Args:
path: Path to an existing .pptx.
layout_index: 0-based index into prs.slide_layouts. The
default 1 is the "Title and Content" layout in
python-pptx's default template.
title: Optional title text. When None the title
placeholder is left empty.
folder: Optional base folder for relative paths.
Returns:
{"index": <int>} — the 0-based index of the new slide.
Raises:
OfficeMCPError: ERR_INVALID_PARAMS for a non-int or
out-of-range layout_index (the deck is not
modified in this case — VAL-PPTX-020),
ERR_FILE_NOT_FOUND if the file is missing,
ERR_UNSUPPORTED_FMT for non-.pptx extensions.
Inputs: path (required), layout_index, title, folder. Returns: object
pptx_add_tableA
Add a table to a slide.
When data is None the table is created with every cell
empty (VAL-PPTX-047). When data is shorter than
rows x cols the missing cells are padded with empty strings
(VAL-PPTX-048). All cell values are coerced to str before
being written.
Args:
path: Path to an existing .pptx.
rows: Number of rows (must be > 0; VAL-PPTX-050).
cols: Number of columns (must be > 0).
x: Left edge in inches.
y: Top edge in inches.
w: Width in inches.
h: Height in inches.
slide: 0-based slide index (VAL-PPTX-049).
folder: Optional base folder for relative paths.
data: Optional 2D list of cell values, row-major.
Returns:
{"shape_index": <int>} — 0-based index of the new table
within the slide's shape list.
Raises:
OfficeMCPError: ERR_INVALID_PARAMS for non-positive
rows / cols (VAL-PPTX-050), out-of-range
slide (VAL-PPTX-049), or non-numeric geometry;
ERR_FILE_NOT_FOUND if the file is missing;
ERR_UNSUPPORTED_FMT for non-.pptx extensions.
Inputs: path (required), rows (required), cols (required), x (required), +6 more. Returns: object
pptx_add_text_boxA
Add a text box with optional font properties to a slide.
The text box is appended to the slide's shape list and its
0-based index within that slide is returned (VAL-PPTX-031).
When font_size / bold / italic / font_name /
color are given, they are applied to the text box's first
run (VAL-PPTX-032); when any of them is None the
corresponding attribute is left at the layout / default value.
Args:
path: Path to an existing .pptx.
text: Text to put inside the text box.
x: Left edge in inches (e.g. 1.0).
y: Top edge in inches (e.g. 2.0).
w: Width in inches (e.g. 3.0).
h: Height in inches (e.g. 0.5).
slide: 0-based slide index (VAL-PPTX-034).
folder: Optional base folder for relative paths.
font_size: Optional font size in points (VAL-PPTX-032).
bold: Optional bold flag (VAL-PPTX-032).
italic: Optional italic flag.
font_name: Optional font name (e.g. "Arial").
color: Optional hex color string ("FF0000").
Returns:
{"shape_index": <int>} — 0-based index of the new
text box within the slide's shape list.
Raises:
OfficeMCPError: ERR_INVALID_PARAMS for non-numeric or
out-of-range geometry, out-of-range slide, or invalid
color; ERR_FILE_NOT_FOUND if the file is missing;
ERR_UNSUPPORTED_FMT for non-.pptx extensions.
Inputs: path (required), text (required), x (required), y (required), +9 more. Returns: object
pptx_create_presentationA
Create a new .pptx file at path, optionally with a title slide.
Args:
path: Target .pptx path. May be absolute or relative to
folder (or to the default folder when folder is
None).
title: Optional text for the first slide's title placeholder.
When None (the default), the file is created with no
slides at all (VAL-PPTX-002). When a non-empty
title is given, a single slide is appended using the
"Title Slide" layout (index 0) and the title is
written into its title placeholder (VAL-PPTX-001).
folder: Optional base folder for relative paths.
Returns:
{"path": "<absolute path>"}.
Raises:
OfficeMCPError: ERR_INVALID_PARAMS if path is empty
or a file already exists at the target,
ERR_UNSUPPORTED_FMT if path does not end in
.pptx.
Inputs: path (required), title, folder. Returns: object
pptx_delete_slideA
Remove a slide from the deck.
Refuses to delete the last remaining slide (VAL-PPTX-024):
the deck must always have at least one slide. After deletion
the remaining slides keep their relative order, but indices
past index shift down by one (VAL-PPTX-023 / -026).
Args:
path: Path to an existing .pptx.
index: 0-based slide index.
folder: Optional base folder for relative paths.
Returns:
{"ok": True}.
Raises:
OfficeMCPError: ERR_INVALID_PARAMS for a non-int or
out-of-range index, or when the deck has only one
slide; ERR_FILE_NOT_FOUND if the file is missing;
ERR_UNSUPPORTED_FMT for non-.pptx extensions.
Inputs: path (required), index (required), folder. Returns: object
pptx_export_htmlA
Convert a .pptx file to HTML via LibreOffice headless.
Delegates to :func:office_mcp.exporters.export_to_html. Unlike
.docx (which uses mammoth), .pptx always requires
LibreOffice (VAL-PPTX-062).
Args:
path: Path to an existing .pptx.
output: Target path for the produced HTML. The parent
directory is created if it does not exist.
folder: Optional base folder for relative paths.
Returns:
{"output_path": "<absolute path of the produced HTML>"}.
Raises:
OfficeMCPError: ERR_FILE_NOT_FOUND if the source is
missing (VAL-PPTX-063); ERR_UNSUPPORTED_FMT for
non-.pptx sources (VAL-PPTX-064);
ERR_LIBREOFFICE_MISSING when soffice is not on
PATH (VAL-PPTX-062); ERR_EXPORT_FAILED for any
other failure.
Inputs: path (required), output (required), folder. Returns: object
pptx_export_pdfA
Convert a .pptx file to PDF via LibreOffice headless.
Delegates to :func:office_mcp.exporters.export_to_pdf. A unique
-env:UserInstallation is used per call so multiple exports
can run concurrently (VAL-WORD-079 pattern).
Args:
path: Path to an existing .pptx.
output: Target path for the produced PDF. The parent
directory is created if it does not exist (VAL-PPTX-057).
folder: Optional base folder for relative paths.
Returns:
{"output_path": "<absolute path of the produced PDF>"}.
Raises:
OfficeMCPError: ERR_FILE_NOT_FOUND if the source is
missing (VAL-PPTX-059); ERR_UNSUPPORTED_FMT for
non-.pptx sources (VAL-PPTX-060);
ERR_LIBREOFFICE_MISSING when soffice is not on
PATH (VAL-PPTX-058); ERR_EXPORT_FAILED for any
other failure.
Inputs: path (required), output (required), folder. Returns: object
pptx_get_infoA
Return a summary of the presentation's structure and dimensions.
The returned dict contains:
path— absolute path of the file on disk.slide_count— number of slides (0 is valid;VAL-PPTX-009).layouts— list of layout names available in the deck's slide master (always non-empty;VAL-PPTX-006/-009).dimensions—{"width_inches": <float>, "height_inches": <float>}. The default widescreen template is 13.333 × 7.5 inches; the defaultPresentation()template is 10.0 × 7.5 inches.
Args:
path: Path to an existing .pptx.
folder: Optional base folder for relative paths.
Raises:
OfficeMCPError: ERR_FILE_NOT_FOUND if the file is
missing, ERR_UNSUPPORTED_FMT for non-.pptx
extensions.
Inputs: path (required), folder. Returns: object
pptx_list_slidesA
Return one dict per slide in insertion order.
Each entry has the following keys (VAL-PPTX-010):
index— 0-based slide index.layout— name of the slide's layout (e.g."Title Slide","Title and Content").title— text of the slide's title placeholder, or""when the layout has no title placeholder.shape_count— number of shapes on the slide (placeholderspictures + text boxes + ...).
An empty deck returns [] (VAL-PPTX-011).
Args:
path: Path to an existing .pptx.
folder: Optional base folder for relative paths.
Raises:
OfficeMCPError: ERR_FILE_NOT_FOUND if the file is
missing, ERR_UNSUPPORTED_FMT for non-.pptx
extensions.
Inputs: path (required), folder. Returns: result
pptx_read_slideA
Return the slide's title, layout, and shape list.
Args:
path: Path to an existing .pptx.
index: 0-based slide index.
folder: Optional base folder for relative paths.
Returns:
{"index": <int>, "layout": <str>, "title": <str>, "shapes": [<shape_dict>, ...]}.
Each ``shape_dict`` has ``type`` (shape class name),
``shape_type`` (the numeric ``MSO_SHAPE_TYPE`` or ``None``),
``name`` (auto-generated name), and ``text`` (text-frame
text, or ``""`` for shape types without text).Raises:
OfficeMCPError: ERR_INVALID_PARAMS for a non-int or
out-of-range index, ERR_FILE_NOT_FOUND if the
file is missing, ERR_UNSUPPORTED_FMT for non-.pptx
extensions.
Inputs: path (required), index (required), folder. Returns: object
pptx_reorder_slidesA
Move a slide from one position to another.
This is a true move: the slide at from_index is removed
first, then inserted at to_index in the resulting sequence
(VAL-PPTX-027). So moving slide 0 to position 2 in a 3-slide
deck [A, B, C] yields [B, C, A], not the swap result
[C, B, A].
Reordering a slide to its own index (from_index == to_index)
is a no-op that still returns {"ok": True}
(VAL-PPTX-028).
Args:
path: Path to an existing .pptx.
from_index: 0-based index of the slide to move.
to_index: 0-based destination index. Must satisfy
0 <= to_index < slide_count (after the slide is
removed from from_index).
folder: Optional base folder for relative paths.
Returns:
{"ok": True}.
Raises:
OfficeMCPError: ERR_INVALID_PARAMS for a non-int or
out-of-range from_index / to_index, in which
case the deck is left unchanged (VAL-PPTX-029);
ERR_FILE_NOT_FOUND if the file is missing;
ERR_UNSUPPORTED_FMT for non-.pptx extensions.
Inputs: path (required), from_index (required), to_index (required), folder. Returns: object
search_textA
Search for a substring across the body of an Office file.
The location format depends on the source file's extension:
.docx→"paragraph:N"(0-based body paragraph index).xlsx→"cell:<coord>"(e.g."cell:B3").pptx→"slide:N:shape:M"(0-based slide + 0-based shape index inslide.shapes)
Each match entry has a location and a context field. The
context is a window of up to ~30 characters on each side of
the match in the original text (case preserved).
Args:
path: Path to the file to search.
query: Substring to look for. Must be a non-empty string.
case_sensitive: When True (default), the match is exact;
when False, matches are case-insensitive.
folder: Optional base folder for relative paths.
Returns:
A list of match dicts. Empty when there are no matches
(VAL-GEN-016).
Raises:
OfficeMCPError: ERR_INVALID_PARAMS if query is not a
string or is empty, ERR_FILE_NOT_FOUND if the file is
missing, ERR_UNSUPPORTED_FMT for non-Office files.
Inputs: path (required), query (required), case_sensitive, folder. Returns: result
word_add_headerA
Set the text of a section's header.
By default, only the first section's header is updated; later
sections keep their own headers (VAL-WORD-058). Pass an
explicit section_index to target a different section.
Args:
path: Path to an existing .docx.
text: New header text. An empty string clears the first
paragraph (VAL-WORD-057).
section_index: Zero-based section index (default 0).
folder: Optional base folder for relative paths.
Returns:
{"ok": True}.
Raises:
OfficeMCPError: ERR_INVALID_PARAMS for a non-int or
out-of-range section_index or a non-string text,
ERR_FILE_NOT_FOUND if the file is missing,
ERR_UNSUPPORTED_FMT for non-.docx extensions.
Inputs: path (required), text (required), section_index, folder. Returns: object
word_add_headingA
Append a heading paragraph at the given level (1..9).
Args:
path: Path to an existing .docx.
text: Heading text.
level: Heading level (1 = Heading 1, ..., 9 = Heading 9).
Out-of-range values are rejected with
ERR_INVALID_PARAMS.
folder: Optional base folder for relative paths.
Returns:
{"index": <n>} for the new heading.
Raises:
OfficeMCPError: ERR_INVALID_PARAMS for an out-of-range
level or a non-int level, ERR_FILE_NOT_FOUND
if the file is missing, ERR_UNSUPPORTED_FMT for non-
.docx extensions.
Inputs: path (required), text (required), level, folder. Returns: object
word_add_imageA
Embed an image in the document body.
When width_inches is provided, the image is scaled to that
width and the height is auto-computed from the image's intrinsic
aspect ratio (VAL-WORD-050 / VAL-WORD-078). When omitted,
the image is embedded at its native pixel size.
Args:
path: Path to an existing .docx.
image_path: Path to the image file (PNG / JPEG / GIF / etc.).
width_inches: Optional target width in inches (must be
positive). When None the image is embedded at its
native size.
folder: Optional base folder for the path argument
(does not apply to image_path, which is always
resolved absolutely or relative to the caller's CWD).
Returns:
{"index": <n>} where <n> is the index of the new
inline shape in doc.inline_shapes.
Raises:
OfficeMCPError: ERR_FILE_NOT_FOUND if either path is
missing, ERR_INVALID_PARAMS for an unsupported image
format or a non-positive width_inches,
ERR_UNSUPPORTED_FMT for non-.docx extensions.
Inputs: path (required), image_path (required), width_inches, folder. Returns: object
word_add_paragraphA
Append a paragraph to the body of the document.
Args:
path: Path to an existing .docx.
text: Text for the new paragraph. An empty string is accepted
and produces an empty paragraph.
style: Optional built-in style name (e.g. "Intense Quote").
folder: Optional base folder for relative paths.
Returns:
{"index": <n>} where <n> is the index of the newly
appended paragraph.
Raises:
OfficeMCPError: ERR_FILE_NOT_FOUND if the file is missing,
ERR_INVALID_PARAMS for bad text type or unknown
style, ERR_UNSUPPORTED_FMT for non-.docx
extensions.
Inputs: path (required), text (required), style, folder. Returns: object
word_add_tableA
Append a table to the body of the document.
Args:
path: Path to an existing .docx.
rows: Number of rows (must be a positive int).
cols: Number of columns (must be a positive int).
data: Optional 2D list of strings to populate the cells. The
list is interpreted in row-major order; shorter rows are
padded with empty strings and longer rows are truncated.
style: Optional built-in table style name (e.g. "Table Grid"
or "Light Grid Accent 1").
folder: Optional base folder for relative paths.
Returns:
{"index": <n>} where <n> is the index of the new table
in doc.tables.
Raises:
OfficeMCPError: ERR_INVALID_PARAMS for non-positive
rows/cols, unknown style, or a non-list
data argument. ERR_FILE_NOT_FOUND if the file is
missing, ERR_UNSUPPORTED_FMT for non-.docx
extensions.
Inputs: path (required), rows (required), cols (required), data, +2 more. Returns: object
word_create_documentA
Create a new .docx file at path with an optional title paragraph.
Args:
path: Target .docx path. May be absolute or relative to
folder (or to the default folder when folder is
None).
title: Optional text for the first paragraph. When None the
document is created with a single empty paragraph (the
python-docx default).
folder: Optional base folder for relative paths.
Returns:
{"path": "<absolute path>"} on success.
Raises:
OfficeMCPError: ERR_INVALID_PARAMS if path is empty or
a file already exists at the target. ERR_UNSUPPORTED_FMT
if path does not end in .docx.
Inputs: path (required), title, folder. Returns: object
word_export_htmlA
Convert a .docx file to HTML via :mod:mammoth.
Delegates to :func:office_mcp.exporters.export_to_html. The
.docx path uses mammoth only (no LibreOffice dependency
— VAL-WORD-072).
Args:
path: Path to an existing .docx file.
output: Target path for the produced HTML. The parent
directory is created if it does not exist.
folder: Optional base folder for relative paths.
Returns:
{"output_path": "<absolute path of the produced HTML>"}.
Raises:
OfficeMCPError: ERR_FILE_NOT_FOUND if the source is
missing, ERR_UNSUPPORTED_FMT for non-.docx
sources, ERR_EXPORT_FAILED for any other failure.
Inputs: path (required), output (required), folder. Returns: object
word_export_pdfA
Convert a .docx file to PDF via LibreOffice headless.
Delegates to :func:office_mcp.exporters.export_to_pdf. A unique
-env:UserInstallation is used per call so multiple exports
can run concurrently (VAL-WORD-079).
Args:
path: Path to an existing .docx file.
output: Target path for the produced PDF. The parent
directory is created if it does not exist. If a relative
path is given, it is resolved against folder (or the
default folder).
folder: Optional base folder for relative paths.
Returns:
{"output_path": "<absolute path of the produced PDF>"}.
Raises:
OfficeMCPError: ERR_FILE_NOT_FOUND if the source is
missing, ERR_UNSUPPORTED_FMT for non-.docx
sources, ERR_LIBREOFFICE_MISSING when soffice is
not on PATH, ERR_EXPORT_FAILED for any other failure.
Inputs: path (required), output (required), folder. Returns: object
word_find_replaceA
Replace every occurrence of find with replace.
Operates on every body paragraph. The reported replacements
count is the sum of occurrences across all paragraphs before the
replacement is applied (case-sensitivity follows the
case_sensitive flag).
Args:
path: Path to an existing .docx.
find: The literal string to search for. Must be non-empty.
replace: The replacement string.
case_sensitive: When True (default), the match is exact;
when False, the match is case-insensitive.
folder: Optional base folder for relative paths.
Returns:
{"replacements": <n>}.
Raises:
OfficeMCPError: ERR_INVALID_PARAMS if find is empty or
non-string, ERR_FILE_NOT_FOUND if the file is missing,
ERR_UNSUPPORTED_FMT for non-.docx extensions.
Inputs: path (required), find (required), replace (required), case_sensitive, +1 more. Returns: object
word_format_runA
Update the formatting of a single run.
Args:
path: Path to an existing .docx.
paragraph_index: Zero-based index of the paragraph.
run_index: Zero-based index of the run inside that paragraph.
bold: True / False to set, or None to leave as-is.
italic: Same convention as bold.
font_size: Point size (int or float) or None.
font_name: Font name string or None.
color: Hex color string (e.g. "FF0000") or None.
folder: Optional base folder for relative paths.
Returns:
{"ok": True}.
Special case:
If all of bold, italic, font_size, font_name,
color are None, the function is a no-op: the file is
not re-saved and the SHA256 is preserved
(VAL-WORD-039 / VAL-WORD-076).
Raises:
OfficeMCPError: ERR_INVALID_PARAMS if indices are out of
range or of the wrong type, or color is not a valid
hex string. ERR_FILE_NOT_FOUND if the file is missing,
ERR_UNSUPPORTED_FMT for non-.docx extensions.
Inputs: path (required), paragraph_index (required), run_index (required), bold, +5 more. Returns: object
word_get_infoA
Return counts and core properties of a .docx file.
The returned dict contains:
paragraphs— number of body paragraphs (including the empty one emitted by :func:docx.Document).sections— number of sections in the document.tables— number of body tables.images— number of inline shapes (images).properties— subset ofcore_properties(title,author) that are populated.
Args:
path: Path to an existing .docx.
folder: Optional base folder for relative paths.
Raises:
OfficeMCPError: ERR_FILE_NOT_FOUND if the file is missing,
ERR_UNSUPPORTED_FMT if the extension is not .docx,
ERR_INVALID_PARAMS if path is empty.
Inputs: path (required), folder. Returns: object
word_list_paragraphsA
Return one dict per body paragraph.
Each entry has index, style, text, and runs keys.
runs is itself a list of run dicts (see
:func:_serialise_run).
Args:
path: Path to an existing .docx.
folder: Optional base folder for relative paths.
Raises:
OfficeMCPError: ERR_FILE_NOT_FOUND if the file is missing,
ERR_UNSUPPORTED_FMT for non-.docx extensions.
Inputs: path (required), folder. Returns: result
word_read_paragraphA
Return a single paragraph's content and metadata.
Args:
path: Path to an existing .docx.
index: Zero-based paragraph index.
folder: Optional base folder for relative paths.
Raises:
OfficeMCPError: ERR_INVALID_PARAMS if index is negative
or out of range, ERR_FILE_NOT_FOUND if the file is
missing, ERR_UNSUPPORTED_FMT for non-.docx
extensions.
Inputs: path (required), index (required), folder. Returns: object
word_set_sectionA
Configure a section's orientation and page size.
Args:
path: Path to an existing .docx.
orientation: "portrait" or "landscape" (case-insensitive).
Any other value raises ERR_INVALID_PARAMS
(VAL-WORD-065).
page_size: A named page size such as "A4" (default),
"Letter", "Legal", "A5", "Tabloid" or
"B5" (case-insensitive).
section_index: Zero-based section index (default 0).
folder: Optional base folder for relative paths.
Returns:
{"ok": True}.
Behavior:
* Portrait: page_width = <width>, page_height = <height>.
* Landscape: page_width = <height>, page_height = <width>
(i.e. dimensions are swapped so the wider edge is horizontal).
Raises:
OfficeMCPError: ERR_INVALID_PARAMS for an unknown
orientation or page_size, ERR_FILE_NOT_FOUND
if the file is missing, ERR_UNSUPPORTED_FMT for
non-.docx extensions.
Inputs: path (required), orientation, page_size, section_index, +1 more. Returns: object
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 47 tools
Tools are clearly separated by format prefix (word_, excel_, pptx_) and generic tools have distinct names. Each tool targets a specific operation, and there is no ambiguity between them.
All tools follow a consistent verb_noun pattern with format prefixes (e.g., word_add_paragraph, excel_read_sheet). Generic tools like convert_document and list_documents also adhere to this pattern. No mixing of conventions.
With 47 tools, the server covers three complex formats thoroughly. While high, it is justified by the need for separate create, read, update, delete, format, and export operations per format. Could be slightly trimmed but still reasonable.
Basic CRUD operations are present for all formats, plus exports and formatting. Minor gaps exist, such as lacking explicit delete for Word paragraphs/tables or Excel cells, but workarounds are available. Overall well-covered.
Maintenance
Related MCP Connectors
Generate, edit, merge, translate and PDF-convert PowerPoint (.pptx) over MCP. 8 tools.
Generate PDF, Word (.docx) and PowerPoint (.pptx) documents from Markdown over MCP.
Document conversion MCP server: PDF to Markdown, image OCR, spreadsheet parsing.
Document-to-Markdown MCP server — convert PDF, Office and HTML into LLM-ready Markdown.
Related MCP Servers
- AlicenseDqualityDmaintenanceEnables reading, writing, editing, and converting Office documents (ODT, DOCX, ODS, XLSX, PDF, etc.) using MCP tools, with no external dependencies.1111 npmMIT
- AlicenseAqualityDmaintenanceA local MCP server for reading and editing Word (.docx) documents with 33 tools for content, formatting, comments, page layout, and track changes, running locally via stdio.271,629 npm11MIT
- FlicenseAqualityCmaintenanceMCP server for Microsoft Office file operations. Read, write, and create Excel, Word, and PowerPoint files directly from your local filesystem.12-
- AlicenseNot gradedqualityCmaintenanceMCP server for creating and editing PowerPoint, Excel, and Word documents. Runs as an SSE server and provides 18 tools for document manipulation.Apache 2.0