Serve the database the MCP command selected

`run_mcp` passed `_path`, the local stand-in a URI-backed ref resolves to for
display. A path overrides `lancedb.uri`, so `--db-name` on an S3 database
served the local default. It passes the ref's own path now, None where a URI
placed the database, and `create_mcp_server` accepts that.

The client opened around the server is gone. It never served a request, and it
opened the scope rather than the derived path, so startup validated the remote
database while the server read the local one.
This commit is contained in:
Yiorgis Gozadinos 2026-08-27 14:41:16 +03:00
parent 9ed66e3a06
commit 474245ab59
No known key found for this signature in database
3 changed files with 66 additions and 19 deletions

View file

@ -918,20 +918,25 @@ class HaikuRAGApp:
host: str = "127.0.0.1",
port: int = 8001,
):
"""Run the MCP server until interrupted."""
async with HaikuRAG._covering(
self.scope, self.config, read_only=self.read_only
):
server = create_mcp_server(
self._path, config=self._store_config, read_only=self.read_only
)
try:
if transport == "stdio":
await server.run_stdio_async()
else:
logger.info(f"Starting MCP server on {host}:{port}")
await server.run_http_async(
transport="streamable-http", host=host, port=port
)
except KeyboardInterrupt:
pass
"""Run the MCP server until interrupted.
The server opens its own client and validates it on startup, so nothing
is opened here first.
"""
# The ref's own path, not `_path`: a URI-backed database has none, and
# the local stand-in would override the URI in `_store_config`.
server = create_mcp_server(
self._one.db_path,
config=self._store_config,
read_only=self.read_only,
)
try:
if transport == "stdio":
await server.run_stdio_async()
else:
logger.info(f"Starting MCP server on {host}:{port}")
await server.run_http_async(
transport="streamable-http", host=host, port=port
)
except KeyboardInterrupt:
pass

View file

@ -22,12 +22,16 @@ def _decode_images(images_base64: list[str] | None) -> list[bytes] | None:
def create_mcp_server(
db_path: Path, config: AppConfig | None = None, read_only: bool = False
db_path: Path | None = None,
config: AppConfig | None = None,
read_only: bool = False,
) -> FastMCP:
"""Create an MCP server with the specified database path.
Args:
db_path: Path to the database file.
db_path: Path to the database file, or None to let `config` place it. A
path overrides a configured `lancedb.uri`, so a URI-backed database
must pass None rather than a local stand-in.
config: Configuration to use.
read_only: If True, write tools (add_document_*, delete_document) are not registered.
"""

View file

@ -603,6 +603,44 @@ class TestMCPClientLifetime:
assert opens == 1
@pytest.mark.asyncio
async def test_a_uri_backed_database_is_not_replaced_by_a_local_path(
self, monkeypatch
):
"""A path overrides `lancedb.uri`, so the server gets the database's own
path None where a URI placed it not the local stand-in a URI-backed
ref resolves to for display."""
from haiku.rag.app import HaikuRAGApp
from haiku.rag.client.scope import DatabaseScope
from haiku.rag.config.models import AppConfig, LanceDBConfig
config = AppConfig(
lancedb=LanceDBConfig(databases={"prod": "s3://bucket/prod.lancedb"})
)
seen: dict = {}
class _Server:
async def run_stdio_async(self):
return None
def fake_create(db_path=None, config=None, read_only=False):
seen["db_path"] = db_path
seen["config"] = config
return _Server()
monkeypatch.setattr("haiku.rag.app.create_mcp_server", fake_create)
app = HaikuRAGApp(
scope=DatabaseScope.resolve(config, database_name="prod"), config=config
)
await app.run_mcp(transport="stdio")
assert seen["db_path"] is None
[ref] = DatabaseScope.resolve(
seen["config"], database_path=seen["db_path"]
).databases
assert ref.uri == "s3://bucket/prod.lancedb"
@pytest.mark.asyncio
async def test_startup_fails_when_the_database_cannot_open(self, tmp_path):
mcp = create_mcp_server(tmp_path / "does-not-exist.lancedb", read_only=True)