Skip to content

Commit cd30a1f

Browse files
HumanBean17claude
andcommitted
fix: MCP server loads embedding config from .java-codebase-rag.yml (#238)
The MCP server now reads embedding.model and embedding.device from .java-codebase-rag.yml at startup and applies them to os.environ, matching the behavior of CLI commands (init, reprocess, etc.). Previously, the MCP server ignored YAML config and always fell back to the HuggingFace hub default, causing search tool failures when offline or behind corporate proxies. Changes: - server.py: Call resolve_operator_config() in main() and apply to os.environ - tests: Add tests for MCP server YAML config loading and precedence Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 6b883d9 commit cd30a1f

2 files changed

Lines changed: 70 additions & 1 deletion

File tree

server.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
emit_lance_cocoindex_finish,
1717
emit_lance_cocoindex_start,
1818
)
19-
from java_codebase_rag.config import emit_legacy_env_hints_if_present, resolved_sbert_model_for_process_env
19+
from java_codebase_rag.config import emit_legacy_env_hints_if_present, resolved_sbert_model_for_process_env, resolve_operator_config
2020
from kuzu_queries import KuzuGraph, resolve_kuzu_path
2121
from mcp.server.fastmcp import FastMCP
2222
from pydantic import BaseModel, Field
@@ -570,6 +570,13 @@ async def resolve(
570570

571571
def main() -> None:
572572
emit_legacy_env_hints_if_present()
573+
574+
# Load YAML config and apply embedding settings to environment
575+
# This ensures SBERT_MODEL and SBERT_DEVICE from .java-codebase-rag.yml are available
576+
# before any tool handler runs (same behavior as CLI path)
577+
cfg = resolve_operator_config(source_root=_project_root())
578+
cfg.apply_to_os_environ()
579+
573580
asyncio.run(create_mcp_server().run_stdio_async())
574581

575582

tests/test_java_codebase_rag_cli.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -909,3 +909,65 @@ def test_cli_unknown_subcommand_returns_error(tmp_path, monkeypatch) -> None:
909909
env = _base_env(tmp_path)
910910
proc = _run_cli(["bogus"], env=env)
911911
assert proc.returncode == 2
912+
913+
914+
def test_mcp_server_loads_yaml_config_at_startup(
915+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch,
916+
) -> None:
917+
"""MCP server main() loads YAML config and applies to os.environ (issue #238).
918+
919+
Verifies that main() calls resolve_operator_config with the correct source_root
920+
and applies the result to os.environ. Uses mocks to avoid loading real models
921+
or leaking env state (e.g. SBERT_DEVICE=cuda) to subsequent tests.
922+
"""
923+
import server as server_mod
924+
from unittest.mock import MagicMock
925+
926+
fake_cfg = MagicMock()
927+
fake_cfg.apply_to_os_environ = MagicMock()
928+
929+
monkeypatch.setenv("JAVA_CODEBASE_RAG_SOURCE_ROOT", str(tmp_path))
930+
monkeypatch.setattr(server_mod, "resolve_operator_config", MagicMock(return_value=fake_cfg))
931+
932+
def fake_asyncio_run(awaitable, *, debug=None):
933+
return None
934+
935+
monkeypatch.setattr("asyncio.run", fake_asyncio_run)
936+
937+
server_mod.main()
938+
939+
# resolve_operator_config should have been called with the project root
940+
server_mod.resolve_operator_config.assert_called_once_with(source_root=server_mod._project_root())
941+
# apply_to_os_environ should have been called to set env vars
942+
fake_cfg.apply_to_os_environ.assert_called_once()
943+
944+
945+
def test_mcp_server_yaml_config_precedence_env_over_yaml(
946+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch,
947+
) -> None:
948+
"""MCP server passes _project_root() to resolve_operator_config (issue #238).
949+
950+
Precedence (env > YAML > default) is already tested by
951+
test_embedding_model_precedence_cli_over_env_over_yaml_over_default.
952+
This test verifies that main() delegates to resolve_operator_config
953+
with the correct source root, which handles precedence internally.
954+
"""
955+
import server as server_mod
956+
from unittest.mock import MagicMock, call
957+
958+
fake_cfg = MagicMock()
959+
fake_cfg.apply_to_os_environ = MagicMock()
960+
961+
# Set source root so _project_root() returns it
962+
monkeypatch.setenv("JAVA_CODEBASE_RAG_SOURCE_ROOT", str(tmp_path))
963+
monkeypatch.setattr(server_mod, "resolve_operator_config", MagicMock(return_value=fake_cfg))
964+
965+
def fake_asyncio_run(awaitable, *, debug=None):
966+
return None
967+
968+
monkeypatch.setattr("asyncio.run", fake_asyncio_run)
969+
970+
server_mod.main()
971+
972+
server_mod.resolve_operator_config.assert_called_once()
973+
assert server_mod.resolve_operator_config.call_args.kwargs["source_root"] == server_mod._project_root()

0 commit comments

Comments
 (0)