flymcprust_core/transport/
stdio.rs1use std::collections::HashMap;
20use std::io;
21use std::sync::{Arc, Mutex};
22use std::time::Duration;
23
24use serde::Deserialize;
25use tokio::io::{AsyncBufRead, AsyncBufReadExt, AsyncRead, AsyncWrite, AsyncWriteExt, BufReader};
26use tokio::sync::{Semaphore, mpsc};
27use tokio::task::JoinSet;
28use tokio_util::sync::CancellationToken;
29
30use crate::context::TransportContext;
31use crate::error::McpError;
32use crate::jsonrpc::{self, Message, Rejection, Response};
33use crate::legacy::{ChannelPeer, LegacySession, PendingRequests};
34use crate::notify::NotifyChannel;
35use crate::server::{McpRequest, Server};
36use crate::spec::{RequestId, methods};
37
38const SHUTDOWN_GRACE: Duration = Duration::from_secs(1);
41
42type InFlight = Arc<Mutex<HashMap<RequestId, CancellationToken>>>;
43
44impl Server {
45 pub async fn serve_stdio(self) -> io::Result<()> {
56 if std::io::IsTerminal::is_terminal(&std::io::stdin()) {
57 explain_interactive_use();
58 }
59 serve_io(self, tokio::io::stdin(), tokio::io::stdout()).await
60 }
61}
62
63#[allow(deprecated)] fn set_log_level(
66 legacy: &Mutex<Option<LegacySession>>,
67 params: Option<&serde_json::value::RawValue>,
68) -> Result<(), McpError> {
69 #[derive(Deserialize)]
70 struct Params {
71 level: crate::spec::LoggingLevel,
72 }
73 let params: Params = serde_json::from_str(params.map_or("{}", |p| p.get()))
74 .map_err(|_| McpError::invalid_params("Invalid log level"))?;
75 if let Some(session) = legacy
76 .lock()
77 .unwrap_or_else(std::sync::PoisonError::into_inner)
78 .as_mut()
79 {
80 session.log_level = Some(params.level);
81 }
82 Ok(())
83}
84
85#[allow(clippy::print_stderr)]
88fn explain_interactive_use() {
89 let program = std::env::current_exe()
90 .ok()
91 .and_then(|p| p.file_name().map(|n| n.to_string_lossy().into_owned()))
92 .unwrap_or_else(|| "this-server".to_owned());
93 eprintln!(
94 "This is an MCP server: it reads JSON-RPC messages on stdin and is meant to be \
95 started by an MCP client.\n \
96 = help: try it with the MCP Inspector:\n \
97 npx @modelcontextprotocol/inspector --cli path/to/{program} --protocol-era modern \
98 --method tools/list\n\
99 (Waiting for messages on stdin; press Ctrl+C to stop.)"
100 );
101}
102
103pub async fn serve_io<R, W>(server: Server, reader: R, writer: W) -> io::Result<()>
110where
111 R: AsyncRead + Unpin,
112 W: AsyncWrite + Unpin + Send + 'static,
113{
114 let limits = server.limits().clone();
115 let (tx, rx) = mpsc::channel::<Vec<u8>>(limits.max_concurrent_calls.max(1));
116 let writer_task = tokio::spawn(write_loop(writer, rx));
117
118 let in_flight: InFlight = Arc::default();
119 let legacy: Arc<Mutex<Option<LegacySession>>> = Arc::default();
122 let peer = ChannelPeer {
123 pending: Arc::new(PendingRequests::default()),
124 owner: 0,
125 out: tx.clone(),
126 timeout: limits.tool_timeout,
127 };
128 let permits = Arc::new(Semaphore::new(limits.max_concurrent_calls.max(1)));
129 let mut tasks = JoinSet::new();
130 let mut reader = BufReader::with_capacity(READ_BUFFER_BYTES, reader);
133 let mut line = Vec::new();
134
135 let read_result = loop {
136 while tasks.try_join_next().is_some() {}
138
139 line.clear();
140 match read_line(&mut reader, &mut line, limits.max_message_bytes).await {
141 Ok(Line::Eof) => break Ok(()),
142 Ok(Line::TooLong) => {
143 let response =
144 Response::error(None, McpError::invalid_request("Message too large"));
145 if tx.send(response.to_vec()).await.is_err() {
146 break Ok(());
147 }
148 continue;
149 }
150 Ok(Line::Complete) => {}
151 Err(e) => break Err(e),
152 }
153 if line.iter().all(u8::is_ascii_whitespace) {
154 continue;
155 }
156
157 let message = match jsonrpc::parse(&line, &limits) {
158 Ok(message) => message,
159 Err(Rejection::Error { id, error }) => {
160 if tx.send(Response::error(id, error).to_vec()).await.is_err() {
161 break Ok(());
162 }
163 continue;
164 }
165 Err(Rejection::UnexpectedResponse) => {
166 let routed = jsonrpc::parse_client_response(&line)
168 .is_some_and(|response| peer.pending.complete(response, 0));
169 if !routed {
170 tracing::debug!("ignoring a JSON-RPC response that answers no pending request");
171 }
172 continue;
173 }
174 };
175
176 let request = match message {
177 Message::Notification(n) => {
178 if n.method == methods::NOTIFICATIONS_CANCELLED {
179 cancel(&in_flight, n.params.as_deref());
180 }
181 continue;
183 }
184 Message::Request(request) => request,
185 };
186
187 if request.method == "initialize" && server.legacy_clients_enabled() {
188 let response = match server.legacy_initialize(request.params.as_deref()) {
189 Ok((result, session)) => {
190 *legacy
191 .lock()
192 .unwrap_or_else(std::sync::PoisonError::into_inner) = Some(session);
193 Response::Result {
194 id: request.id,
195 result,
196 }
197 }
198 Err(error) => Response::error(Some(request.id), error),
199 };
200 if tx.send(response.to_vec()).await.is_err() {
201 break Ok(());
202 }
203 continue;
204 }
205 let session = legacy
206 .lock()
207 .unwrap_or_else(std::sync::PoisonError::into_inner)
208 .clone();
209
210 if request.method == "logging/setLevel" && session.is_some() {
212 let response = match set_log_level(&legacy, request.params.as_deref()) {
213 Ok(()) => serde_json::value::to_raw_value(&serde_json::json!({}))
214 .map(|result| Response::Result {
215 id: request.id.clone(),
216 result,
217 })
218 .unwrap_or_else(|_| {
219 Response::error(Some(request.id.clone()), McpError::internal())
220 }),
221 Err(error) => Response::error(Some(request.id), error),
222 };
223 if tx.send(response.to_vec()).await.is_err() {
224 break Ok(());
225 }
226 continue;
227 }
228 let mut transport = TransportContext::default();
230 transport.insert(NotifyChannel(tx.clone()));
231
232 let token = CancellationToken::new();
233 let duplicate = match lock(&in_flight).entry(request.id.clone()) {
234 std::collections::hash_map::Entry::Occupied(_) => true,
235 std::collections::hash_map::Entry::Vacant(slot) => {
236 slot.insert(token.clone());
237 false
238 }
239 };
240 if duplicate {
241 let response = Response::error(
242 Some(request.id),
243 McpError::invalid_request("Duplicate request id"),
244 );
245 if tx.send(response.to_vec()).await.is_err() {
246 break Ok(());
247 }
248 continue;
249 }
250
251 let Ok(permit) = Arc::clone(&permits).acquire_owned().await else {
253 break Ok(());
254 };
255 let server = server.clone();
256 let tx = tx.clone();
257 let in_flight = Arc::clone(&in_flight);
258 let peer = peer.clone();
259 tasks.spawn(async move {
260 let id = request.id.clone();
261 let response = match session {
262 Some(session) => {
263 server
264 .handle_legacy(request, &session, transport, token, &peer)
265 .await
266 }
267 None => {
268 server
269 .handle(McpRequest {
270 message: Message::Request(request),
271 transport,
272 cancellation: token,
273 })
274 .await
275 }
276 };
277 lock(&in_flight).remove(&id);
278 if let Some(response) = response {
279 let _ = tx.send(response.to_vec()).await;
281 }
282 drop(permit);
283 });
284 };
285
286 let deadline = limits.tool_timeout + SHUTDOWN_GRACE;
288 if tokio::time::timeout(deadline, async {
289 while tasks.join_next().await.is_some() {}
290 })
291 .await
292 .is_err()
293 {
294 tracing::warn!("in-flight requests did not finish before shutdown; aborting them");
295 tasks.abort_all();
296 while tasks.join_next().await.is_some() {}
299 }
300 drop(peer);
303 drop(tx);
304 let write_result = writer_task
305 .await
306 .unwrap_or_else(|e| Err(io::Error::other(e)));
307 read_result.and(write_result)
308}
309
310const READ_BUFFER_BYTES: usize = 64 * 1024;
312
313const WRITE_BATCH_BYTES: usize = 64 * 1024;
315
316async fn write_loop<W: AsyncWrite + Unpin>(
317 mut writer: W,
318 mut rx: mpsc::Receiver<Vec<u8>>,
319) -> io::Result<()> {
320 let mut batch = Vec::new();
324 while let Some(message) = rx.recv().await {
325 batch.clear();
326 batch.extend_from_slice(&message);
327 batch.push(b'\n');
328 while batch.len() < WRITE_BATCH_BYTES {
329 let Ok(more) = rx.try_recv() else { break };
330 batch.extend_from_slice(&more);
331 batch.push(b'\n');
332 }
333 writer.write_all(&batch).await?;
334 writer.flush().await?;
335 if batch.capacity() > 4 * WRITE_BATCH_BYTES {
336 batch = Vec::new();
338 }
339 }
340 writer.shutdown().await.or(Ok(()))
341}
342
343#[derive(Deserialize)]
344struct CancelledParams {
345 #[serde(rename = "requestId")]
346 request_id: RequestId,
347}
348
349fn cancel(in_flight: &InFlight, params: Option<&serde_json::value::RawValue>) {
350 let Some(params) = params else { return };
352 let Ok(params) = serde_json::from_str::<CancelledParams>(params.get()) else {
353 return;
354 };
355 if let Some(token) = lock(in_flight).get(¶ms.request_id) {
356 token.cancel();
357 }
358}
359
360fn lock(in_flight: &InFlight) -> std::sync::MutexGuard<'_, HashMap<RequestId, CancellationToken>> {
361 in_flight
363 .lock()
364 .unwrap_or_else(std::sync::PoisonError::into_inner)
365}
366
367enum Line {
368 Complete,
369 TooLong,
370 Eof,
371}
372
373async fn read_line<R: AsyncBufRead + Unpin>(
376 reader: &mut R,
377 buf: &mut Vec<u8>,
378 max: usize,
379) -> io::Result<Line> {
380 let mut too_long = false;
381 loop {
382 let available = reader.fill_buf().await?;
383 if available.is_empty() {
384 return Ok(if too_long {
386 Line::TooLong
387 } else if buf.is_empty() {
388 Line::Eof
389 } else {
390 Line::Complete
391 });
392 }
393 let (chunk, found, used) = match available.iter().position(|&b| b == b'\n') {
394 Some(i) => (available.get(..i).unwrap_or_default(), true, i + 1),
395 None => (available, false, available.len()),
396 };
397 if !too_long {
398 if buf.len() + chunk.len() > max {
399 too_long = true;
400 buf.clear();
401 } else {
402 buf.extend_from_slice(chunk);
403 }
404 }
405 reader.consume(used);
406 if found {
407 if too_long {
408 return Ok(Line::TooLong);
409 }
410 if buf.last() == Some(&b'\r') {
411 buf.pop();
412 }
413 return Ok(Line::Complete);
414 }
415 }
416}
417
418#[cfg(test)]
419mod tests {
420 use super::*;
421
422 #[tokio::test]
423 async fn reads_bounded_lines() {
424 let input: &[u8] = b"short\r\nthis-line-is-too-long\nok\nlast";
425 let mut reader = BufReader::with_capacity(4, input);
426 let mut buf = Vec::new();
427 let mut lines = Vec::new();
428 loop {
429 buf.clear();
430 match read_line(&mut reader, &mut buf, 8).await.unwrap() {
431 Line::Complete => lines.push(String::from_utf8(buf.clone()).unwrap()),
432 Line::TooLong => lines.push("<too long>".to_owned()),
433 Line::Eof => break,
434 }
435 }
436 assert_eq!(lines, ["short", "<too long>", "ok", "last"]);
437 }
438}