From ce8e71f67b309d5f56cdebfc816a92c315b051fe Mon Sep 17 00:00:00 2001 From: mochan Date: Sat, 30 May 2026 19:24:08 +0530 Subject: [PATCH] fix(mcp): workspace_state distinguishes not-configured from no-repos MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When `~/.codegraph/workspace.yml` didn't exist, `workspace_state` returned `{workspace_size: 0, repos: []}` — same shape as a configured but empty workspace. LLMs read this as "everything is broken". Result now carries a `status` field: - `workspace_not_configured` when the YAML doesn't exist - `workspace_empty` when it exists with no repos - `ok` when there's at least one repo The not-configured case includes a message clarifying that the local single-repo MCP tools work independently — only the cross-repo tools are affected. v0.1.2 backlog item #9. --- CHANGELOG.md | 9 ++++++ codegraph/mcp_server/server.py | 22 ++++++++++++-- tests/test_workspace_state_status.py | 44 ++++++++++++++++++++++++++++ 3 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 tests/test_workspace_state_status.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 8896bcb..9d5fedf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -100,6 +100,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ...}`. New `GraphNotBuiltError` exposed from `codegraph.mcp_server.server` for callers that want to handle it explicitly. +- **`workspace_state` MCP tool now distinguishes "not configured" from + "no repos".** When `~/.codegraph/workspace.yml` didn't exist, the + tool returned `{workspace_size: 0, repos: []}` — indistinguishable + from a configured-but-empty workspace. LLMs paraphrased this as + "everything is broken" and cascaded into wrong-answer territory. + Result now carries a `status` field + (`workspace_not_configured` / `workspace_empty` / `ok`) with an + actionable message: "local-graph MCP tools work independently — this + only affects cross-repo tools. Run `codegraph workspace init` …". ## [0.1.1] — 2026-05-24 diff --git a/codegraph/mcp_server/server.py b/codegraph/mcp_server/server.py index 012f060..0867aba 100644 --- a/codegraph/mcp_server/server.py +++ b/codegraph/mcp_server/server.py @@ -798,8 +798,26 @@ def _handle_workspace_state( from codegraph.workspace.config import load_workspace, resolve_workspace_path from codegraph.workspace.operations import workspace_state - cfg = load_workspace(resolve_workspace_path()) - return workspace_state(cfg) + workspace_path = resolve_workspace_path() + if not workspace_path.exists(): + return { + "status": "workspace_not_configured", + "workspace_path": str(workspace_path), + "message": ( + "polycodegraph: no workspace configured at " + f"{workspace_path}. The local-graph MCP tools (find_symbol, " + "callers, callees, blast_radius, dataflow_trace, …) work " + "independently — this only affects the cross-repo workspace " + "tools. Run `codegraph workspace init` then " + "`codegraph workspace add ` to enable them." + ), + "workspace_size": 0, + "repos": [], + } + cfg = load_workspace(workspace_path) + result = workspace_state(cfg) + result["status"] = "ok" if cfg.repos else "workspace_empty" + return result @_register( diff --git a/tests/test_workspace_state_status.py b/tests/test_workspace_state_status.py new file mode 100644 index 0000000..edc3522 --- /dev/null +++ b/tests/test_workspace_state_status.py @@ -0,0 +1,44 @@ +"""Tests for `workspace_state` status field (v0.1.2 #9).""" +from __future__ import annotations + +from pathlib import Path + +import yaml + +from codegraph.mcp_server.server import _handle_workspace_state + + +def test_workspace_not_configured_when_file_missing( + tmp_path: Path, monkeypatch +) -> None: + monkeypatch.setenv( + "CODEGRAPH_WORKSPACE_FILE", str(tmp_path / "nope" / "workspace.yml") + ) + result = _handle_workspace_state(None, {}) + assert result["status"] == "workspace_not_configured" + assert "codegraph workspace init" in result["message"] + assert "local-graph MCP tools" in result["message"] + assert result["workspace_size"] == 0 + assert result["repos"] == [] + + +def test_workspace_empty_when_file_exists_with_no_repos( + tmp_path: Path, monkeypatch +) -> None: + f = tmp_path / "workspace.yml" + f.write_text(yaml.dump({"repos": []})) + monkeypatch.setenv("CODEGRAPH_WORKSPACE_FILE", str(f)) + result = _handle_workspace_state(None, {}) + assert result["status"] == "workspace_empty" + assert result["workspace_size"] == 0 + + +def test_workspace_ok_when_repos_present(tmp_path: Path, monkeypatch) -> None: + repo = tmp_path / "myrepo" + repo.mkdir() + f = tmp_path / "workspace.yml" + f.write_text(yaml.dump({"repos": [{"path": str(repo), "name": "myrepo"}]})) + monkeypatch.setenv("CODEGRAPH_WORKSPACE_FILE", str(f)) + result = _handle_workspace_state(None, {}) + assert result["status"] == "ok" + assert result["workspace_size"] == 1