Skip to main content
Glama
Gowindude

Claude Desktop ↔ Claude Code MCP Bridge

by Gowindude
README.md
# Claude Desktop ↔ Claude Code MCP Bridge

## What this is

A bidirectional [MCP](https://modelcontextprotocol.io) bridge that lets **Claude Desktop**
and **Claude Code** collaborate on a build with no human in the execution loop. Desktop
writes a plan; Code picks it up, implements it, streams progress, and submits a completion
summary; Desktop approves or rejects (with a reason); Code revises on rejection or finalizes
on approval. The two sides never talk directly — they communicate through a shared SQLite
database and a small set of markdown files under `~/.claude-bridge/`. Two stdio FastMCP
servers expose the tools each side calls.

```
~/.claude-bridge/
├── bridge.db          # SQLite: projects, updates, completions, approvals
├── plans/             # Desktop writes <project_id>.md here
└── status/            # Code writes <project_id>_status.md here

claude_desktop_mcp/
├── desktop_server.py  # FastMCP server registered with Claude Desktop
├── code_server.py     # FastMCP server registered with Claude Code (CLI)
├── db.py              # All SQLite schema + access (async, aiosqlite)
├── models.py          # Shared Pydantic v2 models (rows + tool inputs)
├── config.py          # Paths, server names, status constants
└── requirements.txt
```

Both servers import from `db.py` and `models.py`; no DB logic is duplicated between them.

## Install

A **dedicated virtual environment is strongly recommended** — both servers are spawned by
the host app (Claude Desktop / Claude Code), and a venv with a concrete interpreter path is
the most reliable target (especially on Windows, where the Microsoft Store "App execution
alias" Python can fail when launched by a packaged app).

```bash
# from the project directory
python -m venv .venv

# Windows
.venv\Scripts\python.exe -m pip install -r requirements.txt
# macOS / Linux
.venv/bin/python -m pip install -r requirements.txt
```

The interpreter to reference in the config snippets is then:

* **Windows:** `<project>\.venv\Scripts\python.exe`
* **macOS / Linux:** `<project>/.venv/bin/python`

(You can skip the venv and `pip install -r requirements.txt` into any Python 3.11+, but then
make sure the `command` in the configs points at *that* interpreter.)

Requires Python 3.11+. Dependencies: `mcp[cli]`, `fastmcp`, `aiosqlite`, `aiofiles`,
`pydantic>=2.0`.

Initialize the shared state directory and database (also runs automatically on the first
tool call):

```bash
# Windows
.venv\Scripts\python.exe db.py
# macOS / Linux
.venv/bin/python db.py
```

This creates `~/.claude-bridge/` with `plans/`, `status/`, and `bridge.db`.

## How to register

Both servers run over **stdio** and are launched by the host (Desktop / Code) via its MCP
config. Use the ready-made snippets in this folder:

* `claude_desktop_config.snippet.json` → Claude Desktop
* `claude_code_config.snippet.json` → Claude Code

### Claude Desktop

Add the `claude_bridge_desktop` entry to your `claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "claude_bridge_desktop": {
      "command": "C:\\Users\\quack\\documents\\projects\\claude_desktop_mcp\\.venv\\Scripts\\python.exe",
      "args": ["C:\\Users\\quack\\documents\\projects\\claude_desktop_mcp\\desktop_server.py"],
      "env": {}
    }
  }
}
```

Config file location:

| OS | Path |
|---|---|
| **Windows (standard installer)** | `%APPDATA%\Claude\claude_desktop_config.json` (e.g. `C:\Users\<you>\AppData\Roaming\Claude\claude_desktop_config.json`) |
| **Windows (Microsoft Store / packaged install)** | `%LOCALAPPDATA%\Packages\Claude_<id>\LocalCache\Roaming\Claude\claude_desktop_config.json` (e.g. `...\Packages\Claude_pzs8sxrjxfjjc\LocalCache\Roaming\Claude\...`) |
| **macOS** | `~/Library/Application Support/Claude/claude_desktop_config.json` |
| **Linux** | `~/.config/Claude/claude_desktop_config.json` |

> If Claude Desktop was installed from the Microsoft Store, it uses a *virtualized* Roaming
> folder under `%LOCALAPPDATA%\Packages\Claude_<id>\LocalCache\Roaming\Claude` — editing the
> standard `%APPDATA%\Claude` path will have no effect. Merge into the existing file (it may
> already contain other `mcpServers`); don't overwrite it.

Restart (fully quit and reopen) Claude Desktop after editing — the config is read at launch.

### Claude Code (CLI)

The simplest way is the CLI, which writes the right config for you:

```bash
claude mcp add claude_bridge_code --scope user -- "C:\Users\quack\documents\projects\claude_desktop_mcp\.venv\Scripts\python.exe" "C:\Users\quack\documents\projects\claude_desktop_mcp\code_server.py"
```

`--scope user` makes the bridge available in every project. Restart Claude Code (or start a
new session) afterward so it loads the server.

Or add the `claude_bridge_code` entry manually to the appropriate MCP config (e.g.
`.mcp.json` in your project root, or your user-level Claude Code config):

```json
{
  "mcpServers": {
    "claude_bridge_code": {
      "command": "C:\\Users\\quack\\documents\\projects\\claude_desktop_mcp\\.venv\\Scripts\\python.exe",
      "args": ["C:\\Users\\quack\\documents\\projects\\claude_desktop_mcp\\code_server.py"],
      "env": {}
    }
  }
}
```

> **Path note:** the `command` points at the project's venv interpreter
> (`.venv\Scripts\python.exe` on Windows, `.venv/bin/python` on macOS/Linux) — the one you
> installed the dependencies into. Avoid the bare Microsoft Store `python.exe` alias here;
> packaged host apps may fail to launch it. JSON requires escaped backslashes (`\\`) in
> Windows paths.

## Tool argument shape

> **Important:** every tool whose table lists parameters takes a **single object
> argument named `params`** — the tool input is one Pydantic model, so the JSON the MCP
> client sends is `{"params": { ... }}`, not flat top-level fields. Tools that take no
> parameters (`bridge_get_approval_queue`, `bridge_list_projects`,
> `bridge_get_pending_plan`) are called with `{}`.

For example, `bridge_send_plan` is invoked as:

```json
{ "params": { "title": "Add CSV export", "plan_markdown": "# Goal\n..." } }
```

In practice you just tell Claude what to do ("send this plan to Code") and it fills in the
`params` object; the shape above is what travels over the wire.

## Typical workflow

The examples below show each tool with its `params` payload.

1. **Desktop registers a plan.** In Claude Desktop, call `bridge_send_plan`:
   ```
   bridge_send_plan  params={"title": "Add CSV export", "plan_markdown": "# Goal\n..."}
   → { "project_id": "ab12cd34ef56", "status": "pending", "plan_path": "...", ... }
   ```
2. **Code picks it up.** In Claude Code:
   ```
   bridge_get_pending_plan  (no params)   → returns project_id, title, full plan markdown
   bridge_claim_project  params={"project_id": "ab12cd34ef56"}   → status: in_progress
   ```
3. **Code streams progress** while implementing:
   ```
   bridge_send_update  params={"project_id": "ab12cd34ef56", "message": "Wrote exporter module"}
   bridge_send_update  params={"project_id": "ab12cd34ef56", "message": "Added tests"}
   ```
4. **Code submits completion:**
   ```
   bridge_send_completion  params={
     "project_id": "ab12cd34ef56",
     "files_created": ["export.py", "test_export.py"],
     "files_modified": ["cli.py"],
     "summary": "CSV export end to end",
     "caveats": null}                → status: awaiting_approval
   ```
5. **Desktop reviews:**
   ```
   bridge_get_approval_queue  (no params)            → project + completion summary
   bridge_get_project_updates  params={"project_id": "ab12cd34ef56"}
   ```
6. **Desktop decides:**
   ```
   bridge_approve  params={"project_id": "ab12cd34ef56"}                → status: approved
   # or
   bridge_reject  params={"project_id": "ab12cd34ef56", "reason": "Handle empty rows"}  → status: rejected
   ```
7. **Code learns the outcome** by polling:
   ```
   bridge_get_approval_status  params={"project_id": "ab12cd34ef56"}
   ```
   * **Approved** → the project is finalized to `complete`.
   * **Rejected** → the response includes the rejection `reason`. Code calls
     `bridge_reset_for_revision` with `params={"project_id": "ab12cd34ef56"}` (status →
     `in_progress`), revises,
     and submits a new completion. The loop repeats until approved.

### Status lifecycle

```
pending → in_progress → awaiting_approval → approved → complete
                ▲                              │
                └──────── rejected ◄───────────┘   (bridge_reset_for_revision)
```

Each tool validates the current status before transitioning and returns a clear
`ERROR: ...` string if the transition is invalid.

### Tool reference

**Desktop server (`claude_bridge_desktop`)**

| Tool | Purpose |
|---|---|
| `bridge_send_plan` | Register a new project + plan (writes `plans/<id>.md`) |
| `bridge_get_approval_queue` | List projects awaiting approval, with completion summaries |
| `bridge_get_project_updates` | All progress updates for a project |
| `bridge_approve` | Approve completed work |
| `bridge_reject` | Reject with a reason; sends work back |
| `bridge_get_project_status` | Full project state (status, updates, completion, decision) |
| `bridge_list_projects` | All projects, newest first |

**Code server (`claude_bridge_code`)**

| Tool | Purpose |
|---|---|
| `bridge_get_pending_plan` | Retrieve the oldest pending plan + markdown |
| `bridge_claim_project` | Mark a pending project `in_progress` |
| `bridge_send_update` | Append a progress message (also writes `status/<id>_status.md`) |
| `bridge_send_completion` | Submit finished-work summary → `awaiting_approval` |
| `bridge_get_approval_status` | Poll for approval/rejection (finalizes `approved` → `complete`) |
| `bridge_reset_for_revision` | Re-enter `in_progress` after a rejection |

## State directory

`~/.claude-bridge/` (on Windows, `C:\Users\<you>\.claude-bridge\`):

* **`bridge.db`** — SQLite with four tables: `projects`, `updates`, `completions`,
  `approvals`. Foreign keys link `updates`/`completions`/`approvals` back to `projects`.
* **`plans/<project_id>.md`** — the exact plan markdown Desktop submitted.
* **`status/<project_id>_status.md`** — a running log: one timestamped line per progress
  update, plus a structured `## Completion` block each time Code submits a completion.

You can safely inspect these files by hand. To reset everything, delete `bridge.db` and the
contents of `plans/` and `status/`, then run `python db.py` again.

## HTTP API Server

A lightweight FastAPI server (`api_server.py`) exposes the bridge database over `http://localhost:7823`. A browser-based dashboard artifact in Claude Desktop can poll it for live project status without any MCP or stdio involvement.

### How to start

**Option A — visible terminal:**
```
start_api.bat
```

**Option B — hidden background process:**
Double-click `start_api.vbs` (no terminal window appears). It also runs automatically on login via the Windows startup folder.

### Endpoints

| Method | Path | Description |
|---|---|---|
| `GET` | `/health` | Server health + DB path |
| `GET` | `/projects` | All projects, newest first |
| `GET` | `/projects/{id}` | Full project detail: project + updates + completion + approval |
| `POST` | `/projects/{id}/approve` | Approve a project (must be `awaiting_approval`) |
| `POST` | `/projects/{id}/reject` | Reject a project; body: `{"reason": "..."}` |

### Example curl calls

```bash
curl http://localhost:7823/health
curl http://localhost:7823/projects
curl http://localhost:7823/projects/abc123def456
curl -X POST http://localhost:7823/projects/abc123def456/approve
curl -X POST http://localhost:7823/projects/abc123def456/reject -H "Content-Type: application/json" -d "{\"reason\": \"needs better error handling\"}"
```

### Auto-start on login

`start_api.vbs` is copied to `%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\` so the API server starts automatically every time you log in alongside the bridge loop. To disable, delete `start_api.vbs` from that folder.

## Running the Persistent Loop

The bridge loop keeps Claude Code running in the background so Claude Desktop can push plans at any time and Code picks them up automatically — no manual intervention needed on the Code side.

### Files

| File | Purpose |
|---|---|
| `run_loop.bat` | Activates the venv, launches Claude Code with the bridge prompt, and auto-restarts on exit |
| `start_bridge.vbs` | Launches `run_loop.bat` as a hidden background window (no terminal stays open) |
| `stop_bridge.bat` | Kills the running Claude Code process |
| `bridge_status.bat` | Shows running Claude processes and the last 10 lines of `loop.log` |

### How to start

**Option A — visible terminal (useful for debugging):**
```
run_loop.bat
```

**Option B — hidden background process:**
Double-click `start_bridge.vbs` (or invoke it via `wscript.exe start_bridge.vbs`). No terminal window appears.

### How to stop

Run `stop_bridge.bat` or:
```
taskkill /F /IM claude.exe /T
```

### How to check status

Run `bridge_status.bat` — it lists active `claude.exe` processes and prints the last 10 lines of the log file.

### Startup registration

`start_bridge.vbs` is copied into `%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\` so the loop starts automatically every time you log in. To disable autostart, delete `start_bridge.vbs` from that folder.

### Log file

Every time the loop restarts, a timestamped line is appended to:

```
C:\Users\quack\.claude-bridge\loop.log
```

This lets you see how often Claude Code was restarted and when each session began.

## Troubleshooting

* **`ERROR: cannot <action> project in status '<x>'; expected '<y>'.`** — A status
  transition was attempted out of order (e.g. completing a project that was never claimed,
  or approving one that isn't `awaiting_approval`). Check the current state with
  `bridge_get_project_status` / `bridge_list_projects` and follow the lifecycle above.
* **`ERROR: project not found: <id>`** — Wrong/typo'd `project_id`. Use
  `bridge_list_projects` to find the correct id.
* **`ERROR: a non-empty 'reason' is required to reject a project.`** — `bridge_reject`
  needs a `reason`; supply one.
* **`ERROR: plan file missing for project <id>: <path>`** — The `plans/<id>.md` file was
  deleted out from under the DB. Re-send the plan with `bridge_send_plan` (creates a new
  project) or restore the file.
* **Database is locked.** SQLite serializes writers; the bridge uses short-lived async
  connections so this is rare. If it happens, ensure you don't have an external tool holding
  a long transaction on `bridge.db`, then retry — the offending tool will simply return an
  `ERROR: ...` string and can be called again.
* **Server not found / tools don't appear.** Confirm the `command` path points at the
  interpreter where dependencies are installed (`pip show mcp`), that the `args` path to the
  `*_server.py` file is correct and absolute, and that you restarted the host (Desktop) or
  re-ran `claude mcp add` (Code). On Windows, JSON requires escaped backslashes (`\\`) in
  paths.
* **Nothing happens after `bridge_send_plan`.** Code only acts when *you* ask it to call
  `bridge_get_pending_plan` (and the rest). The bridge is pull-based polling, not a push
  notification system.