Bastion is a macOS menu-bar application that supervises Model Context Protocol (MCP) servers on your Mac instead of letting each editor spawn its own copy. Each client — Claude Code, Claude Desktop, Cursor, VS Code, Codex, the Terminal — is wired to a local gateway URL rather than to a command line, and Bastion keeps exactly one child process per profile alive behind it. The gateway answers on loopback at 127.0.0.1:8720, with one route per profile and server, such as http://127.0.0.1:8720/s/prod/shopify. Credentials move out of configuration files and into the macOS Keychain, and every tool call crossing the gateway is recorded. It is built for developers running MCP servers across several editors and several accounts on the same Mac at once.
The problem Bastion addresses is duplication and plaintext secrets. Every MCP client spawns its own copy of every stdio server, each with its own environment, so four servers kept in a .mcp.json file could hold real secrets in plaintext — readable by anything running as the same user, and one careless git add away from being published. The site cites four public trackers, none of them Bastion's, documenting six processes for three configured servers on every Claude Desktop restart, roughly 3.5 GB of MCP processes at idle with 82 children from one OpenCode launch, and 5.7 GiB across 60 subprocesses. It also notes that the requests to read credentials from the Keychain instead of a config file, and to share a single process, were closed as not planned. Nothing was auditable and nothing was shared, which is the shape of the problem Bastion is built around.
Profiles are the core abstraction. A profile is a named credential and configuration set — prod/shopify, staging/shopify, acme/keycloak, lab/unifi-network — so one installed server can serve two accounts without either knowing about the other. The documentation calls a single global instance unusable, because it would be one identity, and every repository already carries different credentials for the same server. In the Shopify pane, prod is running while staging names the one variable it is waiting for and has therefore never started. The environment table underneath is the server's own, read from its manifest rather than typed here, and the values behind the secret badges sit in the Keychain: the pane can set one and cannot show you one. Write permissions are granted per profile, not per process — which a process boundary could never express — so lab/unifi-network with writes on beside home/unifi-network with them off is expressible.
The gateway serves HTTP to clients and stdio to the child. Each client's mcp.json entry is rewritten: the command, its arguments and three environment variables — a store domain, a client id and a Shopify client secret in plaintext — are removed and replaced with one URL pointing at the Bastion gateway, so what leaks from a leaked .mcp.json is a revocable loopback token rather than the credential itself. Five security rules landed in the first commit that opened a socket rather than in a hardening pass afterwards: bind 127.0.0.1 explicitly and never 0.0.0.0, validate Origin to refuse the page-you-visited case, validate Host as the anti-rebinding rule, require a per-client bearer token minted at wiring time and kept in the Keychain, and never place secrets in a config file. The refusal order is load-bearing: a foreign Origin is refused before the token is even read, which the site says is the ordering CVE-2025-49596 lacked. A make audit loopback-only check over lsof, along with assertions that a foreign Origin and a rebound Host are refused and that profiles.json holds no secret value, is part of the project.
The runtime answers each client's initialize from a single handshake performed once at spawn, which the 2026-07-28 stateless-first revision makes correct rather than a hack. Each client's request ids are remapped into the supervisor's own numbering and back again, so two clients using id 1 at the same moment cannot receive each other's answers. When a child dies, every client waiting on it is told, with a count, alongside backoff and a circuit breaker. Because a shared gateway sends every tool definition to a client before it can call anything, Bastion offers Load tools on demand: prod/appstore-connect is 85 tools and about 26.2k tokens, while serving three tools — bastion_search_tools, bastion_describe_tool and bastion_call_tool — brings the same profile in at about 0.4k, with the full searchable index costing 3.2k only if something asks for it. Every call then reaches the editor as bastion_call_tool, or bastion_call_write_tool for a write, so the editor's approval rules cover a server's reads and writes rather than each individual tool. Bastion decides per server, and Claude Code and Claude Desktop, which already load schemas on demand, are served the real list.
The record is a record of requests, not a sandbox. Bastion sees the JSON-RPC frames crossing the gateway down to what each tool was called with — profile, method, tool name, arguments, results and outcome — and never records credentials: they are redacted before the row is written rather than filtered out afterwards. No argument and no result is written to disk unless you ask for an audit log, which is chained, so an edited or missing record shows. The site is explicit that Bastion does not see what a server then does over the network or on the filesystem. The app is a window rather than a background process: a Running pane lists each started profile and names its clients twice over, once by the identity its bearer token was issued to and once by what the client calls itself; a Stats pane ranks what each server's tool list costs on every connect and charts calls per day with failures stacked; a Log pane shows timestamped calls with their JSON arguments and results; a Claude Code pane shows the config file it is about to write, with hand-configured servers listed underneath untouched; and a Chat pane loads one profile's tools into the on-device model so you can try a tool before wiring it up.
Bastion ships with nothing installed. A catalog of forty-three servers is available to install from, and anything else on npm is added by package name. Thirty packages are fetched on demand into Bastion's own directory and run with the Node runtime inside the app; twelve endpoints are servers their own vendors operate, so nothing is installed and no process is started, with Bastion adding the Keychain credential, one identity per profile and the audit line; Xcode's own server is a command that ships inside Xcode, run and supervised without installing anything. Thirty-seven entries sit behind a write gate that is off until a profile turns it on, and six are read-only with no mutating tool registered at all. Twenty-three packages carry a signed attestation tying the npm version to the repository it links to. What stays closed is how a request picks one: a client names a profile and a server id, never a path and never a command line.
Use cases follow directly from that structure. A developer with separate production and staging Shopify stores wires both through one installed server and switches identities by profile rather than by editing files. Someone running Claude Code, Cursor, VS Code and a terminal against the same server gets one supervised process with four attached clients instead of four copies. A team that needs to know what an agent actually did reads the log, which records the tool name, the arguments and the result for a profile that opts in. Anyone unsure whether a server is worth wiring can load its tools into the on-device Chat pane first. And a developer who wants writes off in one context and on in another enables the write gate per profile.
Bastion requires macOS 26 or later and binds loopback only. It is a paid app: $14.99 once, €14.99 in the EU with VAT included, with no subscription and no per-seat tier. The licence is issued to you rather than to a machine, covers every 1.x release on every Mac you own, and is verified offline as an Ed25519 signature the app checks locally — no activation call and no licence server. A 30-minute trial runs the whole app rather than a crippled version, and there is a thirty-day full refund with no reason needed. The bundle carries its own Node, and the app holds exactly four entitlements: its identity, its team, its own keychain and iCloud sync. Known gaps are published rather than discovered after paying: server-initiated sampling, elicitation and roots are refused with an explanatory error, a remote server's write gate is a filter rather than a boundary, a remote rate limit is shared across every client behind one profile, list_changed and subscriptions/listen cannot be routed, and Mcp-Param headers are forwarded but not validated.
The summary takeaway is that Bastion makes MCP servers a single supervised thing on your Mac rather than a copy per editor: one process per profile, credentials held in the Keychain, a per-profile write gate, an auditable record of every call, and a gateway hardened with the rules a loopback daemon holding every credential you own should have had from the start.