use serde::Deserialize;
use serde::Serialize;
use serde_json::Value;
use std::borrow::Cow;
use crate::error::ErrorCode;
use crate::error::ToolError;
use ::schemars::JsonSchema;
use std::any::TypeId;
use std::collections::HashMap;
use std::sync::Arc;
use std::sync::Mutex;
pub use rmcp;
pub use rmcp::*;
pub use rmcp::ServerHandler;
pub use rmcp::model::CallToolRequestParam;
pub use rmcp::model::CallToolResult;
pub use rmcp::model::CompleteRequestParam;
pub use rmcp::model::CompleteResult;
pub use rmcp::model::Content;
pub use rmcp::model::ErrorData;
pub use rmcp::model::GetPromptRequestParam;
pub use rmcp::model::GetPromptResult;
pub use rmcp::model::Implementation;
pub use rmcp::model::InitializeRequestParam;
pub use rmcp::model::InitializeResult;
pub use rmcp::model::ListPromptsResult;
pub use rmcp::model::ListResourceTemplatesResult;
pub use rmcp::model::ListResourcesResult;
pub use rmcp::model::ListToolsResult;
pub use rmcp::model::PaginatedRequestParam;
pub use rmcp::model::ReadResourceRequestParam;
pub use rmcp::model::ReadResourceResult;
pub use rmcp::model::ServerCapabilities;
pub use rmcp::model::SetLevelRequestParam;
pub use rmcp::model::SubscribeRequestParam;
pub use rmcp::model::Tool;
pub use rmcp::model::UnsubscribeRequestParam;
pub use rmcp::service::RequestContext;
pub use rmcp::service::RoleServer;
pub use rmcp::service::Service;
pub use rmcp::service::ServiceExt;
pub use rmcp::ErrorData as McpError;
pub use rmcp::transport::stdio;
pub use serde_json::Value as JsonValue;
pub use serde_json::json;
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct McpErrorData {
pub code: i32,
pub message: Cow<'static, str>,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<Value>,
}
pub mod error_codes {
pub const RESOURCE_NOT_FOUND: i32 = -32002;
pub const INVALID_REQUEST: i32 = -32600;
pub const METHOD_NOT_FOUND: i32 = -32601;
pub const INVALID_PARAMS: i32 = -32602;
pub const INTERNAL_ERROR: i32 = -32603;
pub const PARSE_ERROR: i32 = -32700;
}
impl From<ToolError> for McpErrorData {
fn from(err: ToolError) -> Self {
let (code, message) = match err.code {
ErrorCode::BadRequest => (error_codes::INVALID_REQUEST, err.message),
ErrorCode::InvalidArgument => (error_codes::INVALID_PARAMS, err.message),
ErrorCode::NotFound => (error_codes::RESOURCE_NOT_FOUND, err.message),
ErrorCode::PermissionDenied => (error_codes::INVALID_REQUEST, err.message),
ErrorCode::Internal => (error_codes::INTERNAL_ERROR, err.message),
ErrorCode::Timeout => (error_codes::INTERNAL_ERROR, err.message),
ErrorCode::Conflict => (error_codes::INVALID_REQUEST, err.message),
ErrorCode::NetworkError => (error_codes::INTERNAL_ERROR, err.message),
ErrorCode::ExternalServiceError => (error_codes::INTERNAL_ERROR, err.message),
ErrorCode::ExecutionFailed => (error_codes::INTERNAL_ERROR, err.message),
ErrorCode::SerializationError => (error_codes::PARSE_ERROR, err.message),
ErrorCode::IoError => (error_codes::INTERNAL_ERROR, err.message),
};
McpErrorData {
code,
message: Cow::Owned(message),
data: err.details.map(|d| Value::Object(d.into_iter().collect())),
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Hash, Eq)]
#[serde(transparent)]
pub struct ProgressToken(pub NumberOrString);
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct ProgressNotification {
pub progress_token: ProgressToken,
pub progress: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub total: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub message: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct ToolAnnotations {
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub read_only_hint: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub destructive_hint: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub idempotent_hint: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub open_world_hint: Option<bool>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct ToolMetadata {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
pub input_schema: Value,
#[serde(skip_serializing_if = "Option::is_none")]
pub annotations: Option<ToolAnnotations>,
}
#[async_trait::async_trait]
pub trait ProgressReporter: Send + Sync {
async fn report(
&self,
progress: u32,
total: Option<u32>,
message: Option<String>,
) -> Result<(), ToolError>;
}
pub struct McpProgressReporter {
progress_token: ProgressToken,
sender: Option<tokio::sync::mpsc::Sender<ProgressNotification>>,
}
impl McpProgressReporter {
pub fn new(progress_token: ProgressToken) -> Self {
Self {
progress_token,
sender: None,
}
}
pub fn with_sender(
progress_token: ProgressToken,
sender: tokio::sync::mpsc::Sender<ProgressNotification>,
) -> Self {
Self {
progress_token,
sender: Some(sender),
}
}
}
#[async_trait::async_trait]
impl ProgressReporter for McpProgressReporter {
async fn report(
&self,
current: u32,
total: Option<u32>,
message: Option<String>,
) -> Result<(), ToolError> {
let notification = ProgressNotification {
progress_token: self.progress_token.clone(),
progress: current,
total,
message,
};
if let Some(sender) = &self.sender {
sender.send(notification).await.map_err(|e| {
ToolError::new(
ErrorCode::Internal,
format!("Failed to send progress notification: {e}"),
)
})?;
}
Ok(())
}
}
pub use tokio_util::sync::CancellationToken;
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Hash, Eq)]
#[serde(untagged)]
pub enum NumberOrString {
Number(i64),
String(String),
}
impl From<i64> for NumberOrString {
fn from(n: i64) -> Self {
NumberOrString::Number(n)
}
}
impl From<String> for NumberOrString {
fn from(s: String) -> Self {
NumberOrString::String(s)
}
}
impl From<&str> for NumberOrString {
fn from(s: &str) -> Self {
NumberOrString::String(s.to_string())
}
}
thread_local! {
static SCHEMA_CACHE: Arc<Mutex<HashMap<TypeId, Arc<Value>>>> = Arc::new(Mutex::new(HashMap::new()));
}
pub fn generate_schema<T: JsonSchema + 'static>() -> Arc<Value> {
let type_id = TypeId::of::<T>();
SCHEMA_CACHE.with(|cache| {
let mut cache_guard = cache.lock().unwrap();
if let Some(schema) = cache_guard.get(&type_id) {
return schema.clone();
}
let mut settings = ::schemars::r#gen::SchemaSettings::draft07();
settings.inline_subschemas = true;
let generator = settings.into_generator();
let schema = generator.into_root_schema_for::<T>();
let schema_value = serde_json::to_value(schema).unwrap_or(Value::Null);
let arc_schema = Arc::new(schema_value);
cache_guard.insert(type_id, arc_schema.clone());
arc_schema
})
}
pub fn extract_parameter_schema<T: JsonSchema + 'static>() -> Value {
let schema = generate_schema::<T>();
if let Some(obj) = schema.as_object() {
let mut clean_schema = obj.clone();
clean_schema.remove("$schema");
clean_schema.remove("title");
Value::Object(clean_schema)
} else {
(*schema).clone()
}
}
pub trait McpFormatter {
fn mcp_format_text(&self) -> String;
}
pub enum McpOutput {
Text(String),
Json(Value),
}
impl From<String> for McpOutput {
fn from(s: String) -> Self {
McpOutput::Text(s)
}
}
impl From<Value> for McpOutput {
fn from(v: Value) -> Self {
McpOutput::Json(v)
}
}
pub trait IntoCallToolResult {
fn into_call_tool_result(self) -> CallToolResult;
}
impl IntoCallToolResult for McpOutput {
fn into_call_tool_result(self) -> CallToolResult {
match self {
McpOutput::Text(text) => CallToolResult::success(vec![Content::text(text)]),
McpOutput::Json(value) => {
let pretty =
serde_json::to_string_pretty(&value).unwrap_or_else(|_| "{}".to_string());
CallToolResult::success(vec![Content::text(pretty)])
}
}
}
}
impl<T: serde::Serialize> IntoCallToolResult for T {
fn into_call_tool_result(self) -> CallToolResult {
match serde_json::to_value(&self) {
Ok(value) => CallToolResult::success(vec![Content::text(
serde_json::to_string_pretty(&value).unwrap_or_else(|_| "{}".to_string()),
)]),
Err(e) => CallToolResult::error(vec![Content::text(format!(
"Failed to serialize result: {e}"
))]),
}
}
}
pub fn convert_tool_definitions(tool_jsons: Vec<JsonValue>) -> Vec<Tool> {
use rmcp::model::ToolAnnotations;
use std::sync::Arc;
tool_jsons
.into_iter()
.filter_map(|json| {
let name = json.get("name")?.as_str()?.to_string();
let description = json
.get("description")
.and_then(|d| d.as_str())
.map(|s| s.to_string());
let input_schema = if let Some(schema_value) = json.get("inputSchema") {
schema_value
.as_object()
.map(|schema_obj| Arc::new(schema_obj.clone()))
} else {
None
};
let annotations = json
.get("annotations")
.map(|_hints| ToolAnnotations::default());
Some(Tool {
name: name.clone().into(),
title: name.into(),
description: description.map(|s| s.into()),
input_schema: input_schema.unwrap_or_else(|| Arc::new(serde_json::Map::new())),
annotations,
output_schema: None,
icons: None,
})
})
.collect()
}
#[macro_export]
macro_rules! implement_mcp_server {
($struct_name:ident, $tools_field:ident) => {
impl $crate::mcp::ServerHandler for $struct_name {
fn initialize(
&self,
_params: $crate::mcp::InitializeRequestParam,
_context: $crate::mcp::RequestContext<$crate::mcp::RoleServer>,
) -> impl ::std::future::Future<
Output = Result<$crate::mcp::InitializeResult, $crate::mcp::McpError>,
> + Send
+ '_ {
async move {
Ok($crate::mcp::InitializeResult {
server_info: $crate::mcp::Implementation {
name: stringify!($struct_name).to_string().into(),
title: env!("CARGO_PKG_NAME").to_string().into(),
version: env!("CARGO_PKG_VERSION").to_string().into(),
website_url: None,
icons: None,
},
capabilities: $crate::mcp::ServerCapabilities::builder()
.enable_tools()
.build(),
..Default::default()
})
}
}
fn list_tools(
&self,
_request: Option<$crate::mcp::PaginatedRequestParam>,
_context: $crate::mcp::RequestContext<$crate::mcp::RoleServer>,
) -> impl ::std::future::Future<
Output = Result<$crate::mcp::ListToolsResult, $crate::mcp::McpError>,
> + Send
+ '_ {
async move {
let tools =
$crate::mcp::convert_tool_definitions(self.$tools_field.get_mcp_tools());
Ok($crate::mcp::ListToolsResult::with_all_items(tools))
}
}
fn call_tool(
&self,
request: $crate::mcp::CallToolRequestParam,
_context: $crate::mcp::RequestContext<$crate::mcp::RoleServer>,
) -> impl ::std::future::Future<
Output = Result<$crate::mcp::CallToolResult, $crate::mcp::McpError>,
> + Send
+ '_ {
async move {
match self
.$tools_field
.handle_mcp_call_mcp(
&request.name,
$crate::mcp::JsonValue::Object(request.arguments.unwrap_or_default()),
)
.await
{
Ok(result) => Ok($crate::mcp::IntoCallToolResult::into_call_tool_result(
result,
)),
Err(e) => Ok($crate::mcp::CallToolResult::error(vec![
$crate::mcp::Content::text(format!("Error: {}", e)),
])),
}
}
}
fn ping(
&self,
_context: $crate::mcp::RequestContext<$crate::mcp::RoleServer>,
) -> impl ::std::future::Future<Output = Result<(), $crate::mcp::McpError>> + Send + '_
{
async move { Ok(()) }
}
fn complete(
&self,
_request: $crate::mcp::CompleteRequestParam,
_context: $crate::mcp::RequestContext<$crate::mcp::RoleServer>,
) -> impl ::std::future::Future<
Output = Result<$crate::mcp::CompleteResult, $crate::mcp::McpError>,
> + Send
+ '_ {
async move {
Err($crate::mcp::McpError::invalid_request(
"Method not implemented",
None,
))
}
}
fn set_level(
&self,
_request: $crate::mcp::SetLevelRequestParam,
_context: $crate::mcp::RequestContext<$crate::mcp::RoleServer>,
) -> impl ::std::future::Future<Output = Result<(), $crate::mcp::McpError>> + Send + '_
{
async move { Ok(()) }
}
fn get_prompt(
&self,
_request: $crate::mcp::GetPromptRequestParam,
_context: $crate::mcp::RequestContext<$crate::mcp::RoleServer>,
) -> impl ::std::future::Future<
Output = Result<$crate::mcp::GetPromptResult, $crate::mcp::McpError>,
> + Send
+ '_ {
async move {
Err($crate::mcp::McpError::invalid_request(
"Method not implemented",
None,
))
}
}
fn list_prompts(
&self,
_request: Option<$crate::mcp::PaginatedRequestParam>,
_context: $crate::mcp::RequestContext<$crate::mcp::RoleServer>,
) -> impl ::std::future::Future<
Output = Result<$crate::mcp::ListPromptsResult, $crate::mcp::McpError>,
> + Send
+ '_ {
async move {
Ok($crate::mcp::ListPromptsResult {
prompts: vec![],
next_cursor: None,
})
}
}
fn list_resources(
&self,
_request: Option<$crate::mcp::PaginatedRequestParam>,
_context: $crate::mcp::RequestContext<$crate::mcp::RoleServer>,
) -> impl ::std::future::Future<
Output = Result<$crate::mcp::ListResourcesResult, $crate::mcp::McpError>,
> + Send
+ '_ {
async move {
Ok($crate::mcp::ListResourcesResult {
resources: vec![],
next_cursor: None,
})
}
}
fn list_resource_templates(
&self,
_request: Option<$crate::mcp::PaginatedRequestParam>,
_context: $crate::mcp::RequestContext<$crate::mcp::RoleServer>,
) -> impl ::std::future::Future<
Output = Result<$crate::mcp::ListResourceTemplatesResult, $crate::mcp::McpError>,
> + Send
+ '_ {
async move {
Ok($crate::mcp::ListResourceTemplatesResult {
resource_templates: vec![],
next_cursor: None,
})
}
}
fn read_resource(
&self,
_request: $crate::mcp::ReadResourceRequestParam,
_context: $crate::mcp::RequestContext<$crate::mcp::RoleServer>,
) -> impl ::std::future::Future<
Output = Result<$crate::mcp::ReadResourceResult, $crate::mcp::McpError>,
> + Send
+ '_ {
async move {
Err($crate::mcp::McpError::invalid_request(
"Method not implemented",
None,
))
}
}
fn subscribe(
&self,
_request: $crate::mcp::SubscribeRequestParam,
_context: $crate::mcp::RequestContext<$crate::mcp::RoleServer>,
) -> impl ::std::future::Future<Output = Result<(), $crate::mcp::McpError>> + Send + '_
{
async move {
Err($crate::mcp::McpError::invalid_request(
"Method not implemented",
None,
))
}
}
fn unsubscribe(
&self,
_request: $crate::mcp::UnsubscribeRequestParam,
_context: $crate::mcp::RequestContext<$crate::mcp::RoleServer>,
) -> impl ::std::future::Future<Output = Result<(), $crate::mcp::McpError>> + Send + '_
{
async move {
Err($crate::mcp::McpError::invalid_request(
"Method not implemented",
None,
))
}
}
}
};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn mcp_output_text_to_result() {
let r = McpOutput::Text("hello".into()).into_call_tool_result();
assert_eq!(r.content.len(), 1);
let content_str = format!("{:?}", r.content[0]);
assert!(content_str.contains("hello"));
}
#[test]
fn mcp_output_json_to_result() {
let val = serde_json::json!({"k": "v"});
let r = McpOutput::Json(val).into_call_tool_result();
let content_str = format!("{:?}", r.content[0]);
assert!(content_str.contains("k"));
assert!(content_str.contains("v"));
}
}