Separate mcp_root from cwd so tool-arg overrides don't clobber the session-level root from initialize. Fallback chain matches RFC spec: cwd → mcp_root → walk tree → fail with guidance. Error messages now include attempted paths and actionable fix suggestions. Added --debug flag to MCP server for file-based DEBUG logging. Phase 2 finding: Claude Code v2.1.19 declares roots capability but sends no roots array. Walk-up is the primary detection path. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
4.7 KiB
4.7 KiB
RFC 0020: MCP Project Detection
| Status | Accepted |
| Created | 2026-01-26 |
| Source | Spike: MCP Project Detection |
| Dialogue | 6-expert alignment, 98% convergence |
Problem
Blue MCP server fails with "Blue not detected in this directory" when:
- Tool calls don't include explicit
cwdparameter - Claude Code doesn't send MCP roots during initialize
- Server process starts from non-project directory
This creates poor UX - users must pass cwd to every tool call.
Proposal
Implement robust project detection with clear fallback chain:
Explicit cwd → MCP Roots → Walk Tree → Fail with Guidance
Detection Algorithm
fn detect_project() -> Result<PathBuf, Error> {
// 1. Use explicit cwd if provided in tool args
if let Some(cwd) = self.cwd {
return Ok(cwd);
}
// 2. Use MCP roots from initialize (set during handshake)
if let Some(root) = self.mcp_root {
return Ok(root);
}
// 3. Walk up from process cwd looking for .blue/
if let Some(found) = walk_up_for_blue() {
return Ok(found);
}
// 4. Fail with actionable guidance
Err("Blue project not found. Either:
- Pass 'cwd' parameter to tool calls
- Run from within a Blue project directory
- Initialize Blue with 'blue init'")
}
MCP Roots Handling
During initialize, extract roots from client:
fn handle_initialize(params: Value) {
// Check for MCP roots
if let Some(roots) = params.get("roots") {
if let Some(uri) = roots[0].get("uri") {
self.mcp_root = Some(uri_to_path(uri));
}
}
// Also check workspaceFolders (some clients use this)
if let Some(folders) = params.get("workspaceFolders") {
// ...
}
}
Walk Tree Implementation
fn walk_up_for_blue() -> Option<PathBuf> {
let mut dir = std::env::current_dir().ok()?;
for _ in 0..20 { // Limit depth
if dir.join(".blue").exists() {
return Some(dir);
}
if !dir.pop() {
return None;
}
}
None
}
Error Messages
Clear, actionable errors:
| Scenario | Message |
|---|---|
| No project found | "Blue project not found. Run 'blue init' or pass 'cwd' parameter." |
| Wrong directory | "Blue not detected in: /path. Expected .blue/ directory." |
Implementation
Phase 1: Improve Detection (Done)
- Add
find_blue_root()walk-up function - Extract roots from initialize params into
mcp_rootfield - Use fallback chain in
ensure_state():cwd→mcp_root→ walk tree → fail - Separate
cwd(tool-arg override) frommcp_root(session-level from initialize) - Unit tests: 17 tests covering construction, roots extraction, field isolation, fallback chain, and integration
Phase 2: Verify Claude Code Behavior (Done)
- Log what Claude Code sends in initialize
- Confirm if roots are provided
- Document client requirements
Findings (2026-01-25, Claude Code v2.1.19):
Claude Code does not send MCP roots. It declares the capability but provides no roots array:
{
"capabilities": { "roots": {} },
"clientInfo": { "name": "claude-code", "version": "2.1.19" },
"protocolVersion": "2025-11-25"
}
Detection succeeds via step 3 (walk-up from process cwd):
| Step | Source | Result |
|---|---|---|
1. cwd |
Tool arg | None (no call yet) |
2. mcp_root |
Initialize | null (not sent by client) |
| 3. Walk tree | find_blue_root() |
Found project root |
Implication: Walk-up is the primary detection path for Claude Code. The mcp_root path exists for future clients or if Claude Code adds roots support.
Phase 3: Improve Error Messages (Done)
- Show attempted paths in error —
BlueNotDetectednow carries context (process cwd, mcp_root, attempted path) - Suggest fixes based on context — errors end with "Run 'blue init' or pass 'cwd' parameter."
- Add
--debugflag to MCP server —blue mcp --debuglogs DEBUG-level output to/tmp/blue-mcp-debug.log
ADR Alignment
| ADR | How Honored |
|---|---|
| ADR 3 (Home) | "You are never lost" - detection finds home |
| ADR 5 (Single Source) | .blue/ directory is the marker |
| ADR 8 (Honor) | Clear errors explain what happened |
Open Questions
Does Claude Code send MCP roots?No. Declarescapabilities.roots: {}but sends no roots array. (Verified 2026-01-25, v2.1.19)- Should we support multiple projects in one session?
- Should detection be cached or run per-call?