#![doc = include_str!("../README.md")]
#![cfg_attr(docsrs, feature(doc_cfg))]
mod config;
#[cfg(any(feature = "jiff", feature = "chrono"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "jiff", feature = "chrono"))))]
mod rfc3339;
mod tools;
pub mod io;
use std::{fmt, marker::PhantomData, mem};
pub use config::McpServerBuilder;
use config::ServerConfig;
#[cfg(any(feature = "jiff", feature = "chrono"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "jiff", feature = "chrono"))))]
pub use rfc3339::Rfc3339;
pub use rust_mcp_schema;
use rust_mcp_schema::{
CallToolRequestParams, ClientCapabilities, INTERNAL_ERROR, INVALID_PARAMS, Implementation,
InitializeRequestParams, InitializeResult, JsonrpcError, JsonrpcMessage, JsonrpcRequestParams,
JsonrpcResponse, LATEST_PROTOCOL_VERSION, ListToolsResult, METHOD_NOT_FOUND, RequestId, Result,
RpcError,
};
#[doc(hidden)]
pub use schemars;
#[doc(hidden)]
pub use serde;
#[doc(hidden)]
pub use serde_json;
use thiserror::Error;
pub use tools::{
IntoToolResponse, NoTools, ToolDef, ToolDefinition, ToolDefinitions, ToolOutput, ToolRegistry,
WithSource,
};
#[derive(Clone, Debug)]
pub struct Client {
pub info: Implementation,
pub capabilities: ClientCapabilities,
}
enum Phase {
WaitingForInitialize,
WaitingForInitialized(Client),
Ready(Client),
}
pub struct McpServer<R: ToolRegistry = NoTools> {
config: ServerConfig,
phase: Phase,
_marker: PhantomData<R>,
}
#[must_use = "message must be sent to client"]
pub struct OutgoingMessage(JsonrpcMessage);
impl OutgoingMessage {
pub fn into_inner(self) -> JsonrpcMessage {
self.0
}
pub fn as_inner(&self) -> &JsonrpcMessage {
&self.0
}
fn empty_response(id: RequestId) -> Self {
let response = JsonrpcResponse::new(id, Default::default());
Self(JsonrpcMessage::Response(response))
}
}
#[must_use = "request must be responded to"]
pub struct Responder {
id: RequestId,
}
impl Responder {
pub fn new(id: RequestId) -> Self {
Self { id }
}
pub fn respond(self, value: impl IntoToolResponse) -> OutgoingMessage {
let call_result = value.into_tool_response();
let json_value =
serde_json::to_value(&call_result).expect("CallToolResult serialization failed");
let extra = json_value.as_object().cloned();
let response = JsonrpcResponse::new(self.id, Result { meta: None, extra });
OutgoingMessage(JsonrpcMessage::Response(response))
}
pub fn rpc_error(self, error: impl Into<JsonRpcError>) -> OutgoingMessage {
let err: JsonRpcError = error.into();
let rpc_error = RpcError {
code: err.code(),
message: err.to_string(),
data: None,
};
let error_msg = JsonrpcError::new(rpc_error, self.id);
OutgoingMessage(JsonrpcMessage::Error(error_msg))
}
}
#[derive(Clone, Debug, Error)]
pub enum JsonRpcError {
#[error("method not found: {msg}")]
MethodNotFound {
msg: String,
},
#[error("invalid params: {msg}")]
InvalidParams {
msg: String,
},
#[error("internal error: {msg}")]
InternalError {
msg: String,
},
}
impl JsonRpcError {
fn code(&self) -> i64 {
match self {
JsonRpcError::MethodNotFound { msg: _ } => METHOD_NOT_FOUND,
JsonRpcError::InvalidParams { msg: _ } => INVALID_PARAMS,
JsonRpcError::InternalError { msg: _ } => INTERNAL_ERROR,
}
}
pub fn into_response(self, id: RequestId) -> OutgoingMessage {
let error = RpcError {
code: self.code(),
message: self.to_string(),
data: None,
};
OutgoingMessage(JsonrpcMessage::Error(JsonrpcError::new(error, id)))
}
}
#[derive(Clone, Debug, Error)]
pub enum ProtocolError {
#[error("unexpected message: expected {expected}, got {got}")]
UnexpectedMessage {
expected: &'static str,
got: String,
},
}
#[must_use = "output must be handled"]
pub enum Output<R: ToolRegistry> {
Send(OutgoingMessage),
ToolCall {
tool: R,
responder: Responder,
},
None,
ProtocolError(ProtocolError),
}
#[derive(Debug, Error)]
#[error("failed to parse JSON-RPC message: {0}")]
pub struct ParseError(#[source] serde_json::Error);
pub fn parse_line(line: &str) -> std::result::Result<JsonrpcMessage, ParseError> {
serde_json::from_str(line).map_err(ParseError)
}
fn parse_params<T: serde::de::DeserializeOwned>(
params: Option<JsonrpcRequestParams>,
) -> std::result::Result<T, serde_json::Error> {
let params_value = params.and_then(|p| p.extra).unwrap_or_default();
serde_json::from_value(serde_json::Value::Object(params_value))
}
impl<R: ToolRegistry> McpServer<R> {
pub fn builder() -> McpServerBuilder<R> {
McpServerBuilder::new()
}
pub fn is_ready(&self) -> bool {
matches!(self.phase, Phase::Ready(_))
}
pub fn client(&self) -> Option<&Client> {
match &self.phase {
Phase::Ready(client) => Some(client),
_ => None,
}
}
fn expectation(&self) -> &'static str {
match self.phase {
Phase::WaitingForInitialize => "initialize",
Phase::WaitingForInitialized(_) => "notifications/initialized",
Phase::Ready(_) => "anything, really",
}
}
pub fn handle(&mut self, msg: JsonrpcMessage) -> Output<R> {
match (&mut self.phase, msg) {
(_, JsonrpcMessage::Request(req)) if req.method == "ping" => {
Output::Send(OutgoingMessage::empty_response(req.id))
}
(Phase::WaitingForInitialized(_), JsonrpcMessage::Notification(notif))
if notif.method == "notifications/initialized" =>
{
let Phase::WaitingForInitialized(client) =
mem::replace(&mut self.phase, Phase::WaitingForInitialize)
else {
unreachable!("already verified phase");
};
self.phase = Phase::Ready(client);
Output::None
}
(Phase::Ready(_), JsonrpcMessage::Notification(notif)) => {
tracing::debug!(method = %notif.method, "ignoring notification");
Output::None
}
(_, JsonrpcMessage::Request(req)) => {
let id = req.id;
self.handle_request(id.clone(), &req.method, req.params)
.unwrap_or_else(|e| Output::Send(e.into_response(id)))
}
(_, msg) => Output::ProtocolError(ProtocolError::UnexpectedMessage {
expected: self.expectation(),
got: describe_message(&msg),
}),
}
}
fn handle_initialize(
&mut self,
id: RequestId,
params: Option<JsonrpcRequestParams>,
) -> std::result::Result<Output<R>, JsonRpcError> {
let params: InitializeRequestParams =
parse_params(params).map_err(|e| JsonRpcError::InvalidParams {
msg: format!("initialize: {e}"),
})?;
let client = Client {
info: params.client_info,
capabilities: params.capabilities,
};
let result = InitializeResult {
protocol_version: LATEST_PROTOCOL_VERSION.into(),
capabilities: self.config.capabilities.clone(),
server_info: self.config.info.clone(),
instructions: self.config.instructions.clone(),
meta: None,
};
self.phase = Phase::WaitingForInitialized(client);
let json_value = serde_json::to_value(result).expect("InitializeResult serialization");
let extra = json_value.as_object().cloned();
let response = JsonrpcResponse::new(id, Result { meta: None, extra });
Ok(Output::Send(OutgoingMessage(JsonrpcMessage::Response(
response,
))))
}
fn handle_tool_call(
&self,
id: RequestId,
params: Option<JsonrpcRequestParams>,
) -> std::result::Result<Output<R>, JsonRpcError> {
let params: CallToolRequestParams =
parse_params(params).map_err(|e| JsonRpcError::InvalidParams {
msg: format!("tools/call: {e}"),
})?;
let arguments = params
.arguments
.map(serde_json::Value::Object)
.unwrap_or(serde_json::Value::Null);
match R::parse(¶ms.name, arguments) {
Ok(tool) => Ok(Output::ToolCall {
tool,
responder: Responder::new(id),
}),
Err(e) => Ok(Output::Send(e.into_response(id))),
}
}
fn handle_tool_list(&self, id: RequestId) -> Output<R> {
let definitions = R::definitions();
let tools: Vec<_> = definitions.into_iter().map(|d| d.into_mcp_tool()).collect();
let result = ListToolsResult {
tools,
meta: None,
next_cursor: None,
};
let json_value = serde_json::to_value(result).expect("ListToolsResult serialization");
let extra = json_value.as_object().cloned();
let response = JsonrpcResponse::new(id, Result { meta: None, extra });
Output::Send(OutgoingMessage(JsonrpcMessage::Response(response)))
}
fn handle_request(
&mut self,
id: RequestId,
method: &str,
params: Option<JsonrpcRequestParams>,
) -> std::result::Result<Output<R>, JsonRpcError> {
match (&mut self.phase, method) {
(Phase::WaitingForInitialize, "initialize") => self.handle_initialize(id, params),
(Phase::Ready(_), "tools/list") if R::ENABLED => Ok(self.handle_tool_list(id)),
(Phase::Ready(_), "tools/call") if R::ENABLED => self.handle_tool_call(id, params),
(Phase::Ready(_), method) => Err(JsonRpcError::MethodNotFound {
msg: method.to_string(),
}),
_ => Ok(Output::ProtocolError(ProtocolError::UnexpectedMessage {
expected: self.expectation(),
got: format!("request:{method}"),
})),
}
}
}
impl<R: ToolRegistry> fmt::Display for McpServer<R> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "# {}", self.config.info.name)?;
writeln!(f)?;
writeln!(f, "Version: {}", self.config.info.version)?;
if let Some(instructions) = &self.config.instructions {
writeln!(f)?;
writeln!(f, "## Instructions")?;
writeln!(f)?;
writeln!(f, "{instructions}")?;
}
let defs = R::definitions();
if !defs.is_empty() {
writeln!(f)?;
write!(f, "{defs}")?;
}
Ok(())
}
}
fn describe_message(msg: &JsonrpcMessage) -> String {
match msg {
JsonrpcMessage::Request(req) => format!("request:{}", req.method),
JsonrpcMessage::Response(_) => "response".into(),
JsonrpcMessage::Notification(notif) => format!("notification:{}", notif.method),
JsonrpcMessage::Error(_) => "error".into(),
}
}
#[cfg(test)]
mod tests {
use rust_mcp_schema::{
JsonrpcMessage, JsonrpcNotification, JsonrpcRequest, JsonrpcRequestParams, RequestId,
};
use crate::{McpServer, NoTools, Output};
fn test_server() -> McpServer<NoTools> {
McpServer::<NoTools>::builder()
.name("test")
.version("1.0")
.build()
}
fn initialize_params() -> JsonrpcRequestParams {
let extra: serde_json::Map<String, serde_json::Value> = serde_json::from_str(
r#"{
"protocolVersion": "2025-06-18",
"capabilities": {},
"clientInfo": { "name": "test-client", "version": "1.0" }
}"#,
)
.expect("valid JSON");
JsonrpcRequestParams {
meta: None,
extra: Some(extra),
}
}
#[test]
fn ping_during_init() {
let mut server = test_server();
let ping = JsonrpcMessage::Request(JsonrpcRequest::new(
RequestId::String("1".into()),
"ping".into(),
None,
));
let output = server.handle(ping);
assert!(matches!(output, Output::Send(_)));
assert!(!server.is_ready());
}
#[test]
fn full_initialization() {
let mut server = test_server();
let init_req = JsonrpcMessage::Request(JsonrpcRequest::new(
RequestId::String("1".into()),
"initialize".into(),
Some(initialize_params()),
));
let output = server.handle(init_req);
assert!(matches!(output, Output::Send(_)));
assert!(!server.is_ready());
let initialized = JsonrpcMessage::Notification(JsonrpcNotification::new(
"notifications/initialized".into(),
None,
));
let output = server.handle(initialized);
assert!(matches!(output, Output::None));
assert!(server.is_ready());
}
#[test]
fn tool_list_returns_error_for_no_tools() {
let mut server = test_server();
initialize_server(&mut server);
let list_req = JsonrpcMessage::Request(JsonrpcRequest::new(
RequestId::String("2".into()),
"tools/list".into(),
None,
));
let output = server.handle(list_req);
match output {
Output::Send(msg) => {
assert!(matches!(msg.as_inner(), JsonrpcMessage::Error(_)));
}
_ => panic!("expected Send with error"),
}
}
#[test]
fn tool_call_returns_error_for_no_tools() {
let mut server = test_server();
initialize_server(&mut server);
let call_params: serde_json::Map<String, serde_json::Value> =
serde_json::from_str(r#"{ "name": "test_tool", "arguments": { "arg1": "value1" } }"#)
.expect("valid JSON");
let call_req = JsonrpcMessage::Request(JsonrpcRequest::new(
RequestId::String("3".into()),
"tools/call".into(),
Some(JsonrpcRequestParams {
meta: None,
extra: Some(call_params),
}),
));
let output = server.handle(call_req);
match output {
Output::Send(msg) => {
assert!(matches!(msg.as_inner(), JsonrpcMessage::Error(_)));
}
_ => panic!("expected Send with error"),
}
}
fn initialize_server(server: &mut McpServer<NoTools>) {
let init_req = JsonrpcMessage::Request(JsonrpcRequest::new(
RequestId::String("init".into()),
"initialize".into(),
Some(initialize_params()),
));
let _ = server.handle(init_req);
let initialized = JsonrpcMessage::Notification(JsonrpcNotification::new(
"notifications/initialized".into(),
None,
));
let _ = server.handle(initialized);
}
}