car-mcp 0.49.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).
//! MCP (Model Context Protocol) server library — transport-agnostic
//! dispatch for exposing CAR capabilities to MCP-aware clients.
//!
//! Two transports use this crate today:
//!
//! - **stdio** — `car-mcp-server` binary, one client per process.
//!   Suitable for Claude Desktop / Cursor / Claude Code's
//!   `--mcp-config` and any other client that follows the MCP spec's
//!   stdio framing. See [`transport::stdio_loop`].
//! - **HTTP-streamable** — `car-server` daemon endpoint, many
//!   concurrent clients sharing the same `Runtime` / policy chain /
//!   eventlog. Implemented in `car_server_core::mcp` against the same
//!   [`Server::handle`] entry point: `POST /mcp` for one JSON-RPC
//!   request/response, `GET /mcp` for the SSE stream, `GET
//!   /mcp/health` for liveness. Bind address is `--mcp-bind` /
//!   `CAR_MCP_BIND`; pass `disabled` to skip the listener.
//!
//! Both share the same `Server::handle(Request) -> Option<Response>`
//! pure function, so transports stay separable from protocol logic.
//!
//! The *tool set*, however, is per-[`Server`], not per-process. A
//! transport may register tools it alone can serve via
//! [`Server::register_tool`]: the daemon holds a live `Runtime`, so it
//! can carry tools the stdio binary has no way to run and must not
//! advertise. With nothing registered, a server advertises exactly the
//! built-in schemas, unchanged.
//!
//! The daemon uses that seam for exactly one thing today:
//! `assistant_start` / `assistant_poll` / `assistant_cancel`
//! (`car_server_core::mcp_assistant`, car#972 §6), which drive the agent
//! behind `car do` through a run handle. They are absent from stdio because
//! `car-mcp-server` is this crate plus telemetry — no `Runtime`, no inference
//! engine — so a stdio client gets `-32601` for a tool that never existed
//! there rather than a tool that exists and cannot work.
//!
//! ## Why a library crate
//!
//! Originally this lived in `car-mcp-server`'s `main.rs` as a
//! self-contained binary. v0.8 + the external-agent work made it
//! clear MCP needs to be a first-class daemon surface so every
//! external client (Claude Code, Codex, Gemini, custom GPTs) gets
//! CAR's tools through the same governance layer. Extracting the
//! dispatch logic into a library lets the daemon embed it without
//! shelling out to a subprocess. See
//! `docs/proposals/external-agent-detection.md`. The daemon endpoint
//! landed in 0542d10f; `docs/websocket-protocol.md` and
//! `docs/cookbook/07-mcp-server.md` document the live surface.

mod schemas;
mod server;
pub mod transport;

pub use schemas::{cached_prompt_schemas, cached_tool_schemas};
pub use server::{RegisterError, Request, Response, Server, ToolError, ToolHandler};

/// Where [`Server::with_store`] should point by default: the same note store
/// the assistant behind `car do` reads and writes.
///
/// Sharing one file is the point. A separate MCP-only store would leave
/// `memory_query` from an editor unable to see anything the user remembered
/// through `car do`, which is half of what car#972 §1 reports as broken.
///
/// Relocating it needs no new knob: the path resolves under `CAR_HOME`, so an
/// editor plugin that wants an isolated memory sets that one variable and
/// every other daemon state path moves with it.
pub fn default_store_path() -> std::path::PathBuf {
    car_memgine::note_store::default_path()
}

/// MCP protocol version this server speaks.
pub const PROTOCOL_VERSION: &str = "2024-11-05";

/// Every protocol revision this server will answer `initialize` with.
///
/// Exactly one entry today, so negotiation is a no-op in practice: every
/// client is answered `2024-11-05` either because it asked for that, or
/// because nothing else is on the list. The list exists so a future bump is
/// an append plus a move of [`PROTOCOL_VERSION`] to the new revision — at
/// which point a client still asking for `2024-11-05` keeps getting it back
/// instead of being handed a revision it never asked for and may refuse.
/// That inversion is what car#972 §3 identifies as the real blocker to the
/// bump; the negotiation in `Server::handle` is what removes it.
pub const SUPPORTED_VERSIONS: &[&str] = &[PROTOCOL_VERSION];

/// Server identifier reported in `initialize` responses.
pub const SERVER_NAME: &str = "car-mcp";

/// JSON-RPC error codes per the spec.
pub mod error_codes {
    pub const PARSE: i32 = -32700;
    pub const INVALID_REQUEST: i32 = -32600;
    pub const METHOD_NOT_FOUND: i32 = -32601;
    pub const INVALID_PARAMS: i32 = -32602;
    pub const INTERNAL: i32 = -32603;
}