Skip to main content

max / makenotwork

2.2 KB · 61 lines History Blame Raw
1 //! stdio MCP transport: newline-delimited JSON-RPC over stdin/stdout.
2 //!
3 //! This is the transport an MCP client like Claude Code uses when it launches
4 //! the server as a subprocess (`claude mcp add <name> -- <binary> --stdio`).
5 //! One JSON-RPC message per line in on stdin, one per line out on stdout.
6 //! Anything the process wants to log must go to **stderr** — stdout is the
7 //! protocol channel and any stray byte there corrupts the stream.
8 //!
9 //! Reuses the same [`dispatch`](super::dispatch) as the HTTP server, so both
10 //! transports expose an identical tool surface and capability gate.
11
12 use serde_json::Value;
13 use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
14
15 use super::ServeConfig;
16 use super::protocol::{JsonRpcResponse, codes};
17 use crate::error::Result;
18 use crate::tool::ToolRegistry;
19
20 /// Serve the registry over stdio until stdin reaches EOF (the client closed the
21 /// pipe). Blocks for the lifetime of the connection.
22 pub async fn serve(registry: ToolRegistry, config: ServeConfig) -> Result<()> {
23 let mut lines = BufReader::new(tokio::io::stdin()).lines();
24 let mut stdout = tokio::io::stdout();
25
26 while let Some(line) = lines.next_line().await? {
27 let line = line.trim();
28 if line.is_empty() {
29 continue;
30 }
31 let response = match serde_json::from_str(line) {
32 Ok(req) => {
33 // stdio has no session, so subscriptions and cancellation are
34 // unavailable here; the resource read surface still works.
35 super::dispatch(
36 &registry,
37 config.resources.as_ref(),
38 config.projection,
39 config.grants.as_ref(),
40 None,
41 req,
42 )
43 .await
44 }
45 Err(e) => Some(JsonRpcResponse::err(
46 Value::Null,
47 codes::PARSE_ERROR,
48 e.to_string(),
49 )),
50 };
51 // Notifications (`dispatch` returned `None`) carry no reply.
52 if let Some(response) = response {
53 let mut buf = serde_json::to_vec(&response)?;
54 buf.push(b'\n');
55 stdout.write_all(&buf).await?;
56 stdout.flush().await?;
57 }
58 }
59 Ok(())
60 }
61