From c17a33152f884470cf25e093e7be45639046c4a0 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 30 Jun 2026 18:15:45 +0800 Subject: [PATCH] feat(files-mcp): mount at /files-mcp via MCP_SERVICES registry - main.py: import files_app, append McpService entry to MCP_SERVICES. Lifespan already iterates the list, so no edit to the lifespan function. - tests/conftest.py: autouse fixture rebinds files_mcp.core.path_guard._ROOT to a per-test tmp_path so the sandbox is fresh for every test. - files_mcp/__init__.py: drop the try/except around the server import now that server.py exists, restoring fail-fast on a missing/corrupt server module in production. The _init_root() try/except stays, since pytest collection can import the package before FILES_MCP_ROOT is set. Verified via uv run pytest: 327 tests pass, no regressions. Co-Authored-By: Claude Fable 5 --- files_mcp/__init__.py | 13 ++++++------- main.py | 6 ++++++ tests/conftest.py | 17 +++++++++++++++++ 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/files_mcp/__init__.py b/files_mcp/__init__.py index aa46cc6..22f4380 100644 --- a/files_mcp/__init__.py +++ b/files_mcp/__init__.py @@ -16,13 +16,12 @@ try: except RuntimeError: _ROOT = None -# Re-export the FastAPI app once the sandbox root has been initialized. -# server.py is created in a later task; importing it lazily keeps this -# package importable (and unit-testable) before that task lands. -try: # pragma: no cover - exercised once server.py exists - from files_mcp.server import app # noqa: E402 -except ImportError: - app = None # type: ignore[assignment] +# Re-export the FastAPI app. server.py now exists, so this import is +# unconditional — a missing/corrupt server.py fails loud at import time +# (production-correct behavior). The sandbox root is only needed by the +# tool routes at request time, not at import, so this stays safe to import +# even when FILES_MCP_ROOT is unset (e.g. during pytest collection). +from files_mcp.server import app # noqa: E402 ROOT: Path | None = _ROOT diff --git a/main.py b/main.py index b06e52a..d524c32 100644 --- a/main.py +++ b/main.py @@ -10,6 +10,7 @@ from fastapi import FastAPI from common.factory import init_mcp_server from common.logging import logger +from files_mcp import app as files_app from spark_executor import app as spark_executor_app @@ -39,6 +40,11 @@ MCP_SERVICES: list[McpService] = [ app=spark_executor_app, mount_path="/spark-executor-mcp", ), + McpService( + name="files", + app=files_app, + mount_path="/files-mcp", + ), ] diff --git a/tests/conftest.py b/tests/conftest.py index e0591c4..fd108c4 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -6,3 +6,20 @@ from pathlib import Path ROOT = Path(__file__).resolve().parents[1] if str(ROOT) not in sys.path: sys.path.insert(0, str(ROOT)) + + +import pytest + + +@pytest.fixture(autouse=True) +def _isolate_files_mcp_root(tmp_path, monkeypatch): + """Point files_mcp at a fresh tmp_path for every test. + + Sets FILES_MCP_ROOT and rebinds files_mcp.core.path_guard's module-level + _ROOT, so each test starts with an empty sandbox. Spark_executor tests + are unaffected by this fixture — they don't touch the files_mcp sandbox. + """ + monkeypatch.setenv("FILES_MCP_ROOT", str(tmp_path)) + from files_mcp.core import path_guard + path_guard.set_root_for_testing(tmp_path.resolve()) + yield