feat: improve MCP server -- roam_ prefix, 13 new tools, structured errors, roam mcp command#10
Conversation
…ors, roam mcp command Add roam_ namespace prefix to all 61 MCP tools to prevent collisions when agents run multiple MCP servers. Add 13 new tool wrappers for high-value CLI commands (diff, symbol, deps, uses, weather, debt, n1, auth-gaps, over-fetch, missing-index, orphan-routes, migration-safety, api-drift). Add structured error handling with error codes and recovery hints. Add `roam mcp` CLI command with stdio/SSE transport and auto- indexing. Update lite mode from 15 to 16 core tools.
8acae14 to
1c0ecc9
Compare
The --json flag is a global option on the CLI group and must appear before the subcommand name. The workflows had it after the subcommand which caused "No such option: --json" errors.
Lite mode (16 core tools) is now the default. Set ROAM_MCP_LITE=0 to expose all 61 tools. This keeps the tool list manageable for agents by default.
Cranot
left a comment
There was a problem hiding this comment.
Review: PR #10 — MCP Server Improvements
Hugo, first off — genuinely appreciate the care you put into this PR. The write-up is excellent, the motivation is well-reasoned, and the code follows our existing patterns closely. The "WHEN TO USE" docstrings on the new tools are particularly well done. Let me go through everything in detail.
What I like
- CI workflow fixes — The
--jsonflag position fix is correct and valuable.--jsonis a global option on the Click group, so it must come before the subcommand. Good catch. roam_namespace prefix — This is the right approach. Tool-space interference with other MCP servers is a real problem.- Structured error handling —
_classify_error()with error codes + hints is excellent for agent self-recovery. This is the kind of thing that makes MCP tools actually usable by agents. - Graceful FastMCP import — Deferring the
ImportErrorto runtime instead of crashing onimport roam.mcp_serveris a meaningful improvement. IDE tooling and documentation generators can now scan the module without needing fastmcp installed. roam mcpCLI command — Eliminates thefastmcp run roam.mcp_server:mcpfriction. Clean integration with the LazyGroup pattern.- New tool selection —
diff,symbol,deps,uses,weather,debtare indeed the highest-value missing wrappers. The backend analysis suite (n1,auth-gaps, etc.) is a good addition too.
Issues to address
1. Missing pyproject.toml change (critical)
The PR body says:
Added
fastmcp>=2.0to[project.optional-dependencies]under amcpextra
But pyproject.toml is not in the changed files. Without this, pip install roam-code[mcp] won't work, and the README/llms-install docs will be misleading.
Need to add:
[project.optional-dependencies]
mcp = ["fastmcp>=2.0"]
dev = [...]2. description → instructions in FastMCP constructor
The PR changes the constructor kwarg from description= to instructions=. This is a FastMCP 2.0 API change. Two concerns:
- Without the pyproject.toml dependency, users with FastMCP 1.x will hit a silent failure (kwarg ignored) or crash.
- Need to verify this is the correct kwarg name for FastMCP 2.x. If FastMCP 2.0 still accepts
description=, we should keep it for broader compatibility.
3. Lite mode default flip — silent breaking change
Current: _LITE = os.environ.get("ROAM_MCP_LITE", "").lower() in ("1", "true", "yes") — off by default
PR: _LITE = os.environ.get("ROAM_MCP_LITE", "1").lower() in ("1", "true", "yes") — on by default
This silently removes 45 tools for existing MCP users who didn't set the env var. I actually agree that lite-by-default is the right call for agents (fewer tools = better tool selection), but this is a breaking change and needs to be called out clearly.
Suggestion: rather than flipping the existing env var's default, consider introducing ROAM_MCP_ALL=1 as the opt-in for full mode, making the semantics clearer. Or at minimum, document this as a breaking change.
4. _ensure_fresh_index() always re-indexes
Both code paths call _run_roam(["index"], root) unconditionally:
if not index_path.exists():
result = _run_roam(["index"], root) # create
...
return None
result = _run_roam(["index"], root) # always re-index even if freshThe roam index command is incremental (checks mtimes/hashes), so this is correct but adds subprocess startup latency on every roam mcp start (~1-3s). For large repos it could be more. The --no-auto-index flag mitigates this, but it would be better to only re-index if the DB is actually stale.
Could check index.db mtime vs the newest source file, or just rely on the agent calling roam_health to detect staleness. Not blocking, but worth considering.
5. __main__ block not updated
The existing if __name__ == "__main__": mcp.run() at the bottom will crash with AttributeError: 'NoneType' object has no attribute 'run' when fastmcp isn't installed (since mcp = None in that case). Should be guarded.
6. _classify_error pattern matching is broad
"not found" in s will match any stderr containing "not found", including unrelated messages like Python tracebacks saying "module not found". Consider making patterns more specific:
if "not found in index" in s or "symbol not found" in s:7. import click placement
The import click at line ~1298 (mid-file, right before mcp_cmd) works but is unconventional. Since mcp_server.py is imported both for the MCP server and for the CLI command (via LazyGroup), and the @click.command() decorator needs click at definition time, this is fine functionally. But moving it to the top-level imports section would be cleaner.
8. No tests
The PR adds ~430 lines of new code including:
_classify_error()— pure logic, easy to unit test_ensure_fresh_index()— can be tested with mocked subprocess- 13 new tool wrappers — at minimum smoke tests
mcp_cmd— Click CliRunner test
We currently have zero MCP tests. This would be a good opportunity to add at least basic coverage. I can help with this.
Suggestions (non-blocking)
-
Core tools list: I like the swap of
visualize/closurefordiff/deps/uses— the new set is more aligned with daily workflow. Consider also includingroam_tourorroam_repo_mapfor the initial exploration use case that agents frequently need. -
roam mcp --list-toolsflag: Would be helpful for debugging which tools are exposed in a given configuration (especially with the lite/full mode toggle). -
Timeout on
_run_roam: Currently hardcoded at 60s. Some commands (fullindexon a large repo) can take longer. Consider making this configurable or per-command.
Summary
This is a well-structured PR that addresses real friction points. The core ideas (namespace prefix, roam mcp command, structured errors, new tool wrappers) are all sound. The main blockers are:
- Missing pyproject.toml change
- FastMCP API version compatibility
- Lite mode default flip needs explicit documentation/discussion
Happy to work together on resolving these. Would you prefer to push fixes to your branch, or should I prepare suggestions?
Address review issues on PR Cranot#10: - Add fastmcp>=2.0 as optional dependency (pip install roam-code[mcp]) - Move imports to top-level (click, sys) instead of mid-file/in-function - Make _tool() decorator require name param, add _REGISTERED_TOOLS tracking - Replace broad _classify_error if/elif chain with data-driven _ERROR_PATTERNS table, fix pattern ordering so permission errors aren't misclassified - Simplify _ensure_fresh_index (both branches did the same call) - Guard __main__ block for mcp=None - Add --list-tools flag to roam mcp command - Update install messages to reference pip install roam-code[mcp] - Add 50 tests: error classification, subprocess mocking, CLI runner, tool arg construction, pattern table validation - Update CLAUDE.md, README, llms-install with new counts and MCP docs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Resolve README.md conflict: keep 95 commands (main) + 61 MCP tools (PR). Update comparison table tool count from 48 to 61. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
@Cranot Thank you for addressing the problems I created 😅 |
feat: improve MCP server -- roam_ prefix, 13 new tools, structured errors, roam mcp command
Summary
Improve the MCP server: add
roam_prefix to all 61 tools (namespace safety), 13 new tool wrappers for missing high-value commands, structured error handling, and aroam mcpCLI entry point.A note before reviewing
I know you prefer issues first -- I apologize for jumping straight to a PR this time. I've been spending time integrating roam into my Claude Code workflow and hit a few friction points with the MCP server specifically. Rather than describe them abstractly in an issue, it felt more useful to show the changes directly. Please feel free to close this if you'd rather discuss the approach first, or cherry-pick only the parts that make sense. No hard feelings either way.
Why these changes
I've been using roam via both CLI and MCP with Claude Code. The MCP server is already well-designed -- the "WHEN TO USE" pattern in tool descriptions is genuinely excellent for agent tool selection. But I ran into three issues that kept coming up:
1. Namespace collisions with other MCP servers
When running roam alongside other MCP servers, generic tool names like
health,context, andsearch_symbolcollide. Agents get confused about whichsearchto call. Microsoft Research documented this as "tool-space interference" -- the standard fix is prefixing. This PR addsroam_to all tool names.2. Missing tools for daily workflow commands
The commands I use most via CLI --
diff,deps,uses,symbol-- weren't exposed via MCP. Same for the backend analysis suite (n1,auth-gaps,missing-index, etc.) and the health tools (weather,debt). This meant falling back to CLI via Bash for the most common operations, losing the structured JSON responses that make MCP valuable.3. No
roam mcpcommandHaving to run
fastmcp run roam.mcp_server:mcpis friction. Most MCP servers ship a built-in subcommand (tool mcp). This PR addsroam mcpwith stdio/SSE transport options and automatic index freshness checking on startup.What changed
Tool renaming (48 existing tools)
All tools renamed from
tool_nametoroam_tool_namevia the_tool()decorator's name parameter. No function signatures changed._CORE_TOOLSupdated accordingly.13 new tool wrappers
roam_diff,roam_symbol,roam_deps,roam_usesroam_weather,roam_debtroam_n1,roam_auth_gaps,roam_over_fetch,roam_missing_index,roam_orphan_routes,roam_migration_safety,roam_api_driftAll follow the existing pattern: docstring with "WHEN TO USE", typed parameters,
_run_roam()call.Structured error handling
Added
_classify_error()that maps stderr patterns to error codes and recovery hints. Agents can now self-recover from common failures:{ "error": "No .roam directory found", "error_code": "INDEX_NOT_FOUND", "hint": "run `roam init` to create the codebase index." }roam mcpCLI command--transport stdio|sse,--host,--port--no-auto-indexto skip freshness check on startuproam indexon startup if index is stale (incremental, fast)Lite mode on by default (15 -> 16 core tools)
Lite mode is now the default (ROAM_MCP_LITE=1). 16 core tools are exposed out of the box, keeping the tool list manageable for agents. Set ROAM_MCP_LITE=0 to expose all 61 tools.
Added roam_diff, roam_deps, roam_uses to _CORE_TOOLS. Removed roam_visualize and roam_closure (less frequent in daily agent workflows).
fastmcp as optional dependency
Added
fastmcp>=2.0to[project.optional-dependencies]under amcpextra. Users install withpip install roam-code[mcp]. The MCP server gracefully degrades when fastmcp is not installed --roam mcpprints a clear error message pointing to the install command.Documentation
llms-install.md: updated command/language counts, addedpip install roam-code[mcp]README.md: updated tool count (48 -> 61), replaced allfastmcp runwithroam mcp, added install hint for MCP extraFiles changed
src/roam/mcp_server.pysrc/roam/cli.pypyproject.tomlmcpoptional dependency (fastmcp>=2.0)llms-install.mdREADME.md.github/workflows/roam-ci.yml--jsonflag position (pre-existing bug).github/workflows/roam.yml--jsonflag position (pre-existing bug)Test plan
roam mcp --helpshows all optionsroam mcpstarts stdio server successfullyROAM_MCP_LITE=1 roam mcpexposes 16 toolsroam --json diff,roam --json deps <path>, etc.roam --json symbol NonExistentreturns structured error with hintroam_names