Skip to main content
Glama
kumabear917

bearier-mcp

by kumabear917
README.md
# 🐻 Bearier MCP

> **Multi-Agent Desktop Toolkit for Everyone**

[English](README.md) | [简体中文](README.zh-CN.md)

Bearier MCP lets two desktop AI agents collaborate asynchronously through a shared local task queue — **no API keys, no IDE, no cloud**. Just one SQLite file bridging agents that otherwise can't talk to each other.

Pair a **brain** agent (plans, dispatches, reviews) with a **hands** agent (executes, reports). They coordinate through Bearier without either one needing an open API, an SDK, or a single line of glue code.

```
  Brain agent                    Hands agent
  (plans, dispatches,            (claims, executes,
   reviews results)               reports back)
        │                              │
        │ MCP stdio                    │ MCP stdio
        ▼                              ▼
   ┌─────────────────────────────────────────┐
   │         Bearier MCP server (stdio)      │
   │                                          │
   │            shared SQLite (local)         │
   └─────────────────────────────────────────┘
```

## Why

Most multi-agent tooling assumes you're a developer who lives in an IDE, configures API keys, and wires up coding agents inside VS Code. **Bearier is built for everyone else** — people who use desktop AI apps that have no open API, no SDK, and no extension surface beyond an MCP config file and natural-language conversation.

If your agent can load an MCP server over stdio, it can join Bearier. That's the only requirement.

Bearier itself is just a **task post office**: it reliably relays tasks, persists state and result pointers, and gets out of the way. It never reasons, never executes commands, and never bypasses either agent's own permissions.

## Features

- **6-state machine with terminal-state protection** — a finished task can never be re-claimed by another worker
- **Atomic task claiming** — `UPDATE ... WHERE status='pending'` inside a transaction; the database guarantees a single winner even under concurrent `fetch_pending_tasks`
- **Heartbeat lease renewal + dual-layer timeout** — pending tasks that nobody claims expire to `failed`; running tasks whose lease lapses are reaped to `failed/TIMEOUT`
- **Approval gating** — tasks flagged `approval_required` or `destructive` are reported as `needs_approval` and never auto-executed
- **Path safety** — `working_directory`, `context_path`, `result_path` are validated by realpath against an allowlist; symlink traversal is rejected
- **Zero heavy dependencies** — Python stdlib `sqlite3` + `mcp`; no Node, no Rust, no Docker
- **Cross-platform** — runs on macOS, Windows, and Linux; paths adapt via `os.pathsep`

## Quick start

### 1. Install Python dependencies

Bearier needs Python 3.13+. Create a venv and install:

```bash
python -m venv .venv
source .venv/bin/activate   # Windows: .venv\Scripts\activate
pip install -r requirements.txt
```

> **macOS note (no Rust):** `mcp==1.28.1` pulls `pyjwt[crypto]` → `cryptography`, which needs Rust to build. Since stdio never uses RS256 JWT, install in two steps to skip it:
> ```bash
> pip install --no-deps mcp==1.28.1
> pip install --only-binary :all httpx-sse==0.4.3 jsonschema==4.26.0 \
>   python-multipart==0.0.32 sse-starlette==3.4.6 starlette \
>   typing-inspection uvicorn pyjwt==2.13.0
> ```

Verify: `python -c "from mcp.server.fastmcp import FastMCP; print('ok')"`

### 2. Configure both agents

Both agents must point at the **same `BRIDGE_DB_PATH`** — that's how they see each other's writes.

**WorkBuddy** — `~/.workbuddy/mcp.json`:

```json
{
  "mcpServers": {
    "bearier": {
      "command": "/path/to/python",
      "args": ["/path/to/bearier-mcp/src/server.py"],
      "env": {
        "PYTHONPATH": "/path/to/bearier-mcp/src",
        "BRIDGE_DB_PATH": "/path/to/bridge.db",
        "ALLOWED_WORKSPACES": "/path/to/your/workspace",
        "SHARED_DIR": "/path/to/your/shared"
      }
    }
  }
}
```

**Codex desktop** — `~/.codex/config.toml` (TOML, not JSON):

```toml
[mcp_servers.bearier]
type = "stdio"
command = "/path/to/python"
args = ["/path/to/bearier-mcp/src/server.py"]
startup_timeout_sec = 30

[mcp_servers.bearier.env]
PYTHONPATH = "/path/to/bearier-mcp/src"
BRIDGE_DB_PATH = "/path/to/bridge.db"
ALLOWED_WORKSPACES = "/path/to/your/workspace"
SHARED_DIR = "/path/to/your/shared"
```

Restart both apps after writing. In WorkBuddy, also click **Trust** on the connector management page.

### 3. Verify connectivity

Ask each agent to call `ping(caller="codex" / "workbuddy")`, then `list_ping_log(limit=5)`. If you see both pings, the bridge is live.

## Tools (7)

| Tool | Called by | Purpose |
|---|---|---|
| `ping(caller, note)` | both | connectivity check, writes `ping_log` |
| `list_ping_log(limit)` | both | view ping history |
| `db_info()` | both | database path + stats (debugging) |
| `assign_task(...)` | brain | dispatch a self-contained task |
| `fetch_pending_tasks(worker_id, limit, claim)` | hands | pull work; `claim=true` atomically transitions `pending→running` |
| `report_result(task_id, worker_id, status, ...)` | hands | report `done` / `failed` / `needs_approval` / `cancelled` / `running` (heartbeat) |
| `get_task_result(task_id)` | brain | fetch result + full event timeline |

## State machine

```
pending → running              claim
pending → failed               claim timeout (no worker)
running → running              heartbeat / lease renewal
running → done                 success
running → failed               failure / lease timeout (TIMEOUT)
running → needs_approval       requests human approval
running → cancelled            cancelled
```

`done` / `failed` / `needs_approval` / `cancelled` are **terminal** — re-submitting the same terminal state is idempotent and returns the stored result. Only `pending→running` and `running→running` change task ownership; all other transitions are reported by the holding worker.

## Automation (hands agent auto-pull)

Schedule the hands agent to periodically call `fetch_pending_tasks` so dispatched work gets picked up without human prompting. Suggested automation prompt:

> Call `fetch_pending_tasks(worker_id="workbuddy-default", limit=1, claim=true)`. If `count=0`, return silently. If a task is claimed: execute per `instruction` inside `working_directory`. On success call `report_result(status="done", task_id=..., worker_id="workbuddy-default", summary="...")`. On failure call `report_result(status="failed", task_id=..., worker_id="workbuddy-default", error_code="EXEC_ERROR", error_message="...")`. Tasks marked `destructive` or `approval_required=true` are reported as `needs_approval` and not executed.

For real-time collaboration, just tell the hands agent "pull pending tasks" — it responds in seconds. Automation is the offline backstop.

## Dashboard

Visualize the full collaboration timeline in a browser:

```bash
PYTHONPATH=src python src/dashboard.py
```

Open `http://127.0.0.1:8765`. Bound to loopback only — never exposed publicly. Each task card shows title, status badge, worker, timestamps, error (if any), and the event timeline (`created → claimed → completed`).

## Security

- **stdio only** — no network ports (dashboard binds `127.0.0.1` exclusively)
- **Never expose the database or dashboard to the public internet**
- Database lives on the local system disk — **not on ExFAT/removable drives** (multi-process SQLite locking is unreliable there)
- `working_directory` must fall inside `ALLOWED_WORKSPACES`
- `context_path` / `result_path` must fall inside `working_directory` or `SHARED_DIR` (realpath-validated, anti-traversal)
- `summary` ≤ 4 KB, `metadata` ≤ 16 KB — larger payloads are forced to disk via `result_path`
- `destructive` / `approval_required` tasks are never auto-executed by automation

## Error codes

`VALIDATION_ERROR` / `TASK_NOT_FOUND` / `INVALID_STATE` / `TASK_ALREADY_CLAIMED` / `WORKER_MISMATCH` / `TIMEOUT` / `PERMISSION_DENIED` / `PATH_VIOLATION` / `RESULT_TOO_LARGE` / `EXEC_ERROR` / `DATABASE_BUSY` / `UNKNOWN`

Uniform response shape: `{ok: bool, data: dict|null, error: {code, message, retryable}|null}`

## How it compares

Bearier occupies a lane most multi-agent tools don't:

| | Bearier MCP | agent-orchestration | beads-village |
|---|---|---|---|
| Target user | everyone (non-coders) | developers in IDEs | developers in IDEs |
| Target agent | desktop apps, no API needed | IDE coding agents (Cursor, Copilot) | IDE coding agents |
| Agent discovery | by `worker_id`, no shared `cwd` | requires same `cwd` | team-based, same project |
| Transport | stdio | stdio | stdio |
| Task timeout | dual-layer (pending + lease) | agent-level only | none |
| Approval gating | yes | research gate | no |
| Dependencies | Python stdlib only | Node 18+ | Node + Python + optional Go |

If you live in an IDE and want rich in-editor coordination, those projects fit better. If you have two desktop AI apps that only speak MCP and want them to hand off tasks — Bearier is the bridge.

## Project structure

```
bearier-mcp/
├── README.md                   this file (English)
├── README.zh-CN.md             中文版
├── requirements.txt            pinned dependencies
├── src/
│   ├── server.py               MCP server (7 tools)
│   ├── config.py               config loading + path boundary checks
│   ├── errors.py               12 error codes + uniform response
│   ├── models.py               6-state machine + Task data structure
│   ├── db.py                   SQLite connection + schema init
│   ├── repository.py           task repo (CRUD + atomic claim + timeout sweep + path validation)
│   └── dashboard.py            collaboration timeline HTTP server (127.0.0.1:8765)
├── tests/
│   ├── test_ping_client.py     connectivity test
│   ├── test_repository.py      data layer (29 cases)
│   └── test_mcp_tools.py       MCP tool end-to-end (22 cases)
└── config-examples/
    ├── workbuddy.mcp.json      WorkBuddy config example
    └── codex-desktop.config.toml  Codex desktop config example
```

## Testing

```bash
PYTHONPATH=src python tests/test_repository.py   # 29 cases
PYTHONPATH=src python tests/test_mcp_tools.py    # 22 cases
PYTHONPATH=src python tests/test_ping_client.py  # connectivity
```

## License

MIT