One binary. Every client. Every server. Zero friction.
A ruthlessly minimal MCP gateway written in Rust. The single point of connection between all your AI coding clients and all your MCP servers — simultaneously, concurrently, without conflicts.
Claude Code ──┐ ┌── github (12 tools)
Claude Code ──┤ ├── notion (8 tools)
Cursor ───────┤── plug ─────────────┤── filesystem (4 tools)
Gemini CLI ───┤ (single binary) ├── postgres (6 tools)
Codex ────────┤ └── brave-search (1 tool)
OpenCode ─────┘
Choose one supported path for your platform. Do not install a second plug
binary beside the one that owns your runtime.
Download the signed DMG from the Plug website or GitHub Releases, move Plug.app to Applications, and open it once. Plug.app then owns the GUI, plug command, background daemon, client links, and Sparkle updates.
Or install the same app with the Homebrew Cask:
brew install --cask cyberpapiii/tap/plug-appOpen Plug.app once after either installation. First launch needs a logged-in macOS GUI session so ServiceManagement and Keychain consent can complete. Headless macOS is unsupported.
Build from source with a Rust toolchain:
cargo install --git https://github.com/cyberpapiii/plug plug-mcpThe daemon, socket IPC, and HTTP server all run on Linux; only the app and its packaging are macOS-only. Prebuilt Linux archives stopped at 0.8.10 and will come back if someone asks.
./scripts/dev-install.shBuilds Plug.app from the working tree, signs it with the Developer ID in
your login keychain, installs it, and lets the app replace its daemon. See
CONTRIBUTING.md.
Plug lives in the menu bar. The panel shows whether everything is working, an on/off switch for Plug itself (separate from Quit), and the most recent tool calls. The window has three sections:
- Servers: every MCP server, its health, sign-in, and its tools
- Apps: the AI clients using Plug, grouped by connected, on this Mac, and remote
- Activity: what was called, by which app, and how it went
Everything the app does is also available from the plug command.
Link Claude Desktop with:
plug link claude-desktopPlug writes Claude Desktop's normal MCP configuration and routes it through the same bundled runtime as the app. There is no separate executable extension to install or update.
1. Run the guided setup flow:
plug setupThis discovers existing MCP servers, imports them into plug, and walks you through linking your AI clients.
In Plug.app, the question mark in the window's toolbar opens the same steps as a guide. To have an agent do the setup, give it docs/guides/agent-setup.md.
Or create a config file manually at
~/Library/Application Support/plug/config.toml on macOS
(~/.config/plug/config.toml on Linux):
[servers.github]
command = "npx"
args = ["-y", "@modelcontextprotocol/server-github"]
env = { GITHUB_TOKEN = "$GITHUB_TOKEN" }
[servers.filesystem]
command = "npx"
args = ["-y", "@modelcontextprotocol/server-filesystem", "~/projects"]2. Link an AI client to plug (instead of to each server individually):
Interactive:
plug linkNon-interactive:
plug link claude-code cursorFor Claude Code (.mcp.json in your project root):
{
"mcpServers": {
"plug": {
"command": "plug",
"args": ["connect"]
}
}
}For Cursor, Devin, Gemini CLI, and others — see docs/CLIENT-COMPAT.md.
3. That's it. All your servers are available through every client simultaneously.
Both modern-protocol gates default to false. A cautious upstream-only canary:
modern_upstream_enabled = true
[http]
modern_downstream_enabled = false
[servers.modern-example]
transport = "http"
url = "https://example.com/mcp"
protocol = "auto"protocol = "auto" tries modern discovery and falls back to legacy initialize
when the server does not implement it. Existing servers default to
protocol = "legacy". See docs/guides/mcp-2026-dual-era.md.
You use 10 different AI coding tools. Each one needs its own MCP server configuration. Each one runs its own copies of the same servers. They conflict with each other. Configuration is scattered across a dozen files in different formats.
plug fixes this:
- One config — define your servers once (
~/Library/Application Support/plug/config.tomlon macOS) - Every client — Claude Code, Cursor, Gemini CLI, Codex, Grok Build, Devin, VS Code Copilot, GitHub Copilot CLI, Pi, Warp, Kiro, OpenCode, Zed
- Shared connections — N clients share 1 upstream connection per server (not N connections)
- Client-aware — automatically respects per-client tool limits (VS Code Copilot: 128, Devin's Cascade agent: 100)
- Lazy tool discovery — clients like OpenCode can start with a tiny search bridge instead of seeing hundreds of tool schemas up front
- Zero dependencies — one app (or one binary on Linux), no Docker, no database, no account required
- OAuth built in — authenticate to remote MCP servers with
plug auth login, background token refresh handles the rest - Every transport — upstream stdio, HTTP, and legacy SSE; downstream stdio and Streamable HTTP/HTTPS
- Remote access — serve the same tools to Claude and ChatGPT connectors over HTTPS with built-in OAuth and an owner passkey
plug # Show a compact overview and next actions
plug start # Start the shared background service (Plug.app does this on macOS)
plug setup # Discover servers and link clients
plug link # Link plug to your AI clients
plug clients # View and manage linked, detected, and live clients
plug clients rename <client> "<name>" # Give a client a name of your choosing
plug servers # View and manage configured servers
plug server add-account slack work # The same server again, for a second account
plug tools # View and manage the effective tool surface
plug status # Show runtime health and next useful action
plug doctor # Diagnose connectivity and configuration issues
plug repair # Refresh linked client configuration files
plug config check # Validate config syntax and core rules
plug tools off --server slack
plug tools on --server slack
plug tools --output json # Machine-readable output for agent use
plug auth login --server name # OAuth login for remote MCP servers
plug auth status # Show per-server auth status
plug auth clients list # Remote clients authorized through OAuth
plug connect # Internal stdio adapter AI clients invoke
plug serve # Run standalone HTTP/HTTPS in the foreground
plug serve --daemon # Run the shared background service (IPC + HTTP)Full configuration reference:
# macOS: ~/Library/Application Support/plug/config.toml
# Linux: ~/.config/plug/config.toml
# Global settings
enable_prefix = true # Legacy compatibility field; tool names are always prefixed
prefix_delimiter = "__" # Delimiter between server name and tool name
daemon_grace_period_secs = 0 # Default: keep the shared daemon alive until explicit shutdown
modern_upstream_enabled = false # Development preview: allow per-server modern negotiation
[lazy_tools]
mode = "auto" # auto, standard, native, bridge
[lazy_tools.clients]
opencode = "bridge" # search bridge, then direct-call loaded routed tools
"claude-code" = "native" # let native client-side lazy discovery handle large catalogs
"codex-cli" = "native"
[http]
bind_address = "127.0.0.1"
port = 3282
modern_downstream_enabled = false # Development preview: accept modern downstream clients
[servers.github]
command = "npx"
args = ["-y", "@modelcontextprotocol/server-github"]
env = { GITHUB_TOKEN = "$GITHUB_TOKEN" }
[servers.notion]
command = "npx"
args = ["-y", "@notionhq/notion-mcp-server"]
env = { NOTION_API_KEY = "$NOTION_API_KEY" }
[servers.filesystem]
command = "npx"
args = ["-y", "@modelcontextprotocol/server-filesystem", "~/projects"]
[servers.postgres]
command = "npx"
args = ["-y", "@modelcontextprotocol/server-postgres", "$DATABASE_URL"]
env = { DATABASE_URL = "$DATABASE_URL" }
max_concurrent = 1 # Limit concurrent requests
enrichment = true # Infer tool annotations from name patterns
# Optional naming controls per server
[servers.workspace]
transport = "http"
url = "http://localhost:8000/mcp"
protocol = "legacy" # legacy (default), auto, or modern
[servers.workspace.tool_renames]
search_docs = "get_doc_search_results"
[[servers.workspace.tool_groups]]
prefix = "Gmail"
contains = ["gmail"]
strip = ["gmail"]
# Remote HTTP server with OAuth authentication
[servers.remote-notion]
transport = "http"
url = "https://mcp.notion.so/mcp"
auth = "oauth"
oauth_scopes = ["mcp:read", "mcp:write"]Environment variable references ($VAR_NAME) in config values are expanded at startup.
plug can choose a lazy tool mode per downstream client:
standard: expose the normal routed tool catalog.native: expose the normal routed catalog and let clients like Claude Code, Cursor, or Codex apply their own deferred tool loading.bridge: exposeplug__search_toolsfirst, then let search load a bounded set of real routed tools by name.
OpenCode defaults to bridge, so it initially sees only plug__search_tools. Search returns ranked machine-readable matches, loads the matched tool definitions into that session's bounded working set, emits tools/list_changed, and the selected tool is then called directly under its normal routed name, for example Slack__search_messages.
The older meta_tool_mode = true setting remains a deprecated compatibility path for the legacy meta-tool surface. It is not the default bridge UX.
Use plug clients to inspect the resolved mode and whether it came from an automatic default, global override, or per-client override.
plug exposes MCP tools with a stable prefixed wire name and separate human-facing display metadata:
name: stable machine identifier used for routing and tool calls, e.g.Slack__channels_listtitle: canonical display label generated byplug, e.g.Slack: Channels Listannotations.title: compatibility display label;plugnormalizes this to match the canonical top-leveltitlefor merged toolsicons: spec-shaped MCP icons. Plug keeps its own top-level server icon, preserves upstream tool icons, and falls back to each upstream server's icon for routed tools/resources/prompts when the item has no icon of its own. Plug advertises embedded PNG icons first for broad client compatibility and keeps SVG as a fallback.
Notes:
- Wire names are always prefixed in the current release, regardless of
enable_prefix - Some servers can be split into sub-service prefixes via
tool_groups - A second account is the same server under another name.
plug server add-account slack workaddsslack-work, whose tools areSlack-work__…; a grouped server keeps its groups with the account added,GmailWork__… - Some clients still render raw
nameor synthesize their own labels, so perfect cross-client visual consistency is not always possible - Icon metadata is normalized before forwarding: HTTPS and bounded
data:icon URIs are allowed; PNG/JPEG/WebP are forwarded for upstream icons, untrusted SVG is dropped, invalid schemes, invalid sizes, and oversized inline icons are dropped.
| Document | Purpose |
|---|---|
| STATUS.md | Open work |
| ARCHITECTURE.md | Technical architecture, component design, data flow |
| VISION.md | Core principles, design philosophy, non-negotiable rules |
| CLIENT-COMPAT.md | AI client quirks, limits, and configuration |
| OPERATOR-GUIDE.md | Production operation: TLS, auth, observability, sandboxing |
| guides/mcp-2026-dual-era.md | Legacy and MCP 2026-07-28 side by side, and how to turn the modern path on |
| slack-mcp-events.md | Opt-in Slack event delivery to one remote client |
| testing/MCP-CONFORMANCE.md | What is proven locally and against the official conformance suite |
| RELEASING.md | How a release is built, signed, and published |
| CONTRIBUTING.md | Build, check, and ship a change |
| CHANGELOG.md | What changed, release by release |
| archive/ | Plans, research, design reviews, and audits. History, not maintained |
- One install, one owner — Plug.app on macOS; a source build on Linux
- Ruthlessly minimal — if a feature can't be explained in one sentence, simplify it
- Dual-audience UX — every command works for humans (pretty) AND agents (
--output json) - Token-efficient — 5-layer optimization, client-aware tool filtering
- Clean pass-through — faithful proxy by default, optional enrichment
- Rock-solid reliable — circuit breakers, merge cache, graceful degradation
- Dual-era — legacy MCP by default, MCP 2026-07-28 behind explicit gates
- Language: Rust (2024 edition)
- MCP SDK: rmcp 3.1.0 (official Rust SDK)
- App: SwiftUI, Sparkle updates
- CLI: Clap (derive pattern)
- HTTP: Axum + Tower + Hyper
- Async: Tokio (multi-threaded with work-stealing)
- Config: TOML via Figment (layered)
Apache-2.0 — see LICENSE