claude-bridge

claude-bridge

Enables remote-controlled shell/GUI access to your machine from Claude via MCP, with phone approval and audit logging.

Category
访问服务器

README

claude-bridge

Ever wanted to tell Claude to do something on your PC while you're away? It's a really bad idea, but here's a way to do it anyway: setting up a custom MCP connector. It requires a fair number of moving parts, but it's doable.

Remote-controlled shell/GUI access to your own machine from Claude (or any MCP-speaking client), gated by phone approval and audit logging. Every action — reading a file, running a command, logging into an app — requires an explicit tap on your phone via ntfy. Root-level commands additionally require a TOTP code.

This is not a toy. It gives an AI agent the ability to run real commands as root on your machine, once you approve it. Read the Security model section before you deploy this anywhere.

claude-bridge status dashboard

Unofficial, community project. Not affiliated with or endorsed by Anthropic. "Claude" and "MCP" are used here only to describe compatibility.

Quickstart

git clone <this-repo-url> claude-bridge
cd claude-bridge
cp .env.example .env        # edit CLAUDE_BRIDGE_PUBLIC_URL once you have a hostname
./setup.sh                  # generates secrets, prints your OAuth password + TOTP QR
docker compose up -d        # brings up the OAuth/MCP server + bundled ntfy
sudo ./agent/install-agent.sh "$(pwd)/data"   # installs the native executor

That's the "brain" (containerized, no host access) and the "agent" (native, the only piece that actually touches your machine) both running. Now:

  1. Put a reverse proxy or tunnel (Caddy, nginx, Cloudflare Tunnel, Tailscale Funnel, whatever you already use) in front of port 8000, and set CLAUDE_BRIDGE_PUBLIC_URL in .env to that public hostname before running setup.sh/docker compose up (it becomes the OAuth issuer and the JWT's aud/iss claims — it must match exactly what you expose).
  2. If your phone needs to reach ntfy from outside your LAN, expose port 8091 too (same proxy/tunnel), and subscribe to the notify topic printed by setup.sh in the ntfy app.
  3. In claude.ai: Settings → Connectors → Add custom connector, URL https://your-hostname/mcp, authorize with the password setup.sh printed.
  4. Ask Claude to do something. Approve on your phone. Done.

Architecture

The core design decision: the container never touches your host. Everything that needs real host access runs natively, outside Docker, installed by a plain shell script you can read top to bottom.

┌─────────────────────────── "brain" (Docker) ───────────────────────────┐
│  server.py        OAuth 2.1 + PKCE, and the MCP endpoint (merged into  │
│                    one ASGI app -- no separate reverse proxy needed)   │
│  agent_server.py   Unix-socket listener: submit/status/list/cancel/    │
│                    confirm_totp                                       │
│  control_listener  subscribes to ntfy, processes Approve/Deny          │
│  status_server.py  read-only dashboard + JSON API                      │
└──────────────────────────────┬──────────────────────────────────────────┘
                                │  shared bind-mounted ./data volume
                                │  (queue.db, agent.sock, secrets)
┌───────────────────────────────┴──────────────────────────────────────────┐
│  "agent" (native, installed by agent/install-agent.sh)                   │
│  executor.py       polls the SAME queue.db, actually runs approved       │
│                    requests as a dedicated system user, with a           │
│                    narrowly-scoped sudo rule for the exec_sudo tier      │
└────────────────────────────────────────────────────────────────────────┘

A request's life cycle: MCP tool call → written to the shared queue → ntfy push notification → you tap Approve → control_listener records the decision → (exec_sudo: you also submit a TOTP code) → the native executor.py picks it up and actually runs it → result written back to the queue → Claude reads it via check_status.

Every request carries a content_hash (sha256 of action+detail+path+window_name+id) that approval/TOTP steps must match exactly, so an approval for one request can never be replayed against a different one.

Action tiers

run_command(action, summary, detail, path, window_name, timeout_seconds):

action uses notes
read path reads a file directly, no shell, capped at 300KB
write path + detail (new content) approval notification shows a unified diff
exec detail (shell command) runs as the agent user; set CLAUDE_BRIDGE_DISPLAY to let it launch GUI apps (see below); default timeout 60s
exec_sudo detail (shell command) runs as root via a denylist-backed sudo wrapper; default timeout 120s; requires a TOTP code after phone approval
gui_type detail (text to type) + window_name (required) types into a specific already-open window via xdotool; refuses if no window matches; the typed text is never logged or shown in the notification -- only its length and target window

TTLs before auto-expiry: read/write 600s, exec/exec_sudo 300s, gui_type 180s. timeout_seconds overrides the exec/exec_sudo default (max 1800s).

MCP tools

  • run_command(...) — submit a request, returns {id, status: "pending", expires_at}
  • check_status(request_id) — poll one request
  • list_pending() — everything still pending or awaiting_totp
  • cancel_request(request_id) — withdraw your own not-yet-executed request
  • confirm_totp(request_id, code) — submit the TOTP code for an exec_sudo request stuck at awaiting_totp, entirely from chat. This is what makes the whole thing phone-only capable — no terminal needed for the sudo tier.

GUI apps (optional)

To let exec/gui_type interact with a real desktop session:

  1. On the desktop, grant the agent user X11 access (re-run this every login, e.g. via your desktop environment's autostart):
    xhost +SI:localuser:claude-bridge-agent
    
  2. Uncomment CLAUDE_BRIDGE_DISPLAY=:0 in /etc/systemd/system/claude-bridge-agent.service and systemctl daemon-reload && systemctl restart claude-bridge-agent.

Understand what this means: the agent user can now draw on and interact with whatever's on that real, logged-in desktop. This is a deliberate capability, not a default — don't enable it unless you actually want it.

Security model

  • Nothing executes without a phone tap. No exceptions, no bypass flags.
  • exec_sudo additionally requires a TOTP code after the phone approval — two independent factors. The TOTP secret lives only in data/totp_secret.json.
  • Approval/cancel/TOTP steps are bound to content_hash, preventing cross-request replay (cancel_request is the one exception, by design — it can only move a request further from execution, never closer).
  • sudo_runner.py is the entire sudo attack surface for the agent user — root-owned so the agent can execute it (via the scoped NOPASSWD sudoers rule install-agent.sh installs) but never modify it. It has a denylist regex for catastrophic patterns (rm -rf /, raw-device dd/mkfs, fork bombs) as a last-resort backstop — the real boundary is the approval pipeline upstream of it.
  • The brain container has zero host access. No privileged mode, no host PID namespace, no docker socket mount. If it's fully compromised, the blast radius is "attacker controls your OAuth server and can submit fake requests to the same approval queue" — which still requires your phone tap (and TOTP, for root) to do anything.
  • Cross-UID data sharing: the brain container and the native agent are different UIDs sharing one bind-mounted directory. install-agent.sh and the app code both default to permissive (777/666) permissions on that directory as the pragmatic default for a single-host setup. If you want tighter isolation, put both under a shared group instead and adjust accordingly.
  • GUI access is opt-in and explicit (see above) — a compromised or buggy agent process with CLAUDE_BRIDGE_DISPLAY set could interact with whatever's on the real desktop session.

Configuration reference

All via .env (read by docker compose) or docker-compose.yml's environment: block:

variable default what
CLAUDE_BRIDGE_PUBLIC_URL http://localhost:8000 your externally-reachable hostname, no path suffix — becomes the OAuth issuer/audience
CLAUDE_BRIDGE_PORT 8000 host port for the MCP/OAuth endpoint
CLAUDE_BRIDGE_STATUS_PORT 8092 host port for the status dashboard

Native agent (agent/install-agent.sh writes these into the systemd unit):

variable what
CLAUDE_BRIDGE_DB_PATH, CLAUDE_BRIDGE_AUDIT_LOG, CLAUDE_BRIDGE_HEARTBEAT_PATH, CLAUDE_BRIDGE_SOCK_PATH paths into the shared ./data directory
CLAUDE_BRIDGE_SUDO_RUNNER, CLAUDE_BRIDGE_AGENT_PYTHON how the exec_sudo tier invokes sudo_runner.py
CLAUDE_BRIDGE_DISPLAY optional, e.g. :0 — enables GUI apps (see above)

Troubleshooting

  • sqlite3.OperationalError: attempt to write a readonly database — the queue.db was created by one side (container or native agent) with permissions the other side's UID can't write to. bridge_queue.py chmods it to 666 on every init_db() call, but if you're still hitting this, check ls -la ./data.
  • PermissionError reaching the data directory from the native agent — the shared data directory needs to be traversable by the agent user through every parent directory, not just have open permissions itself. Putting your checkout inside a locked-down home directory (some distros ship ~ as drwx--x---) will break this even if ./data itself is wide open. Either put the project somewhere globally traversable (e.g. /opt/claude-bridge), or add the agent user to a group with access to your home directory.
  • ntfy: "auth is unconfigured for this server"NTFY_AUTH_FILE must be set for ntfy user add to work at all; docker-compose.yml already sets this, but if you changed it, check that first.
  • SELinux (Fedora/RHEL/etc): containers failing to write to bind mounts — the compose file already appends :z to the volume mounts. If you added new mounts, do the same, or you'll see avc: denied entries in journalctl / ausearch -m avc.
  • Fresh chat claims a tool (usually confirm_totp) doesn't exist — claude.ai caches the tool list per connector session, not per conversation. Fully disconnect/reconnect the connector in Settings → Connectors rather than just starting a new chat.

License

MIT — see LICENSE.

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选