{
  "markdown": "# mcp-ooxml-ledger\n\n<!-- The MCP registry proves package ownership by fetching this README (served via PyPI,\n     per pyproject.toml's `readme = \"README.md\"`) and grepping for the marker below.\n     Do not delete it as \"stray\" — removing it fails registry publication validation. -->\n<!-- mcp-name: io.github.Anselmoo/mcp-ooxml-ledger -->\n\n[![CI](https://github.com/Anselmoo/mcp-ooxml-ledger/actions/workflows/cicd.yml/badge.svg)](https://github.com/Anselmoo/mcp-ooxml-ledger/actions/workflows/cicd.yml)\n[![codecov](https://codecov.io/gh/Anselmoo/mcp-ooxml-ledger/branch/main/graph/badge.svg)](https://codecov.io/gh/Anselmoo/mcp-ooxml-ledger)\n[![PyPI](https://img.shields.io/pypi/v/mcp-ooxml-ledger.svg)](https://pypi.org/project/mcp-ooxml-ledger/)\n[![TestPyPI](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Ftest.pypi.org%2Fpypi%2Fmcp-ooxml-ledger%2Fjson&query=%24.info.version&label=TestPyPI)](https://test.pypi.org/project/mcp-ooxml-ledger/)\n[![Python](https://img.shields.io/pypi/pyversions/mcp-ooxml-ledger.svg)](https://pypi.org/project/mcp-ooxml-ledger/)\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)\n[![MCP](https://img.shields.io/badge/MCP-server-6E56CF.svg)](https://modelcontextprotocol.io)\n\n**An MCP server that edits Office documents and refuses to write one where an edit went unrecorded.**\n\nBefore sealing a session it replays every recorded operation against the document's baseline\nand compares the result to what is actually on disk. A change no operation explains means the\ncommit is **refused**. Of ~18 competing MCP document-editing projects, none gates the write on\nthat check. The refusal is the product.\n\n## Setup\n\n```bash\nuv add mcp-ooxml-ledger\n```\n\nAdd to `.mcp.json` (project) or `claude_desktop_config.json` (desktop — use an absolute path,\n`${CLAUDE_PROJECT_DIR}` isn't expanded there):\n\n```json\n{\n  \"mcpServers\": {\n    \"ooxml-ledger\": {\n      \"command\": \"uv\",\n      \"args\": [\"run\", \"--project\", \"${CLAUDE_PROJECT_DIR}\", \"ooxml-ledger-mcp\"],\n      \"env\": { \"OOXML_LEDGER_ROOTS\": \"${CLAUDE_PROJECT_DIR}\" }\n    }\n  }\n}\n```\n\nNeeds `uv` on `PATH`; nothing else installed globally. Invoking the `ooxml-ledger-mcp` script\ndirectly gives `ENOENT` — it lives in the project venv, not your shell's `PATH`.\n\n> **`OOXML_LEDGER_ROOTS` is the security boundary.** An `os.pathsep`-separated list; every\n> path any tool receives is resolved inside it and refused outside. Unset, it defaults to the\n> server's working directory — set it deliberately, since `export_receipt` writes anywhere\n> inside a root.\n\n## Tools\n\n| | Tool | |\n|---|---|---|\n| **session** | `open_document` · `close_document` | writes |\n| **read** | `describe_structure` · `find_text` | read-only |\n| **edit** | `preview_edits` · `apply_edits` · `delete_paragraph` · `insert_paragraph` | writes |\n| **seal** | `commit_document` | writes · enforces the gate |\n| **stateless** | `server_info` · `digest` · `verify` · `list_receipts` | read-only |\n| | `export_receipt` | writes |\n\nTypical loop:\n\n```\nopen_document → find_text → preview_edits → apply_edits → commit_document → verify\n```\n\n```python\nsid = open_document(document=\"report.docx\")[\"session_id\"]\nfind_text(sid, query=\"Q3 revenue\")                     # → part, para_id, para_hash\npreview_edits(sid, edits=[...], author=\"alice\")        # → what WOULD happen; writes nothing\napply_edits(sid, edits=[...], author=\"alice\", mode=\"tracked\")\ncommit_document(sid)                                   # → refuses if anything is unaccounted for\nverify(\"report.docx\")                                  # → verified | unknown | failed\n```\n\n`preview_edits` runs the **same engine function** as `apply_edits` against a throwaway copy, so\nthe two cannot disagree. Batches are all-or-nothing: a failing edit leaves the document\nbyte-identical.\n\n`mode=\"tracked\"` emits Word revision marks a reviewer sees in the document. `mode=\"direct\"`\nrewrites the text with none — still fully recorded, and the receipt discloses that a direct\nedit touched a revision-capable part, so it is never silently indistinguishable from an\nordinary save.\n\n## Format matrix\n\n| Format | Verify | Edit |\n|---|---|---|\n| Word `.docx` | Yes | Yes — tracked + direct, paragraph insert/delete |\n| PowerPoint `.pptx` | Yes | Direct only — PresentationML has **no revision model**, so every edit carries a mandatory disclosure |\n| Excel `.xlsx` | Yes | **No** — editing verbs refuse, naming the format |\n\nVerification, digests, the gate and the receipt model are format-agnostic. Only the *editing*\nengines are format-specific: `wml.py` (Word) and `pml.py` (PowerPoint).\n\n## Read-only deployment\n\n`OOXML_LEDGER_READ_ONLY=1` leaves exactly `server_info`, `digest`, `verify`, `list_receipts`.\nThe others aren't merely hidden — calling one answers `Unknown tool`. No write surface inside\nthe roots at all.\n\n## CLI\n\n```bash\nooxml-ledger verify report.docx    # exit 0 only when verified\n```\n\nNo server, no session — digests the file, finds its receipt by content address, checks it.\nWire it into CI or a pre-commit hook and an unaccounted-for change fails the build.\n\n## Desktop bundle (.mcpb)\n\nEvery GitHub Release attaches a `.mcpb` file — a one-click Claude Desktop install: drag it onto\nthe app and it runs with a vendored Python runtime, no `uv` or manual server config needed.\n`mcpb/manifest.json` exposes the document root and read-only toggle as install-time settings\ninstead of environment variables; the tool list matches the stdio server's.\n\nCI builds and smoke-tests the bundle on `macos-latest` only, and the manifest's\n`compatibility.platforms` declares `darwin` only — **the bundle is built and proven on\nmacOS/arm64, nothing else.** It vendors native extensions (pydantic-core, cryptography, and\nmore) as platform-specific wheels; installing it on Windows or Linux would fail to import them.\n\n## Honest limits\n\n- **An unsigned receipt is accident-evident, not tamper-evident.** It catches an agent falling\n  back to a generic file write, an Office round-trip, a careless collaborator — not someone who\n  rewrites the receipt alongside the document. Anchoring its hash somewhere the holder doesn't\n  control (a git commit, a DOI, a submission portal) is what buys tamper-evidence.\n- **`verify` never replays.** It checks the digest and the receipt's internal consistency; the\n  replay runs once, at commit, and `verify` reports that verdict rather than recomputing it.\n- **pptx and xlsx have no human-visible record.** Word tracked changes are a second recording\n  layer inside the document; those two formats have none, so the ledger is the only record.\n- **The Word engine reaches paragraph text only** (`w:p`/`w:r`/`w:t`). Styles, numbering,\n  settings and relationships are uneditable and covered by the accountability check alone.\n\n## Contributing and security\n\n[CONTRIBUTING.md](CONTRIBUTING.md) covers setup, the branch and commit naming CI\nenforces, and the release flow. [SECURITY.md](SECURITY.md) covers private vulnerability\nreporting, and is explicit about which of this project's documented limits are design\nrather than defects.\n\nMIT licensed. Design notes and specifications live in [`docs/superpowers/`](docs/superpowers/).\n",
  "bytes": 7186,
  "sha": "a723b9b380b571c87170937c05046541917afe92ad474984e47b7c7e8bb28243",
  "repo_slug": "anselmoo/mcp-ooxml-ledger",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_anselmoo_mcp_ooxml_ledger_b9985389/readme"
}