Skip to main content
Glama
mcp-agent-bridge

mcp-agent-bridge

by Wolrym
README.md
# MCP Agent Bridge

Two MCP servers that give an AI agent real hands on this Windows machine -
one for files, one for commands, background jobs and skills - with token
auth, live project switching, an undo history and an optional Cloudflare
Tunnel for remote clients. Pure Python, with a PySide6 control panel.

See `ARCHITECTURE.md` for the design and the reasoning behind it.

## Works with

Any MCP client that can reach a remote (streamable HTTP) server and send a
Bearer token - for example Notion AI custom connections, Claude Code,
Cursor or VS Code. A client on the same machine can use
`http://127.0.0.1:9500/mcp` and `http://127.0.0.1:9501/mcp` directly; the
tunnel is only needed for clients that run elsewhere.

## Quick start (Windows)

1. **Python 3.10+** from [python.org](https://www.python.org/downloads/)
   (tick "Add python.exe to PATH"), then in the project folder:
   ```cmd
   pip install -r requirements.txt
   ```
2. **cloudflared** (only for remote clients):
   `winget install Cloudflare.cloudflared`
3. Create the shortcuts once - nothing is built or installed:
   ```cmd
   python tools\make_shortcuts.py            :: add --desktop for desktop copies
   ```
   **MCP Agent Bridge** opens the panel without a console window (log in
   `logs\bridge.log`); **MCP Agent Bridge (debug)** keeps a console open
   with the live log and any traceback. Shortcuts hold absolute paths, so
   run the script again after moving or renaming the folder.
   `start_gui.bat` and `python -m gui.app` still work too.
4. On the **Tunnel** page set the base domain and your user prefix,
   point it at your cloudflared `config.yml`, then press **Start
   everything** at the top of the sidebar.
5. Open **Setup guide**: it has the endpoint URLs, the token, a config
   snippet and a ready-made "connect an MCP server" request to copy.

Headless:

```cmd
python run_core.py                 :: servers + tunnel
python run_core.py --no-tunnel     :: local only
python run_core.py --print-config  :: token, ports, projects, endpoints
```

Settings-only commands (safer than editing the JSON by hand):

```cmd
python run_core.py --set-tunnel-config "C:\Users\<you>\.cloudflared\config.yml"
python run_core.py --domain example.com --user-slug alex
python run_core.py --enable-tunnel
python run_core.py --disable-tunnel
python run_core.py --enable-backups
python run_core.py --regenerate-token
```

Only one instance can hold the ports: do not run `run_core.py` and the
control panel at the same time. A second panel refuses to start.

When sharing the folder, leave out `config/settings.json` (it holds your
token and is recreated on first launch), `logs/`, `.mcp-runs/`,
`.mcp-backups/` and `__pycache__/`.

## Tools

**Files System** (port 9500)

| Tool | What it does |
| --- | --- |
| `read_file` | Paged text read (`offset`/`limit`, long lines clipped); PNG/JPEG/GIF/WebP come back as images |
| `read_multiple_files` | Several files in one call, each paged |
| `write_file` | Create or replace; keeps the existing line endings and BOM |
| `edit_file` | Exact snippet replace (`replace_all`, `dry_run`); refuses non-UTF-8 files instead of corrupting them |
| `list_directory`, `get_file_info` | Listing (capped) and metadata |
| `search_files`, `grep` | Glob and regex search, skipping `.git`, `node_modules` and friends |
| `create_directory`, `move_file`, `delete_file` | The usual, with undo |
| `list_changes`, `undo_change` | Undo history (when backups are enabled) |
| `pack_files`, `unpack_files` | Base64 zip transfer to and from an agent's own sandbox - for images, PDFs, office files, whole folders |

**Terminal** (port 9501) - commands go through `cmd.exe`

| Tool | What it does |
| --- | --- |
| `run_command` | Runs a command and waits up to 45 s; if it is still running it stays alive in the background and the reply gives its job id |
| `start_process` | Starts a dev server, watcher or long build in the background |
| `wait_process` | Waits for a job and returns early when it ends or a line matches `until` (regex); `tail` for the last lines |
| `get_output`, `stop_process`, `list_processes` | Job output (paged or `tail`), stop the whole process tree, list jobs |
| `get_active_project` | The default folder, whether outside access is allowed, other projects |
| `list_skills`, `get_skill` | Global skills - the bridge's own (`skills/core/`) and those installed with `npx skills` (`skills/.agents/skills/`), grouped by folder or by `skills/categories.json` - plus project skills (`.agents/skills`, `.agent/skills`, `.claude/skills`, `.opencode/skills`) and the project's `AGENTS.md` / `CLAUDE.md` / `GEMINI.md`; `skills/bin/` is hidden. `tools/pack_skill.py` zips one, several or all skills |
| `notify_user` | Telegram "task finished" message, if configured |

Every result is sent once, as plain text. (FastMCP's default also sends a
structured copy, which doubles what the agent pays for each reply.)

## Projects and paths

The active project is the default for relative paths and for commands
without `cwd`. It is read on every call, so switching takes effect at
once - for every connected agent, because they all share this machine.
Agents should therefore use absolute paths. File tools refuse paths
outside the active project unless `security.allow_outside_project` is on.

## Security

- **The token is full control of this PC.** `run_command` runs anything
  the user can run, so the path checks of the file tools are a convenience,
  not a sandbox. Keep the token secret; regenerate it if it leaks.
- Every request needs `Authorization: Bearer <token>` (`X-API-Key` and
  `X-Auth-Token` also work). 20 wrong tokens from one address within five
  minutes block that address for 15 minutes (HTTP 429); behind the tunnel
  the real address comes from `CF-Connecting-IP`.
- `/health` needs no token and reveals only `{"status", "server"}`.
- An accident guard (`security.block_dangerous_commands`, on by default)
  refuses a short list of commands that wreck a machine (`format`,
  `diskpart`, `shutdown`, deleting a drive root, ...) or kill this app or
  its tunnel (`taskkill /im python.exe`, `cloudflared tunnel delete`, ...).
- Agents cannot overwrite `config/settings.json` through the file tools
  (`security.allow_settings_edit`).

## Settings

`config/settings.json` is created on first run. Prefer the panel or the
CLI flags: an invalid file is moved aside as `settings.broken.json` and
replaced with defaults. Useful keys:

| Key | Default | Meaning |
| --- | --- | --- |
| `limits.command_timeout` | 45 | How long `run_command` waits before backgrounding (never more than 50) |
| `limits.max_output_chars` | 100000 | Cap on one command output or file page |
| `limits.max_read_lines` | 900 | Default page size of `read_file` |
| `limits.max_image_bytes` | 5000000 | Largest image `read_file` returns |
| `limits.max_pack_mb` | 25 | Largest `pack_files` / `unpack_files` archive |
| `servers.session_idle_seconds` | 900 | Drop MCP sessions idle this long |
| `security.allow_outside_project` | false | Let file tools leave the active project |
| `security.block_dangerous_commands` | true | Accident guard |
| `backups.enabled` | false | Keep the undo history in `.mcp-backups/` |

## Logs

The panel's Activity page shows one line per tool call (tool, key
arguments, duration, error) plus server and tunnel events; long lines wrap
under their message. Calls shows only tool calls, Problems only warnings
and errors. Everything also goes to `logs/bridge.log` (rotated at about
1 MB), including cloudflared's start-up details, which the panel leaves
out. cloudflared lines that only mean a client hung up ("context
canceled", "stream ... canceled by remote with error code 0") are dropped.

## Cloudflare Tunnel

One-time setup, with your own tunnel name and hostnames:

```cmd
cloudflared tunnel login
cloudflared tunnel create mcp-agent-bridge
cloudflared tunnel route dns mcp-agent-bridge alex-files.example.com
cloudflared tunnel route dns mcp-agent-bridge alex-term.example.com
```

Ingress in the cloudflared config, keeping `http_status:404` last:

```yaml
tunnel: mcp-agent-bridge
credentials-file: C:\Users\<you>\.cloudflared\<tunnel-id>.json
ingress:
  - hostname: alex-files.example.com
    service: http://localhost:9500
  - hostname: alex-term.example.com
    service: http://localhost:9501
  - service: http_status:404
```

cloudflared runs as a child process of the panel: it starts with it and
stops when the panel exits. A wildcard CNAME (`*` -> your tunnel) lets
every teammate pick a prefix without touching DNS.

## Tests

Plain scripts, each safe to run while the real panel is live (throwaway
settings, ports 9400/9401 at most):

```cmd
python tests\tools_test.py     :: tool behaviour through FastMCP
python tests\smoke_test.py     :: both servers over HTTP: auth, tool lists, /health
python tests\jobs_test.py
python tests\backups_test.py
python tests\test_notifier.py
```