Skip to main content
Glama
Asutorufa
by Asutorufa
README.md
# Workspace Bridge

Workspace Bridge is a single-user, Linux-first MCP server for giving ChatGPT controlled access to local development projects.

It runs locally and exposes only registered projects. It supports both Streamable HTTP and stdio MCP transports. An OpenAI Secure MCP Tunnel client can start the stdio command directly, without a separate HTTP service process.

## Quick start

```bash
npm install
npm run build
npm run start
```

`npm run start` listens on `127.0.0.1:3001` and exposes:

- `GET /health`
- MCP Streamable HTTP at `/mcp`

When the server listens on loopback (the default), the MCP endpoint does not require a Bearer token. This is the intended setup for a local OpenAI Secure MCP Tunnel client.

For a stdio MCP client, build the project and start the stdio entrypoint:

```bash
npm run build
npm run stdio
```

The stdio entrypoint writes only MCP JSON-RPC messages to stdout. Keep logs and diagnostics on stderr so the transport remains valid.

If you intentionally bind the server to a non-loopback address, configure a Bearer token before starting it:

```bash
node dist/cli.js auth generate
```

Retrieve the configured token later with:

```bash
export WORKSPACE_BRIDGE_TOKEN="$(node dist/cli.js auth show)"
```

Register a project from another terminal:

```bash
node dist/cli.js project add my-project /absolute/path/to/project
node dist/cli.js project list
node dist/cli.js project show my-project

# Add another folder to the same project under a named root.
node dist/cli.js project root add my-project frontend /absolute/path/to/frontend
node dist/cli.js project root list my-project
```

The configuration is stored at `~/.config/workspace-bridge/config.json` unless `--config` is supplied.

A project starts with a `main` root. The legacy single-folder configuration is
stored as `{ "path": "..." }`; after adding another folder it is represented as
named roots, for example:

```json
{
  "projects": {
    "my-project": {
      "roots": {
        "main": { "path": "/absolute/path/to/project" },
        "frontend": { "path": "/absolute/path/to/frontend" }
      }
    }
  }
}
```

Use `root` on file, command, process, and Git tools to select a named root.
It defaults to `main`, so existing calls remain compatible. Paths and command
`cwd` values are relative to the selected root. File moves are restricted to a
single root; Git operations run from the selected root.

## Permission modes

The default mode is `guarded`. Change it locally with:

```bash
node dist/cli.js policy set-mode guarded
node dist/cli.js policy set-mode permissive
```

`guarded` marks writes and process execution as consequential so ChatGPT can request confirmation. `permissive` removes that confirmation hint. Both modes still enforce project selection, path validation, output limits, and timeouts.

Commands run with a root-relative cwd (the `main` root by default), optional per-process environment overrides, and optional one-shot UTF-8 stdin. Cwd is resolved and checked inside the selected project root; this still does not provide a complete filesystem sandbox for arbitrary commands.

## Coding-focused MCP tools

The bridge includes batch tools so a coding task does not need one MCP round trip per file or command:

- `workspace_context`: returns project overview, a bounded tree, and optionally Git diff in one read-only call.
- `file_read_many`: reads multiple files concurrently while deduplicating paths and bounding the response size.
- `file_write_many`: writes multiple UTF-8 files after validating every path and expected hash, with one confirmation.
- `file_patch_many`: applies multiple exact unified diffs after validating every path, patch, and expected hash, with one confirmation.
- `apply_patch`: applies a Codex-style `*** Begin Patch` envelope with Add/Delete/Update/Move operations after validating all hunks.
- `file_replace`: replaces an exact text fragment when its occurrence count matches `expectedOccurrences` (default 1), failing safely when it is missing or ambiguous.
- `file_stat`: returns lstat-style metadata for a known path and can stream a SHA-256 when requested.
- `file_mkdir`: creates a project directory with explicit recursive behavior.
- `bridge_info`: reports bridge capabilities, limits, policy, and project-root restrictions without secrets.
- `project_root_add` / `project_root_remove`: manage named folders inside an existing project; `project_info` and `project_list` return all configured roots.
- `git_show`: shows a bounded local revision, optionally for one project-relative historical path.
- `command_run_batch`: runs bounded commands with configurable concurrency, per-command cwd/env/stdin, and independent results.

Use the batch tools for independent files or checks. Keep dependent commands and edits in separate calls so their ordering remains explicit. The existing single-file and single-command tools remain available for focused work.

`file_patch` and `file_patch_many` require standard unified diffs with `---`, `+++`, and `@@` headers. Use `apply_patch` for Codex-style `*** Begin Patch` input. Unsupported or malformed input is rejected instead of being treated as a successful no-op.

`command_run` accepts optional `cwd`, `env`, `stdin`, and `yieldTimeMs`. If the command is still running after that wait, the result includes a `sessionId`; use `process_read` with that ID and stop it with `process_stop` when finished. Command summaries retain the beginning and end of noisy output and report omitted bytes.

File writes and patches are UTF-8 only. Images under the configured byte and pixel limits are returned as MCP image content; other binary files return metadata. Symlinks are allowed only when their resolved target stays inside the selected project root. Directory listings and trees do not follow symlinks.

The audit log is stored beside the config as `audit.log` and contains request metadata only: tool, project, duration, command name, exit code, and errors. It does not record tokens, file contents, environment variables, or full command output.

## OpenAI Secure MCP Tunnel

The tunnel client can launch the stdio entrypoint itself, so no HTTP bridge process is needed:

```bash
tunnel-client init \
  --sample sample_mcp_stdio_local \
  --profile workspace-bridge-stdio \
  --tunnel-id tunnel_0123456789abcdef0123456789abcdef \
  --mcp-command "node /absolute/path/to/workspace-bridge/dist/cli.js stdio"
tunnel-client doctor --profile workspace-bridge-stdio
tunnel-client run --profile workspace-bridge-stdio
```

Replace the tunnel ID and absolute project path with the local values. `CONTROL_PLANE_API_KEY` must be present in the tunnel client's environment.

For the existing HTTP transport, run the bridge and the OpenAI Secure MCP Tunnel client in separate terminals:

```bash
npm run start
./tunnel-client run --profile workspace-bridge --health.listen-addr 127.0.0.1:18081
```

The tunnel client forwards to the local MCP endpoint configured by the `workspace-bridge` profile. No extra Authorization header is needed for the default loopback listener. `CONTROL_PLANE_API_KEY` is unrelated to Workspace Bridge MCP authentication. The systemd setup below loads it from an environment file.

The tunnel client's health endpoint is available at `http://127.0.0.1:18081`.

### Run both with systemd

`systemd/workspace-bridge.service` is a user-service unit for the current checkout. It starts both `npm run start` and `./tunnel-client run ...`, forwards both processes' logs to the journal, and restarts the pair if either process exits.

Create the tunnel client's environment file without committing it:

```bash
mkdir -p ~/.config/workspace-bridge
editor ~/.config/workspace-bridge/tunnel-client.env
chmod 600 ~/.config/workspace-bridge/tunnel-client.env
```

Put the following in that file:

```bash
CONTROL_PLANE_API_KEY=your_runtime_api_key
```

Install and start the user unit:

```bash
mkdir -p ~/.config/systemd/user
cp systemd/workspace-bridge.service ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now workspace-bridge.service
systemctl --user status workspace-bridge.service
```

Follow logs with:

```bash
journalctl --user -u workspace-bridge.service -f
```

If the repository is moved, update `WorkingDirectory`, `ExecStart`, and `RequiresMountsFor` in the unit. To run it without an active login session, enable user lingering once with `loginctl enable-linger "$USER"`.

## Cloudflare Tunnel

For a later HTTPS deployment, point a Cloudflare Tunnel published application at `http://127.0.0.1:3001` and protect it with Cloudflare Access. The bridge's loopback exception is intentional: it does not replace an Access policy for a publicly published hostname. This is a transport option, not a replacement for Workspace Bridge project authorization.

The optional tunnel child process can be configured and managed locally:

```bash
node dist/cli.js tunnel set cloudflared -- tunnel --url http://127.0.0.1:3001
node dist/cli.js tunnel enable
node dist/cli.js serve
```

For a named Cloudflare Tunnel or another client, replace the command and arguments. `serve` stops the managed tunnel and all bridge-owned project processes during shutdown.

## Development

```bash
npm run typecheck
npm test
npm run build
```