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:
parent
79834b6211
commit
f63718d202
21 changed files with 1568 additions and 1110 deletions
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
// cargo test --features "server client" --package rmcp test_client_initialization
|
||||
#![cfg(feature = "client")]
|
||||
|
||||
mod common;
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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(¬ification).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 =
|
||||
|
|
|
|||
|
|
@ -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) {}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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?;
|
||||
|
|
|
|||
|
|
@ -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?;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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(¶ms)?;
|
||||
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(¶ms)?;
|
||||
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());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>) {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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!(
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Reference in a new issue