use mcpkit_core::error::McpError;
use mcpkit_core::protocol::Request;
use serde_json::Value;
pub mod methods {
pub const INITIALIZE: &str = "initialize";
pub const PING: &str = "ping";
pub const TOOLS_LIST: &str = "tools/list";
pub const TOOLS_CALL: &str = "tools/call";
pub const RESOURCES_LIST: &str = "resources/list";
pub const RESOURCES_READ: &str = "resources/read";
pub const RESOURCES_TEMPLATES_LIST: &str = "resources/templates/list";
pub const RESOURCES_SUBSCRIBE: &str = "resources/subscribe";
pub const RESOURCES_UNSUBSCRIBE: &str = "resources/unsubscribe";
pub const PROMPTS_LIST: &str = "prompts/list";
pub const PROMPTS_GET: &str = "prompts/get";
pub const TASKS_LIST: &str = "tasks/list";
pub const TASKS_GET: &str = "tasks/get";
pub const TASKS_CANCEL: &str = "tasks/cancel";
pub const SAMPLING_CREATE_MESSAGE: &str = "sampling/createMessage";
pub const COMPLETION_COMPLETE: &str = "completion/complete";
pub const LOGGING_SET_LEVEL: &str = "logging/setLevel";
pub const ELICITATION_CREATE: &str = "elicitation/create";
}
pub mod notifications {
pub const INITIALIZED: &str = "notifications/initialized";
pub const CANCELLED: &str = "notifications/cancelled";
pub const PROGRESS: &str = "notifications/progress";
pub const MESSAGE: &str = "notifications/message";
pub const RESOURCES_UPDATED: &str = "notifications/resources/updated";
pub const RESOURCES_LIST_CHANGED: &str = "notifications/resources/list_changed";
pub const TOOLS_LIST_CHANGED: &str = "notifications/tools/list_changed";
pub const PROMPTS_LIST_CHANGED: &str = "notifications/prompts/list_changed";
}
#[derive(Debug)]
pub enum ParsedRequest {
Initialize(InitializeParams),
Ping,
ToolsList(ListParams),
ToolsCall(ToolCallParams),
ResourcesList(ListParams),
ResourcesRead(ResourceReadParams),
ResourcesTemplatesList(ListParams),
ResourcesSubscribe(ResourceSubscribeParams),
ResourcesUnsubscribe(ResourceUnsubscribeParams),
PromptsList(ListParams),
PromptsGet(PromptGetParams),
TasksList(ListParams),
TasksGet(TaskGetParams),
TasksCancel(TaskCancelParams),
SamplingCreateMessage(SamplingParams),
CompletionComplete(CompletionParams),
LoggingSetLevel(LogLevelParams),
Unknown(String),
}
#[derive(Debug, Default)]
pub struct ListParams {
pub cursor: Option<String>,
}
#[derive(Debug)]
pub struct InitializeParams {
pub protocol_version: String,
pub client_info: ClientInfo,
pub capabilities: Value,
}
#[derive(Debug)]
pub struct ClientInfo {
pub name: String,
pub version: String,
}
#[derive(Debug)]
pub struct ToolCallParams {
pub name: String,
pub arguments: Value,
}
#[derive(Debug)]
pub struct ResourceReadParams {
pub uri: String,
}
#[derive(Debug)]
pub struct ResourceSubscribeParams {
pub uri: String,
}
#[derive(Debug)]
pub struct ResourceUnsubscribeParams {
pub uri: String,
}
#[derive(Debug)]
pub struct PromptGetParams {
pub name: String,
pub arguments: Option<Value>,
}
#[derive(Debug)]
pub struct TaskGetParams {
pub task_id: String,
}
#[derive(Debug)]
pub struct TaskCancelParams {
pub task_id: String,
}
#[derive(Debug)]
pub struct SamplingParams {
pub messages: Vec<Value>,
pub model_preferences: Option<Value>,
pub system_prompt: Option<String>,
pub max_tokens: Option<u32>,
}
#[derive(Debug)]
pub struct CompletionParams {
pub ref_type: String,
pub ref_value: String,
pub argument: Option<CompletionArgument>,
}
#[derive(Debug)]
pub struct CompletionArgument {
pub name: String,
pub value: String,
}
#[derive(Debug)]
pub struct LogLevelParams {
pub level: String,
}
pub fn parse_request(request: &Request) -> Result<ParsedRequest, McpError> {
let method = request.method.as_ref();
let params = request.params.as_ref();
match method {
methods::INITIALIZE => {
let params =
params.ok_or_else(|| McpError::invalid_params(method, "missing params"))?;
Ok(ParsedRequest::Initialize(InitializeParams {
protocol_version: params
.get("protocolVersion")
.and_then(|v| v.as_str())
.unwrap_or("unknown")
.to_string(),
client_info: ClientInfo {
name: params
.get("clientInfo")
.and_then(|v| v.get("name"))
.and_then(|v| v.as_str())
.unwrap_or("unknown")
.to_string(),
version: params
.get("clientInfo")
.and_then(|v| v.get("version"))
.and_then(|v| v.as_str())
.unwrap_or("unknown")
.to_string(),
},
capabilities: params
.get("capabilities")
.cloned()
.unwrap_or_else(|| Value::Object(serde_json::Map::new())),
}))
}
methods::PING => Ok(ParsedRequest::Ping),
methods::TOOLS_LIST => Ok(ParsedRequest::ToolsList(parse_list_params(params))),
methods::TOOLS_CALL => {
let params =
params.ok_or_else(|| McpError::invalid_params(method, "missing params"))?;
let name = params
.get("name")
.and_then(|v| v.as_str())
.ok_or_else(|| McpError::invalid_params(method, "missing name"))?
.to_string();
let arguments = params
.get("arguments")
.cloned()
.unwrap_or_else(|| Value::Object(serde_json::Map::new()));
Ok(ParsedRequest::ToolsCall(ToolCallParams { name, arguments }))
}
methods::RESOURCES_LIST => Ok(ParsedRequest::ResourcesList(parse_list_params(params))),
methods::RESOURCES_READ => {
let params =
params.ok_or_else(|| McpError::invalid_params(method, "missing params"))?;
let uri = params
.get("uri")
.and_then(|v| v.as_str())
.ok_or_else(|| McpError::invalid_params(method, "missing uri"))?
.to_string();
Ok(ParsedRequest::ResourcesRead(ResourceReadParams { uri }))
}
methods::RESOURCES_TEMPLATES_LIST => Ok(ParsedRequest::ResourcesTemplatesList(
parse_list_params(params),
)),
methods::RESOURCES_SUBSCRIBE => {
let params =
params.ok_or_else(|| McpError::invalid_params(method, "missing params"))?;
let uri = params
.get("uri")
.and_then(|v| v.as_str())
.ok_or_else(|| McpError::invalid_params(method, "missing uri"))?
.to_string();
Ok(ParsedRequest::ResourcesSubscribe(ResourceSubscribeParams {
uri,
}))
}
methods::RESOURCES_UNSUBSCRIBE => {
let params =
params.ok_or_else(|| McpError::invalid_params(method, "missing params"))?;
let uri = params
.get("uri")
.and_then(|v| v.as_str())
.ok_or_else(|| McpError::invalid_params(method, "missing uri"))?
.to_string();
Ok(ParsedRequest::ResourcesUnsubscribe(
ResourceUnsubscribeParams { uri },
))
}
methods::PROMPTS_LIST => Ok(ParsedRequest::PromptsList(parse_list_params(params))),
methods::PROMPTS_GET => {
let params =
params.ok_or_else(|| McpError::invalid_params(method, "missing params"))?;
let name = params
.get("name")
.and_then(|v| v.as_str())
.ok_or_else(|| McpError::invalid_params(method, "missing name"))?
.to_string();
let arguments = params.get("arguments").cloned();
Ok(ParsedRequest::PromptsGet(PromptGetParams {
name,
arguments,
}))
}
methods::TASKS_LIST => Ok(ParsedRequest::TasksList(parse_list_params(params))),
methods::TASKS_GET => {
let params =
params.ok_or_else(|| McpError::invalid_params(method, "missing params"))?;
let task_id = params
.get("taskId")
.and_then(|v| v.as_str())
.ok_or_else(|| McpError::invalid_params(method, "missing taskId"))?
.to_string();
Ok(ParsedRequest::TasksGet(TaskGetParams { task_id }))
}
methods::TASKS_CANCEL => {
let params =
params.ok_or_else(|| McpError::invalid_params(method, "missing params"))?;
let task_id = params
.get("taskId")
.and_then(|v| v.as_str())
.ok_or_else(|| McpError::invalid_params(method, "missing taskId"))?
.to_string();
Ok(ParsedRequest::TasksCancel(TaskCancelParams { task_id }))
}
methods::SAMPLING_CREATE_MESSAGE => {
let params =
params.ok_or_else(|| McpError::invalid_params(method, "missing params"))?;
let messages = params
.get("messages")
.and_then(|v| v.as_array())
.ok_or_else(|| McpError::invalid_params(method, "missing messages"))?
.clone();
Ok(ParsedRequest::SamplingCreateMessage(SamplingParams {
messages,
model_preferences: params.get("modelPreferences").cloned(),
system_prompt: params
.get("systemPrompt")
.and_then(|v| v.as_str())
.map(String::from),
max_tokens: params
.get("maxTokens")
.and_then(serde_json::Value::as_u64)
.map(|v| v as u32),
}))
}
methods::COMPLETION_COMPLETE => {
let params =
params.ok_or_else(|| McpError::invalid_params(method, "missing params"))?;
let ref_obj = params
.get("ref")
.ok_or_else(|| McpError::invalid_params(method, "missing ref"))?;
Ok(ParsedRequest::CompletionComplete(CompletionParams {
ref_type: ref_obj
.get("type")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string(),
ref_value: ref_obj
.get("uri")
.or_else(|| ref_obj.get("name"))
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string(),
argument: params.get("argument").map(|arg| CompletionArgument {
name: arg
.get("name")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string(),
value: arg
.get("value")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string(),
}),
}))
}
methods::LOGGING_SET_LEVEL => {
let params =
params.ok_or_else(|| McpError::invalid_params(method, "missing params"))?;
let level = params
.get("level")
.and_then(|v| v.as_str())
.ok_or_else(|| McpError::invalid_params(method, "missing level"))?
.to_string();
Ok(ParsedRequest::LoggingSetLevel(LogLevelParams { level }))
}
_ => Ok(ParsedRequest::Unknown(method.to_string())),
}
}
fn parse_list_params(params: Option<&Value>) -> ListParams {
ListParams {
cursor: params
.and_then(|p| p.get("cursor"))
.and_then(|v| v.as_str())
.map(String::from),
}
}
use crate::context::Context;
use crate::handler::{PromptHandler, ResourceHandler, ToolHandler};
use mcpkit_core::types::CallToolResult;
pub async fn route_tools<TH: ToolHandler + Send + Sync>(
handler: &TH,
method: &str,
params: Option<&serde_json::Value>,
ctx: &Context<'_>,
) -> Option<Result<serde_json::Value, McpError>> {
match method {
methods::TOOLS_LIST => {
tracing::debug!("Listing available tools");
let result = handler.list_tools(ctx).await;
match &result {
Ok(tools) => tracing::debug!(count = tools.len(), "Listed tools"),
Err(e) => tracing::warn!(error = %e, "Failed to list tools"),
}
Some(result.map(|tools| serde_json::json!({ "tools": tools })))
}
methods::TOOLS_CALL => {
let result = async {
let params = params.ok_or_else(|| {
McpError::invalid_params(methods::TOOLS_CALL, "missing params")
})?;
let name = params.get("name").and_then(|v| v.as_str()).ok_or_else(|| {
McpError::invalid_params(methods::TOOLS_CALL, "missing tool name")
})?;
let args = params
.get("arguments")
.cloned()
.unwrap_or_else(|| serde_json::json!({}));
tracing::info!(tool = %name, "Calling tool");
let start = std::time::Instant::now();
let output = handler.call_tool(name, args, ctx).await;
let duration = start.elapsed();
match &output {
Ok(_) => tracing::info!(
tool = %name,
duration_ms = duration.as_millis(),
"Tool call completed"
),
Err(e) => tracing::warn!(
tool = %name,
duration_ms = duration.as_millis(),
error = %e,
"Tool call failed"
),
}
let output = output?;
let result: CallToolResult = output.into();
Ok(serde_json::to_value(result).unwrap_or_else(|_| serde_json::json!({})))
}
.await;
Some(result)
}
_ => None,
}
}
pub async fn route_resources<RH: ResourceHandler + Send + Sync>(
handler: &RH,
method: &str,
params: Option<&serde_json::Value>,
ctx: &Context<'_>,
) -> Option<Result<serde_json::Value, McpError>> {
match method {
methods::RESOURCES_LIST => {
tracing::debug!("Listing available resources");
let result = handler.list_resources(ctx).await;
match &result {
Ok(resources) => tracing::debug!(count = resources.len(), "Listed resources"),
Err(e) => tracing::warn!(error = %e, "Failed to list resources"),
}
Some(result.map(|resources| serde_json::json!({ "resources": resources })))
}
methods::RESOURCES_TEMPLATES_LIST => {
tracing::debug!("Listing available resource templates");
let result = handler.list_resource_templates(ctx).await;
match &result {
Ok(templates) => {
tracing::debug!(count = templates.len(), "Listed resource templates");
}
Err(e) => tracing::warn!(error = %e, "Failed to list resource templates"),
}
Some(result.map(|templates| serde_json::json!({ "resourceTemplates": templates })))
}
methods::RESOURCES_READ => {
let result = async {
let params = params.ok_or_else(|| {
McpError::invalid_params(methods::RESOURCES_READ, "missing params")
})?;
let uri = params.get("uri").and_then(|v| v.as_str()).ok_or_else(|| {
McpError::invalid_params(methods::RESOURCES_READ, "missing uri")
})?;
tracing::info!(uri = %uri, "Reading resource");
let start = std::time::Instant::now();
let contents = handler.read_resource(uri, ctx).await;
let duration = start.elapsed();
match &contents {
Ok(_) => tracing::info!(
uri = %uri,
duration_ms = duration.as_millis(),
"Resource read completed"
),
Err(e) => tracing::warn!(
uri = %uri,
duration_ms = duration.as_millis(),
error = %e,
"Resource read failed"
),
}
let contents = contents?;
Ok(serde_json::json!({ "contents": contents }))
}
.await;
Some(result)
}
_ => None,
}
}
pub async fn route_prompts<PH: PromptHandler + Send + Sync>(
handler: &PH,
method: &str,
params: Option<&serde_json::Value>,
ctx: &Context<'_>,
) -> Option<Result<serde_json::Value, McpError>> {
match method {
methods::PROMPTS_LIST => {
tracing::debug!("Listing available prompts");
let result = handler.list_prompts(ctx).await;
match &result {
Ok(prompts) => tracing::debug!(count = prompts.len(), "Listed prompts"),
Err(e) => tracing::warn!(error = %e, "Failed to list prompts"),
}
Some(result.map(|prompts| serde_json::json!({ "prompts": prompts })))
}
methods::PROMPTS_GET => {
let result = async {
let params = params.ok_or_else(|| {
McpError::invalid_params(methods::PROMPTS_GET, "missing params")
})?;
let name = params.get("name").and_then(|v| v.as_str()).ok_or_else(|| {
McpError::invalid_params(methods::PROMPTS_GET, "missing prompt name")
})?;
let args = params.get("arguments").and_then(|v| v.as_object()).cloned();
tracing::info!(prompt = %name, "Getting prompt");
let start = std::time::Instant::now();
let prompt_result = handler.get_prompt(name, args, ctx).await;
let duration = start.elapsed();
match &prompt_result {
Ok(_) => tracing::info!(
prompt = %name,
duration_ms = duration.as_millis(),
"Prompt retrieval completed"
),
Err(e) => tracing::warn!(
prompt = %name,
duration_ms = duration.as_millis(),
error = %e,
"Prompt retrieval failed"
),
}
let result = prompt_result?;
Ok(serde_json::to_value(result).unwrap_or_else(|_| serde_json::json!({})))
}
.await;
Some(result)
}
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use mcpkit_core::protocol::Request;
fn make_request(method: &'static str, params: Option<Value>) -> Request {
if let Some(p) = params {
Request::with_params(method, 1u64, p)
} else {
Request::new(method, 1u64)
}
}
#[test]
fn test_parse_ping() -> Result<(), Box<dyn std::error::Error>> {
let request = make_request("ping", None);
let parsed = parse_request(&request)?;
assert!(matches!(parsed, ParsedRequest::Ping));
Ok(())
}
#[test]
fn test_parse_tools_list() -> Result<(), Box<dyn std::error::Error>> {
let request = make_request("tools/list", None);
let parsed = parse_request(&request)?;
assert!(matches!(parsed, ParsedRequest::ToolsList(_)));
Ok(())
}
#[test]
fn test_parse_tools_list_with_cursor() -> Result<(), Box<dyn std::error::Error>> {
let request = make_request(
"tools/list",
Some(serde_json::json!({ "cursor": "abc123" })),
);
let parsed = parse_request(&request)?;
if let ParsedRequest::ToolsList(params) = parsed {
assert_eq!(params.cursor, Some("abc123".to_string()));
} else {
panic!("Expected ToolsList");
}
Ok(())
}
#[test]
fn test_parse_tools_call() -> Result<(), Box<dyn std::error::Error>> {
let request = make_request(
"tools/call",
Some(serde_json::json!({
"name": "search",
"arguments": {"query": "test"}
})),
);
let parsed = parse_request(&request)?;
if let ParsedRequest::ToolsCall(params) = parsed {
assert_eq!(params.name, "search");
assert_eq!(params.arguments["query"], "test");
} else {
panic!("Expected ToolsCall");
}
Ok(())
}
#[test]
fn test_parse_tools_call_missing_params() {
let request = make_request("tools/call", None);
let result = parse_request(&request);
assert!(result.is_err());
}
#[test]
fn test_parse_tools_call_missing_name() {
let request = make_request("tools/call", Some(serde_json::json!({"arguments": {}})));
let result = parse_request(&request);
assert!(result.is_err());
}
#[test]
fn test_parse_unknown_method() -> Result<(), Box<dyn std::error::Error>> {
let request = make_request("unknown/method", None);
let parsed = parse_request(&request)?;
if let ParsedRequest::Unknown(method) = parsed {
assert_eq!(method, "unknown/method");
} else {
panic!("Expected Unknown");
}
Ok(())
}
#[test]
fn test_parse_initialize() -> Result<(), Box<dyn std::error::Error>> {
let request = make_request(
"initialize",
Some(serde_json::json!({
"protocolVersion": "2025-11-25",
"clientInfo": {
"name": "test-client",
"version": "1.0.0"
},
"capabilities": {}
})),
);
let parsed = parse_request(&request)?;
if let ParsedRequest::Initialize(params) = parsed {
assert_eq!(params.protocol_version, "2025-11-25");
assert_eq!(params.client_info.name, "test-client");
assert_eq!(params.client_info.version, "1.0.0");
} else {
panic!("Expected Initialize");
}
Ok(())
}
#[test]
fn test_parse_initialize_missing_params() {
let request = make_request("initialize", None);
let result = parse_request(&request);
assert!(result.is_err());
}
#[test]
fn test_parse_resources_list() -> Result<(), Box<dyn std::error::Error>> {
let request = make_request("resources/list", None);
let parsed = parse_request(&request)?;
assert!(matches!(parsed, ParsedRequest::ResourcesList(_)));
Ok(())
}
#[test]
fn test_parse_resources_list_with_cursor() -> Result<(), Box<dyn std::error::Error>> {
let request = make_request(
"resources/list",
Some(serde_json::json!({ "cursor": "page2" })),
);
let parsed = parse_request(&request)?;
if let ParsedRequest::ResourcesList(params) = parsed {
assert_eq!(params.cursor, Some("page2".to_string()));
} else {
panic!("Expected ResourcesList");
}
Ok(())
}
#[test]
fn test_parse_resources_read() -> Result<(), Box<dyn std::error::Error>> {
let request = make_request(
"resources/read",
Some(serde_json::json!({ "uri": "file:///etc/hosts" })),
);
let parsed = parse_request(&request)?;
if let ParsedRequest::ResourcesRead(params) = parsed {
assert_eq!(params.uri, "file:///etc/hosts");
} else {
panic!("Expected ResourcesRead");
}
Ok(())
}
#[test]
fn test_parse_resources_read_missing_uri() {
let request = make_request("resources/read", Some(serde_json::json!({})));
let result = parse_request(&request);
assert!(result.is_err());
}
#[test]
fn test_parse_resources_templates_list() -> Result<(), Box<dyn std::error::Error>> {
let request = make_request("resources/templates/list", None);
let parsed = parse_request(&request)?;
assert!(matches!(parsed, ParsedRequest::ResourcesTemplatesList(_)));
Ok(())
}
#[test]
fn test_parse_resources_subscribe() -> Result<(), Box<dyn std::error::Error>> {
let request = make_request(
"resources/subscribe",
Some(serde_json::json!({ "uri": "file:///var/log/app.log" })),
);
let parsed = parse_request(&request)?;
if let ParsedRequest::ResourcesSubscribe(params) = parsed {
assert_eq!(params.uri, "file:///var/log/app.log");
} else {
panic!("Expected ResourcesSubscribe");
}
Ok(())
}
#[test]
fn test_parse_resources_unsubscribe() -> Result<(), Box<dyn std::error::Error>> {
let request = make_request(
"resources/unsubscribe",
Some(serde_json::json!({ "uri": "file:///var/log/app.log" })),
);
let parsed = parse_request(&request)?;
if let ParsedRequest::ResourcesUnsubscribe(params) = parsed {
assert_eq!(params.uri, "file:///var/log/app.log");
} else {
panic!("Expected ResourcesUnsubscribe");
}
Ok(())
}
#[test]
fn test_parse_prompts_list() -> Result<(), Box<dyn std::error::Error>> {
let request = make_request("prompts/list", None);
let parsed = parse_request(&request)?;
assert!(matches!(parsed, ParsedRequest::PromptsList(_)));
Ok(())
}
#[test]
fn test_parse_prompts_get() -> Result<(), Box<dyn std::error::Error>> {
let request = make_request(
"prompts/get",
Some(serde_json::json!({
"name": "code-review",
"arguments": { "language": "rust" }
})),
);
let parsed = parse_request(&request)?;
if let ParsedRequest::PromptsGet(params) = parsed {
assert_eq!(params.name, "code-review");
assert!(params.arguments.is_some());
} else {
panic!("Expected PromptsGet");
}
Ok(())
}
#[test]
fn test_parse_prompts_get_without_arguments() -> Result<(), Box<dyn std::error::Error>> {
let request = make_request(
"prompts/get",
Some(serde_json::json!({ "name": "simple-prompt" })),
);
let parsed = parse_request(&request)?;
if let ParsedRequest::PromptsGet(params) = parsed {
assert_eq!(params.name, "simple-prompt");
assert!(params.arguments.is_none());
} else {
panic!("Expected PromptsGet");
}
Ok(())
}
#[test]
fn test_parse_prompts_get_missing_name() {
let request = make_request("prompts/get", Some(serde_json::json!({})));
let result = parse_request(&request);
assert!(result.is_err());
}
#[test]
fn test_parse_tasks_list() -> Result<(), Box<dyn std::error::Error>> {
let request = make_request("tasks/list", None);
let parsed = parse_request(&request)?;
assert!(matches!(parsed, ParsedRequest::TasksList(_)));
Ok(())
}
#[test]
fn test_parse_tasks_get() -> Result<(), Box<dyn std::error::Error>> {
let request = make_request(
"tasks/get",
Some(serde_json::json!({ "taskId": "task-123" })),
);
let parsed = parse_request(&request)?;
if let ParsedRequest::TasksGet(params) = parsed {
assert_eq!(params.task_id, "task-123");
} else {
panic!("Expected TasksGet");
}
Ok(())
}
#[test]
fn test_parse_tasks_get_missing_id() {
let request = make_request("tasks/get", Some(serde_json::json!({})));
let result = parse_request(&request);
assert!(result.is_err());
}
#[test]
fn test_parse_tasks_cancel() -> Result<(), Box<dyn std::error::Error>> {
let request = make_request(
"tasks/cancel",
Some(serde_json::json!({ "taskId": "task-456" })),
);
let parsed = parse_request(&request)?;
if let ParsedRequest::TasksCancel(params) = parsed {
assert_eq!(params.task_id, "task-456");
} else {
panic!("Expected TasksCancel");
}
Ok(())
}
#[test]
fn test_parse_sampling_create_message() -> Result<(), Box<dyn std::error::Error>> {
let request = make_request(
"sampling/createMessage",
Some(serde_json::json!({
"messages": [
{ "role": "user", "content": { "type": "text", "text": "Hello" } }
],
"maxTokens": 100,
"systemPrompt": "You are a helpful assistant."
})),
);
let parsed = parse_request(&request)?;
if let ParsedRequest::SamplingCreateMessage(params) = parsed {
assert_eq!(params.messages.len(), 1);
assert_eq!(params.max_tokens, Some(100));
assert_eq!(
params.system_prompt,
Some("You are a helpful assistant.".to_string())
);
} else {
panic!("Expected SamplingCreateMessage");
}
Ok(())
}
#[test]
fn test_parse_sampling_create_message_missing_messages() {
let request = make_request("sampling/createMessage", Some(serde_json::json!({})));
let result = parse_request(&request);
assert!(result.is_err());
}
#[test]
fn test_parse_completion_complete() -> Result<(), Box<dyn std::error::Error>> {
let request = make_request(
"completion/complete",
Some(serde_json::json!({
"ref": {
"type": "ref/resource",
"uri": "file:///home"
},
"argument": {
"name": "path",
"value": "/home/user"
}
})),
);
let parsed = parse_request(&request)?;
if let ParsedRequest::CompletionComplete(params) = parsed {
assert_eq!(params.ref_type, "ref/resource");
assert_eq!(params.ref_value, "file:///home");
assert!(params.argument.is_some());
let arg = params.argument.unwrap();
assert_eq!(arg.name, "path");
assert_eq!(arg.value, "/home/user");
} else {
panic!("Expected CompletionComplete");
}
Ok(())
}
#[test]
fn test_parse_completion_complete_prompt_ref() -> Result<(), Box<dyn std::error::Error>> {
let request = make_request(
"completion/complete",
Some(serde_json::json!({
"ref": {
"type": "ref/prompt",
"name": "code-review"
}
})),
);
let parsed = parse_request(&request)?;
if let ParsedRequest::CompletionComplete(params) = parsed {
assert_eq!(params.ref_type, "ref/prompt");
assert_eq!(params.ref_value, "code-review");
assert!(params.argument.is_none());
} else {
panic!("Expected CompletionComplete");
}
Ok(())
}
#[test]
fn test_parse_completion_complete_missing_ref() {
let request = make_request("completion/complete", Some(serde_json::json!({})));
let result = parse_request(&request);
assert!(result.is_err());
}
#[test]
fn test_parse_logging_set_level() -> Result<(), Box<dyn std::error::Error>> {
let request = make_request(
"logging/setLevel",
Some(serde_json::json!({ "level": "debug" })),
);
let parsed = parse_request(&request)?;
if let ParsedRequest::LoggingSetLevel(params) = parsed {
assert_eq!(params.level, "debug");
} else {
panic!("Expected LoggingSetLevel");
}
Ok(())
}
#[test]
fn test_parse_logging_set_level_missing_level() {
let request = make_request("logging/setLevel", Some(serde_json::json!({})));
let result = parse_request(&request);
assert!(result.is_err());
}
#[test]
fn test_method_constants() {
assert_eq!(methods::INITIALIZE, "initialize");
assert_eq!(methods::PING, "ping");
assert_eq!(methods::TOOLS_LIST, "tools/list");
assert_eq!(methods::TOOLS_CALL, "tools/call");
assert_eq!(methods::RESOURCES_LIST, "resources/list");
assert_eq!(methods::RESOURCES_READ, "resources/read");
assert_eq!(
methods::RESOURCES_TEMPLATES_LIST,
"resources/templates/list"
);
assert_eq!(methods::RESOURCES_SUBSCRIBE, "resources/subscribe");
assert_eq!(methods::RESOURCES_UNSUBSCRIBE, "resources/unsubscribe");
assert_eq!(methods::PROMPTS_LIST, "prompts/list");
assert_eq!(methods::PROMPTS_GET, "prompts/get");
assert_eq!(methods::TASKS_LIST, "tasks/list");
assert_eq!(methods::TASKS_GET, "tasks/get");
assert_eq!(methods::TASKS_CANCEL, "tasks/cancel");
assert_eq!(methods::SAMPLING_CREATE_MESSAGE, "sampling/createMessage");
assert_eq!(methods::COMPLETION_COMPLETE, "completion/complete");
assert_eq!(methods::LOGGING_SET_LEVEL, "logging/setLevel");
}
#[test]
fn test_notification_constants() {
assert_eq!(notifications::INITIALIZED, "notifications/initialized");
assert_eq!(notifications::CANCELLED, "notifications/cancelled");
assert_eq!(notifications::PROGRESS, "notifications/progress");
assert_eq!(notifications::MESSAGE, "notifications/message");
assert_eq!(
notifications::RESOURCES_UPDATED,
"notifications/resources/updated"
);
assert_eq!(
notifications::RESOURCES_LIST_CHANGED,
"notifications/resources/list_changed"
);
assert_eq!(
notifications::TOOLS_LIST_CHANGED,
"notifications/tools/list_changed"
);
assert_eq!(
notifications::PROMPTS_LIST_CHANGED,
"notifications/prompts/list_changed"
);
}
}