vibedev-bridge
by hoan9an
README.md
# vibedev-bridge
A local MCP server that gives web-based AI agents (claude.ai, ChatGPT, Grok — experimental: Gemini, plus Postman's AI agent) read/edit access to your machine's files and a shell allowlist, over HTTPS through a swappable public edge.
- **Transport:** Streamable HTTP MCP behind OAuth 2.1
- **Default edge:** [Tailscale Funnel](https://tailscale.com/docs/features/tailscale-funnel) — bring-your-own Cloudflare named tunnel or any stable HTTPS origin also supported
- **Platforms:** Windows · Linux · macOS (Node.js 22)
- **npm:** private/unpublished — run from source
## Why
Web AI subscriptions (Claude Pro, ChatGPT Plus…) are far cheaper than per-token API use for everyday work, but an agent without access to your files can only talk. The official desktop apps tie local access to a device ID and one login at a time; with a remote MCP server you instead get:
- **Web-quota file and shell access** directly from the browser
- **Multi-account flexibility** — switch browser profiles, point every account at the same machine
- **Fail-safe execution** — the shell tool is a hand-curated allowlist, not a blocklist ([Security](#security))
Typical scenarios: at your desk use a native CLI (fastest). Away from the desk — mobile/web/another machine — use this to check jobs, read logs, pull code or clean temp files. Combined with Grok's scheduled prompts it also runs as a headless cloud-triggered local node.
## Install & run
```bash
git clone https://github.com/hoan9an/vibedev-bridge.git
cd vibedev-bridge
npm install
```
```bash
# Development mode (~/.vibedev/mcpsv-dev, ports 9997/9996)
npm run dev
# Production mode (~/.vibedev/mcpsv, ports 9999/9998)
npm start
```
`npm start` (foreground; Ctrl+C to stop) handles everything:
- **Passphrase** and **OAuth client ID/secret** generated once under `~/.vibedev/mcpsv/`, reused on later runs
- **Funnel**: checks `tailscale funnel status`; if port 9999 is not Funnel-enabled yet, runs `tailscale funnel --bg 9999` (idempotent)
- Prints the four values you need for the connectors: **MCP URL**, **Client ID**, **Client Secret**, **Passphrase**
- Opens the **control panel** at `http://127.0.0.1:9998/?t=<token>` — sections: `0 Setup` (ingress picker) → `1 Connectors` → `2 Install vibedev-rules` → `3 Instructions` → `4 Browser utilities` → `5 Folders` → `6 Shell allowlist`
Nothing is installed system-wide, no background daemon is created, and no admin rights are required. Closing the terminal stops the server.
### Allowed folders
The default allowed root is your home directory. Add/remove absolute paths in panel section 5 — saves take effect on the next tool call, no restart. Beyond the configured roots, the tools are also granted `~/.vibedev` (where [vibedev-rules](https://github.com/hoan9an/vibedev-rules) deploys) and `~/.claude`, so web agents can read your native `CLAUDE.md` and the rule corpus the way Claude Code does. `~/.claude` is granted at folder level (known tradeoff — session tokens inside it are reachable; the panel row is locked, edit `~/.vibedev/mcpsv/setting.json` directly if you want it removed).
## Connecting
All clients use the same MCP URL and the same passphrase consent screen; only *how* they authenticate differs.
### Claude web (claude.ai) — primary
1. `claude.ai → Settings → Connectors → Add custom connector`
2. Paste the **Remote MCP server URL** (`https://your-machine.ts.net/mcp`)
3. **Advanced settings** → paste Client ID + Client Secret printed by `npm start`
4. Click Connect → enter the **passphrase** on the local consent page
### ChatGPT — needs Developer mode
1. Enable **Developer mode** (Settings → Security and login)
2. Add a custom connector with the same MCP URL
3. Same passphrase on the consent page
ChatGPT self-registers via RFC 7591 DCR (`POST /register`, PKCE, no secret). Do not paste Claude's credentials — each client binds to its own registration.
### Grok
Self-registers via the same DCR path — paste only the MCP URL, no Client ID. Its redirect URI is allowlisted; if Grok ever moves it, the server logs `register REJECTED (redirect_uri not allowlisted)` with the new value to re-allowlist.
### Gemini (experimental)
Same confidential-client flow as Claude (client ID + secret). The OAuth handshake works, but tool discovery/drive is unreliable in testing — treat Claude and Grok as the dependable clients today.
### Postman (AI agent)
No OAuth redirect and no persistent system prompt on Postman's side:
1. Panel → Postman tab → copy the filled JSON (live MCP URL + minted token)
2. Postman → Settings → Connected Accounts → add MCP server → paste the JSON
3. Paste the panel's prompt at the start of each chat
The Postman tab also hosts a **Launch** button that attaches a CDP-driven control daemon to the Postman desktop app (auto-clicking Approve/Run, toggling Thinking/Auto-run, opening a new window). It starts only when you click Launch — never at boot. Its injected overlay is the **VibeSoft MCP for Postman** panel; external links (the vibedev-bridge and vibedev-rules repos) open in your OS default browser.
## Official tool suite (39 tools)
- **Chromium control:** `vibe__chrome_profiles` `vibe__chrome_launch` `vibe__chrome_tabs` `vibe__chrome_interact` `vibe__chrome_probe_ai` `vibe__chrome_stop`
- **DevTools / CDP:** `vibe__devtools_targets` `vibe__devtools_eval` `vibe__devtools_screenshot`
- **System:** `vibe__notify_user` `vibe__clipboard_read` `vibe__clipboard_write`
- **HTTP:** `vibe__local_fetch` (SSRF-protected localhost/LAN)
- **Background tasks:** `vibe__task_start` `vibe__task_manage`
- **Filesystem:** `vibe__read_text_file` `vibe__write_file` `vibe__edit_file` `vibe__create_directory` `vibe__move_file` `vibe__get_file_info` `vibe__list_allowed_directories`
- **Search & shell:** `vibe__find_path` `vibe__search_content` `vibe__run_cmd`
- **Ports & dev servers:** `vibe__port_status` `vibe__kill_port`
- **Git:** `vibe__git_status` `vibe__git_diff` `vibe__git_log`
- **SQLite:** `vibe__sqlite_schema` `vibe__sqlite_query`
- **Agents & context:** `vibe__agy_run` `vibe__kiro_read` `vibe__vibedev-rules_context`
- **Postman:** `vibe__postman_status` `vibe__postman_eval` `vibe__postman_rename_conversation` `vibe__postman_panel_fullwidth`
## Requirements
- **Node.js 22** — Windows, Linux or macOS
- **Windows only:** [Git for Windows](https://git-scm.com/download/win) on `PATH` — the shell/search tools shell out to Unix binaries (`grep`, `find`, …); `usr/bin` ships what's needed. Prerequisite, not a code dependency.
- **Tailscale** (default edge, one-time): [install](https://tailscale.com/download), sign in, and enable Funnel for your tailnet once (`login.tailscale.com/f/funnel`) — free on every plan. `npm start` enables port 9999 automatically after that.
## Architecture
```
Claude web / ChatGPT / Grok
│ HTTPS + OAuth 2.1 (Claude: pre-registered client · others: DCR self-register)
▼
Tailscale Funnel (https://your-machine.ts.net)
│
▼
gatekeeper.js — public port 9999
│ /.well-known/oauth-* + openid-configuration (discovery)
│ /authorize, /token minimal authorization server (oauth.js)
│ /register RFC 7591 dynamic client registration
│ /mcp Bearer-token protected → streamable-bridge.js (Streamable HTTP)
▼
tools-server.js — one shared in-process McpServer (InMemoryTransport) mounting:
filesystem · search · shell (allowlisted) · agy · kiro · rule-context · chrome/CDP ·
postman · fetch · task · port · git · system · sqlite
panel.js — loopback-only control panel (:9998), token-gated, never exposed
```
The ingress is swappable: Tailscale Funnel (default) → your own `PUBLIC_ORIGIN` → a Cloudflare named tunnel (`--tunnel <cred.json> --origin <host>`). Precedence follows that order; OAuth and tools are unchanged either way. The panel (section 0) also accepts a cloudflared credentials JSON for no-CLI setups.
**Funnel troubleshooting:** a "Couldn't connect" while `tailscale funnel status` says "on" is usually a serve-config sync failure to Tailscale's edge, not a code bug. Don't test with a bare `curl https://<host>` from the tailnet-joined machine (the mesh shortcut hides the failure) — test the public path:
```bash
dig @8.8.8.8 <host> A +short
curl --resolve <host>:443:<IP> https://<host>/.well-known/oauth-authorization-server
```
A TLS error there → re-run `tailscale funnel --bg 9999` to force a re-push. Avoid toggling Funnel repeatedly (Let's Encrypt rate limits).
## Configuration
Copy `.env.example` to `.env` for overrides — `PUBLIC_ORIGIN`, `GATEKEEPER_PORT`, `PANEL_PORT`, `MCP_DATA_DIR`, `MCP_REQUEST_TIMEOUT_MS`. Boot falls back to defaults silently without the file.
## Directory layout
```
vibedev-bridge/
├── bin/vibedev-bridge.js # global CLI entry (npm package stays private)
├── scripts/
│ ├── start.js # single process: gatekeeper + panel + warm tools server
│ ├── gatekeeper.js / oauth.js # public edge: OAuth server + /mcp proxy
│ ├── streamable-bridge.js # Streamable HTTP shim → in-process MCP server
│ ├── tools-server.js # mounts every tool register() module
│ ├── filesystem-mcp.js · search-mcp.js · shell-mcp.js (+ allowlist.js)
│ ├── chrome-mcp.js · chrome-profile.js · cdp-mcp.js · cdp-engine.js
│ ├── postman-mcp.js · rule-context{,-mcp}.js · fetch-mcp.js · task-mcp.js
│ ├── port-mcp.js · git-mcp.js · system-mcp.js · sqlite-mcp.js · agy-mcp.js · kiro-mcp.js
│ ├── panel.js · config-page.js # loopback control panel
│ ├── vibe-pmcontrol/ # CDP-driven Postman desktop control daemon
│ ├── roots.js · log.js · http.js · html.js · open-browser.js · userdata.js
│ └── update-check.js # local-only version snapshot, no remote check
├── test/ # suite (npm test)
└── public/ # panel CSS/JS + images
```
User data lives outside the repo at `~/.vibedev/mcpsv/` (settings, tokens, passphrase, DCR clients — all `0600`), the same convention as `~/.aws` / `~/.docker`. A clone never produces a diff from panel edits.
## Security
- **Whitelist, not blocklist.** Only allowlisted commands run; the default set is read-only (flag-rich binaries whose own flags escape read-only — e.g. `find -delete` — stay out). Adding commands is a deliberate panel edit, and any command you add runs under your responsibility.
- **OAuth 2.1 over token-in-URL.** One pre-issued confidential client (Claude) + DCR public clients (ChatGPT/Grok); tokens bound to their issuing client ID. Details: `docs/ref/security-model.md`.
- **Single public entry point** (`gatekeeper.js`); the panel binds `127.0.0.1` only, is token-gated per request, and is never exposed.
- **Tokens persist** in `~/.vibedev/mcpsv/tokens.json` (`0600`) — a connector is long-lived file access, not a login. Revoke by deleting `tokens.json` (or `oauth-dcr-clients.json`) and restarting.
### Whitelist rationale
Fail-safe by default (unknown commands blocked), minimal attack surface, subcommand granularity (`git` scoped to `status/log/diff/show`), neutralized prompt-injection escalation (nothing unlisted to reach for), read-only by construction. Distinct from Desktop Commander's local blocklist approach by design.
## Notes
- **Connector icon:** claude.ai queries Google's favicon service with the tailnet apex domain, which has no public DNS record — a default letter icon is what you'll see. The gatekeeper still serves a valid `/favicon.ico`; the browser just never asks this server for it.
## License
MIT — see [LICENSE](LICENSE).
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues