car-mcp 0.20.0

MCP (Model Context Protocol) server library — transport-agnostic dispatch for exposing CAR capabilities. Used by car-mcp-server (stdio binary) and car-server (HTTP-streamable daemon endpoint).
Documentation
//! Transport adapters for the MCP server.
//!
//! Each transport wraps the pure [`crate::Server::handle`] dispatch
//! function in I/O appropriate for that wire. The stdio loop is
//! used by `car-mcp-server`; an HTTP-streamable adapter for the
//! daemon endpoint will land alongside Phase 2 stage 4b in
//! `car-server-core`.

use crate::error_codes::PARSE as E_PARSE;
use crate::server::{err, Request, Response, Server};
use serde_json::Value;
use std::sync::Arc;
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader, BufWriter};

/// Newline-delimited JSON-RPC 2.0 over stdio.
///
/// One client per process. Reads requests from stdin until EOF,
/// writes responses to stdout. Logs go to stderr (the caller is
/// responsible for initializing tracing). Returns when stdin closes.
///
/// Uses `tokio::io::stdin()` / `stdout()` so it integrates with the
/// tokio runtime — the binary uses `flavor = "current_thread"` for
/// minimal overhead.
pub async fn stdio_loop(server: Arc<Server>) -> std::io::Result<()> {
    let mut stdin = BufReader::new(tokio::io::stdin()).lines();
    let mut stdout = BufWriter::new(tokio::io::stdout());
    while let Some(line) = stdin.next_line().await? {
        if line.trim().is_empty() {
            continue;
        }
        match serde_json::from_str::<Request>(&line) {
            Ok(req) => {
                if let Some(resp) = server.handle(req).await {
                    write_response(&mut stdout, &resp).await?;
                }
            }
            Err(e) => {
                let resp = err(Value::Null, E_PARSE, format!("parse error: {}", e));
                write_response(&mut stdout, &resp).await?;
            }
        }
    }
    Ok(())
}

async fn write_response<W>(out: &mut W, resp: &Response) -> std::io::Result<()>
where
    W: AsyncWriteExt + Unpin,
{
    let line = serde_json::to_string(resp).map_err(std::io::Error::other)?;
    out.write_all(line.as_bytes()).await?;
    out.write_all(b"\n").await?;
    out.flush().await
}