Two cleanup-plan items, both Python-side.
Item 29 -- `mirage mcp`. TypeScript shipped a six-tool stdio MCP server;
Python had none, so a pip-install user could not point Cursor or Claude
Desktop at a workspace and `mirage --help` differed by distribution.
Adding the entry point alone would have duplicated the tools, because
this side kept them private inside the Claude Agent SDK integration, so
the shared layer comes first:
- agents/tool_descriptions.py -- the six strings, one copy.
- agents/tool_operations.py -- MirageToolOperations, lifted out of the
SDK server's private _MirageTools.
- agents/file_version.py -- stale-write protection, which this side
lacked entirely. TS stamps stored bytes; here the stamp covers the
rendered bytes, because this read tool has always rendered and an
edit must search what the agent was actually shown.
- agents/mcp/server.py + cli/mcp.py -- the server and `mirage mcp`.
- server/workspace_config.py -- config discovery (candidates, env
names, walk up from cwd), which Python had nowhere, so every entry
point had to be handed an explicit path.
The server is the low-level MCP Server rather than FastMCP: FastMCP does
not forward a version, and TS advertises one. Handlers are bound methods,
not decorated closures, so nothing nests.
Item 28 -- the mypy allowlist. 54 modules opted *in* to annotation
checking against 1826, so the default was unchecked and every new file
joined the unchecked side. The default is now strict, with a list of
what is not yet annotated that only shrinks. 166 annotations cleared
along the way; the remainder is named module by module.
Two real defects surfaced by the annotations, neither of them typing:
- Workspace._original_open / _original_os were invented by assignment
in lifecycle.patch_process, so unpatch without a patch raised
AttributeError. Declared, and the restore is guarded.
- sed_generic declared a non-optional writer while its own docstring
and its `write_bytes is None` branch said otherwise; the builder
passes None whenever the backend cannot write.
Tests keep the PathSpec rule instead of full strict: measured, full
strict on python/tests is 2374 errors, of which 634 are `str` where a
pydantic field declares SecretStr -- which pydantic coerces at runtime --
and most of the rest is the monkeypatched-fake pattern CLAUDE.md
sanctions. The rule that is violated for real is PathSpec, 19 times, and
scripts/check_test_pathspec.py now holds that line. One of the 19 was a
latent AttributeError: tests/e2e passes a str to s3 write_bytes, which
reads .mount_path, and the test skips without a live versioned bucket.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds NullIndexCacheStore (NULL_INDEX): a no-op index whose lookups miss
and whose writes are discarded, so behavior is identical to the old
index=None path. Every backend core op now takes a non-optional
index: IndexCacheStore = NULL_INDEX instead of IndexCacheStore | None =
None, and the 'if index is not None' cache guards are deleted (the cache
read/populate run unconditionally against a real store or the no-op).
Why a default and not required: backend ops are also invoked through
untyped Callables (the generic find/grep walk, filetype handlers), which
mypy cannot check; requiring the arg there crashed at runtime (find over
a mount returned nothing). Defaulting to NULL_INDEX is crash-safe and
behavior-preserving, and still lets the typed dispatch/wrapper path
thread the real mount index.
Also fixes a latent bug the change surfaced: the databricks_volume
rename/mkdir/rmdir ops-layer wrappers passed kwargs.get('index')
positionally, sending None into a now-guardless stat; they fall back to
NULL_INDEX. Resource fingerprint() methods and du/exists helpers thread
self._index or NULL_INDEX. Baseline 441 -> 343.
Tests that passed index=None or omitted it now pass NULL_INDEX.
- rename original -> virtual, as_typed -> raw_path
- remove prefix field and strip_prefix/key properties
- add required resource_path stamped by the mount at dispatch
- add strip_mount/mount_key/rekey/mount_prefix_of in utils/key_prefix
- mirror in TypeScript (resourcePath, rawPath, mountKey et al)