feat(mcp): stdio transport adapter (T10.4, FR-MCP-6)

serve_once() runs one MCP session over the current process's stdin/
stdout to completion -- the entry point main.rs's --mcp-stdio flag calls
into (next commit). No bearer-token check here: spawning this process
at all requires the same local-user privilege as any other command, so
process-spawn capability is the trust boundary for stdio, not a header.
This commit is contained in:
iamdoubz
2026-07-07 00:21:16 -05:00
parent 851e89720d
commit 5f690f4b74
+31
View File
@@ -0,0 +1,31 @@
//! stdio transport (FR-MCP-6) — "a thin adapter the agent spawns". A coding
//! agent's MCP client config spawns `whispassist.exe --mcp-stdio` and talks
//! JSON-RPC over that child process's stdin/stdout; `main.rs` checks for that
//! flag before building the Tauri window and calls `serve_once` here instead.
//!
//! There is no bearer-token header to check here (unlike HTTP): the ability
//! to spawn this process at all already requires the same OS-level privilege
//! as running any other local command as the signed-in user, so process-spawn
//! capability is the trust boundary for stdio, same as other local-only MCP
//! servers. `set_mcp_enabled` still mints/stores a token (`mcp::token`) for
//! parity with the HTTP transport and in case a future stdio client wants to
//! pass it, but this transport does not require presenting it.
use crate::mcp::handler::WaMcpHandler;
use crate::mcp::McpError;
use rmcp::ServiceExt;
/// Serves one MCP session over the current process's stdin/stdout until the
/// peer disconnects, then returns.
pub async fn serve_once(handler: WaMcpHandler) -> Result<(), McpError> {
let transport = rmcp::transport::io::stdio();
let running = handler
.serve(transport)
.await
.map_err(|e| McpError::Server(e.to_string()))?;
running
.waiting()
.await
.map_err(|e| McpError::Server(e.to_string()))?;
Ok(())
}