VibeCody is a fully-featured MCP host. The MCP panel inside VibeCoder/VibeAIChat is the unified surface for managing servers, browsing the plugin directory, viewing the lazy-loaded tool registry, and inspecting cache metrics. This page documents the user-facing surface; the underlying client lives in vibecoder/crates/vibe-ai/src/mcp.rs.
What’s in the panel
The panel has five tabs:
| Tab | Purpose |
|---|---|
| Servers | Configure MCP servers — name, command, args, env, OAuth tokens. Test connectivity and see live tool lists. |
| Tools | The lazy-loaded tool registry. Search across servers, load / unload individual tools to manage agent-context budget. |
| Directory | Browse the curated plugin catalog by category, install or uninstall with one click. |
| Installed | Just the installed plugins, with View Tools navigation back to the Tools tab. |
| Metrics | Cache hit rate, average load time, context-savings percentage, and a per-tool load-time chart. |
Server configuration
Server records live in ~/.vibecoder/mcp.json as a plain JSON array. Each entry:
{
"name": "filesystem",
"command": "mcp-fs",
"args": ["--root", "/workspace"],
"env": { "MCP_LOG_LEVEL": "info" }
}
The panel writes this file via the save_mcp_servers Tauri command. OAuth tokens for servers that require auth are not stored alongside the config: they are secrets, so each one is an encrypted row in the ProfileStore (~/.vibecli/profile_settings.db) under mcp.oauth.<server>. Before 0.5.9 they lived in a plaintext ~/.vibecoder/mcp-tokens.json; that file is migrated into the store and deleted the first time the panel reads a token.
Test before save
The Servers tab has a Test button that spins up a short-lived MCP client connection, calls list_tools, and shows what the server would expose. Run this before saving — a server that can’t be reached or doesn’t speak MCP will surface the failure inline (with aria-live="assertive") rather than silently land in the config.
OAuth flow
For servers that need OAuth (e.g. cloud APIs):
- Click Connect via OAuth on the server row.
- Fill in client_id, auth_url, token_url, scopes, and the redirect URI (which the daemon listens on).
- Click Initiate — the system browser opens to the authorization page.
- After authorizing, the IDP redirects to the daemon’s callback. Paste the resulting
codeback into the panel. - The panel exchanges the code for a token via
complete_mcp_oauthand stores it under the server’s name in the encrypted ProfileStore.
The token-status badge on each server row reflects connectivity and expiration — a stale token shows as disconnected even if the row’s config looks fine.
Delete confirmation
Like the Sessions panel, deleting a server is a two-click confirmation: first click arms, second click commits. There’s no recycle bin — the row is removed from mcp.json immediately on commit.
Protocol versions
VibeCody’s MCP client speaks two eras and picks one per server when it connects.
| Era | Revisions | How it opens | What the client does |
|---|---|---|---|
| Modern | 2026-07-28 |
No handshake. Every request carries its protocol version, client identity and capabilities in _meta. |
Uses it whenever the server answers server/discover with a result that names 2026-07-28. |
| Legacy | 2025-11-25 and earlier |
An initialize handshake. |
Used when the probe is answered with any other error, with garbage, or not at all. |
- Probe, not a guess. On connect the client sends
server/discover. A modern answer selects modern. Any other outcome selects legacy and the client sendsinitializeas before, because legacy servers answer an unknown method however they like (-32601,-32602, silence). The probe waits up to 20 s; a legacy server that ignores unknown methods makes the connect wait that long, and a modern server that takes longer than that to start is mistaken for a legacy one. - A modern server we cannot talk to is not a legacy server. If it answers with a version list that does not include
2026-07-28(or theUnsupportedProtocolVersionError), the connection fails and the error names the versions it supports. The client does not fall back toinitialize. - Input requests. A modern server may answer a tool call with
input_required. With no user to ask, the client declines elicitation and reports no roots, then retries with the server’srequestStateechoed exactly. A server that asks the client to run a model (sampling), which this client does not declare, makes the call fail with that reason. A server that keeps asking is abandoned after 8 rounds; the spec sets no limit, so the bound is the client’s own. - List caching. A modern
tools/listcarriesttlMs; while it is fresh the list is reused. Absent, negative or zero means ask every time, which is also how legacy servers behave. Change notifications are not subscribed to, so a list can be up tottlMsout of date.
What has been verified: the rules are unit-tested, and the whole flow is tested over a real stdio pipe against a scripted modern server and scripted legacy servers. The legacy path was also run against the real @modelcontextprotocol/server-everything. No real modern server was available, so interoperability with one is untested. Not implemented: the Tasks extension, subscriptions/listen, pagination cursors, and the HTTP transport.
Tool registry (lazy loading)
The Tools tab reflects the lazy-loading runtime — tools aren’t held in agent context until you load them. The columns:
- Status —
loaded,loading, orunloaded. Loading is animated; the actual load happens in the background. - Size — KB cost when loaded.
- Last used — timestamp from the daemon’s tool-call history.
- Load time — first-load wall-clock from when load was issued.
Click the row toggle to load or unload. The Tools tab shows live tools per configured server when reachable; servers that don’t respond to test_mcp_server within the timeout simply collapse to “no tools listed” rather than blocking the whole tab.
Search
The search box filters the registry across all servers in real time (200ms debounce). Matches against tool name and description. Use this when you can’t remember which server a tool came from.
Plugin directory
The Directory tab is a curated catalog of MCP plugins. Each entry has:
- Author / Author rating — surfaced for user trust signals.
- Category — filterable from the sidebar.
- Install / Uninstall — two-click confirmation. Install adds the plugin id to
~/.vibecoder/mcp-installed.json; uninstall removes it.
Install does not auto-add a server entry to mcp.json. The directory is metadata-only — actually wiring a plugin into your active MCP host happens in the Servers tab, with the plugin’s documented command / args. This separation is deliberate so that “installed” tracks intent (the user wants this plugin available) without conflating it with active runtime state.
/health declaration
features.mcp is a runtime probe of ~/.vibecoder/mcp.json:
{
"available": true,
"transport": "tauri-desktop",
"config_path": "~/.vibecoder/mcp.json",
"server_count": 3,
"configured": true
}
Operators can hit /health.features.mcp.server_count to confirm an environment has MCP servers wired up. The boolean configured is server_count > 0 — clients gating MCP-dependent features should read this single field.
Observability
Backend operations emit structured tracing events under vibecody::mcp:
RUST_LOG=vibecody::mcp=info vibecli serve
Events:
INFO vibecody::mcp: mcp.servers.save server_count=3
INFO vibecody::mcp: mcp.server.test.start server_name=filesystem
INFO vibecody::mcp: mcp.server.test.ok server_name=filesystem tool_count=12 elapsed_ms=420
WARN vibecody::mcp: mcp.server.test.failed server_name=git elapsed_ms=2080
INFO vibecody::mcp: mcp.plugin.install plugin_id=terraform installed_total=4
INFO vibecody::mcp: mcp.plugin.uninstall plugin_id=terraform installed_total=3
WARN vibecody::mcp: mcp.plugin.install.not_found plugin_id=banana
Server commands and arguments are not logged — only the server name. Tool descriptions, OAuth tokens, and plugin metadata are never logged.
Accessibility
- The panel’s tab bar uses
role="tablist"(set by the existingpanel-tab-barclass) — keyboard navigation between tabs is available. - The shared error banner is
role="alert"witharia-live="assertive", so failures from any subsystem (servers, tools, directory) are announced immediately. - Two-click delete on servers and plugins carries the confirmation state in
aria-labelso AT users hear “second click commits” rather than just “Delete”. - The OAuth flow is a multi-step form with clear step labels; each input has an associated
<label>and the busy state disables the submit button rather than just hiding it.
Cross-client behaviour
| Client | MCP UI |
|---|---|
| VibeCoder / VibeAIChat | Full panel (servers, tools, directory, metrics) |
| VibeMobile | None — MCP is a desktop-host concern |
| VibeWatch | None |
| IDE plugins | Read-only — surface configured server names for tool-completion contexts |
The panel runs in the Tauri host. Servers are subprocess-spawned by the host — they inherit the host’s user but run in fresh process trees.
Sandboxing, as of 2026-09-27: MCP server subprocesses launch through the native Tier-0 sandbox (bwrap on Linux, sandbox-exec on macOS) by default. Set VIBECLI_MCP_SANDBOX=0 to opt back out — for example while diagnosing a connector that fails to read a file it needs because the sandbox’s toolchain allow-list guessed wrong for your machine’s node/npm layout.
Two things this does not cover, regardless of the flag:
- The network is always open. The sandbox confines the filesystem (a scratch dir plus read-only binds for your detected toolchain), not the network — every connector can still reach the internet freely. It does not stop a compromised or malicious MCP server from exfiltrating data it can already read. Use the OAuth flow rather than long-lived environment-variable tokens where possible; a sandboxed connector is not a network-isolated one.
- Most connectors still fall back to unconfined if the sandbox can’t start. If sandbox setup fails (a bad toolchain guess, a missing bind target), the connector still runs — just without OS confinement — and a warning is logged (
RUST_LOG=vibecody::mcp=info). The one exception is a connector explicitly markedrequires_sandbox: truein the catalog (today: connectors in thePaymentscategory) — for those, a sandbox failure refuses the connection outright rather than running it unconfined.
The panel and the VibeDesk connector marketplace (a separate, workspace-scoped catalog — see docs/vibedesk.md) share the same underlying client, so this behavior applies to both.
Troubleshooting
“Connect failed: Connection refused” when testing a server
Either the binary isn’t on PATH or the server isn’t speaking MCP on stdio. Check the command field — it must resolve via which from the daemon’s environment, not just the user’s shell. Absolute paths are safer.
“list_tools failed: invalid handshake”
The server connected but isn’t returning a valid MCP tools/list response. Likely an old or non-MCP binary mistakenly named like an MCP server. Cross-check against the server’s documentation — newer servers should support the 2024-11-05 protocol revision.
“Plugin already installed” when nothing seems to be installed
Clear ~/.vibecoder/mcp-installed.json to reset the install ledger. The directory tab tracks intent only — it doesn’t probe the file system.
“Token exchange failed” during OAuth
Most often caused by a redirect URI mismatch between the panel and the IDP’s registered application. The panel defaults to http://localhost:7879/oauth/callback — register that exact URL with your IDP, including the path.
“Servers tab shows no live tools after Test”
The Tools tab probes all servers in parallel, but rendering is gated by serverToolsLoading. If a server hangs (no response, no error within the timeout), the panel resolves it to an empty list. Check RUST_LOG=vibecody::mcp=info for the corresponding mcp.server.test.failed event.
Related
- MCP client core:
vibecoder/crates/vibe-ai/src/mcp.rs— connect, list_tools, call_tool - Source:
vibecoder/src/components/McpPanel.tsx(902 LOC) · backend invibecoder/src-tauri/src/commands.rs - Tests:
vibecoder/src/components/__tests__/McpPanel.test.tsx - Sandbox:
docs/sandbox— MCP server subprocesses are sandboxed by default as of 2026-09-27 (VIBECLI_MCP_SANDBOX, default on); see the caveats above before relying on it as a network boundary.