Skip to content

MCPChannel

The coralstack-cmd-ipc-mcp crate provides McpServerChannel — a CommandChannel that translates between MCP requests and cmd-ipc wire messages. It wraps the rmcp SDK for protocol handling.

Use cases:

  • Exposing application commands as AI agent tools (Claude Desktop, Cursor, etc.)
  • Building MCP-compatible servers from an existing command registry
Terminal window
cargo add coralstack-cmd-ipc-mcp

Stdio ships out of the box. For HTTP, TCP, or other transports, enable the corresponding rmcp feature in your own Cargo.toml and pass the transport to serve() (or use into_handler() for HTTP’s per-session model).

McpServerChannel implements CommandChannel, so it plugs into the registry like any other channel:

use std::sync::Arc;
use coralstack_cmd_ipc::prelude::*;
use coralstack_cmd_ipc_mcp::McpServerChannel;
let registry = CommandRegistry::new(Config::default());
MyService.register(&registry).await?;
let mcp = Arc::new(McpServerChannel::new("mcp"));
let driver = registry.register_channel(mcp.clone()).await?;
tokio::spawn(driver);

The channel is a pure translation layer. Incoming MCP tools/list / tools/call emit ListCommandsRequest / ExecuteCommandRequest on recv(); the registry sends responses back via send(), correlated to the waiting MCP call by thid. Public commands (non-_-prefixed) appear as tools; private commands are never exposed.

Hand any rmcp-compatible transport to serve(transport). Stdio ships out of the box; anything else you pass in yourself (no framework lock-in).

Ideal for Claude Desktop, Cursor, and other local agents that spawn the server as a child process.

// Completes when the MCP client disconnects.
mcp.clone().serve_stdio().await?;

Equivalent to mcp.serve(rmcp::transport::io::stdio()).

let mcp = Arc::new(
McpServerChannel::new("mcp")
.with_implementation("my-app", "1.0.0")
.with_instructions("Optional hint for the agent about what this server does")
.with_timeout(std::time::Duration::from_secs(60)),
);
MethodDefaultDescription
new(id)Channel id used by the registry
with_implementation(name, version)cmd-ipc-mcp / crate versionserverInfo reported on initialize
with_instructions(s)NoneText sent on initialize to orient the agent
with_timeout(Duration)30sTimeout for MCP-originated requests awaiting the registry
with_include(ids)Allowlist of command IDs exposed over MCP. See Filtering exposed commands
with_exclude(ids)emptyDenylist of command IDs hidden from MCP

By default, every (non-private) command on the registry is exposed as an MCP tool. Use with_include and/or with_exclude to narrow what an AI agent can see and call:

let mcp = Arc::new(
McpServerChannel::new("mcp")
// Allowlist mode: only these commands are visible over MCP.
.with_include(["math.add", "math.multiply"])
// Denylist mode (combinable): always hide these regardless of include.
.with_exclude(["math.danger", "admin.reset"]),
);

Semantics:

  • The effective set of exposed commands is (include ?? all) − exclude.
  • If both are set and an ID appears in both, exclude wins.
  • Filtered commands are indistinguishable from never-registered: tools/list hides them and tools/call returns a NOT_FOUND-shaped error. Nothing leaks about what exists internally.
  • Private commands (IDs starting with _) are excluded unconditionally — with_include cannot expose them.
  • Matching is on exact command IDs; there is no wildcard or glob support.