bastion-pilot
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@bastion-pilotlist my bastion sessions, run 'df -h' on the prod one, and show the output"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
BastionPilot
Bring AI agents to bastion-host web terminals.
In many companies the only way to reach production servers is through a bastion host (JumpServer, Qizhi, Aliyun Bastionhost, ...) that exposes a web terminal in the browser. There is no SSH access, so CLI agents like Claude Code or Codex cannot reach your servers. BastionPilot fixes that: a browser extension hooks the web terminal and acts as the agent's hands and eyes, while a local bridge exposes clean, auditable tools to any AI brain you prefer.
Claude Code / Codex Side-panel chat
(MCP stdio) │
│ │ HTTPS → your AI provider
▼ ▼
┌─────────────────────────────────────────────┐
│ local bridge (daemon) │
│ MCP server │ built-in agent │ approvals │ │
│ │ loop │ audit log │ │
└──────────────┬──────────────────────────────┘
│ ws://127.0.0.1 + pairing token
▼
┌─────────────────────────────────────────────┐
│ browser extension (MV3) │
│ MAIN world: WebSocket interception (I/O) │
│ content controller: session + buffers │
│ service worker: tool routing, approvals │
└──────────────┬──────────────────────────────┘
│ the page's own WebSocket
▼
bastion web terminal (JumpServer / any xterm.js product)How it works
The extension is the only owner of terminal I/O. A main-world script injected at
document_startpatcheswindow.WebSocket, tees every downstream frame (the PTY stream) and can type into the terminal on request. Whatever the bastion product renders — xterm.js on canvas, WebGL, anything — the raw stream is captured before it is drawn.Everything else connects to it. Claude Code / Codex talk to the daemon's MCP server; the side-panel chat uses the daemon's built-in agent loop with your own OpenAI-compatible provider. Both brains funnel through the same tool executor, the same approval policy and the same audit log.
Output is made LLM-ready. PTY bytes are decoded (UTF-8, chunk-safe), ANSI/OSC escape sequences are stripped,
\r/\boverwrite semantics are applied (progress bars collapse into their final line), and oversized output is written to a local file whose path is returned to the agent — Claude Code can then read the full file with its own tools.Command completion is detected reliably. After typing a command, BastionPilot appends an
echo __BP_<nonce>_$?__sentinel. The literal$?in the echoed input can never match the result pattern, so the sentinel resolves exactly once, carrying the exit code. Quiet detection and timeouts back it up for interactive situations.Products are abstracted into thin profiles. WebSocket transport is universal; framing (raw / JSON / base64) and DOM hints live in declarative site profiles. JumpServer ships built in; anything xterm.js-based works with the generic profile.
Related MCP server: browserpilot-mcp
What you get
Brain | How to connect |
Claude Code / Codex / any MCP client |
|
Built-in side-panel chat | any OpenAI-compatible endpoint (cloud or self-hosted/intranet) |
Tools exposed to agents: list_sessions, send_command, read_output, wait_output,
send_key, get_screen, upload_file, download_file — see
docs/tools.md for the full contract. Every tool result is also appended to
per-session transcripts for post-incident review (bastion-pilot transcripts).
Safety rails:
Approval modes (
auto/confirm/readonly+ allowlist), enforced on the bridge for every brain. Default isconfirm: each mutating command waits for your click (side panel or OS notification).Local audit log: one JSONL line per tool call under
~/.bastion-pilot/audit/.Read-only tool classification:
list_sessions,read_output,wait_output,get_screennever require approval.Opt-in site access: the extension only runs on origins you explicitly grant in its options.
The bastion's own session recording keeps recording — BastionPilot types like a human, so your compliance story is unchanged.
Quickstart
1. Bridge (local daemon + MCP server)
Requires Node.js ≥ 20.
git clone https://github.com/yueseqaz/bastion-pilot.git
cd bastion-pilot
pnpm install && pnpm build
# create config + pairing token, print setup instructions
node packages/bridge/dist/cli.js init
# start the daemon (keep it running; the MCP server can also auto-start it)
node packages/bridge/dist/cli.js daemon
# check connectivity at any time
node packages/bridge/dist/cli.js doctorinit creates ~/.bastion-pilot/config.json (chmod 600) containing the pairing token, and prints
the token plus copy-paste snippets for Claude Code / Codex.
2. Browser extension
Chrome / Edge (Chromium ≥ 116):
Build it once:
pnpm --filter @bastion-pilot/extension build(or grab the zip from GitHub Releases / CI artifacts and unzip it).Open
chrome://extensions, enable Developer mode, click Load unpacked, selectpackages/extension/dist.Open the BastionPilot side panel (click the toolbar icon) → ⚙ Settings → paste the daemon URL and pairing token → Save → Test connection.
Under Bastion sites, grant access to your bastion origin, e.g.
https://bastion.example.com.
3. Wire up a brain
Claude Code
claude mcp add bastion-pilot -- node /absolute/path/to/bastion-pilot/packages/bridge/dist/cli.js mcpCodex CLI — add to ~/.codex/config.toml:
[mcp_servers.bastion-pilot]
command = "node"
args = ["/absolute/path/to/bastion-pilot/packages/bridge/dist/cli.js", "mcp"]Then, in your agent, ask something like: "list the bastion sessions, and on web-01 check disk
usage". The agent will call list_sessions, then send_command df -h, read the output, and
answer — subject to your approval mode.
Built-in side-panel chat (optional) — open the extension's Settings → AI providers
section, add an API Key service, enter only its endpoint URL and API key, then click Fetch
models and choose a model. The same settings can also be edited directly in
~/.bastion-pilot/config.json:
{
"providers": [
{
"id": "default",
"baseUrl": "https://api.openai.com/v1",
"apiKey": "sk-...",
"model": "gpt-4o"
}
],
"activeProviderId": "default"
}The side panel's Mode selector is mutually exclusive: API Key calls the configured provider
directly, while Local Agent lists connected Claude Code / Codex MCP conversations so you can
target one conversation. The selected agent reads the request with get_shared_context and reports
back with append_shared_context; set BASTION_PILOT_CONVERSATION_ID (or CODEX_THREAD_ID) in
the MCP process environment for a human-readable conversation identity.
Self-hosted / intranet gateways (vLLM, Ollama's OpenAI adapter, corporate LLM gateways) work with the same shape — the terminal output never has to leave your network if you don't want it to. Side-panel history survives restarts: reopening the panel restores the latest conversation.
Configuration
~/.bastion-pilot/config.json (created by bastion-pilot init):
Key | Default | Meaning |
|
| daemon listen port (always bound to |
| generated | pairing token for the extension & MCP server |
|
|
|
|
| every command, keystroke and file operation needs an explicit panel approval |
|
| unanswered approvals are denied after this |
|
| regex list; in |
|
| OpenAI-compatible providers for the built-in chat |
|
| provider used by the side panel |
|
| loop cap for the built-in agent |
| built-in | replace the default safety-first prompt |
|
| larger outputs are truncated + saved to a file |
|
| per-session terminal transcripts under |
Extension-side settings (options page): daemon URL, pairing token, AI providers, granted origins
and the site profile per origin (generic-xterm / jumpserver). AI provider settings are written
by the authenticated local daemon and are not stored in browser sync storage.
Troubleshooting
Symptom | Fix |
| Is the daemon running? Token pasted in the options page? Click the toolbar icon to wake the service worker. |
Tool calls fail with "no terminal session" | Open the bastion terminal tab after granting the site (or reload it). Check the origin was granted in options. |
Garbled output | Your product frames the PTY as JSON/base64 — duplicate a profile and set |
Agent types but nothing happens | Some products intercept keystrokes client-side; try |
Long commands "time out" | They don't fail — pass a larger |
Approval never appears | The daemon sends it to the extension; check the side panel and OS notifications, and confirm only one browser profile is paired. |
Development
pnpm install
pnpm all # build + typecheck + test + lint
pnpm test # 123 tests across protocol / bridge / extension
pnpm --filter @bastion-pilot/extension zip # release zip for the extension
node scripts/gen-icons.mjs # regenerate iconsMonorepo layout:
packages/protocol shared wire protocol, tool contract, terminal text renderer
packages/bridge daemon, MCP server, built-in agent loop, approvals, audit
packages/extension MV3 extension (interceptor, controller, worker, panel, options)
profiles docs for adapting further bastion productsSee docs/tools.md for the agent-facing tool contract and profiles/README.md for adding support for new bastion products.
Security notes
See SECURITY.md. Short version: everything local is bound to 127.0.0.1 and
token-authenticated; config/audit/output files are chmod 600; approval policy is enforced
centrally; the bastion's own audit trail stays fully intact. You are responsible for complying
with your company's policies — including whether terminal output may be sent to a cloud LLM.
Roadmap
File upload/download over the terminal (
upload_file/download_file, base64 chunks)iframe-based terminals (one session per terminal frame)
More site profiles — Teleport, Qizhi, Aliyun Bastionhost (experimental, field-test welcome)
Session transcripts replay for post-incident review (
bastion-pilot transcripts)npm publish-ready bridge package (
pnpm --filter bastion-pilot publish)Published npm package +
npx bastion-pilot mcp(needs the maintainer's npm account)Vision fallback for non-xterm terminals (Guacamole-style canvas streaming)
Multiple terminals inside a single browser frame
License
This server cannot be deployed
Maintenance
Related MCP Connectors
Remote MCP server exposing your Node Command fleet (files, shell, processes, browser) to AI hosts.
Browser MCP for logged-in tasks. Uses your Chrome — credentials stay local. Zero-token replay.
MCP server for your apps' tools and custom tools, plus hosted AI agents and approval-gated workflows
A paid remote MCP for AI agent browser approval MCP, built to return verdicts, receipts, usage logs,
Related MCP Servers
- AlicenseNot gradedqualityBmaintenanceEnables AI agents to drive a real, already logged-in browser from the CLI or over MCP, with enforceable approval gates for actions and safeguards against prompt injection.1,133,569 npmMozilla Public 2.0
- AlicenseNot gradedqualityBmaintenanceEnables remote AI agents to control a local, authenticated desktop browser over MCP through an outbound WebSocket bridge and Chrome extension, supporting tab management, navigation, page reading, clicking, typing, scrolling, screenshots, and JavaScript evaluation.1MIT
- AlicenseNot gradedqualityAmaintenanceEnables any MCP-capable AI client to drive the user's own Chrome or Edge browser through a set of browse_* tools — navigating, clicking, typing, extracting, and managing tabs with their existing logins on their own machine. Runs locally and self-hosted with a live viewport and pause/approve controls so every verified step stays under user watch.4MIT
- AlicenseNot gradedqualityAmaintenanceEnables AI coding tools to drive the user's real, already-logged-in Chrome through an MCP stdio interface, exposing 25 tools for navigating, clicking, typing, reading pages, taking screenshots, capturing network traffic, and accessing cookies and storage. The bundled Chrome extension talks to a local bridge instead of an open remote-debugging port, so logged-in sessions and bot-detection-proof human browsing work without extra setup.1 npmISC