diff --git a/server/tools/_services.py b/server/tools/_services.py new file mode 100644 index 0000000..197ade8 --- /dev/null +++ b/server/tools/_services.py @@ -0,0 +1,22 @@ +from __future__ import annotations + +from server.state import get_service_registry +from server.store.service_registry import load_effective_services + + +async def unknown_service_message(service: str) -> str: + """Explain an unknown-service lookup in terms of what the server can actually see. + + The effective service list merges config.yaml with dynamically-registered + services, so a miss has two very different causes: a genuine typo, or a server + that loaded no config at all (e.g. config.yaml not mounted into the container). + Listing the known names tells those apart instead of guessing at config.yaml. + """ + services = await load_effective_services(get_service_registry()) + if not services: + return ( + f"Service `{service}` not found — this server has no services at all " + f"(config.yaml is empty or was never loaded, and nothing is registered)." + ) + known = ", ".join(f"`{s.name}`" for s in sorted(services, key=lambda s: s.name)) + return f"Service `{service}` not found. Known services: {known}." diff --git a/server/tools/history.py b/server/tools/history.py index 4f51c3e..0038ddb 100644 --- a/server/tools/history.py +++ b/server/tools/history.py @@ -5,6 +5,7 @@ from server.embeddings import get_embedding_provider from server.indexer.git_history import GitHistoryPipeline from server.state import get_commit_store +from server.tools._services import unknown_service_message def register_history_tools(mcp: MCPServer) -> None: @@ -126,7 +127,7 @@ async def index_history( if service: result = await pipeline.index_service(service, force=force) if "error" in result: - return f"Service `{service}` not found in config.yaml." + return await unknown_service_message(service) lines = [ f"Git history indexed for `{service}`:", f"- New commits: {result['new']}", diff --git a/server/tools/index.py b/server/tools/index.py index c2038a8..f6d8d16 100644 --- a/server/tools/index.py +++ b/server/tools/index.py @@ -4,6 +4,7 @@ from server.indexer.pipeline import IndexPipeline from server.state import get_store +from server.tools._services import unknown_service_message def register_index_tools(mcp: MCPServer) -> None: @@ -25,7 +26,7 @@ async def reindex( if service: result = await pipeline.index_service(service, force=force) if "error" in result: - return f"Service `{service}` not found in config.yaml." + return await unknown_service_message(service) return ( f"Reindex complete for `{service}`:\n" f"- Files indexed: {result['files']}\n" diff --git a/tests/tools/test_history.py b/tests/tools/test_history.py index 1122aa4..b0054cf 100644 --- a/tests/tools/test_history.py +++ b/tests/tools/test_history.py @@ -1,5 +1,6 @@ from __future__ import annotations +from types import SimpleNamespace from unittest.mock import AsyncMock, patch from server.tools.history import register_history_tools @@ -111,7 +112,7 @@ async def test_get_commit_formats_changed_files_and_truncation() -> None: assert "Diff truncated" in result -async def test_index_history_reports_unknown_service() -> None: +async def test_index_history_unknown_service_lists_known_services() -> None: index_history = _tool("index_history") pipeline = AsyncMock() pipeline.index_service.return_value = {"error": 1} @@ -119,10 +120,15 @@ async def test_index_history_reports_unknown_service() -> None: with ( patch("server.tools.history.get_commit_store", return_value=AsyncMock()), patch("server.tools.history.GitHistoryPipeline", return_value=pipeline), + patch("server.tools._services.get_service_registry"), + patch( + "server.tools._services.load_effective_services", + AsyncMock(return_value=[SimpleNamespace(name="orders")]), + ), ): result = await index_history(service="unknown") - assert result == "Service `unknown` not found in config.yaml." + assert result == "Service `unknown` not found. Known services: `orders`." async def test_index_history_single_service_reports_diff_updates() -> None: diff --git a/tests/tools/test_index.py b/tests/tools/test_index.py index 376ce2b..ba677f8 100644 --- a/tests/tools/test_index.py +++ b/tests/tools/test_index.py @@ -1,5 +1,6 @@ from __future__ import annotations +from types import SimpleNamespace from unittest.mock import AsyncMock, patch from server.tools.index import register_index_tools @@ -10,7 +11,7 @@ def _tool(name: str): return get_tool(register_index_tools, name) -async def test_reindex_reports_unknown_service() -> None: +async def test_reindex_unknown_service_lists_known_services() -> None: reindex = _tool("reindex") pipeline = AsyncMock() pipeline.index_service.return_value = {"error": 1, "files": 0, "chunks": 0} @@ -18,10 +19,40 @@ async def test_reindex_reports_unknown_service() -> None: with ( patch("server.tools.index.get_store", return_value=AsyncMock()), patch("server.tools.index.IndexPipeline", return_value=pipeline), + patch("server.tools._services.get_service_registry"), + patch( + "server.tools._services.load_effective_services", + AsyncMock( + return_value=[ + SimpleNamespace(name="orders"), + SimpleNamespace(name="catalog"), + ] + ), + ), ): result = await reindex(service="unknown") - assert result == "Service `unknown` not found in config.yaml." + assert result == "Service `unknown` not found. Known services: `catalog`, `orders`." + + +async def test_reindex_unknown_service_flags_empty_service_list() -> None: + reindex = _tool("reindex") + pipeline = AsyncMock() + pipeline.index_service.return_value = {"error": 1, "files": 0, "chunks": 0} + + with ( + patch("server.tools.index.get_store", return_value=AsyncMock()), + patch("server.tools.index.IndexPipeline", return_value=pipeline), + patch("server.tools._services.get_service_registry"), + patch( + "server.tools._services.load_effective_services", + AsyncMock(return_value=[]), + ), + ): + result = await reindex(service="auth-server") + + assert "has no services at all" in result + assert "never loaded" in result async def test_reindex_single_service_reports_counts() -> None: