Port Authority Agent Terminal Mcp
FreeNot checkedCross-platform (Windows + macOS) lane coordinator for AI coding agents (Claude, Codex, etc.) — keeps multiple local agents from colliding on dev-server ports, C
About
Cross-platform (Windows + macOS) lane coordinator for AI coding agents (Claude, Codex, etc.) — keeps multiple local agents from colliding on dev-server ports, Chrome debug ports, and Chrome user profiles. Includes a CLI, an MCP server, and a native Tauri
README
Port Authority Agent Terminal (PAAT)
Air-traffic control for the AI agents that use your web browser. Give Claude, Codex, Cursor and friends each their own browser — Chrome, Edge, or Firefox — with its own port, profile, and window, so they never collide on the same machine.
A fast-growing share of what happens in a web browser is now driven by AI agents, not people. They log into sites, fill out forms, shop, and pull data, and to do any of it they each need to drive a real browser. Run several agents on one machine and they start fighting over the same browser: one ends up steering another's window, breaks a login, or reports on the wrong page.
PAAT is a small CLI, MCP server, and native dashboard you run locally. Before
any agent opens a browser, it asks PAAT for a lane: its own app port, its
own remote-debugging port, and its own dedicated browser profile (Chrome, Edge,
or Firefox). PAAT then guarantees, on every check, that an agent only ever
attaches to the browser running its own profile, never a sibling agent's — and
its page_* tools drive all three backends through one interface.
Four agents (Cloudflare API keys, AWS IAM, eBay, Newegg), each in its own isolated Chrome lane on one live dashboard. ▶ Watch as video (MP4)
Install
Prereq: Node.js ≥ 22.4 (from https://nodejs.org/). CDP and Firefox BiDi use Node's native WebSocket client.
npm install -g port-authority-agent-terminal-mcp
Pre-built, ~5 seconds, no build step. You get three interchangeable CLI aliases
(paat, port-authority, portpilot, all the same binary), a dashboard you
open with paat dashboard, and the MCP server auto-wired into Claude Desktop /
Codex Desktop / Claude Code if any are installed. Upgrading is the same command.
Other ways to install
- From GitHub
main(bleeding edge; builds on your machine, needsgit+ Rust):npm install -g github:charlesonogwu/port-authority-agent-terminal - PowerShell installer (also runs
paat config init+ prints next steps; add-Autostartto start at login):iwr -useb https://raw.githubusercontent.com/charlesonogwu/port-authority-agent-terminal/main/scripts/install.ps1 | iex
Platform. Windows and macOS have CI coverage. On macOS, PortPilot only attaches after
lsofpluspsproves the browser command line contains the lane's dedicated profile. Unknown, personal, and foreign profiles are refused. Native macOS.apppackaging, signing, Dock integration, and login-start are not included yet.
The problem
Run more than one local AI agent and they collide. Two agents both reach for
Chrome debug port 9222 (the port every CDP tutorial uses), and the second
silently attaches to the first one's browser. It tests the wrong app,
inherits stale cookies, overwrites work. You only notice when an agent
confidently reports "the homepage looks great" about a different repo. Worse,
two agents sharing one --user-data-dir corrupt each other's profile:
Chrome's SingletonLock breaks, sessions invalidate, extensions reset.
PAAT hands each (agent, project) pair a separate lane so that never
happens.
What a lane is
| Field | What it is |
|---|---|
owner |
Which agent: claude, codex, gemini, cursor, copilot, … |
project |
Slug derived from the project directory |
cwd |
Absolute project working directory |
appPort |
A free port in the app range (default 3000-3099) |
chromeDebugPort |
A free port in the Chrome debug range (default 9322-9399) |
chromeProfileDir |
Dedicated --user-data-dir (persists logins across sessions) |
sessionId |
Optional: lets one agent run several Chromes in the same project |
status |
reserved → active → stale → released |
Lanes live in ~/.portpilot/lanes.json, guarded by a lockfile so concurrent
writes can't corrupt the registry.
Wire it into your agents
The installer auto-wires the MCP server into Claude Desktop / Codex Desktop / Claude Code. To do it by hand, add this block to the desktop's config.
Claude Desktop, in %APPDATA%\Claude\claude_desktop_config.json:
{ "mcpServers": { "paat": { "command": "paat", "args": ["mcp"] } } }
Codex Desktop, in ~\.codex\config.toml:
[mcp_servers.paat]
command = "paat"
args = ["mcp"]
Restart the app and the agent has these tools:
open— reserve + launch + navigate in one call, the one to reach for. Takesbrowser: "chrome" | "edge" | "firefox"(default chrome, or whatever you picked as the dashboard's default browser).- Lane management:
reserve_lane,check_lane,release_lane,launch_browser_lane(any backend),launch_chrome_lane(kept for back-compat),list_lanes,find_free_lane,scan_ports,doctor. - Page control — drive the lane's browser through one uniform interface,
whatever the backend:
page_tabs,page_goto(navigate + wait for load),page_eval(JS expression → JSON value),page_text,page_click,page_fill,page_screenshot. Chrome/Edge are driven over CDP, Firefox over WebDriver BiDi — the tools hide the difference. Page control only ever touches the lane's own browser (safe-attach verdict required), never your personal one.
Then a prompt like "Open https://example.com via PAAT in this folder" just works:
the agent calls open and the session appears on the dashboard.
Browser backends
Every lane has a browser backend — pass browser to open / reserve_lane,
or set a default in the dashboard (used only when the agent doesn't ask for
one explicitly):
| Backend | Protocol on the debug port | Modes | Tab list on dashboard |
|---|---|---|---|
chrome (default) |
Chrome DevTools Protocol (CDP) | visible, background, headless | yes |
edge |
CDP (Edge is Chromium) | visible, background, headless | yes |
firefox |
WebDriver BiDi (ws://127.0.0.1:<port>/session) — NOT CDP |
visible, headless (background is refused: Firefox has no off-screen positioning) |
no — use page_tabs |
Each backend gets its own dedicated PortPilot profile (never your personal
browser), and chrome/edge/firefox lanes for the same (owner, cwd, session) are
three distinct lanes. Agents with their own CDP tooling can drive chrome/edge
directly; for firefox, use the page_* tools — they speak BiDi for you.
Launch modes
open and launch_browser_lane (and paat open / paat launch-chrome) take a mode:
| Mode | Window | Use it for |
|---|---|---|
visible (default) |
Normal window on your desktop | Manual steps: logging in, solving a captcha |
background |
Real headed browser rendered fully off-screen. Never appears on a monitor, never steals focus | Non-interactive read/click automation. Same cookies, extensions, and anti-bot fingerprint as a normal browser. Chrome/Edge only. |
headless |
No window (--headless=new; -headless on Firefox) |
Lowest footprint, but many sites detect and block headless browsers. |
Set a machine-wide default with chromeMode in ~/.portpilot/config.json or the
PORTPILOT_CHROME_MODE env var. Precedence: **per-call mode > env var > config
visible**.
Background-mode contract: to keep the window off-screen, your CDP client must not call
Page.bringToFront()orBrowser.setWindowBoundswith on-screen coordinates, since both re-raise the window. Read and click freely; just don't re-home the window.
Drop-in prompt for your agents
Running several agents across different folders? Paste this into any of them
(system prompt, CLAUDE.md, AGENTS.md, or a one-off message):
This machine runs multiple AI agents in parallel across different project
folders. Whenever a task here needs to drive a real browser (logging into a
site, scraping, clicking through a web app or dashboard, or any automation),
go through PortPilot instead of launching a browser yourself on a hardcoded
debug port. PortPilot hands you your own debug port and an isolated profile
so you never collide with another agent's browser, and the human can watch
every live session on a dashboard.
How to use it:
1. Claim a browser in one call. Use the `open` MCP tool with:
- owner: identify WHICH AGENT YOU ARE and pass that name only. If you are
Claude use "claude", if you are Codex use "codex", likewise "cursor",
"gemini", "windsurf", "copilot", "goose", "opencode", etc. This is how
the dashboard shows the human which agent is driving each browser, so be
honest about your own identity. No suffixes, batch numbers, or task IDs;
put any per-task distinction in sessionId instead.
- cwd: this project's absolute path.
- browser (optional): "chrome", "edge", or "firefox". Omit it unless the
human asked for a specific browser — the human's configured default
applies when you don't specify.
- url (optional): the first page to open.
- mode: "background" for non-interactive read/click automation (a real
headed browser that stays off-screen and never steals focus; chrome/edge
only); "visible" when a human must log in or solve a captcha; "headless"
only if you know the target site allows it. Firefox supports visible and
headless only.
- sessionId (optional): only when you need several separate browsers in
this same project at once.
2. Drive the page. Two options:
- PortPilot's page_* tools work on EVERY backend: page_goto (navigate +
wait), page_eval (run a JS expression, get JSON back), page_text,
page_click, page_fill, page_screenshot, page_tabs. For FIREFOX lanes
these are the way — the debug port speaks WebDriver BiDi, not CDP.
- For chrome/edge lanes you may instead connect your own CDP client
(puppeteer-core browserURL, Playwright connectOverCDP) to
http://127.0.0.1:<chromeDebugPort> from the response.
3. Respect the safety verdict: if check_lane returns unsafe-foreign-chrome or
unsafe-unknown, that port belongs to another agent, so back off, do not
attach, and never force-kill someone else's browser.
4. Background-mode rule: if you launched in "background" mode, do NOT call
Page.bringToFront() or Browser.setWindowBounds with on-screen coordinates,
since both yank the window onto the user's screen.
5. Release when finished: call release_lane (it does not kill the browser).
No PortPilot MCP tools in this agent? Use the CLI instead:
paat open --owner <you> --cwd "<path>" --browser chrome --mode background
paat page goto --owner <you> --cwd "<path>" --url https://example.com
then drive it with `paat page <tabs|goto|eval|text|click|fill|screenshot>`.
`paat dashboard` opens the live view.
The whole point is composability: every agent passes owner = <its own name>
and cwd = <its own folder>, and PortPilot keeps their browsers (ports,
profiles, and focus) from ever stepping on each other.
Real-world recipes
How PAAT actually gets used. Two patterns cover most of it.
Recipe 1: Log in once, automate for hours
The most common flow. An agent has to drive a site behind a login it can't (or shouldn't) do itself, like a marketplace seller dashboard, a SaaS console, or an account gated by SMS / billing / identity screens.
- The agent opens a visible lane:
openwithowner=<llm>,cwd=<project>, asessionIdfor the task, the login/settingsurl, andmode="visible". A real Chrome window appears. - The human logs in once in that window, including any first-time verification (phone, bank, captcha) the agent must not touch.
- The agent drives the page — with the
page_*tools (any backend), or its own CDP client on chrome/edge lanes: reads the DOM, clicks, fills forms, uploads files. - It keeps working. The lane's dedicated profile is persistent, so the login survives across agent sessions. Come back hours later, reattach to the same debug port, still signed in, no re-auth.
The human does the one thing only a human can; the agent does everything else, indefinitely, without ever clobbering another agent's browser.
Recipe 2: Raw CDP, no framework required
You don't need Puppeteer or Playwright. Every chrome/edge lane exposes a
standard Chrome DevTools-Protocol endpoint at
http://127.0.0.1:<chromeDebugPort>/json, so a dozen lines in any language is
enough (Firefox lanes speak WebDriver BiDi instead — use the page_* MCP
tools or paat page, which work on every backend):
import requests, json, asyncio, websockets
PORT = 9322 # the chromeDebugPort PAAT assigned this lane
async def run(expr):
tabs = requests.get(f"http://127.0.0.1:{PORT}/json", timeout=5).json()
page = next(t for t in tabs if t["type"] == "page")
async with websockets.connect(page["webSocketDebuggerUrl"]) as ws:
await ws.send(json.dumps({"id": 1, "method": "Runtime.evaluate",
"params": {"expression": expr, "returnByValue": True, "awaitPromise": True}}))
print(json.loads(await ws.recv()))
asyncio.run(run("document.body.innerText"))
The debug-port URL is a stable contract: read the DOM, click buttons, set file
inputs (DOM.setFileInputFiles), or synthesize real keystrokes
(Input.insertText) for stubborn framework-driven forms that ignore programmatic
value writes. This is the reliable fallback when a browser-automation extension
isn't connected.
Tips from the field
stale≠ dead. A lane can readstale(the owning agent hasn't checked in) while its Chrome is alive and the debug port still answers. Before re-opening, probehttp://127.0.0.1:<port>/json/version. If it responds, just reattach. Re-opening would spin up a fresh, logged-out profile and lose the session.- One tab, navigate in place.
window.openis popup-blocked without a user gesture. To visit another page mid-task, setlocation.hrefand come back via a stable URL (a draft link, a permalink) rather than juggling tabs. - Headed beats headless for real sites. Many sites block
--headless. Usemode="background"for a real headed browser that renders off-screen, with the same fingerprint as a normal window, but nothing pops up or steals focus.
Safety contract
Verdict from check_lane |
Meaning |
|---|---|
safe-free |
Debug port is free. You may launch the lane's browser with its profile. |
safe-attach |
Debug port holds the lane's browser with the matching profile. You may attach. |
unsafe-foreign-chrome |
Debug port holds the right kind of browser but a different profile. Do not attach. |
unsafe-unknown |
Debug port holds some other process (for a Firefox lane, even a Chrome counts). Do not attach. |
unsafe-* verdicts make paat check exit 3 and launch-chrome / open refuse.
PAAT never kills a process on its own. The dashboard's manual Kill button is
the only path to termination, and it refuses anything that isn't Chromium-family.
CLI
| Command | Purpose |
|---|---|
paat list |
List every lane |
paat status |
List + live port observations + warnings |
paat reserve --owner <n> --cwd <p> --task <s> |
Reserve a lane (idempotent per owner+cwd+session) |
paat check --owner <n> --cwd <p> |
Verify the lane is safe to use right now |
paat release --owner <n> --cwd <p> |
Release a lane (does NOT kill Chrome) |
paat open --owner <n> --cwd <p> [--browser chrome|edge|firefox] [--url <u>] [--mode ...] |
Reserve + launch + navigate in one step |
paat launch-chrome --owner <n> --cwd <p> [--mode visible|background|headless] |
Launch Chrome bound to the lane |
paat page <tabs|goto|eval|text|click|fill|screenshot> --owner <n> --cwd <p> |
Drive the lane's browser (CDP or BiDi, same commands) |
paat next [--range 9322-9399] |
Print the next free port in a range |
paat doctor |
Audit registry vs. live ports |
paat prune [--all] [--older-than 24h] |
Garbage-collect released lanes |
paat config show / config init |
Show / regenerate per-machine config |
paat dashboard |
Open the live dashboard |
paat mcp |
Run as an MCP stdio server |
Add --json to any command for machine-readable output.
Live dashboard
paat dashboard (or the desktop shortcut) opens a native window listing every
live lane: one row per Chrome process, grouped by project, with the owner agent,
debug port, current tabs, and a click-to-confirm Kill button that refuses
non-Chrome PIDs. It polls a few times a minute and pauses while minimized, so it
costs nothing when you're not looking. No browser tab, no localhost server.
Storage layout
~/.portpilot/
├── config.json # per-machine cap, port ranges, default mode + default browser
├── lanes.json # the lane registry, single source of intent
├── lanes.json.lock # exclusive lock for safe concurrent writes
├── profiles/ # one persistent browser profile per lane
│ # (--user-data-dir for chrome/edge, -profile for firefox)
└── shots/ # PNGs saved by page_screenshot
Override the location with PORTPILOT_HOME=<path>.
Building from source
git clone https://github.com/charlesonogwu/port-authority-agent-terminal.git
cd port-authority-agent-terminal
npm install
npm run build # CLI + MCP + native dashboard (the dashboard needs Rust)
npm link # exposes paat / port-authority / portpilot on PATH
Just want the CLI + MCP without the dashboard? npm run build:server (TypeScript
only, no Rust). npm test runs the full suite (300+ tests covering the allocator,
registry, lockfile, browser safety verdicts, page-control JS generation, prune
semantics, security validators, and end-to-end CLI integration).
Architecture
src/
core/ lane types, paths, lockfile, registry, port scanner,
Chrome safety verdicts + launch planning, allocator, config
cli/ argument parsing + command dispatch
dashboard/ live snapshot builder, agent inference, Chrome kill/focus/launch
mcp/ MCP stdio server exposing the same core lib
gui/ native Tauri dashboard (React + Vite frontend, Rust shell)
scripts/ install.ps1, build helpers, postinstall
tests/ node:test suites
Contributing
Bug reports, ideas, and PRs welcome. See CONTRIBUTING.md.
Changelog
See CHANGELOG.md.
License
Install Port Authority Agent Terminal Mcp in Claude Desktop, Claude Code & Cursor
unyly install port-authority-agent-terminal-mcpInstalls into Claude Desktop, Claude Code, Cursor & VS Code — handles npx, uvx and build-from-source repos for you.
First time? Get the CLI: curl -fsSL https://unyly.org/install | sh
Or configure manually
Run in your terminal:
claude mcp add port-authority-agent-terminal-mcp -- npx -y port-authority-agent-terminal-mcpFAQ
Is Port Authority Agent Terminal Mcp MCP free?
Yes, Port Authority Agent Terminal Mcp MCP is free — one-click install via Unyly at no cost.
Does Port Authority Agent Terminal Mcp need an API key?
No, Port Authority Agent Terminal Mcp runs without API keys or environment variables.
Is Port Authority Agent Terminal Mcp hosted or self-hosted?
Self-hosted: the server runs locally on your machine via the install command above.
How do I install Port Authority Agent Terminal Mcp in Claude Desktop, Claude Code or Cursor?
Open Port Authority Agent Terminal Mcp on unyly.org, pick your client tab (Claude Desktop, Claude Code, Cursor) and press Install — the config is generated automatically, no JSON editing.
Related MCPs
Playwright
Browser automation, scraping, screenshots
by MicrosoftPuppeteer
Browser automation and web scraping.
by modelcontextprotocolopentabs-dev/opentabs
Plugin-based MCP server + Chrome extension that gives AI agents access to web applications through the user's authenticated browser session. 100+ plugins with a
by opentabs-devrobhunter/agentdeals
1,500+ developer infrastructure deals, free tiers, and startup programs across 54 categories. Search deals, compare vendors, plan stacks, and track pricing chan
by robhunterCompare Port Authority Agent Terminal Mcp with
Not sure what to pick?
Find your stack in 60 seconds
Author?
Embed badge for your README
Browse similar
All browse MCPs
