Skip to main content
Glama

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 → html is pure-Python (via mammoth); 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-8 so 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 *.pdf and .xlsx / .pptx → .html export. The classic install paths are auto-detected; on Windows the installer places soffice.exe at C:\Program Files\LibreOffice\program\soffice.exe. The headless mode used by office-mcp does not require a display or a running LibreOffice instance.

  • All Python dependencies are listed in pyproject.toml and pre-installed in .venv/: mcp[cli]>=1.27,<2, python-docx, openpyxl, python-pptx, xlsxwriter, mammoth, defusedxml, pydantic. The optional Pillow is 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.sh

What it does:

  1. export PYTHONUTF8=1 and export PYTHONIOENCODING=utf-8 so Unicode survives every subprocess boundary.

  2. Prepends the LibreOffice program directory to PATH (no effect if it is already there).

  3. Activates .venv/ (Scripts/activate on Windows, bin/activate elsewhere). If .venv/ is missing it bootstraps a fresh one.

  4. Imports every required package and prints a confirmation line.

  5. Prints soffice --version (or a warning if LibreOffice is missing — export tools will then raise ERR_LIBREOFFICE_MISSING at 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.py

3. 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.py

The 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.py

To 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.py

Expected 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 to server.py for your checkout. The forward slashes in the example work on Windows; use backslashes only if you escape them.

  • OFFICE_MCP_DEFAULT_FOLDER is the directory the server will resolve relative file paths against. Any tool that accepts a folder argument overrides this default for that call.

  • If you prefer the venv's Python, set command to 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 its sys.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

list_documents

Non-recursive folder scan; returns one entry per .docx/.xlsx/.pptx.

get_document_info

Auto-detect format by extension; dispatch to the format-specific *_get_info.

search_text

Cross-format text search with locations paragraph:N / cell:<coord> / slide:N:shape:M.

convert_document

Dispatch to the right exporter based on source extension and target_format (pdf / html / csv).

Word (15)

Tool

Purpose

word_create_document

Create a new .docx, optionally with a title.

word_get_info

Counts of paragraphs / sections / tables / images + core properties.

word_list_paragraphs

All paragraphs with index, style, text, runs.

word_read_paragraph

One paragraph by index.

word_add_paragraph

Append a paragraph (optional style).

word_add_heading

Append a heading at a level (1-9).

word_find_replace

Find and replace text (case-sensitive toggle).

word_format_run

Update a run's bold / italic / size / font / color.

word_add_table

Append a table (rows × cols, optional data + style).

word_add_image

Embed an image (optional width_inches).

word_add_header

Set the text of a section's header.

word_add_footer

Set the text of a section's footer.

word_set_section

Configure a section's orientation + page size.

word_export_pdf

Convert a .docx to PDF via LibreOffice.

word_export_html

Convert a .docx to HTML via mammoth.

Excel (14)

Tool

Purpose

excel_create_workbook

Create a new .xlsx (optionally with a custom sheet name).

excel_get_info

Sheet count + names + per-sheet dimensions.

excel_list_sheets

Names + indices + dimensions of all sheets.

excel_read_sheet

Read a sheet (or an A1:C3-style range) as a 2-D list.

excel_write_cell

Write a single cell (string, number, or =formula).

excel_write_range

Write a 2-D list of values starting at a cell.

excel_create_sheet

Append a new sheet.

excel_delete_sheet

Remove a sheet (refuse to delete the last).

excel_rename_sheet

Rename a sheet (refuse duplicates).

excel_format_cells

Apply font / fill / border / number-format to a range.

excel_add_chart

Add a bar / line / pie / area / scatter chart.

excel_export_csv

Export a sheet to CSV (pure Python).

excel_export_pdf

Export a workbook to PDF via LibreOffice.

excel_export_html

Export a workbook to HTML via LibreOffice.

PowerPoint (14)

Tool

Purpose

pptx_create_presentation

Create a new .pptx with an optional title slide.

pptx_get_info

Slide count + layouts + slide dimensions.

pptx_list_slides

All slides with index, title, layout, shape count.

pptx_read_slide

One slide's title + shapes.

pptx_add_slide

Add a slide (layout index, optional title).

pptx_delete_slide

Remove a slide (refuse to delete the last).

pptx_reorder_slides

Move a slide to a new index (move, not swap).

pptx_add_text_box

Add a text box (x / y / w / h in inches, optional font).

pptx_add_image

Embed an image (x / y / w / h).

pptx_add_shape

Add an MSO shape (type, x / y / w / h, optional text).

pptx_add_table

Add a table (rows / cols / data, x / y / w / h).

pptx_add_chart

Add a chart (type, data dict, x / y / w / h).

pptx_export_pdf

Export to PDF via LibreOffice.

pptx_export_html

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_csv

Critical idioms (also enforced by the test suite):

  • Singleton via from server import mcp. Every tool function is decorated with @mcp.tool() from mcp.server.fastmcp, imported as from server import mcp. This works because server.py registers itself under both __main__ and server in sys.modules via sys.modules.setdefault("server", sys.modules[__name__]) at module top. Do not remove that line.

  • Side-effect imports in server.py use importlib.import_module. This avoids pyflakes F401 false positives on the tool module side-effect imports.

  • No print() to stdout. All logging goes to sys.stderr (configured at the top of server.py).

  • Office files are ZIPs — open in binary mode ("rb"/"wb"). Text files (HTML, CSV, README) with encoding="utf-8".

  • *_create_* tools refuse to overwrite. All other tools raise ERR_FILE_NOT_FOUND (-32001) if the target does not exist.

  • Error model. Every tool raises OfficeMCPError(code, message, details). FastMCP serialises the exception to a CallToolResult with isError=True and a structuredContent dict carrying the error code. The JSON-RPC error code stays clean.

Configuration

Env var

Default

Effect

OFFICE_MCP_DEFAULT_FOLDER

server CWD

Base folder for relative paths in tool calls.

OFFICE_MCP_SOFFICE

auto-detected

Override the LibreOffice executable path.

PYTHONUTF8

unset

Set to 1 to enable UTF-8 mode in the interpreter.

PYTHONIOENCODING

unset

Set to utf-8 so sys.stdin / sys.stdout are UTF-8.

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).

  1. Install LibreOffice. On Windows, use the official MSI (soffice.exe lands in C:\Program Files\LibreOffice\program). On macOS, drag the .dmg to /Applications. On Debian / Ubuntu: sudo apt install libreoffice.

  2. Check the executable. init.sh prints soffice: LibreOffice ... when the binary is found. If it prints WARNING: soffice not on PATH, either re-run init.sh or set OFFICE_MCP_SOFFICE=/full/path/to/soffice(.exe) in the agent config.

  3. Watch out for the Windows .COM shim. shutil.which on Windows can return soffice.COM (a 16-bit DOS wrapper) when the LibreOffice program directory is on PATH but not in the auto-detect candidate list. find_libreoffice() in office_mcp/config.py explicitly prefers the .exe candidates and rejects .COM files. If you see the server hang for the full 180s timeout before raising ERR_EXPORT_FAILED, you are probably hitting this — set OFFICE_MCP_SOFFICE to the absolute soffice.exe path.

  4. 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 run word_export_pdf in parallel from two different agent sessions without locking.

Encoding issues (mojibake, UnicodeDecodeError)

  • Always launch the server with PYTHONUTF8=1 and PYTHONIOENCODING=utf-8 in the env. init.sh does this for the local shell; the Claude Desktop config snippet above sets them in the env block.

  • 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-8 in the server's env.

  • 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-8 is 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

  • openpyxl writes 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 in stderr. 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.py

The 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 tools
convert_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 has name (str), index (int, 0-based), rows (int, openpyxl's max_row), and cols (int, openpyxl's max_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 w given — h is computed from the image's intrinsic aspect ratio (VAL-PPTX-038). Aspect ratio preserved within 1% tolerance.

  • Only h given — w is 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 default Presentation() 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 (placeholders

    • pictures + 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 in slide.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 of core_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

    A4.4/5.0

    Scored across 47 tools

    Disambiguation5/5

    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.

    Naming Consistency5/5

    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.

    Tool Count4/5

    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.

    Completeness4/5

    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

    ActivityInactive
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      D
      quality
      D
      maintenance
      Enables reading, writing, editing, and converting Office documents (ODT, DOCX, ODS, XLSX, PDF, etc.) using MCP tools, with no external dependencies.
      11
      11 npm
      MIT
    • A
      license
      A
      quality
      D
      maintenance
      A 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.
      27
      1,629 npm
      11
      MIT