blue/.blue/docs/rfcs/0020-mcp-project-detection.md
Eric Garcia c9acd1a4ad feat: implement RFC 0020 MCP project detection (all phases)
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>
2026-01-25 21:47:24 -05:00

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:

  1. Tool calls don't include explicit cwd parameter
  2. Claude Code doesn't send MCP roots during initialize
  3. 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_root field
  • Use fallback chain in ensure_state(): cwdmcp_root → walk tree → fail
  • Separate cwd (tool-arg override) from mcp_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 — BlueNotDetected now carries context (process cwd, mcp_root, attempted path)
  • Suggest fixes based on context — errors end with "Run 'blue init' or pass 'cwd' parameter."
  • Add --debug flag to MCP server — blue mcp --debug logs 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

  1. Does Claude Code send MCP roots? No. Declares capabilities.roots: {} but sends no roots array. (Verified 2026-01-25, v2.1.19)
  2. Should we support multiple projects in one session?
  3. Should detection be cached or run per-call?

References