Skip to main content

Local-first Office document tooling with a shared CLI and MCP-ready core

Project description

Office-Agent

Python MCP License FOSS Pluralism OpenSpec

Office-Agent (offagent) is a local-first Office document tool with a shared application core and a CLI entrypoint, offagent.

Detailed technical implementation notes are available in technical-implementation.md.

Office Agent

Office-Agent supports local-first workflows for .docx, .pptx, and .xlsx documents through both a CLI and an MCP server. The app indexes document structure into a local SQLite + FTS5 store, can optionally persist embeddings for semantic and hybrid retrieval, and routes all reads and writes through one shared service layer with path-policy enforcement, stale-locator checks, versioned outputs, and automatic reindexing.

Current Features

Core App

  • TOML configuration with environment variable overrides
  • office-agent doctor runtime checks for imports, SQLite, FTS5, index writability, configured roots, and vector-search readiness
  • document discovery and indexing for .docx, .pptx, and .xlsx
  • allowed-root guards for indexed reads and output-root guards for writes
  • human-readable, JSON, and quiet CLI output modes
  • default versioned write outputs, with optional in-place overwrite when explicitly enabled
  • keyword, semantic, and hybrid search modes over indexed content

Structured Document Model

  • typed locators for DOCX, PPTX, and XLSX objects, with legacy locator compatibility where needed
  • object traversal with get_object and list_children
  • per-object capability reporting for read, update, delete, add_child, move, copy, and style
  • generic object lifecycle operations: create_object, update_object, move_object, copy_object, batch_edit, and delete_object
  • stale-locator detection across object and node workflows

DOCX Support

  • paragraph, run, section, table, table row, table cell, image, and page-break object resolution
  • paragraph-level indexing and search with stable para:<n> compatibility
  • node reads and writes for paragraphs and table cells
  • paragraph insertion through the existing content-insert workflow
  • DOCX escape hatches for paragraph style changes, page-break insertion, table creation, and table-cell merging

PPTX Support

  • presentation, slide, notes, shape, text shape, image shape, table, table row, table cell, and group-shape object resolution
  • text-frame indexing and search with slide:<n>:shape:<id> compatibility
  • node reads and writes for editable text-bearing targets
  • slide move and copy operations through the generic object layer
  • PPTX escape hatches for adding slides, duplicating slides, changing slide layouts, and inserting text shapes

XLSX Support

  • workbook, worksheet, row, column, cell, range, table, merged range, formula cell, and named-range object resolution
  • cell-level indexing and search with sheet:<name>!<coordinate> compatibility
  • direct cell writes plus guarded append semantics for string-compatible cells
  • generic object deletion for worksheets, rows, columns, cells, and ranges where supported
  • XLSX escape hatches for range writes, row insertion, column insertion, formula writes, and cell merging

MCP Server

  • stdio MCP server implemented with FastMCP
  • document indexing and refresh tools
  • canonical V2 search via search_objects, with deprecated search_documents alias compatibility
  • structure and section tools, node read/write tools, object traversal tools, generic object mutation tools, and format-specific escape hatches
  • structured MCP responses backed by the same application services used by the CLI

Vector Search

  • optional embedding generation during indexing and reindexing
  • SQLite-backed embedding side tables keyed to indexed document items
  • semantic retrieval over stored vectors
  • hybrid retrieval that combines keyword and semantic scores
  • match_mode and per-source score metadata in JSON and MCP search results

Installation

Install the package from Pypi with:

uv install offagent

and running it straight from Pypi:

uvx offagent

The project uses uv for development as well. To install all of the dependencies.

uv sync

Run the CLI with:

uv run offagent --help

Configuration

The CLI accepts an optional config file via --config <path>.

Example office-agent.toml:

[offagent]
index_path = ".offagent/index.sqlite3"
document_roots = ["./docs"]
allowed_roots = ["./docs", "./edited"]
output_directory = "./edited"
output_roots = ["./edited", "./docs"]
allow_inplace_overwrite = true
embedding_model = "BAAI/bge-small-en-v1.5"
embedding_dimensions = 384
vector_search_top_k = 20
hybrid_keyword_weight = 0.4
hybrid_semantic_weight = 0.6

Environment variables override file settings:

  • OFFAGENT_CONFIG
  • OFFAGENT_INDEX_PATH
  • OFFAGENT_DOCUMENT_ROOTS
  • OFFAGENT_ALLOWED_ROOTS
  • OFFAGENT_OUTPUT_DIRECTORY
  • OFFAGENT_OUTPUT_ROOTS
  • OFFAGENT_ALLOW_INPLACE_OVERWRITE
  • OFFAGENT_EMBEDDING_MODEL
  • OFFAGENT_EMBEDDING_DIMENSIONS
  • OFFAGENT_VECTOR_SEARCH_TOP_K
  • OFFAGENT_HYBRID_KEYWORD_WEIGHT
  • OFFAGENT_HYBRID_SEMANTIC_WEIGHT

OFFAGENT_DOCUMENT_ROOTS uses the platform path separator, for example : on macOS/Linux.

Configuration fields:

  • index_path: SQLite index file location
  • document_roots: directories scanned by discovery and directory-based indexing
  • allowed_roots: canonical roots allowed for indexed read operations; empty means no read guard
  • output_directory: optional directory for versioned write outputs; defaults to the source document directory
  • output_roots: canonical roots allowed for write outputs; if omitted and output_directory is set, this defaults to that directory
  • allow_inplace_overwrite: when true, write commands may use --output-mode inplace; default is true
  • embedding_model: embedding model name used when generating or querying vectors
  • embedding_dimensions: expected vector dimensionality for the configured model
  • vector_search_top_k: candidate pool size used by semantic and hybrid retrieval
  • hybrid_keyword_weight: keyword contribution to hybrid ranking
  • hybrid_semantic_weight: semantic contribution to hybrid ranking

CLI

All commands accept --config <path> after the command name. Example:

uv run offagent search "supplier shall" --config office-agent.toml

Structured commands also accept:

  • --json: emit machine-readable JSON with no extra prose
  • --quiet: suppress successful stdout output

--json and --quiet are mutually exclusive.

Write commands also accept --output-mode <mode> where <mode> is:

  • versioned: default; writes <name>.edited.<timestamp>.<ext> and reindexes the new file
  • inplace: overwrites the source file, but only if allow_inplace_overwrite = true

Common exit codes:

  • 0: success
  • 1: other runtime failure
  • 2: invalid arguments or incompatible flag combinations
  • 3: not-found target, missing indexed item/document, or stale-locator write failure
  • 4: not-editable target
  • 5: policy-refused operation

Doctor

Checks runtime health, including vector-search readiness.

Options:

  • --config <path>: optional config file path
uv run offagent doctor
uv run offagent doctor --config office-agent.toml

Index And Reindex

index indexes one file or all supported Office files under a directory. reindex currently re-runs the same indexing flow. Add --with-embeddings to generate and persist embeddings during the run.

Arguments:

  • path: file or directory to index

Options:

  • --with-embeddings: generate embeddings for indexed items during this run
  • --config <path>: optional config file path
uv run offagent index ./docs
uv run offagent index ./docs --with-embeddings
uv run offagent index ./docs/sample.docx
uv run offagent index ./docs/sample.pptx
uv run offagent index ./docs/sample.xlsx
uv run offagent reindex ./docs/sample.docx
uv run offagent reindex ./docs/sample.docx --with-embeddings
uv run offagent reindex ./docs/sample.pptx
uv run offagent reindex ./docs/sample.xlsx

Search

Searches indexed content through SQLite FTS5, stored embeddings, or both.

Arguments:

  • query: search text

Options:

  • --type <docx|pptx|xlsx>: restrict search to one document type
  • --doc <path>: restrict search to one indexed document path
  • --mode <keyword|semantic|hybrid>: retrieval mode; default keyword
  • --config <path>: optional config file path
uv run offagent search "supplier shall"
uv run offagent search "supplier shall" --mode semantic
uv run offagent search "supplier shall" --mode hybrid --json
uv run offagent search "supplier shall" --type docx
uv run offagent search "supplier shall" --type pptx
uv run offagent search "supplier shall" --type xlsx
uv run offagent search "supplier shall" --type docx --doc ./docs/sample.docx
uv run offagent search "supplier shall" --type pptx --doc ./docs/sample.pptx
uv run offagent search "supplier shall" --type xlsx --doc ./docs/sample.xlsx

Notes:

  • keyword behaves like the original FTS-only search path
  • semantic requires prior indexing with --with-embeddings
  • hybrid combines keyword and semantic candidates and returns merged ranking metadata
  • JSON output includes match_mode and scores; semantic and hybrid human-readable output also displays that metadata

List

Lists indexed documents with document id, path, type, modified time, and indexed item count.

uv run offagent list
uv run offagent list --json

Show

Shows one indexed document summary, or one indexed item within that document.

uv run offagent show --doc ./docs/sample.docx
uv run offagent show --doc ./docs/sample.docx --item para:3
uv run offagent show --doc ./docs/sample.xlsx --item sheet:Budget2026!B12 --json

Locate

Resolves a direct item reference for one document.

Options:

  • --doc <path>: required document path
  • --paragraph <n>: DOCX-only paragraph lookup
  • --slide <n>: PPTX-only slide lookup
  • --shape <id>: optional PPTX shape narrowing; only valid with --slide
  • --sheet <name>: XLSX-only worksheet lookup
  • --cell <coordinate>: XLSX-only cell coordinate; only valid with --sheet
  • --config <path>: optional config file path

Valid option combinations:

  • DOCX: --doc + --paragraph
  • PPTX: --doc + --slide with optional --shape
  • XLSX: --doc + --sheet + --cell
uv run offagent locate --doc ./docs/sample.docx --paragraph 3
uv run offagent locate --doc ./docs/sample.pptx --slide 1
uv run offagent locate --doc ./docs/sample.pptx --slide 1 --shape 7
uv run offagent locate --doc ./docs/sample.xlsx --sheet Budget2026 --cell B12

Read

Reads the current source content for one indexed item.

Options:

  • --doc <path>: required document path
  • --item <item-id>: required item id
  • --config <path>: optional config file path
uv run offagent read --doc ./docs/sample.docx --item para:3
uv run offagent read --doc ./docs/sample.pptx --item slide:1:shape:7
uv run offagent read --doc ./docs/sample.xlsx --item sheet:Budget2026!B12

Replace

Replaces the full text of one DOCX paragraph or PPTX text shape.

Options:

  • --doc <path>: required document path
  • --item <item-id>: required item id
  • --text <value>: replacement text
  • --output-mode <versioned|inplace>: write mode; default versioned
  • --config <path>: optional config file path

Notes:

  • replace is supported for DOCX and PPTX only
  • XLSX uses write-cell instead of replace
uv run offagent replace --doc ./docs/sample.docx --item para:3 --text "Updated paragraph text."
uv run offagent replace --doc ./docs/sample.pptx --item slide:1:shape:7 --text "Updated slide text."
uv run offagent replace --doc ./docs/sample.docx --item para:3 --text "Updated paragraph text." --output-mode versioned

Append

Appends text to one supported target.

Options:

  • --doc <path>: required document path
  • --item <item-id>: required item id
  • --text <value>: appended text
  • --output-mode <versioned|inplace>: write mode; default versioned
  • --config <path>: optional config file path

Notes:

  • DOCX appends to the target paragraph
  • PPTX appends to the target text frame
  • XLSX appends only to empty or string-compatible cells
  • stale-locator failures exit with code 3
uv run offagent append --doc ./docs/sample.docx --item para:3 --text " Additional text."
uv run offagent append --doc ./docs/sample.pptx --item slide:1:shape:7 --text "\nAdditional slide text."
uv run offagent append --doc ./docs/sample.xlsx --item sheet:Notes!A1 --text " Additional note."

Write Cell

Writes one XLSX cell value directly.

Options:

  • --doc <path>: required .xlsx document path
  • --sheet <name>: required worksheet name
  • --cell <coordinate>: required cell coordinate
  • --value <value>: new cell value
  • --output-mode <versioned|inplace>: write mode; default versioned
  • --config <path>: optional config file path
uv run offagent write-cell --doc ./docs/sample.xlsx --sheet Budget2026 --cell B12 --value "125000"
uv run offagent write-cell --doc ./docs/sample.xlsx --sheet Budget2026 --cell B12 --value "125000" --output-mode inplace

MCP

Starts the FastMCP server on stdio.

Options:

  • --config <path>: optional config file path
uv run offagent mcp
uv run offagent mcp --config office-agent.toml

The MCP server exposes these tools:

  • index_documents(paths)
  • refresh_document(document_id)
  • list_documents()
  • create_document(format, output_path, initial_sheet_name=None, output_mode="versioned")
  • add_content_block(document_id, block_type, properties, output_mode="versioned")
  • search_objects(query, file_type=None, document_id=None, mode="keyword", limit=20)
  • search_documents(...) as a deprecated alias for pre-V2 consumers
  • get_structure(document_id)
  • get_section(document_id, section_id, cell_range=None)
  • get_node(document_id, node_id)
  • write_node(document_id, node_id, content, output_mode="versioned")
  • insert_content(document_id, content, style_name=None, after_node_id=None, output_mode="versioned")
  • style_inline(document_id, locator, style, range=None, clear_fields=None, output_mode="versioned")
  • style_block(document_id, locator, style, clear_fields=None, output_mode="versioned")
  • set_structural_role(document_id, locator, role, level=None, output_mode="versioned")
  • docx_get_tables(document_id)
  • get_object(document_id, locator)
  • list_children(document_id, locator, child_type=None, limit=None)
  • create_object(document_id, parent_locator, object_type, properties, segments=None, range=None, position=None, output_mode="versioned")
  • update_object(document_id, locator, properties, segments=None, range=None, output_mode="versioned")
  • move_object(document_id, locator, new_parent_locator, position=None, output_mode="versioned")
  • copy_object(document_id, locator, target_parent_locator, position=None, output_mode="versioned")
  • batch_edit(document_id, operations, output_mode="versioned", dry_run=False)
  • delete_object(document_id, locator, output_mode="versioned")
  • docx_set_paragraph_style(document_id, locator, style_name, output_mode="versioned")
  • docx_insert_page_break(document_id, locator, output_mode="versioned")
  • docx_add_table(document_id, row_count, column_count, position=None, column_widths=None, style_name=None, output_mode="versioned")
  • docx_merge_table_cells(document_id, start_locator, end_locator, output_mode="versioned")
  • pptx_add_slide(document_id, layout_index=None, layout_name=None, output_mode="versioned")
  • pptx_duplicate_slide(document_id, locator, position=None, output_mode="versioned")
  • pptx_set_slide_layout(document_id, locator, layout_index=None, layout_name=None, output_mode="versioned")
  • pptx_add_text_shape(document_id, locator, text, left, top, width, height, output_mode="versioned")
  • xlsx_write_range(document_id, locator, values, output_mode="versioned")
  • xlsx_insert_rows(...) for both append-rows compatibility mode and worksheet row insertion mode
  • xlsx_insert_columns(document_id, locator, column_index, count, output_mode="versioned")
  • xlsx_set_formula(document_id, locator, formula, output_mode="versioned")
  • xlsx_merge_cells(document_id, locator, output_mode="versioned")

Notes:

  • search_objects is the canonical MCP search entrypoint; search_documents remains for compatibility.
  • create_object and update_object support additive segments inputs for partial-formatting workflows.
  • style_inline supports visible-text range inputs for partial inline styling across supported DOCX, PPTX, and XLSX targets.

Example query session

QUery session

Item Id Reference

Use these item-id formats with read, replace, and append:

  • DOCX: para:<n>
  • PPTX: slide:<slide-number>:shape:<shape-id>
  • XLSX: sheet:<worksheet-name>!<cell-coordinate>

Write Behavior

By default, all write commands create a versioned output file:

  • sample.docx -> sample.edited.20260318-214512345678.docx
  • deck.pptx -> deck.edited.20260318-214512345678.pptx
  • budget.xlsx -> budget.edited.20260318-214512345678.xlsx

After a successful write:

  • the output file is automatically reindexed
  • CLI output reports the written output path
  • search results can resolve against the new version immediately
  • managed output roots are treated as readable workflow roots so versioned-write follow-up commands continue to work

If the source file changed after indexing:

  • the service compares the current file hash to the indexed content_hash
  • it tries to re-resolve the same item id against the current file
  • if that fails, the command exits with stale-locator error code 3

Development

Run the test suite with:

uv run pytest

This repository also includes an example skill for Office-document workflows at .github/skills/office-agent. The location is VSCode specific but it should work for Claude Code and Codex as well.

Acceptance coverage uses checked-in golden Office fixtures under tests/fixtures, including DOCX, PPTX, and XLSX documents exercised by the CLI and FastMCP integration suites.

The test suite is written in pytest and includes:

  • configuration and diagnostics coverage
  • path-policy coverage
  • SQLite store coverage
  • DOCX adapter tests
  • DOCX service workflow tests
  • PPTX adapter tests
  • PPTX service workflow tests
  • XLSX adapter tests
  • XLSX service workflow tests
  • CLI round-trip and acceptance tests for DOCX, PPTX, and XLSX
  • FastMCP stdio integration and acceptance tests for the MCP tool surface
  • CLI/MCP parity tests for the core index → search → locate → read → replace workflow

Project Status

Implemented:

  • shared CLI/application-core structure
  • configuration and doctor checks
  • allowed-root and output-root policy enforcement
  • SQLite index bootstrap and FTS5-backed search
  • embedding-backed semantic and hybrid retrieval with mode-aware CLI/MCP output
  • DOCX paragraph extraction and editing workflow
  • PPTX text-shape extraction and editing workflow
  • XLSX cell extraction and editing workflow
  • indexed document summary commands (list and show)
  • JSON and quiet CLI output modes
  • versioned write outputs with automatic reindex
  • stale-locator protection for write commands
  • MCP interface over stdio with FastMCP

Not implemented yet:

  • SSE or streamable HTTP MCP transports
  • MCP resources and prompts
  • richer locator resolution beyond the current direct DOCX, PPTX, and XLSX flows

Principles of Participation

Everyone is invited and welcome to contribute: open issues, propose pull requests, share ideas, or help improve documentation. Participation is open to all, regardless of background or viewpoint.

This project follows the FOSS Pluralism Manifesto,
which affirms respect for people, freedom to critique ideas, and space for diverse perspectives.

License and Copyright

Copyright (c) 2026, Iwan van der Kleijn

This project is licensed under the MIT License. See the LICENSE file for details.

Project details


Download files

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

Source Distribution

offagent-0.10.3.tar.gz (133.2 kB view details)

Uploaded Source

Built Distribution

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

offagent-0.10.3-py3-none-any.whl (103.2 kB view details)

Uploaded Python 3

File details

Details for the file offagent-0.10.3.tar.gz.

File metadata

  • Download URL: offagent-0.10.3.tar.gz
  • Upload date:
  • Size: 133.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.21 {"installer":{"name":"uv","version":"0.9.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for offagent-0.10.3.tar.gz
Algorithm Hash digest
SHA256 268aa5c1314aaf8edf5c78c60115eaebc9e4731fafbea7470eff6a37b8a11ee1
MD5 553bf19120064afdeb0e1de7bb1df61a
BLAKE2b-256 edb6d3e67603942d78b36389c194b74d33eb7be5d3b1c9d5ab0a9f046f67874f

See more details on using hashes here.

File details

Details for the file offagent-0.10.3-py3-none-any.whl.

File metadata

  • Download URL: offagent-0.10.3-py3-none-any.whl
  • Upload date:
  • Size: 103.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.21 {"installer":{"name":"uv","version":"0.9.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for offagent-0.10.3-py3-none-any.whl
Algorithm Hash digest
SHA256 5f3dc4f7547e0b48e802159b33a97c72a130b0ad9abea81a573216c1565b7a71
MD5 4c4144ce0fb6857c4e15c3aa6d8f82b5
BLAKE2b-256 4ce69a77c4e6c08ac071366130a2bd3e6961aa3ab085a2aa9277a746da67dbfe

See more details on using hashes here.

Supported by

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