# MCP Bridge

> MCP detection and unified state operations. Provides seamless integration with Control Tower MCP when available, with file-based fallback.

- Skill: `majiayu000/mcp-bridge` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds add majiayu000/mcp-bridge`
- Raw SKILL.md: https://api.skillmd.com/api/skills/majiayu000/mcp-bridge/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: majiayu000 (https://skillmd.com/u/majiayu000)
- Updated: 2026-09-09
- Page: https://skillmd.com/skills/majiayu000/mcp-bridge

---


# MCP Bridge

// Project Autopilot - MCP Bridge Skill
// Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>

**Core Principle:** Detect MCP availability once at command start, use unified interfaces throughout execution.

---

## MCP Configuration Detection

### Configuration File Locations

MCP configuration is checked in order of precedence:

| Scope | Path | Description |
|-------|------|-------------|
| Project | `.mcp.json` | Project-scoped, shared with team |
| User | `~/.claude.json` | User-scoped, all projects |
| User Local | `~/.claude/settings.local.json` | User local settings |

### Detection Function

```
FUNCTION detectControlTower():
    """
    Detect whether Control Tower MCP is configured and available.
    Returns detection result for use throughout command execution.
    """

    # Configuration file locations (checked in order)
    configPaths = [
        "{cwd}/.mcp.json",                    # Project scope
        "~/.claude.json",                     # User scope
        "~/.claude/settings.local.json"       # User local
    ]

    FOR each configPath IN configPaths:
        IF fileExists(configPath):
            TRY:
                config = parseJSON(readFile(configPath))

                # Check for control-tower or autopilot server entry
                IF config.mcpServers:
                    IF "control-tower" IN config.mcpServers:
                        RETURN {
                            available: true,
                            source: 'mcp',
                            serverName: 'control-tower',
                            configPath: configPath
                        }
                    IF "autopilot" IN config.mcpServers:
                        RETURN {
                            available: true,
                            source: 'mcp',
                            serverName: 'autopilot',
                            configPath: configPath
                        }
            CATCH:
                # Ignore parse errors, try next config
                CONTINUE

    # No MCP configuration found
    RETURN {
        available: false,
        source: 'local'
    }
```

### Example Detection Result

```json
// MCP Available
{
  "available": true,
  "source": "mcp",
  "serverName": "control-tower",
  "configPath": "/Users/dev/.claude.json"
}

// Local Mode
{
  "available": false,
  "source": "local"
}
```

---

## Unified State Operations

### Save Checkpoint

```
FUNCTION saveCheckpoint(state, context):
    """
    Save checkpoint via MCP if available, otherwise file-based.

    Parameters:
        state: {
            phase: string,
            task: string,
            decisions: array,
            todos: array,
            blockers: array
        }
        context: detection result from detectControlTower()
    """

    IF context.available:
        TRY:
            result = CALL_MCP "checkpoint_save" {
                project_id: context.projectId,
                execution_id: context.executionId,
                state: state,
                phase: state.phase,
                task: state.task,
                context_usage_pct: getContextUsage(),
                name: "Auto-checkpoint"
            }

            LOG "Checkpoint saved via Control Tower"
            RETURN {source: 'mcp', id: result.checkpoint_id}

        CATCH error:
            # MCP configured but failed - show clear error, don't silently fallback
            handleMCPError(error)
            THROW error
    ELSE:
        # MCP not configured - use file-based (no warning, this is expected)
        formatted = formatTransponderState(state)
        writeFile(".autopilot/TRANSPONDER.md", formatted)
        LOG "State saved locally"
        RETURN {source: 'local', path: '.autopilot/TRANSPONDER.md'}
```

### Load Checkpoint

```
FUNCTION loadCheckpoint(projectId, context):
    """
    Load latest checkpoint via MCP if available, otherwise file-based.

    Parameters:
        projectId: string (UUID for MCP, ignored for local)
        context: detection result from detectControlTower()

    Returns:
        Checkpoint state object
    """

    IF context.available:
        TRY:
            result = CALL_MCP "checkpoint_load" {
                project_id: projectId
                # checkpoint_id omitted for latest
            }

            LOG "Checkpoint loaded from Control Tower"
            RETURN {
                source: 'mcp',
                state: result.state,
                checkpoint_id: result.checkpoint_id,
                created_at: result.created_at
            }

        CATCH error:
            handleMCPError(error)
            THROW error
    ELSE:
        # Local state
        IF fileExists(".autopilot/TRANSPONDER.md"):
            content = readFile(".autopilot/TRANSPONDER.md")
            state = parseTransponderState(content)
            RETURN {source: 'local', state: state}
        ELSE:
            RETURN {source: 'local', state: null}
```

### Update Progress

```
FUNCTION updateProgress(projectId, executionId, phase, task, status, message, context):
    """
    Update progress via MCP if available, otherwise file-based.

    Parameters:
        projectId: string (UUID)
        executionId: string (UUID)
        phase: string (e.g., "03-api")
        task: string (e.g., "03.2")
        status: "pending" | "in_progress" | "completed" | "failed" | "skipped"
        message: string (description of progress)
        context: detection result from detectControlTower()
    """

    IF context.available:
        TRY:
            result = CALL_MCP "progress_update" {
                project_id: projectId,
                execution_id: executionId,
                phase: phase,
                task: task,
                status: status,
                message: message
            }

            # Progress update is silent (no log spam)
            RETURN {source: 'mcp', id: result.id}

        CATCH error:
            handleMCPError(error)
            THROW error
    ELSE:
        # Append to progress.md
        entry = formatProgressEntry(phase, task, status, message)
        appendFile(".autopilot/progress.md", entry)
        RETURN {source: 'local', path: '.autopilot/progress.md'}
```

### Record Activity

```
FUNCTION recordActivity(projectId, executionId, message, context):
    """
    Record activity log entry via MCP if available, otherwise file-based.

    Parameters:
        projectId: string (UUID)
        executionId: string (UUID)
        message: string (activity description)
        context: detection result from detectControlTower()
    """

    IF context.available:
        TRY:
            result = CALL_MCP "activity_log" {
                project_id: projectId,
                execution_id: executionId,
                message: message
            }

            RETURN {source: 'mcp', id: result.id}

        CATCH error:
            handleMCPError(error)
            THROW error
    ELSE:
        # Append to progress.md with timestamp
        timestamp = formatTimestamp(now())
        entry = "{timestamp} | {message}\n"
        appendFile(".autopilot/progress.md", entry)
        RETURN {source: 'local', path: '.autopilot/progress.md'}
```

### Record Cost

```
FUNCTION recordCost(projectId, executionId, model, inputTokens, outputTokens, cost, context):
    """
    Record token/cost usage via MCP if available, otherwise file-based.

    Parameters:
        projectId: string (UUID)
        executionId: string (UUID)
        model: string (e.g., "claude-sonnet-4-20250514")
        inputTokens: number
        outputTokens: number
        cost: number (USD)
        context: detection result from detectControlTower()
    """

    IF context.available:
        TRY:
            result = CALL_MCP "cost_record" {
                project_id: projectId,
                execution_id: executionId,
                model: model,
                input_tokens: inputTokens,
                output_tokens: outputTokens,
                cost: cost
            }

            RETURN {source: 'mcp', id: result.id}

        CATCH error:
            handleMCPError(error)
            THROW error
    ELSE:
        # Append to token-usage.md
        entry = formatCostEntry(model, inputTokens, outputTokens, cost)
        appendFile(".autopilot/token-usage.md", entry)
        RETURN {source: 'local', path: '.autopilot/token-usage.md'}
```

---

## Error Handling

### MCP Error Handler

```
FUNCTION handleMCPError(error):
    """
    Handle MCP errors with clear troubleshooting steps.
    Do NOT silently fallback - if MCP is configured but fails, user needs to know.
    """

    LOG ""
    LOG "Control Tower MCP unavailable."
    LOG ""
    LOG "Troubleshooting steps:"
    LOG "  1) Verify MCP server running:"
    LOG "     cd control-tower && npm run start:mcp"
    LOG ""
    LOG "  2) Check Claude Code MCP config:"
    LOG "     Run /mcp to verify control-tower server listed"
    LOG ""
    LOG "  3) Verify database connection:"
    LOG "     Ensure PostgreSQL is running"
    LOG ""
    LOG "  4) Check server logs:"
    LOG "     Look for connection errors in MCP server output"
    LOG ""

    IF error.message:
        LOG "Error details: {error.message}"
```

### Error Categories

| Error Type | Behavior | User Action |
|------------|----------|-------------|
| MCP not configured | Use local files silently | None - expected |
| MCP configured, server unreachable | Show error, halt | Start MCP server |
| MCP configured, auth failed | Show error, halt | Check API key |
| MCP configured, database error | Show error, halt | Check PostgreSQL |

---

## Output Formatting

### Source Indicator

```
FUNCTION getSourceIndicator(context):
    """
    Returns indicator text for command banner.
    """

    IF context.available:
        RETURN "Control Tower"
    ELSE:
        RETURN "Local"
```

### Banner Format

```
# With Control Tower MCP:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
AUTOPILOT: TAKEOFF                               Control Tower
   Execute flight plan with wave-based parallelization
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

# With local file-based state:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
AUTOPILOT: TAKEOFF                                      Local
   Execute flight plan with wave-based parallelization
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
```

### Dashboard Link

```
FUNCTION showDashboardLink(projectId, context):
    """
    Show dashboard link only when Control Tower is active and projectId valid.
    """

    IF context.available AND projectId:
        # Get dashboard URL from config or use default
        dashboardUrl = context.dashboardUrl OR "http://localhost:3000"

        LOG ""
        LOG "View in dashboard: {dashboardUrl}/projects/{projectId}"
```

### Dashboard Link Display Rules

| Condition | Show Link? | Reason |
|-----------|------------|--------|
| MCP available, valid projectId | Yes | Full functionality |
| MCP available, no projectId | No | Project not registered |
| Local mode | No | No dashboard in local mode |
| --local flag | No | Explicitly local mode |

---

## Flag Handling

### --local Flag

```
FUNCTION initializeStateBackend(arguments):
    """
    Initialize state backend based on MCP availability and flags.
    Called at command startup.
    """

    # Check for --local flag first
    IF "--local" IN arguments:
        LOG "Using local file-based state (--local flag)"
        RETURN {
            available: false,
            source: 'local',
            forced: true
        }

    # Detect Control Tower
    detection = detectControlTower()

    IF detection.available:
        LOG "Control Tower MCP detected"

        # Auto-register project if needed
        projectId = ensureProjectRegistered(detection)
        detection.projectId = projectId

        # Get or create execution
        executionId = getOrCreateExecution(projectId)
        detection.executionId = executionId
    ELSE:
        LOG "Using local file-based state"

    RETURN detection
```

### Ensure Project Registered

```
FUNCTION ensureProjectRegistered(context):
    """
    Ensure current project is registered with Control Tower.
    Returns projectId (existing or newly created).
    """

    # Check for existing project by path
    result = CALL_MCP "project_lookup" {
        path: getCurrentDirectory()
    }

    IF result.project_id:
        RETURN result.project_id

    # Register new project
    projectName = getProjectName()  # From package.json or directory name

    result = CALL_MCP "project_register" {
        name: projectName,
        path: getCurrentDirectory(),
        description: getProjectDescription()  # From clearance.md if exists
    }

    RETURN result.project_id
```

---

## Integration Examples

### Takeoff Command Integration

```
# At command start (Phase 0)
context = initializeStateBackend(arguments)
sourceIndicator = getSourceIndicator(context)

# Display banner with source indicator
LOG "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
LOG "AUTOPILOT: TAKEOFF                               {sourceIndicator}"
LOG "   Execute flight plan with wave-based parallelization"
LOG "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"

# During execution - save checkpoint
saveCheckpoint({
    phase: currentPhase,
    task: currentTask,
    decisions: recentDecisions,
    todos: pendingTodos,
    blockers: currentBlockers
}, context)

# During execution - update progress
updateProgress(
    context.projectId,
    context.executionId,
    "03-api",
    "03.2",
    "completed",
    "Created user endpoints with validation",
    context
)

# During execution - record cost
recordCost(
    context.projectId,
    context.executionId,
    "claude-sonnet-4-20250514",
    15000,
    8500,
    0.12,
    context
)

# At completion
showDashboardLink(context.projectId, context)
```

### Cockpit Command Integration

```
# At command start
context = initializeStateBackend(arguments)
sourceIndicator = getSourceIndicator(context)

# Display banner with source indicator
LOG "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
LOG "AUTOPILOT: COCKPIT                              {sourceIndicator}"
LOG "   Return to cockpit - resume flight from waypoint"
LOG "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"

# Load checkpoint
checkpoint = loadCheckpoint(context.projectId, context)

IF checkpoint.state:
    LOG "Checkpoint loaded: Phase {checkpoint.state.phase}, Task {checkpoint.state.task}"
ELSE:
    LOG "No checkpoint found - starting fresh"

# At resume
showDashboardLink(context.projectId, context)
```

---

## State File Paths

### Local Mode Files

| File | Purpose |
|------|---------|
| `.autopilot/TRANSPONDER.md` | Session state bridge |
| `.autopilot/progress.md` | Activity log |
| `.autopilot/token-usage.md` | Cost tracking |
| `.autopilot/holding-pattern.md` | Mid-phase resume |

### MCP Mode Storage

| Tool | Data Stored |
|------|-------------|
| `checkpoint_save` | State snapshots with full context |
| `progress_update` | Phase/task progress entries |
| `activity_log` | Activity timeline |
| `cost_record` | Token/cost records |

---

## Testing Checklist

Before deploying MCP integration:

```
[ ] Test with MCP not configured → uses local files silently
[ ] Test with MCP configured and working → uses MCP
[ ] Test with MCP configured but server down → shows error, does not fallback
[ ] Test with --local flag → forces local even when MCP configured
[ ] Test dashboard link only shows when MCP active
[ ] Test source indicator displays correctly in banner
[ ] Test all commands maintain identical output format except indicator
```

---

## Quick Reference

### Detection Priority

1. `--local` flag (force local)
2. `.mcp.json` (project scope)
3. `~/.claude.json` (user scope)
4. `~/.claude/settings.local.json` (user local)
5. Fallback to local

### Operation Priority

1. If MCP available: Use MCP tools
2. If MCP fails: Show error, halt (no silent fallback)
3. If MCP not configured: Use local files (no warning)

### Source Indicators

| Mode | Indicator |
|------|-----------|
| Control Tower MCP | `Control Tower` |
| Local files | `Local` |
| Forced local (--local) | `Local` |

