Skip to main content
Glama

TaskBridge

Claude plans. OpenCode builds. Dispatch real coding work from Claude to headless OpenCode agents via a durable SQLite queue.

Claude submits tasks over MCP. An auto-worker picks them up in ~60 s, runs opencode run in an isolated worktree, and returns a structured summary, git-aware diff, logs, and usage for Claude to read. Works from Claude Desktop (stdio), any MCP client over HTTP, and in reverse — OpenCode can drive Claude too.

Python 3.12 MCP SQLite Worker License

Why this exists

Claude is a powerful planner that can't touch your filesystem. OpenCode runs code locally but lives outside Claude's context. The bridge closes the loop:

  • Claude stays the interface — breaks work down, delegates, and reads results.

  • OpenCode does the execution — edits, tests, shell, headlessly and reproducibly.

  • SQLite is the durable handoff — survives restarts, supports retries, priorities, dependencies, and two-way questions.

Related MCP server: CatHub Nyaanes

Features

  • 16 MCP tools for Claude — submit_task, submit_batch (up to 20), get_task / get_task_result, get_task_logs (live tail), wait_for, answer_task, list_tasks, queue_status / worker_heartbeat, add_task_note, cancel_task / clear_finished, list_models / set_default_model, ping. No polling loops, full observability.

  • Headless auto-worker (worker.py) — launchd polls every 60 s, claims oldest-first by priority, streams output to task_logs/<id>.log, returns structured {summary, files_touched, tests_run, open_questions} plus a git-aware diff.

  • Two-way questions — worker can pause with a question block → task goes waiting + macOS banner → Claude answers via answer_task → run resumes with the answer in context.

  • Worktree isolation — per-task branch + git worktree, auto-committed on success, cleaned up unless worktree_keep.

  • Honest permissions — readonly reverts writes and reports reverted files; no-network / no-shell are prompt-level and explicitly labeled.

  • Reverse bridge (claude_control_mcp.py) — 6 tools for OpenCode to drive Claude: claude_ask (sync), claude_submit / claude_poll / claude_result / claude_cancel (background), claude_status. Uses Anthropic API if ANTHROPIC_API_KEY is set, else claude -p.

  • Public-URL mode (install.sh / start_public.sh) — one command to expose MCP via ngrok (the installer prompts for your Authtoken) or Cloudflare quick tunnel with automatic MCP_ALLOWED_HOSTS pairing.

  • Live dispatch board (bridge_dashboard.html) — served at GET / on the same host. Tally counts, per-task live logs, reverse jobs, engine tail, and a scrolling feed of fresh output. Polls /api/*; MCP stays at /mcp.

  • Caller detection — submit_task/submit_batch stamp the true caller from the MCP handshake clientInfo, so Claude and ChatGPT dockets stay separate. origin can be overridden explicitly.

  • Production reliability — per-attempt timeouts, 15-min stale-running reaper with pid-reuse guard, single-instance flock, heartbeat file, macOS banners (WORKER_NOTIFY=0 to silence).

Quickstart

Prerequisites: Python 3.12, opencode CLI on PATH, and (for public access) a free ngrok account.

One command — install, tunnel, serve, print your connector box:

git clone https://github.com/Parithosh-Varma/TaskBridge.git
cd TaskBridge
./install.sh

install.sh prompts for your ngrok Authtoken (blank = already configured), installs dependencies into .venv, generates or reuses the bearer token in .bridge-token, opens the ngrok tunnel (fixed domain from .ngrok-domain when present), pairs MCP_ALLOWED_HOSTS, prints the public URL + token box, then serves MCP in the foreground. Ctrl+C stops the server; the tunnel stays up for the next run. Rotate the token with ./install.sh --rotate.

Manual setup (stdio / local dev):

git clone https://github.com/Parithosh-Varma/TaskBridge.git
cd TaskBridge
python3.12 -m venv .venv
./.venv/bin/pip install -r requirements.txt

Run the MCP server (stdio — Claude Desktop spawns this):

./.venv/bin/python opencode_bridge_mcp.py

Or serve HTTP locally:

./.venv/bin/python opencode_bridge_mcp.py --transport streamable-http --port 8000
# → http://127.0.0.1:8000/mcp

Wire into Claude Desktop — merge claude_desktop_config.snippet.json into ~/Library/Application Support/Claude/claude_desktop_config.json under mcpServers (replace <REPO_PATH> with your checkout path), then restart Claude Desktop. Verify: ask Claude to list MCP tools — you should see submit_task, get_task, list_tasks under taskbridge.

Authentication (HTTP mode)

install.sh generates the bearer token and serves the public endpoint:

./install.sh            # reuse or generate .bridge-token, tunnel + serve
./install.sh --rotate   # regenerate the bearer token first

Token is loaded from .bridge-token file or BRIDGE_AUTH_TOKEN env var. install.sh and start_public.sh (the background / Cloudflare-tunnel variant) both pick it up automatically.

Public URL access requires:

Authorization: Bearer <your-token>
  • /health is open (monitoring)

  • / dashboard prompts for token (stored in sessionStorage)

  • /api/* and /mcp require Bearer token

  • stdio transport (Claude Desktop) is local-only, no token needed

To rotate token: ./install.sh --rotate

Run one worker pass manually:

./.venv/bin/python worker.py

Or schedule it every 60s with launchd (macOS) — see worker.py header and adjust the plist ProgramArguments to your checkout path.

Usage

From Claude:

  1. submit_task(title="Add retry tests", instructions="Steps..., paths..., acceptance criteria...", working_dir="/path/to/target-repo") → returns id

  2. wait_for(task_ids=["<id>"], timeout_sec=120) — blocks instead of polling

  3. get_task_result(task_id="<id>") → result, structured, usage, diff

Batch + follow-ups:

  • submit_batch(tasks=[...]) — up to 20 tasks with per-item model, depends_on, priority, timeout_sec, max_retries / retry_model, tags, idempotency_key, permission, worktree_*

  • get_task_logs(task_id="...", since_offset=0) — tail live output

  • answer_task(task_id="...", answer="...") — answer a waiting task's question

  • queue_status() / worker_heartbeat() — queue counts + daemon liveness

From OpenCode (manual worker loop):

./.venv/bin/python tasks_cli.py pending
./.venv/bin/python tasks_cli.py show <id>
./.venv/bin/python tasks_cli.py claim <id>
# ... do the work (edits, shell, tests) ...
./.venv/bin/python tasks_cli.py complete <id> --result "..."
./.venv/bin/python tasks_cli.py fail <id> --error "..."

Reverse (OpenCode → Claude) via claude_control_mcp.py (registered as an OpenCode MCP server): claude_ask for one-shot answers, claude_submit + claude_poll / claude_result for background jobs.

How it works

flowchart LR
  Claude -- MCP stdio/HTTP --> Bridge[opencode_bridge_mcp.py]
  Bridge -- read/write --> DB[(tasks.db - SQLite)]
  CLI[tasks_cli.py] -- claim/complete --> DB
  Worker[worker.py - launchd 60s] -- polls DB, runs opencode run --> Target[working_dir / worktree]
  Worker -- streams logs --> Logs[task_logs/ID.log]
  Worker -- result/structured/usage --> DB
  Claude -- get_task_result / wait_for --> Bridge
  OpenCode -- claude_ask/submit --> Reverse[claude_control_mcp.py]
  Reverse --> ClaudeCLI[claude -p / Anthropic API]

Key components:

File

Role

opencode_bridge_mcp.py

MCP server (Claude side), stdio + streamable-http / sse, host allowlist

task_store.py

Shared SQLite logic: submit/claim/complete, retries, deps, notes, heartbeat

tasks_cli.py

CLI for manual runs: pending list show claim complete fail logs heartbeat answer

worker.py

Headless executor: opencode run --auto, timeouts, reaper, worktrees, banners

claude_control_mcp.py

Reverse MCP server (OpenCode → Claude)

install.sh

One command: deps + bearer token + ngrok tunnel + allowlist + serve

start_public.sh

Background variant: tunnel + allowlisted MCP server (ngrok fixed domain or Cloudflare)

claude_desktop_config.snippet.json

Claude Desktop wiring template (<REPO_PATH> placeholder)

Configuration

Variable

Used by

Purpose

TASK_DB_PATH

bridge, CLI

SQLite path (default: <repo>/tasks.db)

WORKER_MODEL

worker

Default model, e.g. opencode/cat-jam-free

OPENCODE_BIN

worker

opencode binary (default: opencode on PATH)

WORKER_NOTIFY

worker

1 (default) macOS banners, 0 to silence

MCP_ALLOWED_HOSTS

bridge (HTTP)

Comma-separated tunnel hosts (required behind ngrok/cloudflared, else 421)

BRIDGE_AUTH_TOKEN

bridge (HTTP)

Bearer token for MCP/API access (defaults to .bridge-token file)

TASKBRIDGE_CONSENT_SECRET

OAuth consent

Secret for the /approve "this was me" page (defaults to the bridge token)

TASKBRIDGE_TOKEN_TTL_SEC

OAuth

Access-token lifetime (default 86400 = 24h)

TASKBRIDGE_REFRESH_TTL_SEC

OAuth

Refresh-token lifetime (default 2592000 = 30d)

MCP_PUBLIC_URL

bridge (HTTP)

Force the public origin in OAuth documents when behind unusual proxies

ANTHROPIC_API_KEY

reverse bridge

Direct API backend; else claude -p CLI

CLAUDE_API_MODEL / CLAUDE_MCP_HOME

reverse bridge

Model override / shadow HOME for CLI backend

start_public.sh: uses .ngrok-domain if present (fixed domain), else Cloudflare quick tunnel. It re-pairs MCP_ALLOWED_HOSTS automatically — rerun it whenever the URL changes. If .bridge-token exists, BRIDGE_AUTH_TOKEN is loaded automatically. install.sh is the interactive equivalent — it prompts for the ngrok Authtoken and serves MCP in the foreground.

Project structure

.
├── opencode_bridge_mcp.py          # MCP server (Claude → queue)
├── task_store.py                   # SQLite queue logic
├── tasks_cli.py                    # Manual worker CLI
├── worker.py                       # Auto-worker (launchd, 60s)
├── claude_control_mcp.py           # Reverse bridge (OpenCode → Claude)
├── install.sh                      # One-command installer: deps, token, tunnel, serve
├── start_public.sh                 # Public tunnel + server launcher (background)
├── claude_desktop_config.snippet.json
├── claude-clean.settings.json
├── requirements.txt                # mcp>=1.0,<2
└── .gitignore                      # excludes .venv/, tasks.db, logs, task_logs/, claude_jobs/, worktrees/

Runtime state (never committed): tasks.db, task_logs/, claude_jobs/, worktrees/, *.log, heartbeat.json.

Troubleshooting / FAQ

  • 421 behind tunnel: MCP_ALLOWED_HOSTS must exactly match the tunnel host. Run ./install.sh or ./start_public.sh — they set this automatically.

  • Tunnel URL changed (Cloudflare): quick-tunnel hostnames rotate every restart. Rerun ./install.sh (or ./start_public.sh), or reserve an ngrok fixed domain via .ngrok-domain.

  • Worker looks stuck: check ./.venv/bin/python tasks_cli.py heartbeat and task_logs/<id>.log. Stale running tasks are reaped after 15 min (per-task timeout_sec + slack honored).

  • Double-run after kill: every attempt spawns opencode run --standalone --title br-<id>-a<n> (per-attempt argv marker, ps-visible). The reaper kills the orphaned CLI child plus its ppid tree (loop-until-empty, hard-capped so a matching bug can't mass-kill); a per-pass sweep catches tagged strays of non-running tasks. Reaper deadline is per-task (max(900s, timeout_sec + 120s)). macOS gives no session/env signal (ps sess= is 0 everywhere, ps -E shows no env), so ppid walk + argv marker are the whole toolkit — reparented-to-1/setsid grandchildren are the known residual. NOTE: never pass --session to the CLI: it makes free-tier models refuse to run.

  • No banners: macOS only. Set WORKER_NOTIFY=0 to silence intentionally; full output is always in tasks.db + worker.log.

  • Reverse bridge fails fast (~1s): run claude login once or set ANTHROPIC_API_KEY.

  • opencode not found: put it on PATH or set OPENCODE_BIN=/path/to/opencode.

Limitations

  • Token counts aren't exposed by the opencode CLI — usage reports model, latency, attempts, and sandbox status, not tokens.

  • no-network / no-shell are prompt-level restrictions, not OS-enforced sandboxes (documented in worker.py).

  • Desktop banners and launchd scheduling are macOS-specific; Linux needs its own scheduler (systemd/cron) + worker.py still runs manually.

  • Long tasks (up to 1800s timeout_sec) are supported, but the 60s launchd cadence means pickup latency averages ~30s.

Verified

  • task_store submit → claim → complete round-trip: OK

  • MCP submit_task / get_task / list_tasks via call_tool: OK

  • CLI pending / show / claim / complete: OK

  • submit_task → auto-picked-up in ~60s → get_task_result with zero manual steps: OK

License

Apache-2.0 — see LICENSE. Fork it, self-host it, build on it.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables orchestrating multiple AI CLI agents (Codex, Grok, Claude, etc.) via Claude Code, providing tools for dispatching tasks, broadcasting, debating, and managing jobs through a local MCP server.
    MIT
  • F
    license
    Not graded
    quality
    B
    maintenance
    Enables MCP clients like Claude Code to delegate coding tasks to a local model through a configurable coding-agent CLI backed by any OpenAI-compatible endpoint, with tools for health checks, task execution, and context-aware file modifications.
    -
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables multiple AI coding agents to work concurrently on the same repository by coordinating task ownership, file locks, architectural decisions, investigation gaps, handoffs, and audit activity through a local SQLite-backed MCP interface.
    15 npm
    MIT