use serde::{Deserialize, Serialize};
use serde_json::Value;
pub const MCP_VERSION: &str = "2024-11-05";
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum McpMessage {
Request(McpRequest),
Response(McpResponse),
Notification(McpNotification),
Error(McpError),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpRequest {
pub id: RequestId,
pub method: String,
#[serde(default)]
pub params: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpResponse {
pub id: RequestId,
#[serde(skip_serializing_if = "Option::is_none")]
pub result: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<McpErrorData>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpNotification {
pub method: String,
#[serde(default)]
pub params: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpError {
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<RequestId>,
pub error: McpErrorData,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpErrorData {
pub code: i32,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<Value>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(untagged)]
pub enum RequestId {
String(String),
Number(i64),
}
impl Default for RequestId {
fn default() -> Self {
RequestId::Number(0)
}
}
pub const METHOD_INITIALIZE: &str = "initialize";
pub const METHOD_INITIALIZED: &str = "notifications/initialized";
pub const METHOD_SHUTDOWN: &str = "shutdown";
pub const METHOD_LIST_TOOLS: &str = "tools/list";
pub const METHOD_CALL_TOOL: &str = "tools/call";
pub const METHOD_LIST_RESOURCES: &str = "resources/list";
pub const METHOD_READ_RESOURCE: &str = "resources/read";
pub const METHOD_LIST_PROMPTS: &str = "prompts/list";
pub const METHOD_GET_PROMPT: &str = "prompts/get";
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolDefinition {
pub name: String,
#[serde(default)]
pub description: Option<String>,
#[serde(default)]
pub input_schema: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolResult {
#[serde(default)]
pub is_error: bool,
pub content: Vec<ContentBlock>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ContentBlock {
Text { text: String },
Image { data: String, mime_type: String },
Resource { resource: ResourceContent },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResourceContent {
pub uri: String,
#[serde(default)]
pub mime_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub text: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub blob: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InitializeParams {
#[serde(default = "default_protocol_version")]
pub protocol_version: String,
pub capabilities: ClientCapabilities,
#[serde(default)]
pub client_info: Option<Implementation>,
}
fn default_protocol_version() -> String {
MCP_VERSION.to_string()
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ClientCapabilities {
#[serde(default)]
pub experimental: Option<Value>,
#[serde(default)]
pub roots: Option<RootsCapability>,
#[serde(default)]
pub sampling: Option<Value>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct RootsCapability {
#[serde(default)]
pub list_changed: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Implementation {
pub name: String,
pub version: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InitializeResult {
pub protocol_version: String,
pub capabilities: ServerCapabilities,
pub server_info: Implementation,
#[serde(skip_serializing_if = "Option::is_none")]
pub instructions: Option<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ServerCapabilities {
#[serde(default)]
pub experimental: Option<Value>,
#[serde(default)]
pub logging: Option<Value>,
#[serde(default)]
pub prompts: Option<PromptsCapability>,
#[serde(default)]
pub resources: Option<ResourcesCapability>,
#[serde(default)]
pub tools: Option<ToolsCapability>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct PromptsCapability {
#[serde(default)]
pub list_changed: Option<bool>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ResourcesCapability {
#[serde(default)]
pub subscribe: Option<bool>,
#[serde(default)]
pub list_changed: Option<bool>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ToolsCapability {
#[serde(default)]
pub list_changed: Option<bool>,
}
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 SERVER_NOT_INITIALIZED: i32 = -32002;
pub const UNKNOWN_ERROR: i32 = -32001;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_serialize_request() {
let request = McpRequest {
id: RequestId::Number(1),
method: "tools/list".to_string(),
params: None,
};
let json = serde_json::to_string(&request).unwrap();
assert!(json.contains("tools/list"));
}
#[test]
fn test_deserialize_response() {
let json = r#"{"id":1,"result":{"tools":[]}}"#;
let response: McpResponse = serde_json::from_str(json).unwrap();
assert_eq!(response.id, RequestId::Number(1));
}
#[test]
fn test_tool_definition() {
let tool = ToolDefinition {
name: "test_tool".to_string(),
description: Some("A test tool".to_string()),
input_schema: None,
};
let json = serde_json::to_string(&tool).unwrap();
assert!(json.contains("test_tool"));
}
#[test]
fn test_content_block_text() {
let block = ContentBlock::Text {
text: "Hello".to_string(),
};
let json = serde_json::to_string(&block).unwrap();
assert!(json.contains("text"));
assert!(json.contains("Hello"));
}
}