chore: add #[non_exhaustive] and mutation methods to improve compatibility (#715)

* chore: add #[non_exhaustive] to reduce backwards-incompatible changes going forward

* fix: remove ProtocolVersion import

* fix: add a few more with_ mutator methods

---------

Co-authored-by: Alex Hancock <alexhancock@block.xyz>
This commit is contained in:
Jack Amadeo 2026-03-03 10:38:01 -05:00 • committed by GitHub
commit f63718d202
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 1568 additions and 1110 deletions

View file

@ -53,10 +53,7 @@ impl Calculator {
impl ServerHandler for Calculator {
fn get_info(&self) -> ServerInfo {
ServerInfo {
instructions: Some("A simple calculator".into()),
capabilities: ServerCapabilities::builder().enable_tools().build(),
..Default::default()
}
ServerInfo::new(ServerCapabilities::builder().enable_tools().build())
.with_instructions("A simple calculator")
}
}

View file

@ -3,15 +3,17 @@ use std::{
sync::{Arc, Mutex},
};
use rmcp::{
ClientHandler, ErrorData as McpError, RoleClient, RoleServer, ServerHandler,
model::*,
service::{NotificationContext, RequestContext},
};
#[cfg(feature = "client")]
use rmcp::service::NotificationContext;
#[cfg(feature = "client")]
use rmcp::{ClientHandler, RoleClient};
use rmcp::{ErrorData as McpError, RoleServer, ServerHandler, model::*, service::RequestContext};
#[cfg(feature = "client")]
use serde_json::json;
use tokio::sync::Notify;
#[derive(Clone)]
#[allow(dead_code)]
pub struct TestClientHandler {
pub honor_this_server: bool,
pub honor_all_servers: bool,
@ -46,6 +48,7 @@ impl TestClientHandler {
}
}
#[cfg(feature = "client")]
impl ClientHandler for TestClientHandler {
async fn create_message(
&self,
@ -71,11 +74,11 @@ impl ClientHandler for TestClientHandler {
_ => "Test response without context",
};
Ok(CreateMessageResult {
message: SamplingMessage::assistant_text(response.to_string()),
model: "test-model".to_string(),
stop_reason: Some(CreateMessageResult::STOP_REASON_END_TURN.to_string()),
})
Ok(CreateMessageResult::new(
SamplingMessage::assistant_text(response.to_string()),
"test-model".to_string(),
)
.with_stop_reason(Some(CreateMessageResult::STOP_REASON_END_TURN.to_string())))
}
fn on_logging_message(
@ -106,10 +109,7 @@ impl TestServer {
impl ServerHandler for TestServer {
fn get_info(&self) -> ServerInfo {
ServerInfo {
capabilities: ServerCapabilities::builder().enable_logging().build(),
..Default::default()
}
ServerInfo::new(ServerCapabilities::builder().enable_logging().build())
}
fn set_level(

View file

@ -1,4 +1,6 @@
// cargo test --features "server client" --package rmcp test_client_initialization
#![cfg(feature = "client")]
mod common;
use std::borrow::Cow;

View file

@ -52,15 +52,14 @@ fn test_complete_request_param_serialization() {
let mut args = HashMap::new();
args.insert("previous_input".to_string(), "test".to_string());
let request = CompleteRequestParams {
meta: None,
r#ref: Reference::for_prompt("weather_prompt"),
argument: ArgumentInfo {
let request = CompleteRequestParams::new(
Reference::for_prompt("weather_prompt"),
ArgumentInfo {
name: "location".to_string(),
value: "San".to_string(),
},
context: Some(CompletionContext::with_arguments(args)),
};
)
.with_context(CompletionContext::with_arguments(args));
let json = serde_json::to_value(&request).unwrap();
assert!(json["ref"]["name"].as_str().unwrap() == "weather_prompt");
@ -196,15 +195,13 @@ fn test_completion_context_empty() {
#[test]
fn test_mcp_schema_compliance() {
// Test that our types serialize correctly according to MCP specification
let request = CompleteRequestParams {
meta: None,
r#ref: Reference::for_resource("file://{path}"),
argument: ArgumentInfo {
let request = CompleteRequestParams::new(
Reference::for_resource("file://{path}"),
ArgumentInfo {
name: "path".to_string(),
value: "src/".to_string(),
},
context: None,
};
);
let json_str = serde_json::to_string(&request).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&json_str).unwrap();

View file

@ -730,10 +730,7 @@ async fn test_server_rejects_unsupported_protocol_version() {
impl ServerHandler for TestHandler {
fn get_info(&self) -> ServerInfo {
ServerInfo {
capabilities: ServerCapabilities::builder().build(),
..Default::default()
}
ServerInfo::new(ServerCapabilities::builder().build())
}
}

View file

@ -141,15 +141,13 @@ async fn test_elicitation_json_rpc_protocol() {
let request = JsonRpcRequest {
jsonrpc: JsonRpcVersion2_0,
id: RequestId::Number(1),
request: CreateElicitationRequest {
method: ElicitationCreateRequestMethod,
params: CreateElicitationRequestParams::FormElicitationParams {
request: CreateElicitationRequest::new(
CreateElicitationRequestParams::FormElicitationParams {
meta: None,
message: "Do you want to continue?".to_string(),
requested_schema: schema,
},
extensions: Default::default(),
},
),
};
// Test serialization of complete request
@ -710,30 +708,24 @@ async fn test_elicitation_multi_select_enum() {
assert_eq!(
schema,
&EnumSchema::Multi(MultiSelectEnumSchema::Titled(
TitledMultiSelectEnumSchema {
type_: ArrayTypeConst,
title: None,
description: None,
min_items: Some(1),
max_items: Some(2),
items: TitledItems {
any_of: vec![
ConstTitle {
const_: "A".to_string(),
title: "A name".to_string()
},
ConstTitle {
const_: "B".to_string(),
title: "B name".to_string()
},
ConstTitle {
const_: "C".to_string(),
title: "C name".to_string()
}
],
},
default: None
}
TitledMultiSelectEnumSchema::new(TitledItems {
any_of: vec![
ConstTitle {
const_: "A".to_string(),
title: "A name".to_string()
},
ConstTitle {
const_: "B".to_string(),
title: "B name".to_string()
},
ConstTitle {
const_: "C".to_string(),
title: "C name".to_string()
},
],
})
.with_min_items(1)
.with_max_items(2)
))
)
}
@ -789,26 +781,20 @@ async fn test_elicitation_single_select_enum() {
assert_eq!(
schema,
&EnumSchema::Single(SingleSelectEnumSchema::Titled(
TitledSingleSelectEnumSchema {
type_: StringTypeConst,
title: None,
description: None,
one_of: vec![
ConstTitle {
const_: "A".to_string(),
title: "A name".to_string()
},
ConstTitle {
const_: "B".to_string(),
title: "B name".to_string()
},
ConstTitle {
const_: "C".to_string(),
title: "C name".to_string()
}
],
default: None
}
TitledSingleSelectEnumSchema::new(vec![
ConstTitle {
const_: "A".to_string(),
title: "A name".to_string()
},
ConstTitle {
const_: "B".to_string(),
title: "B name".to_string()
},
ConstTitle {
const_: "C".to_string(),
title: "C name".to_string()
}
])
))
)
}
@ -850,11 +836,8 @@ async fn test_elicitation_direction_server_to_client() {
assert_eq!(serialized["requestedSchema"]["type"], "object");
// Test that elicitation requests are part of ServerRequest
let _server_request = ServerRequest::CreateElicitationRequest(CreateElicitationRequest {
method: ElicitationCreateRequestMethod,
params: elicitation_request,
extensions: Default::default(),
});
let _server_request =
ServerRequest::CreateElicitationRequest(CreateElicitationRequest::new(elicitation_request));
// Test that client can respond with elicitation results
let client_result = ClientResult::CreateElicitationResult(CreateElicitationResult {
@ -889,15 +872,13 @@ async fn test_elicitation_json_rpc_direction() {
// 1. Server creates elicitation request
let server_request = ServerJsonRpcMessage::request(
ServerRequest::CreateElicitationRequest(CreateElicitationRequest {
method: ElicitationCreateRequestMethod,
params: CreateElicitationRequestParams::FormElicitationParams {
ServerRequest::CreateElicitationRequest(CreateElicitationRequest::new(
CreateElicitationRequestParams::FormElicitationParams {
meta: None,
message: "Do you want to continue?".to_string(),
requested_schema: schema,
},
extensions: Default::default(),
}),
)),
RequestId::Number(1),
);
@ -1051,15 +1032,14 @@ async fn test_elicitation_capability_structure() {
#[tokio::test]
async fn test_client_capabilities_with_elicitation() {
// Test ClientCapabilities with elicitation capability
let capabilities = ClientCapabilities {
elicitation: Some(ElicitationCapability {
let capabilities = ClientCapabilities::builder()
.enable_elicitation_with(ElicitationCapability {
form: Some(FormElicitationCapability {
schema_validation: Some(true),
}),
url: None,
}),
..Default::default()
};
})
.build();
// Verify elicitation capability is present
assert!(capabilities.elicitation.is_some());
@ -1084,10 +1064,7 @@ async fn test_client_capabilities_with_elicitation() {
);
// Test ClientCapabilities without elicitation
let capabilities_without = ClientCapabilities {
elicitation: None,
..Default::default()
};
let capabilities_without = ClientCapabilities::default();
assert!(capabilities_without.elicitation.is_none());
}
@ -1096,27 +1073,17 @@ async fn test_client_capabilities_with_elicitation() {
#[tokio::test]
async fn test_initialize_request_with_elicitation() {
// Test InitializeRequestParam with elicitation capability
let init_param = InitializeRequestParams {
meta: None,
protocol_version: ProtocolVersion::LATEST,
capabilities: ClientCapabilities {
elicitation: Some(ElicitationCapability {
let init_param = InitializeRequestParams::new(
ClientCapabilities::builder()
.enable_elicitation_with(ElicitationCapability {
form: Some(FormElicitationCapability {
schema_validation: Some(true),
}),
url: None,
}),
..Default::default()
},
client_info: Implementation {
name: "test-client".to_string(),
version: "1.0.0".to_string(),
title: None,
description: None,
website_url: None,
icons: None,
},
};
})
.build(),
Implementation::new("test-client", "1.0.0"),
);
// Verify the structure
assert!(init_param.capabilities.elicitation.is_some());
@ -1148,49 +1115,27 @@ async fn test_capability_checking_logic() {
// Simulate the logic that would be used in supports_elicitation()
// Case 1: Client with elicitation capability
let client_with_capability = InitializeRequestParams {
meta: None,
protocol_version: ProtocolVersion::LATEST,
capabilities: ClientCapabilities {
elicitation: Some(ElicitationCapability {
let client_with_capability = InitializeRequestParams::new(
ClientCapabilities::builder()
.enable_elicitation_with(ElicitationCapability {
form: Some(FormElicitationCapability {
schema_validation: Some(true),
}),
url: None,
}),
..Default::default()
},
client_info: Implementation {
name: "test-client".to_string(),
version: "1.0.0".to_string(),
title: None,
description: None,
website_url: None,
icons: None,
},
};
})
.build(),
Implementation::new("test-client", "1.0.0"),
);
// Simulate supports_elicitation() logic
let supports_elicitation = client_with_capability.capabilities.elicitation.is_some();
assert!(supports_elicitation);
// Case 2: Client without elicitation capability
let client_without_capability = InitializeRequestParams {
meta: None,
protocol_version: ProtocolVersion::LATEST,
capabilities: ClientCapabilities {
elicitation: None,
..Default::default()
},
client_info: Implementation {
name: "test-client".to_string(),
version: "1.0.0".to_string(),
title: None,
description: None,
website_url: None,
icons: None,
},
};
let client_without_capability = InitializeRequestParams::new(
ClientCapabilities::default(),
Implementation::new("test-client", "1.0.0"),
);
let supports_elicitation = client_without_capability.capabilities.elicitation.is_some();
assert!(!supports_elicitation);
}
@ -1910,16 +1855,14 @@ async fn test_url_elicitation_json_rpc_protocol() {
let request = JsonRpcRequest {
jsonrpc: JsonRpcVersion2_0,
id: RequestId::Number(1),
request: CreateElicitationRequest {
method: ElicitationCreateRequestMethod,
params: CreateElicitationRequestParams::UrlElicitationParams {
request: CreateElicitationRequest::new(
CreateElicitationRequestParams::UrlElicitationParams {
meta: None,
message: "Please authorize this action at the following URL".to_string(),
url: "https://auth.example.com/authorize/abc123".to_string(),
elicitation_id: "auth-request-456".to_string(),
},
extensions: Default::default(),
},
),
};
// Test serialization of complete request
@ -1977,11 +1920,7 @@ async fn test_elicitation_completion_notification() {
assert_eq!(deserialized.elicitation_id, "elicit-789");
// Test complete notification structure
let notification = ElicitationCompletionNotification {
method: ElicitationCompletionNotificationMethod,
params: notification_params,
extensions: Default::default(),
};
let notification = ElicitationCompletionNotification::new(notification_params);
let json = serde_json::to_value(&notification).unwrap();
assert_eq!(json["method"], "notifications/elicitation/complete");
@ -2142,15 +2081,14 @@ async fn test_url_elicitation_required_error_code() {
#[tokio::test]
async fn test_client_capabilities_elicitation_modes() {
// Test with form-only capability
let form_only_caps = ClientCapabilities {
elicitation: Some(ElicitationCapability {
let form_only_caps = ClientCapabilities::builder()
.enable_elicitation_with(ElicitationCapability {
form: Some(FormElicitationCapability {
schema_validation: Some(true),
}),
url: None,
}),
..Default::default()
};
})
.build();
let json = serde_json::to_value(&form_only_caps).unwrap();
assert!(json["elicitation"]["form"].is_object());
@ -2160,13 +2098,12 @@ async fn test_client_capabilities_elicitation_modes() {
);
// Test with URL-only capability
let url_only_caps = ClientCapabilities {
elicitation: Some(ElicitationCapability {
let url_only_caps = ClientCapabilities::builder()
.enable_elicitation_with(ElicitationCapability {
form: None,
url: Some(UrlElicitationCapability::default()),
}),
..Default::default()
};
})
.build();
let json = serde_json::to_value(&url_only_caps).unwrap();
assert!(json["elicitation"]["url"].is_object());
@ -2179,15 +2116,14 @@ async fn test_client_capabilities_elicitation_modes() {
);
// Test with both capabilities
let both_caps = ClientCapabilities {
elicitation: Some(ElicitationCapability {
let both_caps = ClientCapabilities::builder()
.enable_elicitation_with(ElicitationCapability {
form: Some(FormElicitationCapability {
schema_validation: Some(false),
}),
url: Some(UrlElicitationCapability::default()),
}),
..Default::default()
};
})
.build();
let json = serde_json::to_value(&both_caps).unwrap();
assert!(json["elicitation"]["form"].is_object());
@ -2201,11 +2137,8 @@ async fn test_elicitation_completion_in_server_notification() {
elicitation_id: "notify-123".to_string(),
};
let completion_notification = ElicitationCompletionNotification {
method: ElicitationCompletionNotificationMethod,
params: notification_param.clone(),
extensions: Default::default(),
};
let completion_notification =
ElicitationCompletionNotification::new(notification_param.clone());
// Test that it's part of ServerNotification
let server_notification =

View file

@ -4,8 +4,8 @@ mod common;
use std::sync::Arc;
use common::handlers::{TestClientHandler, TestServer};
use rmcp::{ClientHandler, ServerHandler};
use common::handlers::TestServer;
use rmcp::ServerHandler;
#[test]
fn test_wrapped_server_handlers() {
@ -16,8 +16,11 @@ fn test_wrapped_server_handlers() {
accepts_server_handler(Arc::new(TestServer::new()));
}
#[cfg(feature = "client")]
#[test]
fn test_wrapped_client_handlers() {
use common::handlers::TestClientHandler;
use rmcp::ClientHandler;
// This test asserts that, when T: ClientHandler, both Box<T> and Arc<T> also implement ClientHandler.
fn accepts_client_handler<H: ClientHandler>(_handler: H) {}

View file

@ -63,7 +63,7 @@ async fn test_logging_spec_compliance() -> anyhow::Result<()> {
] {
client
.peer()
.set_level(SetLevelRequestParams { meta: None, level })
.set_level(SetLevelRequestParams::new(level))
.await?;
// Wait for each message response
@ -121,10 +121,7 @@ async fn test_logging_user_scenarios() -> anyhow::Result<()> {
// Test 1: Error reporting scenario
client
.peer()
.set_level(SetLevelRequestParams {
meta: None,
level: LoggingLevel::Error,
})
.set_level(SetLevelRequestParams::new(LoggingLevel::Error))
.await?;
receive_signal.notified().await; // Wait for response
{
@ -148,10 +145,7 @@ async fn test_logging_user_scenarios() -> anyhow::Result<()> {
// Test 2: Debug scenario
client
.peer()
.set_level(SetLevelRequestParams {
meta: None,
level: LoggingLevel::Debug,
})
.set_level(SetLevelRequestParams::new(LoggingLevel::Debug))
.await?;
receive_signal.notified().await; // Wait for response
{
@ -172,10 +166,7 @@ async fn test_logging_user_scenarios() -> anyhow::Result<()> {
// Test 3: Production monitoring scenario
client
.peer()
.set_level(SetLevelRequestParams {
meta: None,
level: LoggingLevel::Info,
})
.set_level(SetLevelRequestParams::new(LoggingLevel::Info))
.await?;
receive_signal.notified().await; // Wait for response
{
@ -259,7 +250,7 @@ async fn test_logging_edge_cases() -> anyhow::Result<()> {
] {
client
.peer()
.set_level(SetLevelRequestParams { meta: None, level })
.set_level(SetLevelRequestParams::new(level))
.await?;
receive_signal.notified().await;
@ -319,7 +310,7 @@ async fn test_logging_optional_fields() -> anyhow::Result<()> {
for level in [LoggingLevel::Info, LoggingLevel::Debug] {
client
.peer()
.set_level(SetLevelRequestParams { meta: None, level })
.set_level(SetLevelRequestParams::new(level))
.await?;
// Wait for each message response

View file

@ -39,24 +39,10 @@ async fn test_context_inclusion_integration() -> anyhow::Result<()> {
let client = handler.clone().serve(client_transport).await?;
// Test ThisServer context inclusion
let request = ServerRequest::CreateMessageRequest(CreateMessageRequest {
method: Default::default(),
params: CreateMessageRequestParams {
meta: None,
task: None,
messages: vec![SamplingMessage::user_text("test message")],
include_context: Some(ContextInclusion::ThisServer),
model_preferences: None,
system_prompt: None,
temperature: None,
max_tokens: 100,
stop_sequences: None,
metadata: None,
tools: None,
tool_choice: None,
},
extensions: Default::default(),
});
let request = ServerRequest::CreateMessageRequest(CreateMessageRequest::new(
CreateMessageRequestParams::new(vec![SamplingMessage::user_text("test message")], 100)
.with_include_context(ContextInclusion::ThisServer),
));
let result = handler
.handle_request(
@ -90,24 +76,10 @@ async fn test_context_inclusion_integration() -> anyhow::Result<()> {
}
// Test AllServers context inclusion
let request = ServerRequest::CreateMessageRequest(CreateMessageRequest {
method: Default::default(),
params: CreateMessageRequestParams {
meta: None,
task: None,
messages: vec![SamplingMessage::user_text("test message")],
include_context: Some(ContextInclusion::AllServers),
model_preferences: None,
system_prompt: None,
temperature: None,
max_tokens: 100,
stop_sequences: None,
metadata: None,
tools: None,
tool_choice: None,
},
extensions: Default::default(),
});
let request = ServerRequest::CreateMessageRequest(CreateMessageRequest::new(
CreateMessageRequestParams::new(vec![SamplingMessage::user_text("test message")], 100)
.with_include_context(ContextInclusion::AllServers),
));
let result = handler
.handle_request(
@ -141,24 +113,10 @@ async fn test_context_inclusion_integration() -> anyhow::Result<()> {
}
// Test No context inclusion
let request = ServerRequest::CreateMessageRequest(CreateMessageRequest {
method: Default::default(),
params: CreateMessageRequestParams {
meta: None,
task: None,
messages: vec![SamplingMessage::user_text("test message")],
include_context: Some(ContextInclusion::None),
model_preferences: None,
system_prompt: None,
temperature: None,
max_tokens: 100,
stop_sequences: None,
metadata: None,
tools: None,
tool_choice: None,
},
extensions: Default::default(),
});
let request = ServerRequest::CreateMessageRequest(CreateMessageRequest::new(
CreateMessageRequestParams::new(vec![SamplingMessage::user_text("test message")], 100)
.with_include_context(ContextInclusion::None),
));
let result = handler
.handle_request(
@ -212,24 +170,10 @@ async fn test_context_inclusion_ignored_integration() -> anyhow::Result<()> {
let client = handler.clone().serve(client_transport).await?;
// Test that context requests are ignored
let request = ServerRequest::CreateMessageRequest(CreateMessageRequest {
method: Default::default(),
params: CreateMessageRequestParams {
meta: None,
task: None,
messages: vec![SamplingMessage::user_text("test message")],
include_context: Some(ContextInclusion::ThisServer),
model_preferences: None,
system_prompt: None,
temperature: None,
max_tokens: 100,
stop_sequences: None,
metadata: None,
tools: None,
tool_choice: None,
},
extensions: Default::default(),
});
let request = ServerRequest::CreateMessageRequest(CreateMessageRequest::new(
CreateMessageRequestParams::new(vec![SamplingMessage::user_text("test message")], 100)
.with_include_context(ContextInclusion::ThisServer),
));
let result = handler
.handle_request(
@ -282,27 +226,16 @@ async fn test_message_sequence_integration() -> anyhow::Result<()> {
let handler = TestClientHandler::new(true, true);
let client = handler.clone().serve(client_transport).await?;
let request = ServerRequest::CreateMessageRequest(CreateMessageRequest {
method: Default::default(),
params: CreateMessageRequestParams {
meta: None,
task: None,
messages: vec![
let request = ServerRequest::CreateMessageRequest(CreateMessageRequest::new(
CreateMessageRequestParams::new(
vec![
SamplingMessage::user_text("first message"),
SamplingMessage::assistant_text("second message"),
],
include_context: Some(ContextInclusion::ThisServer),
model_preferences: None,
system_prompt: None,
temperature: None,
max_tokens: 100,
stop_sequences: None,
metadata: None,
tools: None,
tool_choice: None,
},
extensions: Default::default(),
});
100,
)
.with_include_context(ContextInclusion::ThisServer),
));
let result = handler
.handle_request(
@ -359,28 +292,16 @@ async fn test_message_sequence_validation_integration() -> anyhow::Result<()> {
let client = handler.clone().serve(client_transport).await?;
// Test valid sequence: User -> Assistant -> User
let request = ServerRequest::CreateMessageRequest(CreateMessageRequest {
method: Default::default(),
params: CreateMessageRequestParams {
meta: None,
task: None,
messages: vec![
let request = ServerRequest::CreateMessageRequest(CreateMessageRequest::new(
CreateMessageRequestParams::new(
vec![
SamplingMessage::user_text("first user message"),
SamplingMessage::assistant_text("first assistant response"),
SamplingMessage::user_text("second user message"),
],
include_context: None,
model_preferences: None,
system_prompt: None,
temperature: None,
max_tokens: 100,
stop_sequences: None,
metadata: None,
tools: None,
tool_choice: None,
},
extensions: Default::default(),
});
100,
),
));
let result = handler
.handle_request(
@ -398,24 +319,12 @@ async fn test_message_sequence_validation_integration() -> anyhow::Result<()> {
assert!(matches!(result, ClientResult::CreateMessageResult(_)));
// Test invalid: No user message
let request = ServerRequest::CreateMessageRequest(CreateMessageRequest {
method: Default::default(),
params: CreateMessageRequestParams {
meta: None,
task: None,
messages: vec![SamplingMessage::assistant_text("assistant message")],
include_context: None,
model_preferences: None,
system_prompt: None,
temperature: None,
max_tokens: 100,
stop_sequences: None,
metadata: None,
tools: None,
tool_choice: None,
},
extensions: Default::default(),
});
let request = ServerRequest::CreateMessageRequest(CreateMessageRequest::new(
CreateMessageRequestParams::new(
vec![SamplingMessage::assistant_text("assistant message")],
100,
),
));
let result = handler
.handle_request(
@ -452,24 +361,10 @@ async fn test_selective_context_handling_integration() -> anyhow::Result<()> {
let client = handler.clone().serve(client_transport).await?;
// Test ThisServer is honored
let request = ServerRequest::CreateMessageRequest(CreateMessageRequest {
method: Default::default(),
params: CreateMessageRequestParams {
meta: None,
task: None,
messages: vec![SamplingMessage::user_text("test message")],
include_context: Some(ContextInclusion::ThisServer),
model_preferences: None,
system_prompt: None,
temperature: None,
max_tokens: 100,
stop_sequences: None,
metadata: None,
tools: None,
tool_choice: None,
},
extensions: Default::default(),
});
let request = ServerRequest::CreateMessageRequest(CreateMessageRequest::new(
CreateMessageRequestParams::new(vec![SamplingMessage::user_text("test message")], 100)
.with_include_context(ContextInclusion::ThisServer),
));
let result = handler
.handle_request(
@ -501,24 +396,10 @@ async fn test_selective_context_handling_integration() -> anyhow::Result<()> {
}
// Test AllServers is ignored
let request = ServerRequest::CreateMessageRequest(CreateMessageRequest {
method: Default::default(),
params: CreateMessageRequestParams {
meta: None,
task: None,
messages: vec![SamplingMessage::user_text("test message")],
include_context: Some(ContextInclusion::AllServers),
model_preferences: None,
system_prompt: None,
temperature: None,
max_tokens: 100,
stop_sequences: None,
metadata: None,
tools: None,
tool_choice: None,
},
extensions: Default::default(),
});
let request = ServerRequest::CreateMessageRequest(CreateMessageRequest::new(
CreateMessageRequestParams::new(vec![SamplingMessage::user_text("test message")], 100)
.with_include_context(ContextInclusion::AllServers),
));
let result = handler
.handle_request(
@ -567,24 +448,10 @@ async fn test_context_inclusion() -> anyhow::Result<()> {
let client = handler.clone().serve(client_transport).await?;
// Test context handling
let request = ServerRequest::CreateMessageRequest(CreateMessageRequest {
method: Default::default(),
params: CreateMessageRequestParams {
meta: None,
task: None,
messages: vec![SamplingMessage::user_text("test")],
include_context: Some(ContextInclusion::ThisServer),
model_preferences: None,
system_prompt: None,
temperature: None,
max_tokens: 100,
stop_sequences: None,
metadata: None,
tools: None,
tool_choice: None,
},
extensions: Default::default(),
});
let request = ServerRequest::CreateMessageRequest(CreateMessageRequest::new(
CreateMessageRequestParams::new(vec![SamplingMessage::user_text("test")], 100)
.with_include_context(ContextInclusion::ThisServer),
));
let result = handler
.handle_request(

View file

@ -15,14 +15,13 @@ pub struct Server {}
impl ServerHandler for Server {
fn get_info(&self) -> ServerInfo {
ServerInfo {
capabilities: ServerCapabilities::builder()
ServerInfo::new(
ServerCapabilities::builder()
.enable_resources()
.enable_resources_subscribe()
.enable_resources_list_changed()
.build(),
..Default::default()
}
)
}
async fn subscribe(
@ -87,10 +86,7 @@ async fn test_server_notification() -> anyhow::Result<()> {
.serve(client_transport)
.await?;
client
.subscribe(SubscribeRequestParams {
meta: None,
uri: "test://test-resource".to_owned(),
})
.subscribe(SubscribeRequestParams::new("test://test-resource"))
.await?;
receive_signal.notified().await;
client.cancel().await?;

View file

@ -107,12 +107,9 @@ async fn test_progress_subscriber() -> anyhow::Result<()> {
let client_service = client.serve(transport_client).await?;
let handle = client_service
.send_cancellable_request(
ClientRequest::CallToolRequest(Request::new(CallToolRequestParams {
meta: None,
name: "some_progress".into(),
arguments: None,
task: None,
})),
ClientRequest::CallToolRequest(Request::new(CallToolRequestParams::new(
"some_progress",
))),
PeerRequestOptions::no_options(),
)
.await?;

View file

@ -109,13 +109,11 @@ async fn complex_args_prompt(
_server: &TestServer,
_args: Parameters<ComplexArgs>,
) -> GetPromptResult {
GetPromptResult {
description: Some("Complex args result".to_string()),
messages: vec![PromptMessage::new_text(
PromptMessageRole::Assistant,
"Complex response",
)],
}
GetPromptResult::new(vec![PromptMessage::new_text(
PromptMessageRole::Assistant,
"Complex response",
)])
.with_description("Complex args result")
}
// Test sync prompt

View file

@ -107,22 +107,20 @@ impl<DS: DataService> GenericServer<DS> {
#[prompt(description = "Get contextual help from the service")]
async fn get_help(&self) -> GetPromptResult {
let context = self.data_service.get_context();
GetPromptResult {
description: Some("Contextual help based on service data".to_string()),
messages: vec![
PromptMessage::new_text(
PromptMessageRole::User,
"I need help with the current context.".to_string(),
GetPromptResult::new(vec![
PromptMessage::new_text(
PromptMessageRole::User,
"I need help with the current context.".to_string(),
),
PromptMessage::new_text(
PromptMessageRole::Assistant,
format!(
"Based on the context '{}', here's how I can help...",
context
),
PromptMessage::new_text(
PromptMessageRole::Assistant,
format!(
"Based on the context '{}', here's how I can help...",
context
),
),
],
}
),
])
.with_description("Contextual help based on service data")
}
}
@ -250,13 +248,11 @@ impl OptionalSchemaTester {
None => "Received null count".to_string(),
};
GetPromptResult {
description: Some("Test result for optional i64".to_string()),
messages: vec![PromptMessage::new_text(
PromptMessageRole::Assistant,
message,
)],
}
GetPromptResult::new(vec![PromptMessage::new_text(
PromptMessageRole::Assistant,
message,
)])
.with_description("Test result for optional i64")
}
}
@ -327,10 +323,8 @@ async fn test_optional_i64_field_with_null_input() -> anyhow::Result<()> {
// Test null case
let result = client
.get_prompt(GetPromptRequestParams {
meta: None,
name: "test_optional_i64".into(),
arguments: Some(
.get_prompt(
GetPromptRequestParams::new("test_optional_i64").with_arguments(
serde_json::json!({
"count": null,
"mandatory_field": "test_null"
@ -339,7 +333,7 @@ async fn test_optional_i64_field_with_null_input() -> anyhow::Result<()> {
.unwrap()
.clone(),
),
})
)
.await?;
let result_text = match &result.messages.first().unwrap().content {
@ -354,10 +348,8 @@ async fn test_optional_i64_field_with_null_input() -> anyhow::Result<()> {
// Test Some case
let some_result = client
.get_prompt(GetPromptRequestParams {
meta: None,
name: "test_optional_i64".into(),
arguments: Some(
.get_prompt(
GetPromptRequestParams::new("test_optional_i64").with_arguments(
serde_json::json!({
"count": 42,
"mandatory_field": "test_some"
@ -366,7 +358,7 @@ async fn test_optional_i64_field_with_null_input() -> anyhow::Result<()> {
.unwrap()
.clone(),
),
})
)
.await?;
let some_result_text = match &some_result.messages.first().unwrap().content {

View file

@ -64,13 +64,11 @@ async fn async_function(Parameters(Request { fields }): Parameters<Request>) ->
#[rmcp::prompt]
fn async_function2<T>(_callee: &TestHandler<T>) -> BoxFuture<'_, GetPromptResult> {
Box::pin(async move {
GetPromptResult {
description: Some("Async function 2".to_string()),
messages: vec![PromptMessage::new_text(
PromptMessageRole::Assistant,
"Async function 2 response",
)],
}
GetPromptResult::new(vec![PromptMessage::new_text(
PromptMessageRole::Assistant,
"Async function 2 response",
)])
.with_description("Async function 2")
})
}

View file

@ -82,10 +82,10 @@ fn test_resource_link_roundtrip() {
}
// Test with prompt message
let prompt_message = PromptMessage {
role: PromptMessageRole::User,
content: PromptMessageContent::resource_link(resource.no_annotation()),
};
let prompt_message = PromptMessage::new(
PromptMessageRole::User,
PromptMessageContent::resource_link(resource.no_annotation()),
);
let prompt_json = serde_json::to_string(&prompt_message).unwrap();
let prompt_deserialized: PromptMessage = serde_json::from_str(&prompt_json).unwrap();

View file

@ -23,27 +23,20 @@ async fn test_basic_sampling_message_creation() -> Result<()> {
#[tokio::test]
async fn test_sampling_request_params() -> Result<()> {
let params = CreateMessageRequestParams {
meta: None,
task: None,
messages: vec![SamplingMessage::user_text("Hello, world!")],
model_preferences: Some(ModelPreferences {
hints: Some(vec![ModelHint {
name: Some("claude".to_string()),
}]),
cost_priority: Some(0.5),
speed_priority: Some(0.8),
intelligence_priority: Some(0.7),
}),
system_prompt: Some("You are a helpful assistant.".to_string()),
temperature: Some(0.7),
max_tokens: 100,
stop_sequences: Some(vec!["STOP".to_string()]),
include_context: Some(ContextInclusion::None),
metadata: Some(serde_json::json!({"test": "value"})),
tools: None,
tool_choice: None,
};
let params =
CreateMessageRequestParams::new(vec![SamplingMessage::user_text("Hello, world!")], 100)
.with_model_preferences(
ModelPreferences::new()
.with_hints(vec![ModelHint::new("claude")])
.with_cost_priority(0.5)
.with_speed_priority(0.8)
.with_intelligence_priority(0.7),
)
.with_system_prompt("You are a helpful assistant.")
.with_temperature(0.7)
.with_stop_sequences(vec!["STOP".to_string()])
.with_include_context(ContextInclusion::None)
.with_metadata(serde_json::json!({"test": "value"}));
let json = serde_json::to_string(&params)?;
let deserialized: CreateMessageRequestParams = serde_json::from_str(&json)?;
@ -58,11 +51,11 @@ async fn test_sampling_request_params() -> Result<()> {
#[tokio::test]
async fn test_sampling_result_structure() -> Result<()> {
let result = CreateMessageResult {
message: SamplingMessage::assistant_text("The capital of France is Paris."),
model: "test-model".to_string(),
stop_reason: Some(CreateMessageResult::STOP_REASON_END_TURN.to_string()),
};
let result = CreateMessageResult::new(
SamplingMessage::assistant_text("The capital of France is Paris."),
"test-model".to_string(),
)
.with_stop_reason(Some(CreateMessageResult::STOP_REASON_END_TURN.to_string()));
let json = serde_json::to_string(&result)?;
let deserialized: CreateMessageResult = serde_json::from_str(&json)?;
@ -112,31 +105,22 @@ async fn test_sampling_integration_with_test_handlers() -> Result<()> {
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
let request = ServerRequest::CreateMessageRequest(CreateMessageRequest {
method: Default::default(),
params: CreateMessageRequestParams {
meta: None,
task: None,
messages: vec![SamplingMessage::user_text("What is the capital of France?")],
include_context: Some(ContextInclusion::ThisServer),
model_preferences: Some(ModelPreferences {
hints: Some(vec![ModelHint {
name: Some("test-model".to_string()),
}]),
cost_priority: Some(0.5),
speed_priority: Some(0.8),
intelligence_priority: Some(0.7),
}),
system_prompt: Some("You are a helpful assistant.".to_string()),
temperature: Some(0.7),
max_tokens: 100,
stop_sequences: None,
metadata: None,
tools: None,
tool_choice: None,
},
extensions: Default::default(),
});
let request = ServerRequest::CreateMessageRequest(CreateMessageRequest::new(
CreateMessageRequestParams::new(
vec![SamplingMessage::user_text("What is the capital of France?")],
100,
)
.with_include_context(ContextInclusion::ThisServer)
.with_model_preferences(
ModelPreferences::new()
.with_hints(vec![ModelHint::new("test-model")])
.with_cost_priority(0.5)
.with_speed_priority(0.8)
.with_intelligence_priority(0.7),
)
.with_system_prompt("You are a helpful assistant.")
.with_temperature(0.7),
));
let result = handler
.handle_request(
@ -196,24 +180,10 @@ async fn test_sampling_no_context_inclusion() -> Result<()> {
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
let request = ServerRequest::CreateMessageRequest(CreateMessageRequest {
method: Default::default(),
params: CreateMessageRequestParams {
meta: None,
task: None,
messages: vec![SamplingMessage::user_text("Hello")],
include_context: Some(ContextInclusion::None),
model_preferences: None,
system_prompt: None,
temperature: None,
max_tokens: 50,
stop_sequences: None,
metadata: None,
tools: None,
tool_choice: None,
},
extensions: Default::default(),
});
let request = ServerRequest::CreateMessageRequest(CreateMessageRequest::new(
CreateMessageRequestParams::new(vec![SamplingMessage::user_text("Hello")], 50)
.with_include_context(ContextInclusion::None),
));
let result = handler
.handle_request(
@ -269,26 +239,15 @@ async fn test_sampling_error_invalid_message_sequence() -> Result<()> {
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
let request = ServerRequest::CreateMessageRequest(CreateMessageRequest {
method: Default::default(),
params: CreateMessageRequestParams {
meta: None,
task: None,
messages: vec![SamplingMessage::assistant_text(
let request = ServerRequest::CreateMessageRequest(CreateMessageRequest::new(
CreateMessageRequestParams::new(
vec![SamplingMessage::assistant_text(
"I'm an assistant message without a user message",
)],
include_context: Some(ContextInclusion::None),
model_preferences: None,
system_prompt: None,
temperature: None,
max_tokens: 50,
stop_sequences: None,
metadata: None,
tools: None,
tool_choice: None,
},
extensions: Default::default(),
});
50,
)
.with_include_context(ContextInclusion::None),
));
let result = handler
.handle_request(
@ -357,22 +316,14 @@ async fn test_sampling_with_tools() -> Result<()> {
),
);
let params = CreateMessageRequestParams {
meta: None,
task: None,
messages: vec![SamplingMessage::user_text(
let params = CreateMessageRequestParams::new(
vec![SamplingMessage::user_text(
"What's the weather in San Francisco?",
)],
model_preferences: None,
system_prompt: None,
include_context: None,
temperature: None,
max_tokens: 100,
stop_sequences: None,
metadata: None,
tools: Some(vec![tool]),
tool_choice: Some(ToolChoice::auto()),
};
100,
)
.with_tools(vec![tool])
.with_tool_choice(ToolChoice::auto());
let json = serde_json::to_string(&params)?;
let deserialized: CreateMessageRequestParams = serde_json::from_str(&json)?;
@ -472,8 +423,8 @@ async fn test_sampling_message_with_tool_result() -> Result<()> {
#[tokio::test]
async fn test_create_message_result_tool_use_stop_reason() -> Result<()> {
let result = CreateMessageResult {
message: SamplingMessage::assistant_tool_use(
let result = CreateMessageResult::new(
SamplingMessage::assistant_tool_use(
"call_123",
"get_weather",
serde_json::json!({
@ -483,9 +434,9 @@ async fn test_create_message_result_tool_use_stop_reason() -> Result<()> {
.unwrap()
.clone(),
),
model: "test-model".to_string(),
stop_reason: Some(CreateMessageResult::STOP_REASON_TOOL_USE.to_string()),
};
"test-model".to_string(),
)
.with_stop_reason(Some(CreateMessageResult::STOP_REASON_TOOL_USE.to_string()));
let json = serde_json::to_string(&result)?;
let deserialized: CreateMessageResult = serde_json::from_str(&json)?;
@ -623,23 +574,13 @@ async fn test_content_conversion_unsupported_variants() {
#[tokio::test]
async fn test_validate_rejects_tool_use_in_user_message() {
let params = CreateMessageRequestParams {
meta: None,
task: None,
messages: vec![SamplingMessage::new(
let params = CreateMessageRequestParams::new(
vec![SamplingMessage::new(
Role::User,
SamplingMessageContent::tool_use("call_1", "some_tool", Default::default()),
)],
model_preferences: None,
system_prompt: None,
include_context: None,
temperature: None,
max_tokens: 100,
stop_sequences: None,
metadata: None,
tools: None,
tool_choice: None,
};
100,
);
let err = params.validate().unwrap_err();
assert!(
@ -650,23 +591,13 @@ async fn test_validate_rejects_tool_use_in_user_message() {
#[tokio::test]
async fn test_validate_rejects_tool_result_in_assistant_message() {
let params = CreateMessageRequestParams {
meta: None,
task: None,
messages: vec![SamplingMessage::new(
let params = CreateMessageRequestParams::new(
vec![SamplingMessage::new(
Role::Assistant,
SamplingMessageContent::tool_result("call_1", vec![Content::text("result")]),
)],
model_preferences: None,
system_prompt: None,
include_context: None,
temperature: None,
max_tokens: 100,
stop_sequences: None,
metadata: None,
tools: None,
tool_choice: None,
};
100,
);
let err = params.validate().unwrap_err();
assert!(
@ -677,26 +608,16 @@ async fn test_validate_rejects_tool_result_in_assistant_message() {
#[tokio::test]
async fn test_validate_rejects_mixed_content_with_tool_result() {
let params = CreateMessageRequestParams {
meta: None,
task: None,
messages: vec![SamplingMessage::new_multiple(
let params = CreateMessageRequestParams::new(
vec![SamplingMessage::new_multiple(
Role::User,
vec![
SamplingMessageContent::tool_result("call_1", vec![Content::text("result")]),
SamplingMessageContent::text("some extra text"),
],
)],
model_preferences: None,
system_prompt: None,
include_context: None,
temperature: None,
max_tokens: 100,
stop_sequences: None,
metadata: None,
tools: None,
tool_choice: None,
};
100,
);
let err = params.validate().unwrap_err();
assert!(
@ -707,23 +628,13 @@ async fn test_validate_rejects_mixed_content_with_tool_result() {
#[tokio::test]
async fn test_validate_rejects_unbalanced_tool_use_result() {
let params = CreateMessageRequestParams {
meta: None,
task: None,
messages: vec![
let params = CreateMessageRequestParams::new(
vec![
SamplingMessage::user_text("Hello"),
SamplingMessage::assistant_tool_use("call_1", "some_tool", Default::default()),
],
model_preferences: None,
system_prompt: None,
include_context: None,
temperature: None,
max_tokens: 100,
stop_sequences: None,
metadata: None,
tools: None,
tool_choice: None,
};
100,
);
let err = params.validate().unwrap_err();
assert!(
@ -734,23 +645,13 @@ async fn test_validate_rejects_unbalanced_tool_use_result() {
#[tokio::test]
async fn test_validate_rejects_tool_result_without_matching_use() {
let params = CreateMessageRequestParams {
meta: None,
task: None,
messages: vec![
let params = CreateMessageRequestParams::new(
vec![
SamplingMessage::user_text("Hello"),
SamplingMessage::user_tool_result("nonexistent_call", vec![Content::text("result")]),
],
model_preferences: None,
system_prompt: None,
include_context: None,
temperature: None,
max_tokens: 100,
stop_sequences: None,
metadata: None,
tools: None,
tool_choice: None,
};
100,
);
let err = params.validate().unwrap_err();
assert!(
@ -761,10 +662,8 @@ async fn test_validate_rejects_tool_result_without_matching_use() {
#[tokio::test]
async fn test_validate_accepts_valid_tool_conversation() {
let params = CreateMessageRequestParams {
meta: None,
task: None,
messages: vec![
let params = CreateMessageRequestParams::new(
vec![
SamplingMessage::user_text("What's the weather?"),
SamplingMessage::assistant_tool_use(
"call_1",
@ -777,27 +676,19 @@ async fn test_validate_accepts_valid_tool_conversation() {
SamplingMessage::user_tool_result("call_1", vec![Content::text("72°F and sunny")]),
SamplingMessage::assistant_text("It's 72°F and sunny in SF."),
],
model_preferences: None,
system_prompt: None,
include_context: None,
temperature: None,
max_tokens: 100,
stop_sequences: None,
metadata: None,
tools: None,
tool_choice: None,
};
100,
);
assert!(params.validate().is_ok());
}
#[tokio::test]
async fn test_create_message_result_validate_rejects_user_role() {
let result = CreateMessageResult {
message: SamplingMessage::user_text("This should not be a user message"),
model: "test-model".to_string(),
stop_reason: Some(CreateMessageResult::STOP_REASON_END_TURN.to_string()),
};
let result = CreateMessageResult::new(
SamplingMessage::user_text("This should not be a user message"),
"test-model".to_string(),
)
.with_stop_reason(Some(CreateMessageResult::STOP_REASON_END_TURN.to_string()));
let err = result.validate().unwrap_err();
assert!(
@ -808,11 +699,11 @@ async fn test_create_message_result_validate_rejects_user_role() {
#[tokio::test]
async fn test_create_message_result_validate_accepts_assistant_role() {
let result = CreateMessageResult {
message: SamplingMessage::assistant_text("Hello!"),
model: "test-model".to_string(),
stop_reason: Some(CreateMessageResult::STOP_REASON_END_TURN.to_string()),
};
let result = CreateMessageResult::new(
SamplingMessage::assistant_text("Hello!"),
"test-model".to_string(),
)
.with_stop_reason(Some(CreateMessageResult::STOP_REASON_END_TURN.to_string()));
assert!(result.validate().is_ok());
}

View file

@ -17,7 +17,7 @@ use std::time::Duration;
use futures::StreamExt;
use rmcp::{
RoleServer, ServerHandler,
model::{Implementation, ProtocolVersion, ServerCapabilities, ServerInfo, ToolsCapability},
model::{Implementation, ServerCapabilities, ServerInfo, ToolsCapability},
service::NotificationContext,
transport::streamable_http_server::{
StreamableHttpServerConfig, StreamableHttpService, session::local::LocalSessionManager,
@ -45,20 +45,14 @@ impl TestServer {
impl ServerHandler for TestServer {
fn get_info(&self) -> ServerInfo {
ServerInfo {
protocol_version: ProtocolVersion::LATEST,
capabilities: ServerCapabilities::builder()
ServerInfo::new(
ServerCapabilities::builder()
.enable_tools_with(ToolsCapability {
list_changed: Some(true),
})
.build(),
server_info: Implementation {
name: "test-server".to_string(),
version: "1.0.0".to_string(),
..Default::default()
},
instructions: None,
}
)
.with_server_info(Implementation::new("test-server", "1.0.0"))
}
async fn on_initialized(&self, context: NotificationContext<RoleServer>) {

View file

@ -327,12 +327,7 @@ async fn test_empty_content_deserializes_as_call_tool_result_variant() {
#[tokio::test]
async fn test_empty_content_roundtrip() {
let result = CallToolResult {
content: vec![],
structured_content: None,
is_error: Some(false),
meta: None,
};
let result = CallToolResult::success(vec![]);
let v = serde_json::to_value(&result).unwrap();
assert_eq!(v["content"], json!([]));
let deserialized: CallToolResult = serde_json::from_value(v).unwrap();

View file

@ -5,6 +5,7 @@
//! - `Required`: MUST be invoked as a task, returns -32601 otherwise
//! - `Forbidden`: MUST NOT be invoked as a task, returns error otherwise
//! - `Optional`: MAY be invoked either way
#![cfg(feature = "client")]
use rmcp::{
ClientHandler, ServerHandler, ServiceError, ServiceExt,
@ -93,12 +94,7 @@ async fn test_required_task_tool_without_task_returns_method_not_found() -> anyh
// Call the task-required tool without a task - should fail with -32601
let result = client
.call_tool(CallToolRequestParams {
meta: None,
name: "required_task_tool".into(),
arguments: None,
task: None, // No task provided!
})
.call_tool(CallToolRequestParams::new("required_task_tool"))
.await;
// Should be an error with code -32601 (METHOD_NOT_FOUND)
@ -147,12 +143,7 @@ async fn test_forbidden_task_tool_with_task_returns_error() -> anyhow::Result<()
// Call the forbidden task tool WITH a task - should fail
let result = client
.call_tool(CallToolRequestParams {
meta: None,
name: "forbidden_task_tool".into(),
arguments: None,
task: make_task(), // Task provided but forbidden!
})
.call_tool(CallToolRequestParams::new("forbidden_task_tool").with_task(make_task()))
.await;
// Should be an error with code INVALID_PARAMS
@ -201,12 +192,7 @@ async fn test_forbidden_task_tool_without_task_succeeds() -> anyhow::Result<()>
// Call the forbidden task tool WITHOUT a task - should succeed
let result = client
.call_tool(CallToolRequestParams {
meta: None,
name: "forbidden_task_tool".into(),
arguments: None,
task: None, // No task - allowed for forbidden
})
.call_tool(CallToolRequestParams::new("forbidden_task_tool"))
.await;
assert!(
@ -242,12 +228,7 @@ async fn test_optional_task_tool_without_task_succeeds() -> anyhow::Result<()> {
// Call the optional task tool WITHOUT a task - should succeed
let result = client
.call_tool(CallToolRequestParams {
meta: None,
name: "optional_task_tool".into(),
arguments: None,
task: None, // No task - allowed for optional
})
.call_tool(CallToolRequestParams::new("optional_task_tool"))
.await;
assert!(

View file

@ -308,10 +308,8 @@ async fn test_optional_i64_field_with_null_input() -> anyhow::Result<()> {
// Test null case
let result = client
.call_tool(CallToolRequestParams {
meta: None,
name: "test_optional_i64".into(),
arguments: Some(
.call_tool(
CallToolRequestParams::new("test_optional_i64").with_arguments(
serde_json::json!({
"count": null,
"mandatory_field": "test_null"
@ -320,8 +318,7 @@ async fn test_optional_i64_field_with_null_input() -> anyhow::Result<()> {
.unwrap()
.clone(),
),
task: None,
})
)
.await?;
let result_text = result
@ -338,10 +335,8 @@ async fn test_optional_i64_field_with_null_input() -> anyhow::Result<()> {
// Test Some case
let some_result = client
.call_tool(CallToolRequestParams {
meta: None,
name: "test_optional_i64".into(),
arguments: Some(
.call_tool(
CallToolRequestParams::new("test_optional_i64").with_arguments(
serde_json::json!({
"count": 42,
"mandatory_field": "test_some"
@ -350,8 +345,7 @@ async fn test_optional_i64_field_with_null_input() -> anyhow::Result<()> {
.unwrap()
.clone(),
),
task: None,
})
)
.await?;
let some_result_text = some_result

Some files were not shown because too many files have changed in this diff Show more