MCP servers are configured per agent, and often per account. A second Claude account with its own config folder starts without your servers, because each one has its own .claude.json. Codex, Antigravity and OpenCode keep their own configs again, and every sign-in lives in a different place. The BeyDock Hub is our answer: connect a server once, sign in once, and every provider and account can use it, with one place for permissions and one record of what happened.
A separate process
The Hub is its own process (sidecar/hub/main.mjs), started by the app, not by the sidecar. If the sidecar crashes, the Hub stays up. It listens on 127.0.0.1 with Streamable HTTP and requires a Bearer token, checks the Host header, and rejects any request with an Origin header to block DNS rebinding.
Only the Hub connects to the real MCP servers: stdio, HTTP, SSE, and stdio servers that run on an SSH host. Each provider gets a single injected entry called beydock, added in that provider's own way:
- Claude through
mcpServers. - Codex through
mcp_serversin the thread config. - OpenCode through ACP
mcpServersplusOPENCODE_CONFIG_CONTENT. - Antigravity through
mcp_config.json, using a small stdio bridge.
The agent sees tools as if they were connected directly, such as notion__search, and the call is relayed. The names stay stable: a server's prefix is fixed when you create it, and renaming only changes the display name, so old transcripts keep working.
Tokens and the Keychain
Secrets live in one place: the macOS Keychain, written by the app. Only BeyDock's own helpers can read an item, through a per-item trusted-app list. That covers environment values, headers, OAuth tokens and external client tokens. They never go to providers or to SSH hosts. The app hands them to the Hub at startup.
Every consumer gets its own Bearer token: a session, an account or an outside app. The app generates it, and the Hub keeps only a digest. That is how the Hub knows which session made a call, so an approval card lands in the right session and the activity log gets the right row. A token lives as long as its session.
Policy: allow, ask or deny
Every Hub tool has a policy of allow, ask or deny, and it can differ per account, provider or connection. Defaults come from the tool's MCP annotations: readOnlyHint starts at allow, destructiveHint or no annotation starts at ask. The annotation is the server's own claim, not a guarantee, so it is only a starting value you can change. A tool can also be hidden entirely, so the model never sees it.
Deny and hide are enforced in the Hub itself, so they hold for every provider, and "Pause all" in Connections blocks every Hub call at once. For "ask", today's default leaves the question to the provider's own approval prompt. The Hub can also hold an "ask" call and show a BeyDock approval card itself, whichever provider made it, including Antigravity, which cannot ask mid-turn. That card shows the input as the model sent it, which connection (which identity) it will use, and the host. "Always allow" writes a rule scoped to that tool, consumer and connection, and the card says so. In that mode each provider is told not to ask again for Hub tools, so you never get two prompts for one call. It is built but not yet the default.
Policy covers only traffic that passes through the Hub. A provider's own MCP servers keep their own permission flow, and Settings says so.
One more guard: when you first connect a server, we store a hash of each tool's name, description and input schema. If the upstream server changes one later, the tool is marked "Changed" in Connections, where you can see the difference, and an allowed call to it falls back to "ask" until you accept it.
Activity
Every call that passes through, allowed or refused, becomes one line in a local JSONL file per day: provider, account, server, connection, tool, the decision and which rule decided it, timing and the arguments. Arguments are truncated to 4 KB, results are recorded only as a size, and retention defaults to 30 days. The log never leaves your Mac. Connections has an Activity view with filters, and "Record arguments" is a switch because arguments can contain customer data.
Other apps, and when BeyDock is closed
The same Hub can serve Claude Desktop, Cursor and others. Connect an App creates a client with its own token and its own policy. The app ships beydock-mcp, a small stdio bridge in Contents/Helpers/, for clients that can only launch a local command. It reads the token from the Keychain instead of taking it on the command line. Writing the config into another app's file asks for approval, makes a backup and shows a diff first.
The Hub itself is off until you turn it on in Settings › Connections › "Use BeyDock Hub". Once on, it lives and dies with the app, and the bridge can start the app in the background. If you want it always available, there is an opt-in background helper, beydock-hubd, registered as a launch agent: Settings › Connections › "Keep Hub running in the background". It is off by default, and turning that switch off unregisters it.
Where it stands
This shipped in 0.3.0 and is still in early access testing. There is no hosted version.