use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::fmt;
pub use crate::MCP_PROTOCOL_VERSION;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsonRpcRequest {
pub jsonrpc: String,
pub id: JsonRpcId,
pub method: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub params: Option<Value>,
}
impl JsonRpcRequest {
pub fn new(id: JsonRpcId, method: impl Into<String>, params: Option<Value>) -> Self {
Self {
jsonrpc: "2.0".to_string(),
id,
method: method.into(),
params,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsonRpcResponse {
pub jsonrpc: String,
pub id: JsonRpcId,
#[serde(flatten)]
pub payload: JsonRpcPayload,
}
impl JsonRpcResponse {
pub fn success(id: impl Into<JsonRpcId>, result: Value) -> Self {
Self {
jsonrpc: "2.0".to_string(),
id: id.into(),
payload: JsonRpcPayload::Success { result },
}
}
pub fn error(
id: impl Into<JsonRpcId>,
code: i32,
message: impl Into<String>,
data: Option<Value>,
) -> Self {
Self {
jsonrpc: "2.0".to_string(),
id: id.into(),
payload: JsonRpcPayload::Error {
error: JsonRpcError {
code,
message: message.into(),
data,
},
},
}
}
pub fn result(&self) -> Option<&Value> {
match &self.payload {
JsonRpcPayload::Success { result } => Some(result),
JsonRpcPayload::Error { .. } => None,
}
}
pub fn is_success(&self) -> bool {
matches!(&self.payload, JsonRpcPayload::Success { .. })
}
pub fn is_error(&self) -> bool {
matches!(&self.payload, JsonRpcPayload::Error { .. })
}
pub fn error_info(&self) -> Option<&JsonRpcError> {
match &self.payload {
JsonRpcPayload::Success { .. } => None,
JsonRpcPayload::Error { error } => Some(error),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsonRpcNotification {
pub jsonrpc: String,
pub method: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub params: Option<Value>,
}
impl JsonRpcNotification {
pub fn new(method: impl Into<String>, params: Option<Value>) -> Self {
Self {
jsonrpc: "2.0".to_string(),
method: method.into(),
params,
}
}
}
#[derive(Debug, Clone)]
pub enum ServerOutbound {
Request(JsonRpcRequest),
Notification(JsonRpcNotification),
Response(JsonRpcResponse),
}
impl ServerOutbound {
pub fn to_json(&self) -> Result<String, serde_json::Error> {
match self {
ServerOutbound::Request(r) => serde_json::to_string(r),
ServerOutbound::Notification(n) => serde_json::to_string(n),
ServerOutbound::Response(r) => serde_json::to_string(r),
}
}
}
#[derive(Debug, Clone)]
pub enum ClientInbound {
Request(JsonRpcRequest),
Response(JsonRpcResponse),
Notification(JsonRpcNotification),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum JsonRpcMessage {
Request(JsonRpcRequest),
Response(JsonRpcResponse),
Notification(JsonRpcNotification),
}
impl JsonRpcMessage {
pub fn parse(s: &str) -> Result<Self, serde_json::Error> {
serde_json::from_str(s)
}
pub fn into_client_inbound(self) -> ClientInbound {
match self {
JsonRpcMessage::Request(r) => ClientInbound::Request(r),
JsonRpcMessage::Response(r) => ClientInbound::Response(r),
JsonRpcMessage::Notification(n) => ClientInbound::Notification(n),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum JsonRpcPayload {
Success { result: Value },
Error { error: JsonRpcError },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsonRpcError {
pub code: i32,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<Value>,
}
impl fmt::Display for JsonRpcError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "JSON-RPC Error {}: {}", self.code, self.message)
}
}
impl std::error::Error for JsonRpcError {}
pub mod error_codes {
pub const PARSE_ERROR: i32 = -32700;
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 URL_ELICITATION_REQUIRED: i32 = -32042;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Icon {
pub src: String,
#[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
pub mime_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sizes: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub theme: Option<IconTheme>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum IconTheme {
Light,
Dark,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BaseMetadata {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Annotations {
#[serde(skip_serializing_if = "Option::is_none")]
pub audience: Option<Vec<Role>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub priority: Option<f64>,
#[serde(rename = "lastModified", skip_serializing_if = "Option::is_none")]
pub last_modified: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum Role {
User,
Assistant,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Implementation {
pub name: String,
pub version: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub icons: Option<Vec<Icon>>,
#[serde(rename = "websiteUrl", skip_serializing_if = "Option::is_none")]
pub website_url: Option<String>,
}
pub type McpServerInfo = Implementation;
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct McpCapabilities {
#[serde(skip_serializing_if = "Option::is_none")]
pub tools: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub resources: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub prompts: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub logging: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub completions: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub elicitation: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tasks: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub experimental: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(untagged)]
pub enum JsonRpcId {
String(String),
Number(i64),
Null,
}
impl From<i64> for JsonRpcId {
fn from(n: i64) -> Self {
JsonRpcId::Number(n)
}
}
impl From<String> for JsonRpcId {
fn from(s: String) -> Self {
JsonRpcId::String(s)
}
}
impl From<&str> for JsonRpcId {
fn from(s: &str) -> Self {
JsonRpcId::String(s.to_string())
}
}
impl From<Value> for JsonRpcId {
fn from(v: Value) -> Self {
match v {
Value::String(s) => JsonRpcId::String(s),
Value::Number(n) => JsonRpcId::Number(n.as_i64().unwrap_or(0)),
Value::Null => JsonRpcId::Null,
_ => JsonRpcId::Null,
}
}
}
impl From<JsonRpcId> for Value {
fn from(id: JsonRpcId) -> Self {
match id {
JsonRpcId::String(s) => Value::String(s),
JsonRpcId::Number(n) => Value::Number(n.into()),
JsonRpcId::Null => Value::Null,
}
}
}
impl fmt::Display for JsonRpcId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
JsonRpcId::String(s) => write!(f, "{}", s),
JsonRpcId::Number(n) => write!(f, "{}", n),
JsonRpcId::Null => write!(f, "null"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ToolAnnotations {
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(rename = "readOnlyHint", skip_serializing_if = "Option::is_none")]
pub read_only_hint: Option<bool>,
#[serde(rename = "destructiveHint", skip_serializing_if = "Option::is_none")]
pub destructive_hint: Option<bool>,
#[serde(rename = "idempotentHint", skip_serializing_if = "Option::is_none")]
pub idempotent_hint: Option<bool>,
#[serde(rename = "openWorldHint", skip_serializing_if = "Option::is_none")]
pub open_world_hint: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
#[serde(rename_all = "snake_case")]
pub enum TaskSupport {
#[default]
Forbidden,
Optional,
Required,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ToolExecution {
#[serde(rename = "taskSupport", skip_serializing_if = "Option::is_none")]
pub task_support: Option<TaskSupport>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpToolDefinition {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub group: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub icons: Option<Vec<Icon>>,
#[serde(rename = "inputSchema", default = "default_input_schema")]
pub input_schema: Value,
#[serde(rename = "outputSchema", skip_serializing_if = "Option::is_none")]
pub output_schema: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub execution: Option<ToolExecution>,
#[serde(skip_serializing_if = "Option::is_none")]
pub annotations: Option<ToolAnnotations>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<Value>,
}
fn default_input_schema() -> Value {
serde_json::json!({"type": "object"})
}
impl McpToolDefinition {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
title: None,
description: None,
group: None,
icons: None,
input_schema: default_input_schema(),
output_schema: None,
execution: None,
annotations: None,
meta: None,
}
}
pub fn with_title(mut self, title: impl Into<String>) -> Self {
self.title = Some(title.into());
self
}
pub fn with_group(mut self, group: impl Into<String>) -> Self {
self.group = Some(group.into());
self
}
pub fn with_description(mut self, desc: impl Into<String>) -> Self {
self.description = Some(desc.into());
self
}
pub fn with_schema(mut self, schema: Value) -> Self {
self.input_schema = schema;
self
}
pub fn with_output_schema(mut self, schema: Value) -> Self {
self.output_schema = Some(schema);
self
}
pub fn with_annotations(mut self, annotations: ToolAnnotations) -> Self {
self.annotations = Some(annotations);
self
}
pub fn with_execution(mut self, execution: ToolExecution) -> Self {
self.execution = Some(execution);
self
}
pub fn description_or_default(&self) -> &str {
self.description.as_deref().unwrap_or("")
}
pub fn parameters(&self) -> Value {
self.input_schema.clone()
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ToolInputSchema {
#[serde(rename = "type")]
pub schema_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub properties: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub required: Option<Vec<String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ListToolsParams {
#[serde(skip_serializing_if = "Option::is_none")]
pub cursor: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ListToolsResult {
pub tools: Vec<McpToolDefinition>,
#[serde(skip_serializing_if = "Option::is_none")]
pub next_cursor: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CallToolParams {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub arguments: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub task: Option<TaskMetadata>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CallToolResult {
#[serde(default)]
pub content: Vec<ToolContent>,
#[serde(rename = "structuredContent", skip_serializing_if = "Option::is_none")]
pub structured_content: Option<Value>,
#[serde(rename = "isError", skip_serializing_if = "Option::is_none")]
pub is_error: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum ToolContent {
#[serde(rename = "text")]
Text {
text: String,
#[serde(skip_serializing_if = "Option::is_none")]
annotations: Option<Annotations>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
meta: Option<Value>,
},
#[serde(rename = "image")]
Image {
data: String,
#[serde(rename = "mimeType")]
mime_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
annotations: Option<Annotations>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
meta: Option<Value>,
},
#[serde(rename = "audio")]
Audio {
data: String,
#[serde(rename = "mimeType")]
mime_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
annotations: Option<Annotations>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
meta: Option<Value>,
},
#[serde(rename = "resource")]
Resource {
uri: String,
#[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
mime_type: Option<String>,
},
#[serde(rename = "resource_link")]
ResourceLink {
uri: String,
name: String,
#[serde(skip_serializing_if = "Option::is_none")]
title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
description: Option<String>,
#[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
mime_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
annotations: Option<Annotations>,
},
}
impl ToolContent {
pub fn text(text: impl Into<String>) -> Self {
Self::Text {
text: text.into(),
annotations: None,
meta: None,
}
}
pub fn image(data: impl Into<String>, mime_type: impl Into<String>) -> Self {
Self::Image {
data: data.into(),
mime_type: mime_type.into(),
annotations: None,
meta: None,
}
}
pub fn audio(data: impl Into<String>, mime_type: impl Into<String>) -> Self {
Self::Audio {
data: data.into(),
mime_type: mime_type.into(),
annotations: None,
meta: None,
}
}
pub fn resource(uri: impl Into<String>, mime_type: Option<String>) -> Self {
Self::Resource {
uri: uri.into(),
mime_type,
}
}
pub fn as_text(&self) -> Option<&str> {
match self {
Self::Text { text, .. } => Some(text),
_ => None,
}
}
pub fn is_text(&self) -> bool {
matches!(self, Self::Text { .. })
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum TaskStatus {
Working,
InputRequired,
Completed,
Failed,
Cancelled,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct TaskMetadata {
#[serde(skip_serializing_if = "Option::is_none")]
pub ttl: Option<u64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RelatedTaskMetadata {
#[serde(rename = "taskId")]
pub task_id: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Task {
#[serde(rename = "taskId")]
pub task_id: String,
pub status: TaskStatus,
#[serde(rename = "statusMessage", skip_serializing_if = "Option::is_none")]
pub status_message: Option<String>,
#[serde(rename = "createdAt")]
pub created_at: String,
#[serde(rename = "lastUpdatedAt")]
pub last_updated_at: String,
pub ttl: Option<u64>,
#[serde(rename = "pollInterval", skip_serializing_if = "Option::is_none")]
pub poll_interval: Option<u64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreateTaskResult {
pub task: Task,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetTaskParams {
#[serde(rename = "taskId")]
pub task_id: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetTaskPayloadParams {
#[serde(rename = "taskId")]
pub task_id: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CancelTaskParams {
#[serde(rename = "taskId")]
pub task_id: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ListTasksParams {
#[serde(skip_serializing_if = "Option::is_none")]
pub cursor: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ListTasksResult {
pub tasks: Vec<Task>,
#[serde(rename = "nextCursor", skip_serializing_if = "Option::is_none")]
pub next_cursor: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TaskStatusNotificationParams {
#[serde(rename = "taskId")]
pub task_id: String,
pub status: TaskStatus,
#[serde(rename = "statusMessage", skip_serializing_if = "Option::is_none")]
pub status_message: Option<String>,
#[serde(rename = "createdAt")]
pub created_at: String,
#[serde(rename = "lastUpdatedAt")]
pub last_updated_at: String,
pub ttl: Option<u64>,
#[serde(rename = "pollInterval", skip_serializing_if = "Option::is_none")]
pub poll_interval: Option<u64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ElicitRequestFormParams {
#[serde(skip_serializing_if = "Option::is_none")]
pub mode: Option<String>,
pub message: String,
#[serde(rename = "requestedSchema")]
pub requested_schema: Value,
#[serde(skip_serializing_if = "Option::is_none")]
pub task: Option<TaskMetadata>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ElicitRequestUrlParams {
pub mode: String,
pub message: String,
#[serde(rename = "elicitationId")]
pub elicitation_id: String,
pub url: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub task: Option<TaskMetadata>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ElicitRequestParams {
Form(ElicitRequestFormParams),
Url(ElicitRequestUrlParams),
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ElicitAction {
Accept,
Decline,
Cancel,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ElicitResult {
pub action: ElicitAction,
#[serde(skip_serializing_if = "Option::is_none")]
pub content: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ElicitationCompleteParams {
#[serde(rename = "elicitationId")]
pub elicitation_id: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StringSchema {
#[serde(rename = "type")]
pub schema_type: String, #[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(rename = "minLength", skip_serializing_if = "Option::is_none")]
pub min_length: Option<u32>,
#[serde(rename = "maxLength", skip_serializing_if = "Option::is_none")]
pub max_length: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub format: Option<StringSchemaFormat>,
#[serde(skip_serializing_if = "Option::is_none")]
pub default: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub enum StringSchemaFormat {
Email,
Uri,
Date,
DateTime,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NumberSchema {
#[serde(rename = "type")]
pub schema_type: String, #[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub minimum: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub maximum: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub default: Option<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BooleanSchema {
#[serde(rename = "type")]
pub schema_type: String, #[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub default: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TitledEnumOption {
#[serde(rename = "const")]
pub const_value: String,
pub title: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MultiSelectEnumItems {
#[serde(rename = "type")]
pub schema_type: String, #[serde(rename = "enum")]
pub enum_values: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TitledMultiSelectEnumItems {
#[serde(rename = "anyOf")]
pub any_of: Vec<TitledEnumOption>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
#[serde(rename_all = "snake_case")]
pub enum ToolChoiceMode {
#[default]
Auto,
Required,
None,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ToolChoice {
#[serde(skip_serializing_if = "Option::is_none")]
pub mode: Option<ToolChoiceMode>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolUseContent {
pub id: String,
pub name: String,
pub input: Value,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolResultContent {
#[serde(rename = "toolUseId")]
pub tool_use_id: String,
pub content: Vec<ToolContent>,
#[serde(rename = "structuredContent", skip_serializing_if = "Option::is_none")]
pub structured_content: Option<Value>,
#[serde(rename = "isError", skip_serializing_if = "Option::is_none")]
pub is_error: Option<bool>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum SamplingContent {
#[serde(rename = "text")]
Text {
text: String,
#[serde(skip_serializing_if = "Option::is_none")]
annotations: Option<Annotations>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
meta: Option<Value>,
},
#[serde(rename = "image")]
Image {
data: String,
#[serde(rename = "mimeType")]
mime_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
annotations: Option<Annotations>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
meta: Option<Value>,
},
#[serde(rename = "audio")]
Audio {
data: String,
#[serde(rename = "mimeType")]
mime_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
annotations: Option<Annotations>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
meta: Option<Value>,
},
#[serde(rename = "tool_use")]
ToolUse {
id: String,
name: String,
input: Value,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
meta: Option<Value>,
},
#[serde(rename = "tool_result")]
ToolResult {
#[serde(rename = "toolUseId")]
tool_use_id: String,
content: Vec<ToolContent>,
#[serde(rename = "structuredContent", skip_serializing_if = "Option::is_none")]
structured_content: Option<Value>,
#[serde(rename = "isError", skip_serializing_if = "Option::is_none")]
is_error: Option<bool>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
meta: Option<Value>,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SamplingMessage {
pub role: Role,
pub content: Vec<SamplingContent>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ModelHint {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ModelPreferences {
#[serde(skip_serializing_if = "Option::is_none")]
pub hints: Option<Vec<ModelHint>>,
#[serde(rename = "costPriority", skip_serializing_if = "Option::is_none")]
pub cost_priority: Option<f64>,
#[serde(rename = "speedPriority", skip_serializing_if = "Option::is_none")]
pub speed_priority: Option<f64>,
#[serde(
rename = "intelligencePriority",
skip_serializing_if = "Option::is_none"
)]
pub intelligence_priority: Option<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreateMessageParams {
pub messages: Vec<SamplingMessage>,
#[serde(rename = "modelPreferences", skip_serializing_if = "Option::is_none")]
pub model_preferences: Option<ModelPreferences>,
#[serde(rename = "systemPrompt", skip_serializing_if = "Option::is_none")]
pub system_prompt: Option<String>,
#[serde(rename = "includeContext", skip_serializing_if = "Option::is_none")]
pub include_context: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub temperature: Option<f64>,
#[serde(rename = "maxTokens")]
pub max_tokens: u32,
#[serde(rename = "stopSequences", skip_serializing_if = "Option::is_none")]
pub stop_sequences: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tools: Option<Vec<McpToolDefinition>>,
#[serde(rename = "toolChoice", skip_serializing_if = "Option::is_none")]
pub tool_choice: Option<ToolChoice>,
#[serde(skip_serializing_if = "Option::is_none")]
pub task: Option<TaskMetadata>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub enum StopReason {
EndTurn,
StopSequence,
MaxTokens,
ToolUse,
#[serde(untagged)]
Other(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreateMessageResult {
pub role: Role,
pub content: Vec<SamplingContent>,
pub model: String,
#[serde(rename = "stopReason", skip_serializing_if = "Option::is_none")]
pub stop_reason: Option<String>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum LoggingLevel {
Debug,
Info,
Notice,
Warning,
Error,
Critical,
Alert,
Emergency,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SetLevelParams {
pub level: LoggingLevel,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LoggingMessageParams {
pub level: LoggingLevel,
#[serde(skip_serializing_if = "Option::is_none")]
pub logger: Option<String>,
pub data: Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProgressNotificationParams {
#[serde(rename = "progressToken")]
pub progress_token: ProgressToken,
pub progress: f64,
#[serde(skip_serializing_if = "Option::is_none")]
pub total: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub message: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ProgressToken {
String(String),
Number(i64),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Root {
pub uri: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ListRootsResult {
pub roots: Vec<Root>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PromptReference {
#[serde(rename = "type")]
pub ref_type: String, pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResourceTemplateReference {
#[serde(rename = "type")]
pub ref_type: String, pub uri: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CompleteParams {
pub r#ref: Value, pub argument: CompleteArgument,
#[serde(skip_serializing_if = "Option::is_none")]
pub context: Option<CompleteContext>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CompleteArgument {
pub name: String,
pub value: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CompleteContext {
#[serde(skip_serializing_if = "Option::is_none")]
pub arguments: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CompleteResult {
pub completion: CompletionData,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CompletionData {
pub values: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub total: Option<u32>,
#[serde(rename = "hasMore", skip_serializing_if = "Option::is_none")]
pub has_more: Option<bool>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_jsonrpc_request_serialization() {
let req = JsonRpcRequest::new(JsonRpcId::Number(1), "tools/list", None);
let json = serde_json::to_string(&req).unwrap();
assert!(json.contains(r#""jsonrpc":"2.0""#));
assert!(json.contains(r#""id":1"#));
assert!(json.contains(r#""method":"tools/list""#));
}
#[test]
fn test_jsonrpc_response_success() {
let json = r#"{"jsonrpc":"2.0","id":1,"result":{"tools":[]}}"#;
let resp: JsonRpcResponse = serde_json::from_str(json).unwrap();
assert!(matches!(resp.payload, JsonRpcPayload::Success { .. }));
}
#[test]
fn test_jsonrpc_response_error() {
let json =
r#"{"jsonrpc":"2.0","id":1,"error":{"code":-32601,"message":"Method not found"}}"#;
let resp: JsonRpcResponse = serde_json::from_str(json).unwrap();
assert!(matches!(resp.payload, JsonRpcPayload::Error { .. }));
}
#[test]
fn test_tool_content_text() {
let content = ToolContent::text("Hello, world!");
assert_eq!(content.as_text(), Some("Hello, world!"));
let json = serde_json::to_string(&content).unwrap();
assert!(json.contains(r#""type":"text""#));
assert!(json.contains(r#""text":"Hello, world!""#));
}
#[test]
fn test_jsonrpc_id_display() {
assert_eq!(JsonRpcId::Number(42).to_string(), "42");
assert_eq!(JsonRpcId::String("test".to_string()).to_string(), "test");
assert_eq!(JsonRpcId::Null.to_string(), "null");
}
#[test]
fn test_mcp_tool_deserialization() {
let json = r#"{
"name": "test_tool",
"description": "A test tool",
"input_schema": {
"type": "object",
"properties": {
"name": {"type": "string"}
},
"required": ["name"]
}
}"#;
let tool: McpToolDefinition = serde_json::from_str(json).unwrap();
assert_eq!(tool.name, "test_tool");
assert_eq!(tool.description, Some("A test tool".to_string()));
assert_eq!(tool.input_schema["type"], "object");
}
}