Skip to content

feat: improve MCP server -- roam_ prefix, 13 new tools, structured errors, roam mcp command#10

Merged
Cranot merged 5 commits intoCranot:mainfrom
holive:holive/improve-mcp
Feb 22, 2026
Merged

feat: improve MCP server -- roam_ prefix, 13 new tools, structured errors, roam mcp command#10
Cranot merged 5 commits intoCranot:mainfrom
holive:holive/improve-mcp

Conversation

@holive
Copy link
Copy Markdown
Contributor

@holive holive commented Feb 21, 2026

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 a roam mcp CLI 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, and search_symbol collide. Agents get confused about which search to call. Microsoft Research documented this as "tool-space interference" -- the standard fix is prefixing. This PR adds roam_ 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 mcp command

Having to run fastmcp run roam.mcp_server:mcp is friction. Most MCP servers ship a built-in subcommand (tool mcp). This PR adds roam mcp with stdio/SSE transport options and automatic index freshness checking on startup.

What changed

Tool renaming (48 existing tools)

All tools renamed from tool_name to roam_tool_name via the _tool() decorator's name parameter. No function signatures changed. _CORE_TOOLS updated accordingly.

13 new tool wrappers

Category Tools CLI commands
Daily workflow (4) roam_diff, roam_symbol, roam_deps, roam_uses diff, symbol, deps, uses
Health (2) roam_weather, roam_debt weather, debt
Backend analysis (7) roam_n1, roam_auth_gaps, roam_over_fetch, roam_missing_index, roam_orphan_routes, roam_migration_safety, roam_api_drift n1, auth-gaps, over-fetch, missing-index, orphan-routes, migration-safety, api-drift

All 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 mcp CLI command

  • Registered in cli.py under "Getting Started"
  • Supports --transport stdio|sse, --host, --port
  • --no-auto-index to skip freshness check on startup
  • Auto-runs roam index on 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.0 to [project.optional-dependencies] under a mcp extra. Users install with pip install roam-code[mcp]. The MCP server gracefully degrades when fastmcp is not installed -- roam mcp prints a clear error message pointing to the install command.

Documentation

  • llms-install.md: updated command/language counts, added pip install roam-code[mcp]
  • README.md: updated tool count (48 -> 61), replaced all fastmcp run with roam mcp, added install hint for MCP extra

Files changed

File Change
src/roam/mcp_server.py rename 48 tools, add 13 tools, error handling, mcp_cmd (+586 lines)
src/roam/cli.py register mcp command
pyproject.toml add mcp optional dependency (fastmcp>=2.0)
llms-install.md update command/language counts, MCP install instructions
README.md update tool count, MCP docs, dependency notes
.github/workflows/roam-ci.yml fix --json flag position (pre-existing bug)
.github/workflows/roam.yml fix --json flag position (pre-existing bug)

Test plan

  • roam mcp --help shows all options
  • roam mcp starts stdio server successfully
  • ROAM_MCP_LITE=1 roam mcp exposes 16 tools
  • All 13 new tools return valid JSON: roam --json diff, roam --json deps <path>, etc.
  • Error handling: roam --json symbol NonExistent returns structured error with hint
  • Existing 48 tools still work under new roam_ names

…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.
@holive holive force-pushed the holive/improve-mcp branch from 8acae14 to 1c0ecc9 Compare February 21, 2026 20:12
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.
Copy link
Copy Markdown
Owner

@Cranot Cranot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 --json flag position fix is correct and valuable. --json is 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 ImportError to runtime instead of crashing on import roam.mcp_server is a meaningful improvement. IDE tooling and documentation generators can now scan the module without needing fastmcp installed.
  • roam mcp CLI command — Eliminates the fastmcp run roam.mcp_server:mcp friction. Clean integration with the LazyGroup pattern.
  • New tool selectiondiff, symbol, deps, uses, weather, debt are 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.0 to [project.optional-dependencies] under a mcp extra

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. descriptioninstructions 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 fresh

The 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)

  1. Core tools list: I like the swap of visualize/closure for diff/deps/uses — the new set is more aligned with daily workflow. Consider also including roam_tour or roam_repo_map for the initial exploration use case that agents frequently need.

  2. roam mcp --list-tools flag: Would be helpful for debugging which tools are exposed in a given configuration (especially with the lite/full mode toggle).

  3. Timeout on _run_roam: Currently hardcoded at 60s. Some commands (full index on 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:

  1. Missing pyproject.toml change
  2. FastMCP API version compatibility
  3. 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?

CosmoHac and others added 2 commits February 22, 2026 09:16
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 Cranot merged commit 6d66f05 into Cranot:main Feb 22, 2026
22 checks passed
@holive
Copy link
Copy Markdown
Contributor Author

holive commented Feb 22, 2026

@Cranot Thank you for addressing the problems I created 😅

Cranot added a commit that referenced this pull request Feb 26, 2026
feat: improve MCP server -- roam_ prefix, 13 new tools, structured errors, roam mcp command
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants