Drive PortBay from an AI Agent (MCP)
PortBay ships an Model Context Protocol server, portbay-mcp. Any MCP-aware agent — Claude Code, Cursor, Codex, Continue, Zed, Windsurf, and others — can drive PortBay directly: register projects, start and stop them, read logs, diagnose failures, and work a project's task board, without clicking through the GUI or remembering CLI flags.
The agent spawns portbay-mcp as a subprocess over stdio. The process boundary is the trust boundary — there is no port to open and no extra auth layer.
Add PortBay to your agent in 60 seconds
Install PortBay, point your agent at the bundled portbay-mcp binary, then ask. Claude Code, one line:
brew install portbay-app/portbay/portbay
claude mcp add portbay -- "$(which portbay-mcp)"Restart Claude Code and ask it to work your stack:
"List my PortBay projects, start the API, and pick up the next card on its board."
Every PortBay tool (prefixed portbay_) is now in the agent's tool list: register projects, start and stop them, read logs, and claim, work, and move task-board cards. Cursor, Zed, Codex, Continue, and Windsurf get the same access; their config snippets are in Configure your agent below.
How it's built and shipped
portbay-mcp is its own Rust workspace crate at src-tauri/crates/mcp/. It depends on portbay_lib with the mcp feature gate, which is what pulls in the rmcp and schemars stacks. Those dependencies are compiled only for the MCP binary, never for the GUI app.
The binary is built by scripts/build-mcp.sh, which compiles the crate and drops the result at src-tauri/binaries/portbay-mcp-<target-triple> — the location Tauri's bundler reads from its externalBin list. The finished PortBay.app includes portbay-mcp as a sidecar alongside the other bundled binaries.
To build from source:
# From the repo root — produces src-tauri/binaries/portbay-mcp-<triple>
./scripts/build-mcp.shOr build the crate directly (useful when iterating without Tauri):
cargo build --release -p portbay-mcp
# binary at src-tauri/target/release/portbay-mcpInstall
portbay-mcp is installed alongside the PortBay app.
brew install portbay-app/portbay/portbay
# installs portbay, portbay-mcp, and the PortBay app
which portbay-mcpcargo build --release -p portbay-mcp
# binary at src-tauri/target/release/portbay-mcpConfirm it runs (it blocks waiting for a client on stdin — press Ctrl-C):
portbay-mcp --helpConfigure your agent
Point your agent at the portbay-mcp binary. Use the absolute path from which portbay-mcp (commonly /opt/homebrew/bin/portbay-mcp on Apple Silicon).
Claude Code
claude mcp add portbay -- /opt/homebrew/bin/portbay-mcpOr edit ~/.claude.json (global) or a project .mcp.json directly:
{
"mcpServers": {
"portbay": {
"command": "/opt/homebrew/bin/portbay-mcp"
}
}
}Cursor
~/.cursor/mcp.json (global) or .cursor/mcp.json (per-project):
{
"mcpServers": {
"portbay": {
"command": "/opt/homebrew/bin/portbay-mcp"
}
}
}Zed
settings.json → context_servers:
{
"context_servers": {
"portbay": {
"command": { "path": "/opt/homebrew/bin/portbay-mcp", "args": [] }
}
}
}Continue
~/.continue/config.yaml:
mcpServers:
- name: PortBay
command: /opt/homebrew/bin/portbay-mcpWindsurf
~/.codeium/windsurf/mcp_config.json:
{
"mcpServers": {
"portbay": {
"command": "/opt/homebrew/bin/portbay-mcp"
}
}
}Codex
~/.codex/config.toml:
[mcp_servers.portbay]
command = "/opt/homebrew/bin/portbay-mcp"After adding the config, restart the agent. PortBay's tools (all prefixed portbay_) appear in its tool list.
End-to-end walkthrough
This shows how an agent brings a freshly-scaffolded Next.js app online and diagnoses a crash, step by step.
Scenario: you ran pnpm create next-app into ~/code/dashboard and want it running at https://dashboard.test.
1. Register and start in one call
You: Set up the app I just scaffolded at ~/code/dashboard.The agent calls portbay_setup with path: "/Users/me/code/dashboard". The tool:
- Auto-detects the framework (
next) from the folder contents. - Registers it in the PortBay registry with hostname
dashboard.testand HTTPS via mkcert. - Starts it via the Process Compose daemon.
- Returns
{ ok: true, project: { url: "https://dashboard.test", status: "running" }, detail: "Registered dashboard at dashboard.test (HTTPS). Started dashboard." }.
The agent reports: "Your app is running at https://dashboard.test."
2. Diagnosing a crash
You: My dashboard project stopped with an error.The agent runs three tools in sequence:
portbay_statuswithid: "dashboard"→ seesstatus: "crashed",restarts: 3.portbay_logswithid: "dashboard", lines: 50→ reads the tail of the process log.- (If the environment looks suspect)
portbay_doctor→ checks the registry, daemon, and required tooling.
It then explains what the log says and suggests a fix. If the fix involves a config change, it calls portbay_update_project and portbay_restart.
3. Registering without starting
You: Add the API at ~/code/api with HTTPS but don't start it yet.The agent calls portbay_detect_project to preview the suggested defaults, confirms them with you, then calls portbay_add_project with https: true. It does not call portbay_start. You start it later from the app or by asking again.
4. Scaffolding from scratch
You: Create a new Laravel project called blog in ~/code and set it up.The agent calls portbay_list_recipes to confirm the laravel recipe exists, then portbay_setup_from_recipe with recipe: "laravel", path: "/Users/me/code/blog". The recipe registers the project at blog.test with PHP-FPM, Caddy, and HTTPS. Because Laravel expects a MySQL database that PortBay can't provision yet, the result includes a warning — the project is registered and usable, with a note to add a database from the app's Databases panel.
For a brand-new scaffold (no folder yet), the agent calls portbay_setup_from_template instead, which runs the upstream scaffolder (pnpm create or composer create-project) before registering.
How it coordinates with the app
portbay-mcp is a client of the same system the GUI and CLI use. It never runs its own copy of the engine.
- Registry changes (add / update / remove / import / export) write to PortBay's registry file and take effect even if the app is not running. The app's reconcile loop picks them up on next boot and converges certs, Caddy routes, and
/etc/hosts. - Lifecycle actions (start / stop / restart / logs) talk to the running Process Compose daemon over HTTP. If the app is not running you get a
SIDECAR_DOWNerror telling you to open it. Passauto_launch: trueonportbay_startorportbay_setupto have the server open the PortBay app and wait for the daemon to come up — use this only when you are at your machine.
Stack recipes
Recipes are named blueprints that compose a project's framework, language version, document root, and HTTPS in one step. The agent maps your intent to a recipe id; PortBay applies it deterministically — no server-side model involved.
Current catalog (browse live with portbay_list_recipes or the portbay://recipes resource):
| Recipe id | Stack | Notes |
|---|---|---|
next | Next.js | Node dev server, HTTPS |
vite | Vite | Node dev server, HTTPS |
astro | Astro | Node dev server, HTTPS |
node | Generic Node | HTTPS |
static | Static HTML/CSS/JS | Caddy-served, HTTPS, no dev server |
php | Plain PHP | Caddy + PHP-FPM 8.3, HTTPS |
laravel | Laravel | PHP-FPM from public/, HTTPS; recommends MySQL 8.0 + Mailpit |
symfony | Symfony | PHP-FPM from public/, HTTPS; recommends MySQL 8.0 + Mailpit |
statamic | Statamic | PHP-FPM from public/, HTTPS; no database required |
Recipes with composes_fully: false (Laravel, Symfony) also recommend a database or mail service. The project is still registered with everything PortBay can wire today; the recommended service surfaces as a warning to add from the app.
Drive the task board
When a project has a task board, an agent can work it directly over MCP. The board is the shared queue between you and the human: cards are Markdown in the repo (.portbay/tasks/), and the GUI, the portbay CLI, and these tools all read and write the same files — there is no separate database to drift out of sync.
A dispatched run follows a simple loop:
- Pick up context.
portbay_handoff_getreads the continuation brief;portbay_task_nextreturns the topTodocard (orportbay_task_getre-reads the one you were dispatched to). - Claim it.
portbay_task_ackwith therun_idfrom your prompt proves you engaged — distinct from the process merely launching — and refreshes the run's lease. - Work and report. Post progress with
portbay_task_update(status +touchpoints), tick checklist items withportbay_task_check, and record decisions withportbay_task_comment. - Hand off.
portbay_handoff_updateappends a minimal "where we left off" entry, then move the card toDone— orReviewif the board requires human approval — withportbay_task_update.
The board enforces its rules even when an agent gets them wrong: an agent can't set Rejected (human-only), a card blocked on others won't dispatch until they land, and a run whose process dies is reclaimed so the card returns to the queue. The full tool list is in the Tasks toolset; the human-facing setup is in the Task Board guide.
Three resources expose board state for the agent to read without tool calls: portbay://projects/{id}/context (the derived environment), portbay://projects/{id}/tasks (the whole board), and portbay://projects/{id}/handoff (the rolling brief).
Governance: read-only and toolsets
Two flags scope what an agent can do. Both have flag and environment-variable forms; the env var wins over the flag (matching the GitHub MCP server convention). Set them in the args / env block of your agent config.
Read-only mode
Removes every mutating tool (add / update / remove, start / stop / restart, import / export, scaffolding, group mutations, runtime mutations, database mutations, DNS suffix change, cert reissue, sandbox enable/disable, remote SSH execute, task-board writes, request log clear). The agent can inspect but never change anything.
{
"mcpServers": {
"portbay": {
"command": "/opt/homebrew/bin/portbay-mcp",
"args": ["--read-only"]
}
}
}Env var equivalent: PORTBAY_MCP_READ_ONLY=1.
In read-only mode the server appends a note to its system instructions telling the agent that mutations are disabled.
Toolsets
Expose only the tool groups you want. Comma-separated list; valid values are projects, lifecycle, diagnostics, scaffold, groups, tunnels, runtimes, databases, dns, sandbox, inspector, certs, migrate, and all (the default, which covers all of those). Two special cases: tasks and connectors exist only in the Pro build, and ssh-exec is never in all — name it explicitly to let an agent run remote commands (see the SSH Exec toolset).
| Toolset | Tools included |
|---|---|
projects | list_projects, status, detect_project, detect_workspace_apps, list_recipes, add_project, update_project, remove_project, export_config, import_config, setup, setup_from_recipe |
lifecycle | start, stop, restart, stop_all |
diagnostics | logs, doctor, sidecar_status |
scaffold | setup_from_template (runs upstream scaffolders; requires network) |
groups | list_groups, create_group, update_group, remove_group, start_group, stop_group, restart_group |
tunnels | list_tunnels, tunnel_status, list_ssh_tunnels, ssh_tunnel_status, list_ssh_connections (read-only; manage tunnels and hosts from the app) |
runtimes | list_runtimes, set_default_runtime, add_runtime_path, remove_runtime_path |
databases | list_database_engines, list_databases, database_connection, db_schema, db_query, db_explain, db_execute, create_database, remove_database, start_database, stop_database, restart_database, link_database, unlink_database, set_database_auto_start |
dns | dns_status, list_dns_records, set_domain_suffix |
sandbox | sandbox_status, sandbox_violations, enable_sandbox, disable_sandbox |
inspector | recent_requests, clear_requests |
certs | cert_info, reissue_cert |
migrate | detect_import_sources, preview_import, import_projects |
ssh-exec | ssh_execute — run one command on a saved SSH host. Off by default, never in all; enable explicitly. |
tasks (Pro) | tasks_list, task_next, task_get, task_create, task_ack, task_update, task_check, task_checklist_add, task_comment, task_complete, handoff_get, handoff_update, learning_add, connectors_status (the per-project board) |
connectors (Pro) | connector_accounts, connector_search, connector_get, connector_create, connector_update, connector_comment (external task sources; writes need human approval) |
{
"mcpServers": {
"portbay": {
"command": "/opt/homebrew/bin/portbay-mcp",
"args": ["--toolsets", "projects,diagnostics"]
}
}
}Env var equivalent: PORTBAY_MCP_TOOLSETS=projects,diagnostics.
Read-only and toolsets compose: --read-only --toolsets projects,diagnostics exposes only the read tools within those two groups. Filtered tools do not appear in tools/list and cannot be called.
Error handling
Tool failures return as MCP tool-execution errors (isError: true) carrying PortBay's standard error envelope. The agent reads the envelope and can recover or explain what to do next.
{
"code": "SIDECAR_DOWN",
"whatHappened": "process-compose is not running",
"whyItMatters": "Projects can't start until process-compose is running again.",
"whoCausedIt": "system",
"actions": [{ "label": "Restart process-compose", "command": "sidecars.restart_pc" }]
}Common error codes:
| Code | Meaning |
|---|---|
PROJECT_NOT_FOUND | No project with that id in the registry. |
SIDECAR_DOWN | The Process Compose daemon is not reachable — open the PortBay app. |
PORT_CONFLICT | The configured port is in use by another process. |
PROJECT_CAP_REACHED | The project limit for the current tier was reached. Sign in or upgrade. |
BAD_INPUT | An argument was invalid or a required path was missing. |
Project caps apply to agent-driven adds exactly as they do in the GUI (anonymous: 3 / free: 6 / Pro: unlimited).
All flags
| Flag | Env var | Default | Purpose |
|---|---|---|---|
--read-only | PORTBAY_MCP_READ_ONLY | off | Inspection tools only; all mutations removed. |
--toolsets <list> | PORTBAY_MCP_TOOLSETS | all | Comma-separated tool groups to expose. |
--elicit-approvals | PORTBAY_MCP_ELICIT_APPROVALS | off | Request write approvals (portbay_db_execute, portbay_connector_*) as an in-client confirmation form on MCP 2025-11-25 clients (elicitation), instead of always pausing in the PortBay app. Clients without form-elicitation support fall back to the app-approval behavior automatically. |
--pc-port <port> | PORTBAY_PC_PORT | 9999 | Process Compose daemon port. |
--registry <path> | — | app data dir | Override the registry file location. |
--log-level <level> | RUST_LOG | info | stderr log verbosity (error / warn / info / debug / trace). |
All diagnostic output goes to stderr. Stdout carries only the MCP JSON-RPC stream.
See the Tool Reference for the full tool and resource inventory.
