MCP-Bridge Plugins
The mcp-bridge runtime kind lets you register an existing MCP server binary as a Wayland plugin without writing any adapter code. The engine spawns the binary, runs the standard MCP handshake, discovers its tools, and registers them as first-class plugin tools. From the engine’s apply pipeline those tools are indistinguishable from statically-linked plugin tools.
The implementation lives in crates/wcore-plugin-subprocess/src/mcp_bridge.rs.
Writing a manifest
Section titled “Writing a manifest”Drop a plugin.toml in a named subdirectory under the plugin install root:
~/.wayland/plugins/mcp-time/plugin.tomlMinimal manifest for a stdio MCP server binary:
plugin_api_version = "1.0"
[plugin]name = "mcp-time"version = "0.1.0"description = "Wraps mcp-server-time as a Wayland plugin"
[permissions]register_mcp_server = trueregister_tools = truetool_namespace = "Time"
[runtime]kind = "mcp-bridge"
[runtime.subprocess]binary_path = "/usr/local/bin/mcp-server-time"args = []The binary_path is resolved relative to the directory containing plugin.toml. An absolute path works too.
Required permission flags
Section titled “Required permission flags”Two [permissions] keys are required for any mcp-bridge plugin:
| Key | Effect |
|---|---|
register_mcp_server = true | The plugin loader checks this before launching the runner. If absent, the plugin is refused. |
register_tools = true | Enables the synthesized tools to be registered into the engine’s tool registry. |
tool_namespace | A string prefix. Every synthesized tool is registered as <namespace>::<tool_name>. Required when register_tools = true. Prevents two plugins from claiming the same name. |
Startup sequence
Section titled “Startup sequence”When the engine loads the plugin, McpBridgePluginRunner::load does the following:
- Spawns the binary with
env_clear(). Only an explicit allowlist of environment variables is forwarded:PATH,HOME,USER,LANG,TZ, locale vars,TMPDIR, and Windows essentials (SYSTEMROOT,WINDIR,COMSPEC,PATHEXT,USERPROFILE,APPDATA, and others). Variables such asOPENAI_API_KEY,ANTHROPIC_API_KEY, andWAYLAND_VAULT_PASSPHRASEare withheld from the child process. - Sends
initializewithprotocolVersion: "2025-03-26"andclientInfo.name: "wayland-mcp-bridge". - Sends
notifications/initialized(fire-and-forget, required by the MCP spec before further requests). - Sends
tools/listand collects the returned tool definitions. - Synthesizes one
PluginToolper discovered tool. Each tool’sexecuteclosure routestools/callback through the same stdio transport. - Returns the
LoadedMcpBridgePluginstruct carrying the tool list.
The tool list is folded into the engine’s InitializeOutcome.tools alongside any other plugin tools. No MCP-bridge-specific branch exists in the apply pipeline.
Wire protocol
Section titled “Wire protocol”The bridge speaks JSON-RPC 2.0 over newline-delimited JSON on stdin and stdout, which is the canonical MCP-stdio framing. Any binary that implements the MCP stdio transport works without modification.
Per-RPC timeout is 30 seconds. Lines from the server’s stdout are capped at 8 MiB. A server that sends a line longer than that cap without a newline causes the transport to be marked dead, preventing out-of-memory growth.
Tool naming
Section titled “Tool naming”Each synthesized tool is named <tool_namespace>::<mcp_tool_name> as declared in the manifest. The ScopedToolRegistry on the host side enforces uniqueness: two plugins cannot register the same <namespace>::<name> pair.
Passing environment variables to the server
Section titled “Passing environment variables to the server”If the MCP server needs credentials or configuration, supply them via env in the [runtime.subprocess] block:
[runtime.subprocess]binary_path = "/usr/local/bin/mcp-server-github"args = []env = { GITHUB_TOKEN = "ghp_xxx" }Values in env are forwarded in addition to the base allowlist.
Shutdown
Section titled “Shutdown”When the engine shuts down or the plugin is unloaded, the runner closes stdin (which signals the MCP server to exit per the spec), waits up to 5 seconds for a clean exit, then sends SIGKILL if the process has not exited. The kill_on_drop(true) flag on the spawned Command also reaps the process if the runner is dropped unexpectedly.