Skip to content

feat: add MCP server for persistent document caching#7

Draft
pablospe wants to merge 22 commits intomainfrom
mcp
Draft

feat: add MCP server for persistent document caching#7
pablospe wants to merge 22 commits intomainfrom
mcp

Conversation

@pablospe
Copy link
Copy Markdown
Owner

@pablospe pablospe commented Feb 6, 2026

Summary

  • Add MCP server (docx_editor_mcp) for persistent DOM caching between tool calls
  • 20 MCP tools covering document lifecycle, track changes, comments, revisions, and read operations
  • 10-30x faster repeated edits by keeping documents loaded in memory
  • Plugin auto-configuration via .mcp.json

Changes

  • New docx_editor_mcp/ package with cache, server, and tools
  • 72 new tests for MCP functionality (561 total tests pass)
  • Updated skill to use MCP-first approach
  • Added [mcp] optional dependency and mcp-server-docx entry point
  • Updated README with MCP documentation

Test plan

  • All 561 tests pass
  • Package installs with pip install .[mcp]
  • Entry points work: mcp-server-docx and python -m docx_editor_mcp
  • Manual: Test plugin auto-configures MCP on enable
  • Manual: Test uvx mcp-server-docx after PyPI publish

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Opt-in MCP server: in-memory DOCX caching with LRU eviction, graceful shutdown saving, session-aware author resolution, external-change detection, and force-save semantics.
    • MCP tools: open/save/close/reload/force-save plus tracked-text edits, comments, revision management, and read/search utilities.
  • Documentation

    • README, design, spec, proposal, tasks, and skill docs updated with MCP workflows and setup.
  • Tests

    • Extensive tests for cache, server lifecycle, and MCP tools.
  • Chores

    • Packaging and CLI entry added; optional MCP install/configuration documented.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 6, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds an opt-in docx_editor_mcp package implementing an MCP server with a persistent DocumentCache (LRU eviction, mtime external-change detection), ~22 MCP tools for lifecycle/edits/comments/revisions, CLI entry/packaging updates, README/specs/docs, a .claude-plugin/.mcp.json config, and extensive unit tests.

Changes

Cohort / File(s) Summary
MCP Package Core
docx_editor_mcp/__init__.py, docx_editor_mcp/__main__.py, docx_editor_mcp/cache.py, docx_editor_mcp/server.py
New package and entrypoint; CachedDocument and DocumentCache with path normalization, mtime tracking, last_access, dirty flag, LRU eviction with save-on-evict; DocxMCPServer, create_server, and graceful shutdown.
MCP Tools API
docx_editor_mcp/tools.py
Large new toolset (~22 functions) implementing document lifecycle (open/save/close/reload/force_save), tracked edits (replace/delete/insert_before/insert_after), comments (add/list/reply/resolve/delete), revisions (list/accept/reject/accept_all/reject_all), and read utilities (find_text/count_matches/get_visible_text). Enforces external-change checks and dirty-state semantics; consistent dict responses.
Packaging & Entrypoint
pyproject.toml, .claude-plugin/.mcp.json
Adds optional mcp dependency group, console script mcp-server-docx, includes docx_editor_mcp in wheel packages, updates isort/coverage first-party lists, and adds plugin .mcp.json mapping docxuvx mcp-server-docx.
Docs, Specs & Tasks
README.md, openspec/changes/add-mcp-server/*
README MCP Server section added; new proposal/design/spec/tasks documents describing architecture, cache semantics (LRU, mtime checks), tool surface, packaging, and migration/rollback/testing guidance.
Skill Documentation
skills/docx/SKILL.md
Rewrote DOCX skill docs to favor MCP-centric workflows and the new MCP toolset; reorganized lifecycle, track-changes, comments, and revision sections; updated dependency/tooling guidance.
Tests
tests/mcp/__init__.py, tests/mcp/test_cache.py, tests/mcp/test_server.py, tests/mcp/test_tools.py
Comprehensive tests for path normalization, cache lifecycle, LRU eviction and save-on-evict, external-change detection, session author handling, server shutdown behavior, and end-to-end MCP tool behavior (success/error and dirty-state handling).

Sequence Diagram(s)

sequenceDiagram
    actor Client as Claude Code
    participant Server as "MCP Server\n(docx_editor_mcp.server)"
    participant Cache as "DocumentCache\n(docx_editor_mcp.cache)"
    participant Disk as Filesystem

    Note over Client,Disk: Open document & caching
    Client->>Server: open_document(path, author?)
    Server->>Cache: get(normalize(path))
    alt cached
        Cache-->>Server: CachedDocument
    else not cached
        Server->>Disk: read file (open)
        Disk-->>Server: document + mtime
        Server->>Cache: put(CachedDocument)
    end
    Server-->>Client: success / hint

    Note over Client,Cache: Edit and mark dirty
    Client->>Server: replace_text(path, old, new)
    Server->>Cache: get(path)
    Cache-->>Server: CachedDocument
    Server->>Server: apply edit (document API)
    Server->>Cache: mark_dirty()
    Server-->>Client: success (change_id)

    Note over Client,Disk: Save with external-change check
    Client->>Server: save_document(path)
    Server->>Cache: get(path)
    Cache-->>Server: CachedDocument
    Server->>Disk: stat path -> mtime
    Disk-->>Server: current mtime
    alt external change detected
        Server-->>Client: error (external)
    else
        Server->>Disk: write file (save)
        Disk-->>Server: ok
        Server->>Cache: update_mtime(), clear_dirty()
        Server-->>Client: success
    end

    Note over Server,Cache: LRU eviction on capacity
    Server->>Cache: put(new_doc) when full
    Cache->>Cache: evict_lru()
    alt evicted dirty
        Cache->>Disk: save before eviction (best-effort)
        Disk-->>Cache: ok / error
    end
    Cache-->>Server: eviction_done
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Poem

🐰 I hop through cached leaves at dawn's first light,
Tidy LRU paths, save the dirty before night,
I nibble mtime crumbs and guard each change,
MCP hums softly across the range,
Hooray — the editor's garden grows just right.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add MCP server for persistent document caching' directly summarizes the main change: introducing an MCP server with persistent document caching, which is the core objective of this PR.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch mcp

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🤖 Fix all issues with AI agents
In `@docx_editor_mcp/cache.py`:
- Around line 94-108: The put method normalizes cached_doc.path for the cache
key but stores the original CachedDocument object unchanged, causing path
inconsistency; update the CachedDocument's path to the normalized value before
storing (e.g., inside put: compute normalized = normalize_path(cached_doc.path)
and set cached_doc.path = normalized) or ensure CachedDocument constructor
always normalizes paths so that the object and cache key match; make this change
in the put method (and/or CachedDocument creation) so functions like
has_external_changes that rely on cached_doc.path see the normalized path.
- Around line 126-140: The eviction currently calls lru_doc.document.save() with
no error handling which can propagate exceptions and corrupt cache state; wrap
the save call in a try/except, catch Exception as e, log the failure (including
lru_path and the exception) using the same logger pattern as server.shutdown,
and if the save fails do NOT delete the entry from self._cache (leave it dirty
so the server can retry later); ensure the code references _evict_lru,
lru_doc.document.save(), and self._cache to locate and update the logic.

In `@docx_editor_mcp/server.py`:
- Around line 28-30: In the except block that currently calls
logger.error(f"Failed to save {cached_doc.path}: {e}"), replace that call with
logger.exception("Failed to save %s", cached_doc.path) (or logger.error("Failed
to save %s", cached_doc.path, exc_info=True)) so the traceback is recorded and
logging uses %-style formatting; this change should be made in the exception
handler around the save logic that references cached_doc.path.
- Around line 23-34: The shutdown method currently iterates self.cache.all()
twice (first to save dirty documents, then to remove entries); consolidate into
a single loop in shutdown that for each cached_doc checks cached_doc.dirty and
calls cached_doc.document.save() inside try/except (logging success or error
using cached_doc.path), then calls self.cache.remove(cached_doc.path)
unconditionally (or in finally) to ensure the entry is cleared; update
references to cached_doc, cached_doc.document.save(), self.cache.remove(...) and
logger to perform both actions in one pass.

In `@docx_editor_mcp/tools.py`:
- Around line 141-148: The cleanup currently swallows exceptions from
cached.document.close() (seen in the block after the comment "# Close the
document" and the similar try/except in reload_document) which hides close
failures; update both places (the cached.document.close() call and the close in
reload_document) to catch Exception as e and log the failure at debug (or
warning) level via the module logger before continuing, e.g., include the
exception message and context (path) in the log, then proceed to
server.cache.remove(path) and return result as before.

In `@skills/docx/SKILL.md`:
- Around line 41-46: The optional-parameter `?` suffix in the function
signatures (open_document, close_document) is unclear for Python users; update
the SKILL.md signatures to use Python conventions—either square-bracket notation
(e.g., open_document(path[, author])) or explicit defaults (e.g.,
open_document(path, author=None), close_document(path, force=False))—and apply
the chosen convention consistently to open_document, close_document
(author?/force?) and any other methods with `?` in the shown list
(save_document, reload_document, force_save remain unchanged except for
consistency if needed).
- Line 229: Update the "MCP server" bullet that currently reads "Installed with
plugin (auto-configured)" to explicitly name the plugin, provide install
instructions or a link to the plugin's install docs, and define what
"auto-configured" means (e.g., which config files, default settings applied, and
any post-install steps). Specifically, replace or expand the line referencing
"MCP server" so it mentions the plugin name (e.g., "<plugin-name>"), a one-line
install command or link to installation documentation, and a short sentence
explaining what auto-configuration does and where users can find or override the
generated config (so readers editing SKILL.md can directly follow setup steps).

In `@tests/mcp/test_server.py`:
- Around line 3-4: The test file imports unused symbols: remove the unused
imports `asyncio` and `AsyncMock` from the top import statement and only import
the mocks actually used (e.g., keep `MagicMock` and `patch`) so the import line
matches the symbols referenced in the file (look for usages of `MagicMock` and
`patch` in this test module and remove `asyncio` and `AsyncMock`).

In `@tests/mcp/test_tools.py`:
- Around line 432-466: Update test_accept_revision and test_reject_revision to
fail loudly when no revisions were produced: after calling replace_text and
retrieving revisions via list_revisions, assert that revisions["revisions"] is
not empty (e.g. assert revisions["revisions"], "replace_text did not produce
revisions") before extracting rev_id and calling
accept_revision/reject_revision; this ensures failures in replace_text or
revision generation surface rather than silently skipping assertions.

Comment thread docx_editor_mcp/cache.py
Comment thread docx_editor_mcp/cache.py
Comment thread docx_editor_mcp/server.py
Comment thread docx_editor_mcp/server.py Outdated
Comment thread docx_editor_mcp/tools.py
Comment thread skills/docx/SKILL.md
Comment thread skills/docx/SKILL.md
Comment thread tests/mcp/test_server.py Outdated
Comment thread tests/test_mcp/test_tools.py
@codecov
Copy link
Copy Markdown

codecov bot commented Feb 6, 2026

Codecov Report

❌ Patch coverage is 60.96552% with 283 lines in your changes missing coverage. Please review.
✅ Project coverage is 85.3%. Comparing base (7382da3) to head (087b361).

Files with missing lines Patch % Lines
docx_editor_mcp/tools.py 63.8% 112 Missing and 44 partials ⚠️
docx_editor_mcp/server.py 23.6% 100 Missing ⚠️
docx_editor/document.py 81.0% 11 Missing and 3 partials ⚠️
docx_editor_mcp/cache.py 88.2% 6 Missing and 4 partials ⚠️
docx_editor_mcp/__main__.py 0.0% 3 Missing ⚠️

❌ Your project check has failed because the head coverage (85.3%) is below the target coverage (90.0%). You can increase the head coverage or adjust the target coverage.

Additional details and impacted files
@@           Coverage Diff           @@
##            main      #7     +/-   ##
=======================================
- Coverage   93.6%   85.3%   -8.3%     
=======================================
  Files         10      14      +4     
  Lines       2133    2849    +716     
  Branches     485     571     +86     
=======================================
+ Hits        1998    2433    +435     
- Misses        35     267    +232     
- Partials     100     149     +49     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@docx_editor_mcp/tools.py`:
- Around line 3-13: The logger initialization (logger =
logging.getLogger(__name__)) is placed between imports and breaks the import
block; move that line below all import statements and ensure imports are grouped
and ordered (standard library: logging, os, typing; third-party: docx_editor and
docx_editor.exceptions; local: .cache and .server) so symbols Document,
TextNotFoundError, CachedDocument, normalize_path, and DocxMCPServer are
imported before initializing logger.

Comment thread docx_editor_mcp/tools.py
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@docx_editor_mcp/tools.py`:
- Around line 151-157: The cache removal uses the raw input path causing
mismatches with the normalized key used by _get_cached_or_error; change the
removal to use the normalized cache key (e.g., use cached.path or call
normalize_path(path)) instead of the raw path so server.cache.remove(...)
removes the correct entry after closing the document (update both occurrences
around cached.document.close / server.cache.remove and the similar block at the
other location).

Comment thread docx_editor_mcp/tools.py Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

♻️ Duplicate comments (2)
tests/mcp/test_tools.py (1)

442-446: ⚠️ Potential issue | 🟡 Minor

Revision tests still skip assertions when no revisions are produced.

Both tests can pass without validating behavior if revisions["revisions"] is empty, which hides failures in revision generation.

Also applies to: 460-463

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/mcp/test_tools.py` around lines 442 - 446, The tests in
tests/mcp/test_tools.py currently skip assertions when revisions["revisions"] is
empty, hiding failures; update the test to assert that revisions["revisions"] is
non-empty before extracting rev_id and calling accept_revision, e.g., add an
assertion like assert revisions["revisions"], and do the same fix for the second
occurrence (the block around lines 460-463) so the test fails fast when no
revisions are produced rather than silently passing.
docx_editor_mcp/tools.py (1)

157-157: ⚠️ Potential issue | 🟠 Major

Remove cache entries with the normalized key.

Lines 157 and 187 remove by raw path, but this module consistently uses normalized paths for server.cache.get(...) and server.cache.put(...). If a caller passes a relative or differently-cased path, close/reload can leave a closed document behind in the cache, and the next open_document() will hand back a stale handle instead of reopening cleanly.

♻️ Proposed fix
-    server.cache.remove(path)
+    server.cache.remove(cached.path)
@@
-    server.cache.remove(path)
+    server.cache.remove(cached.path)

Also applies to: 187-187

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docx_editor_mcp/tools.py` at line 157, The cache removal calls use the raw
path (server.cache.remove(path)) but the module consistently keys cache.get/put
with a normalized path; change both removal sites to compute and use the same
normalized key before removing (e.g., normalized_path = <the existing
normalization helper>(path) and then call server.cache.remove(normalized_path))
so that server.cache.remove uses the identical normalized key used by
server.cache.get and server.cache.put.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docx_editor_mcp/cache.py`:
- Around line 109-113: The current insertion path inserts
self._cache[normalized] even if _evict_lru() failed, which can let size exceed
max_documents; update the logic in the methods that perform the capacity check
(the block using normalized, self._cache, self.size, self.max_documents) to only
insert the new document if eviction succeeded or capacity is already available —
e.g., have _evict_lru() return a boolean (or raise) and check its result before
assigning to self._cache[normalized], or loop-calling _evict_lru() until
self.size < self.max_documents; apply the same guard to the other similar block
(lines referenced around 145-153) so insertion is conditional on successful
eviction.
- Around line 179-185: getpass.getuser() may return an empty string; update the
try/except block around getpass.getuser() so after calling getpass.getuser() you
treat empty-string results as missing and fall back to "Reviewer" before
assigning self._session_author and returning, i.e., change the logic in the
block that calls getpass.getuser() to check if default is truthy (non-empty) and
if not set default = "Reviewer" and then assign self._session_author = default
and return default, True.

In `@docx_editor_mcp/server.py`:
- Around line 50-59: The main() function returns immediately after creating the
server, so start its run loop to keep the MCP server alive: after creating the
server with create_server() call its long-running serve method (e.g.,
server.serve_forever() or server.run() / server.start() depending on the server
object's API), wrap that call in a try/except KeyboardInterrupt to log shutdown
and call any server.close()/shutdown() cleanup method, and remove the TODO
placeholder; reference the main function and the create_server()/server object
(and server.name) to locate where to add the blocking serve loop and graceful
shutdown handling.

In `@docx_editor_mcp/tools.py`:
- Around line 308-343: The insert tools currently call
Document.insert_after()/insert_before() without the required keyword-only
paragraph argument, causing a TypeError; update the tool function signatures
(e.g., insert_after and insert_before) to accept a new paragraph: int | None (or
appropriate type), add it to the docstring/Args list, and forward it to the
underlying API call by passing paragraph=paragraph in the call to
cached.document.insert_after(...) and cached.document.insert_before(...); ensure
you preserve existing defaults and mark_dirty()/error handling remains
unchanged.

---

Duplicate comments:
In `@docx_editor_mcp/tools.py`:
- Line 157: The cache removal calls use the raw path (server.cache.remove(path))
but the module consistently keys cache.get/put with a normalized path; change
both removal sites to compute and use the same normalized key before removing
(e.g., normalized_path = <the existing normalization helper>(path) and then call
server.cache.remove(normalized_path)) so that server.cache.remove uses the
identical normalized key used by server.cache.get and server.cache.put.

In `@tests/mcp/test_tools.py`:
- Around line 442-446: The tests in tests/mcp/test_tools.py currently skip
assertions when revisions["revisions"] is empty, hiding failures; update the
test to assert that revisions["revisions"] is non-empty before extracting rev_id
and calling accept_revision, e.g., add an assertion like assert
revisions["revisions"], and do the same fix for the second occurrence (the block
around lines 460-463) so the test fails fast when no revisions are produced
rather than silently passing.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 755d5d79-024c-440e-b970-6dc9366b410f

📥 Commits

Reviewing files that changed from the base of the PR and between 8e7cb03 and fcd069d.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (17)
  • .claude-plugin/.mcp.json
  • README.md
  • docx_editor_mcp/__init__.py
  • docx_editor_mcp/__main__.py
  • docx_editor_mcp/cache.py
  • docx_editor_mcp/server.py
  • docx_editor_mcp/tools.py
  • openspec/changes/add-mcp-server/design.md
  • openspec/changes/add-mcp-server/proposal.md
  • openspec/changes/add-mcp-server/specs/mcp-server/spec.md
  • openspec/changes/add-mcp-server/tasks.md
  • pyproject.toml
  • skills/docx/SKILL.md
  • tests/mcp/__init__.py
  • tests/mcp/test_cache.py
  • tests/mcp/test_server.py
  • tests/mcp/test_tools.py

Comment thread docx_editor_mcp/cache.py
Comment thread docx_editor_mcp/cache.py
Comment thread docx_editor_mcp/server.py Outdated
Comment thread docx_editor_mcp/tools.py
Comment thread tests/test_mcp/test_tools.py
pablospe added 17 commits April 4, 2026 10:59
Add proposal for persistent DOM caching via MCP server to improve
performance for interactive document editing sessions.

Key decisions:
- Separate docx_editor_mcp/ package (optional dependency)
- LRU cache with 10 document limit
- mtime-based external change detection
- Session author memory with cross-platform default
- Path normalization for consistent cache keys
- Plugin auto-configuration via .mcp.json
- MCP-first skill approach
Implements OpenSpec tasks 1.1-1.6:
- Create docx_editor_mcp/ package structure
- DocumentCache with configurable max documents
- CachedDocument with mtime tracking for external change detection
- Path normalization (resolves ~, relative paths, symlinks)
- LRU eviction with save-on-evict for dirty documents
- Session author memory with cross-platform default (getpass.getuser)

All 24 cache tests pass, no regressions (513 total tests pass).
Implements OpenSpec tasks 2.1-2.2:
- DocxMCPServer wrapper with DocumentCache
- create_server() factory function
- Graceful shutdown saves all dirty documents (best-effort)
- main() entry point for CLI

8 new tests, 32 total MCP tests pass.
Implements OpenSpec task 3.1-3.5:
- Document lifecycle: open, save, close, reload, force_save
- Track changes: replace_text, delete_text, insert_after, insert_before
- Comments: add, list, reply, resolve, delete
- Revisions: list, accept, reject, accept_all, reject_all
- Read tools: find_text, count_matches, get_visible_text
- External change detection blocks edits when file modified

40 new tests, 72 total MCP tests pass.
- Add [project.optional-dependencies] with mcp>=1.0.0
- Add [project.scripts] entry point: mcp-server-docx
- Add __main__.py for python -m docx_editor_mcp
- Update wheel packages to include docx_editor_mcp
- Update coverage and isort settings for new package
- Document all MCP tools: lifecycle, track changes, comments, revisions, read
- Add workflow examples for document review
- Document external change detection and best practices
- Remove Python script examples for editing (now uses MCP tools)
- Keep pandoc and python-docx for reading/navigation
- Document installation with [mcp] extra
- Document running the server
- Document Claude Code configuration (manual and auto)
- List all available MCP tools
- Update tasks.md marking all sections complete
- Handle save failure during LRU eviction (don't evict if save fails)
- Normalize path in CachedDocument.__post_init__ for consistency
- Add logging for eviction errors
- Fix type annotations for proper type narrowing
- Add debug logging when document.close() fails in close_document/reload_document
- Use logger.exception() instead of logger.error() for better tracebacks in shutdown
Add openspec proposal for search_text, get_paragraph_text,
get_document_info, paginated list_paragraphs, and smart
get_visible_text truncation to support large document workflows.
…raphs

- Complete FastMCP server wiring with lifespan, tool registration, and stdio transport
- Add list_paragraphs and batch_edit MCP tools
- Update all track changes tools to require paragraph hash-anchored refs
- Rename tests/mcp/ to tests/test_mcp/ to fix module shadowing with mcp package
- Add tests for FastMCP app creation and async lifespan
- Edit tools now return new_ref (paragraph ref) instead of change_id
- Add rewrite_paragraph and batch_rewrite MCP tools
- Update tests for new return key names
Core library:
- search_text(query, context_chars) with paragraph refs and context
- get_paragraph_text(paragraphs) to read specific paragraphs by ref
- get_document_info() for paragraph count, word count, heading outline
- list_paragraphs now supports start/limit pagination

MCP layer:
- Register search_text, get_paragraph_text, get_document_info tools
- get_visible_text auto-truncates at 10K chars with exploration hint
- list_paragraphs returns total count for pagination
- Updated SERVER_INSTRUCTIONS to guide LLMs toward exploration workflow
pablospe added 4 commits April 4, 2026 13:51
…tools

These tool functions were added in this PR but had zero test coverage.
Skipped: AssertionError "typo" (it's the correct Python builtin name),
result variable reuse pattern (pre-existing, works at runtime).
- Raise RuntimeError when cache eviction fails (prevents capacity violation)
- Handle empty getpass.getuser() with fallback to "Reviewer"
- Use cached.path (normalized) in remove() calls for consistency
CI doesn't install the [mcp] extra, so tests importing from
mcp.server.fastmcp fail with ModuleNotFoundError. Add skipif
markers to TestFastMCPApp and TestLifespan classes.

Also includes ruff format fix for document.py line length.
- Remove unused list_paragraphs import in test_tools.py
- Add ty: ignore[unresolved-import] for mcp imports (optional dep)
@pablospe pablospe marked this pull request as draft April 17, 2026 21:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant