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
63 changed files with 1568 additions and 1110 deletions

View file

@ -42,9 +42,9 @@ impl ResolvedPromptAttribute {
meta,
} = self;
let description = if let Some(description) = description {
quote! { Some(#description.into()) }
quote! { Some::<String>(#description.into()) }
} else {
quote! { None }
quote! { None::<String> }
};
let title = if let Some(title) = title {
quote! { Some(#title.into()) }
@ -63,14 +63,14 @@ impl ResolvedPromptAttribute {
};
let tokens = quote! {
pub fn #fn_ident() -> rmcp::model::Prompt {
rmcp::model::Prompt {
name: #name.into(),
description: #description,
arguments: #arguments,
title: #title,
icons: #icons,
meta: #meta,
}
rmcp::model::Prompt::from_raw(
#name,
#description,
#arguments,
)
.with_title(#title)
.with_icons(#icons)
.with_meta(#meta)
}
};
syn::parse2::<ImplItemFn>(tokens)

View file

@ -42,23 +42,16 @@ pub fn task_handler(attr: TokenStream, input: TokenStream) -> syn::Result<TokenS
.into_iter()
.map(|task_id| {
let timestamp = rmcp::task_manager::current_timestamp();
rmcp::model::Task {
rmcp::model::Task::new(
task_id,
status: rmcp::model::TaskStatus::Working,
status_message: None,
created_at: timestamp.clone(),
last_updated_at: timestamp,
ttl: None,
poll_interval: None,
}
rmcp::model::TaskStatus::Working,
timestamp.clone(),
timestamp,
)
})
.collect::<Vec<_>>();
Ok(rmcp::model::ListTasksResult {
tasks,
next_cursor: None,
total: Some(total),
})
Ok(rmcp::model::ListTasksResult::new(tasks))
}
};
item_impl.items.push(syn::parse2::<ImplItem>(list_fn)?);
@ -106,17 +99,14 @@ pub fn task_handler(attr: TokenStream, input: TokenStream) -> syn::Result<TokenS
))?;
let timestamp = current_timestamp();
let task = rmcp::model::Task {
let task = rmcp::model::Task::new(
task_id,
status: rmcp::model::TaskStatus::Working,
status_message: Some("Task accepted".to_string()),
created_at: timestamp.clone(),
last_updated_at: timestamp,
ttl: None,
poll_interval: None,
};
rmcp::model::TaskStatus::Working,
timestamp.clone(),
timestamp,
).with_status_message("Task accepted");
Ok(rmcp::model::CreateTaskResult { task })
Ok(rmcp::model::CreateTaskResult::new(task))
}
};
item_impl.items.push(syn::parse2::<ImplItem>(enqueue_fn)?);
@ -151,15 +141,15 @@ pub fn task_handler(attr: TokenStream, input: TokenStream) -> syn::Result<TokenS
Err(_) => rmcp::model::TaskStatus::Failed,
};
let timestamp = current_timestamp();
let task = rmcp::model::Task {
let mut task = rmcp::model::Task::new(
task_id,
status,
status_message: None,
created_at: timestamp.clone(),
last_updated_at: timestamp,
ttl: completed_result.descriptor.ttl,
poll_interval: None,
};
timestamp.clone(),
timestamp,
);
if let Some(ttl) = completed_result.descriptor.ttl {
task = task.with_ttl(ttl);
}
return Ok(rmcp::model::GetTaskResult { meta: None, task });
}
@ -167,15 +157,12 @@ pub fn task_handler(attr: TokenStream, input: TokenStream) -> syn::Result<TokenS
let running = processor.list_running();
if running.into_iter().any(|id| id == task_id) {
let timestamp = current_timestamp();
let task = rmcp::model::Task {
let task = rmcp::model::Task::new(
task_id,
status: rmcp::model::TaskStatus::Working,
status_message: None,
created_at: timestamp.clone(),
last_updated_at: timestamp,
ttl: None,
poll_interval: None,
};
rmcp::model::TaskStatus::Working,
timestamp.clone(),
timestamp,
);
return Ok(rmcp::model::GetTaskResult { meta: None, task });
}
@ -207,7 +194,7 @@ pub fn task_handler(attr: TokenStream, input: TokenStream) -> syn::Result<TokenS
match &tool.result {
Ok(call_tool) => {
let value = ::serde_json::to_value(call_tool).unwrap_or(::serde_json::Value::Null);
return Ok(rmcp::model::GetTaskPayloadResult(value));
return Ok(rmcp::model::GetTaskPayloadResult::new(value));
}
Err(err) => return Err(McpError::internal_error(
format!("task failed: {}", err),
@ -254,15 +241,12 @@ pub fn task_handler(attr: TokenStream, input: TokenStream) -> syn::Result<TokenS
if processor.cancel_task(&task_id) {
let timestamp = current_timestamp();
let task = rmcp::model::Task {
let task = rmcp::model::Task::new(
task_id,
status: rmcp::model::TaskStatus::Cancelled,
status_message: None,
created_at: timestamp.clone(),
last_updated_at: timestamp,
ttl: None,
poll_interval: None,
};
rmcp::model::TaskStatus::Cancelled,
timestamp.clone(),
timestamp,
);
return Ok(rmcp::model::CancelTaskResult { meta: None, task });
}

View file

@ -159,17 +159,17 @@ impl ResolvedToolAttribute {
let tokens = quote! {
#doc_attr
pub fn #fn_ident() -> rmcp::model::Tool {
rmcp::model::Tool {
name: #name.into(),
title: #title,
description: #description,
input_schema: #input_schema,
output_schema: #output_schema,
annotations: #annotations,
execution: #execution,
icons: #icons,
meta: #meta,
}
rmcp::model::Tool::new_with_raw(
#name,
#description,
#input_schema,
)
.with_title(#title)
.with_raw_output_schema(#output_schema)
.with_annotations(#annotations)
.with_execution(#execution)
.with_icons(#icons)
.with_meta(#meta)
}
};
syn::parse2::<ImplItemFn>(tokens)
@ -260,13 +260,13 @@ pub fn tool(attr: TokenStream, input: TokenStream) -> syn::Result<TokenStream> {
let idempotent_hint = wrap_option(idempotent_hint);
let open_world_hint = wrap_option(open_world_hint);
let token_stream = quote! {
Some(rmcp::model::ToolAnnotations {
title: #title,
read_only_hint: #read_only_hint,
destructive_hint: #destructive_hint,
idempotent_hint: #idempotent_hint,
open_world_hint: #open_world_hint,
})
Some(rmcp::model::ToolAnnotations::from_raw(
#title,
#read_only_hint,
#destructive_hint,
#idempotent_hint,
#open_world_hint,
))
};
syn::parse2::<Expr>(token_stream)?
} else {
@ -296,9 +296,9 @@ pub fn tool(attr: TokenStream, input: TokenStream) -> syn::Result<TokenStream> {
};
let token_stream = quote! {
Some(rmcp::model::ToolExecution {
task_support: #task_support_expr,
})
Some(rmcp::model::ToolExecution::from_raw(
#task_support_expr,
))
};
syn::parse2::<Expr>(token_stream)?
} else {

View file

@ -19,7 +19,7 @@
Creating a server with tools is simple using the `#[tool]` macro:
```rust,no_run
```rust,ignore
use rmcp::{
ServerHandler, ServiceExt,
handler::server::tool::ToolRouter,
@ -68,11 +68,8 @@ impl Counter {
#[tool_handler]
impl ServerHandler for Counter {
fn get_info(&self) -> ServerInfo {
ServerInfo {
instructions: Some("A simple counter that tallies the number of times the increment tool has been used".into()),
capabilities: ServerCapabilities::builder().enable_tools().build(),
..Default::default()
}
ServerInfo::new(ServerCapabilities::builder().enable_tools().build())
.with_instructions("A simple counter that tallies the number of times the increment tool has been used")
}
}
@ -147,7 +144,7 @@ To expose task support, enable the `tasks` capability when building `ServerCapab
Creating a client to interact with a server:
```rust,no_run
```rust,ignore
use rmcp::{
ServiceExt,
model::CallToolRequestParams,
@ -176,12 +173,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Call a tool
let result = service
.call_tool(CallToolRequestParams {
meta: None,
name: "git_status".into(),
arguments: serde_json::json!({ "repo_path": "." }).as_object().cloned(),
task: None,
})
.call_tool(
CallToolRequestParams::new("git_status")
.with_arguments(serde_json::json!({ "repo_path": "." }).as_object().cloned().unwrap_or_default())
)
.await?;
println!("Result: {result:#?}");

View file

@ -20,6 +20,7 @@ impl std::error::Error for ErrorData {}
/// This is an unified error type for the errors could be returned by the service.
#[derive(Debug, thiserror::Error)]
#[allow(clippy::large_enum_variant)]
#[non_exhaustive]
pub enum RmcpError {
#[cfg(any(feature = "client", feature = "server"))]
#[error("Service error: {0}")]

View file

@ -306,6 +306,7 @@ pub struct ProgressToken(pub NumberOrString);
/// - `extensions`: Additional context data (similar to HTTP headers)
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct Request<M = String, P = JsonObject> {
pub method: M,
pub params: P,
@ -379,6 +380,7 @@ impl<M> GetExtensions for RequestNoParam<M> {
}
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct Notification<M = String, P = JsonObject> {
pub method: M,
pub params: P,
@ -419,6 +421,17 @@ pub struct JsonRpcRequest<R = Request> {
pub request: R,
}
impl<R> JsonRpcRequest<R> {
/// Create a new JsonRpcRequest.
pub fn new(id: RequestId, request: R) -> Self {
Self {
jsonrpc: JsonRpcVersion2_0,
id,
request,
}
}
}
type DefaultResponse = JsonObject;
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
@ -436,6 +449,17 @@ pub struct JsonRpcError {
pub error: ErrorData,
}
impl JsonRpcError {
/// Create a new JsonRpcError.
pub fn new(id: RequestId, error: ErrorData) -> Self {
Self {
jsonrpc: JsonRpcVersion2_0,
id,
error,
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct JsonRpcNotification<N = Notification> {
@ -467,7 +491,7 @@ impl ErrorCode {
///
/// This structure follows the JSON-RPC 2.0 specification for error reporting,
/// providing a standardized way to communicate errors between clients and servers.
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[derive(Default, Debug, Serialize, Deserialize, Clone, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct ErrorData {
/// The error type that occurred (using standard JSON-RPC error codes)
@ -745,6 +769,7 @@ pub type InitializedNotification = NotificationNoParam<InitializedNotificationMe
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct InitializeRequestParams {
/// Protocol-level metadata for this request (SEP-1319)
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
@ -757,6 +782,23 @@ pub struct InitializeRequestParams {
pub client_info: Implementation,
}
impl InitializeRequestParams {
/// Create a new InitializeRequestParams.
pub fn new(capabilities: ClientCapabilities, client_info: Implementation) -> Self {
Self {
meta: None,
protocol_version: ProtocolVersion::default(),
capabilities,
client_info,
}
}
pub fn with_protocol_version(mut self, protocol_version: ProtocolVersion) -> Self {
self.protocol_version = protocol_version;
self
}
}
impl RequestParamsMeta for InitializeRequestParams {
fn meta(&self) -> Option<&Meta> {
self.meta.as_ref()
@ -777,6 +819,7 @@ pub type InitializeRequestParam = InitializeRequestParams;
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct InitializeResult {
/// The MCP protocol version this server supports
pub protocol_version: ProtocolVersion,
@ -789,6 +832,36 @@ pub struct InitializeResult {
pub instructions: Option<String>,
}
impl InitializeResult {
/// Create a new `InitializeResult` with default protocol version and the given capabilities.
pub fn new(capabilities: ServerCapabilities) -> Self {
Self {
protocol_version: ProtocolVersion::default(),
capabilities,
server_info: Implementation::from_build_env(),
instructions: None,
}
}
/// Set instructions on this result.
pub fn with_instructions(mut self, instructions: impl Into<String>) -> Self {
self.instructions = Some(instructions.into());
self
}
/// Set the server info on this result.
pub fn with_server_info(mut self, server_info: Implementation) -> Self {
self.server_info = server_info;
self
}
/// Set the protocol version on this result.
pub fn with_protocol_version(mut self, protocol_version: ProtocolVersion) -> Self {
self.protocol_version = protocol_version;
self
}
}
pub type ServerInfo = InitializeResult;
pub type ClientInfo = InitializeRequestParams;
@ -828,6 +901,7 @@ impl Default for ClientInfo {
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct Icon {
/// A standard URI pointing to an icon resource
pub src: String,
@ -839,9 +913,33 @@ pub struct Icon {
pub sizes: Option<Vec<String>>,
}
impl Icon {
/// Create a new Icon with the given source URL.
pub fn new(src: impl Into<String>) -> Self {
Self {
src: src.into(),
mime_type: None,
sizes: None,
}
}
/// Set the MIME type.
pub fn with_mime_type(mut self, mime_type: impl Into<String>) -> Self {
self.mime_type = Some(mime_type.into());
self
}
/// Set the sizes.
pub fn with_sizes(mut self, sizes: Vec<String>) -> Self {
self.sizes = Some(sizes);
self
}
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct Implementation {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
@ -862,6 +960,18 @@ impl Default for Implementation {
}
impl Implementation {
/// Create a new Implementation.
pub fn new(name: impl Into<String>, version: impl Into<String>) -> Self {
Self {
name: name.into(),
title: None,
version: version.into(),
description: None,
icons: None,
website_url: None,
}
}
pub fn from_build_env() -> Self {
Implementation {
name: env!("CARGO_CRATE_NAME").to_owned(),
@ -872,11 +982,36 @@ impl Implementation {
website_url: None,
}
}
/// Set the human-readable title.
pub fn with_title(mut self, title: impl Into<String>) -> Self {
self.title = Some(title.into());
self
}
/// Set the description.
pub fn with_description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
/// Set the icons.
pub fn with_icons(mut self, icons: Vec<Icon>) -> Self {
self.icons = Some(icons);
self
}
/// Set the website URL.
pub fn with_website_url(mut self, website_url: impl Into<String>) -> Self {
self.website_url = Some(website_url.into());
self
}
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct PaginatedRequestParams {
/// Protocol-level metadata for this request (SEP-1319)
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
@ -885,6 +1020,13 @@ pub struct PaginatedRequestParams {
pub cursor: Option<String>,
}
impl PaginatedRequestParams {
pub fn with_cursor(mut self, cursor: Option<String>) -> Self {
self.cursor = cursor;
self
}
}
impl RequestParamsMeta for PaginatedRequestParams {
fn meta(&self) -> Option<&Meta> {
self.meta.as_ref()
@ -920,6 +1062,30 @@ pub struct ProgressNotificationParam {
pub message: Option<String>,
}
impl ProgressNotificationParam {
/// Create a new ProgressNotificationParam with required fields.
pub fn new(progress_token: ProgressToken, progress: f64) -> Self {
Self {
progress_token,
progress,
total: None,
message: None,
}
}
/// Set the total number of items to process.
pub fn with_total(mut self, total: f64) -> Self {
self.total = Some(total);
self
}
/// Set a message describing the current progress.
pub fn with_message(mut self, message: impl Into<String>) -> Self {
self.message = Some(message.into());
self
}
}
pub type ProgressNotification = Notification<ProgressNotificationMethod, ProgressNotificationParam>;
pub type Cursor = String;
@ -980,6 +1146,7 @@ const_string!(ReadResourceRequestMethod = "resources/read");
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct ReadResourceRequestParams {
/// Protocol-level metadata for this request (SEP-1319)
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
@ -988,6 +1155,22 @@ pub struct ReadResourceRequestParams {
pub uri: String,
}
impl ReadResourceRequestParams {
/// Create a new ReadResourceRequestParams with the given URI.
pub fn new(uri: impl Into<String>) -> Self {
Self {
meta: None,
uri: uri.into(),
}
}
/// Set the metadata for this request.
pub fn with_meta(mut self, meta: Meta) -> Self {
self.meta = Some(meta);
self
}
}
impl RequestParamsMeta for ReadResourceRequestParams {
fn meta(&self) -> Option<&Meta> {
self.meta.as_ref()
@ -1004,11 +1187,19 @@ pub type ReadResourceRequestParam = ReadResourceRequestParams;
/// Result containing the contents of a read resource
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct ReadResourceResult {
/// The actual content of the resource
pub contents: Vec<ResourceContents>,
}
impl ReadResourceResult {
/// Create a new ReadResourceResult with the given contents.
pub fn new(contents: Vec<ResourceContents>) -> Self {
Self { contents }
}
}
/// Request to read a specific resource
pub type ReadResourceRequest = Request<ReadResourceRequestMethod, ReadResourceRequestParams>;
@ -1022,6 +1213,7 @@ const_string!(SubscribeRequestMethod = "resources/subscribe");
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct SubscribeRequestParams {
/// Protocol-level metadata for this request (SEP-1319)
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
@ -1030,6 +1222,16 @@ pub struct SubscribeRequestParams {
pub uri: String,
}
impl SubscribeRequestParams {
/// Create a new SubscribeRequestParams.
pub fn new(uri: impl Into<String>) -> Self {
Self {
meta: None,
uri: uri.into(),
}
}
}
impl RequestParamsMeta for SubscribeRequestParams {
fn meta(&self) -> Option<&Meta> {
self.meta.as_ref()
@ -1051,6 +1253,7 @@ const_string!(UnsubscribeRequestMethod = "resources/unsubscribe");
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct UnsubscribeRequestParams {
/// Protocol-level metadata for this request (SEP-1319)
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
@ -1084,6 +1287,14 @@ pub struct ResourceUpdatedNotificationParam {
/// The URI of the resource that was updated
pub uri: String,
}
impl ResourceUpdatedNotificationParam {
/// Create a new ResourceUpdatedNotificationParam.
pub fn new(uri: impl Into<String>) -> Self {
Self { uri: uri.into() }
}
}
/// Notification sent when a subscribed resource is updated
pub type ResourceUpdatedNotification =
Notification<ResourceUpdatedNotificationMethod, ResourceUpdatedNotificationParam>;
@ -1103,9 +1314,10 @@ paginated_result!(ListPromptsResult {
const_string!(GetPromptRequestMethod = "prompts/get");
/// Parameters for retrieving a specific prompt
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[derive(Default, Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct GetPromptRequestParams {
/// Protocol-level metadata for this request (SEP-1319)
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
@ -1115,6 +1327,29 @@ pub struct GetPromptRequestParams {
pub arguments: Option<JsonObject>,
}
impl GetPromptRequestParams {
/// Create a new `GetPromptRequestParams` with the given prompt name.
pub fn new(name: impl Into<String>) -> Self {
Self {
meta: None,
name: name.into(),
arguments: None,
}
}
/// Set the arguments for this prompt request.
pub fn with_arguments(mut self, arguments: JsonObject) -> Self {
self.arguments = Some(arguments);
self
}
/// Set the metadata for this request.
pub fn with_meta(mut self, meta: Meta) -> Self {
self.meta = Some(meta);
self
}
}
impl RequestParamsMeta for GetPromptRequestParams {
fn meta(&self) -> Option<&Meta> {
self.meta.as_ref()
@ -1163,6 +1398,7 @@ const_string!(SetLevelRequestMethod = "logging/setLevel");
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct SetLevelRequestParams {
/// Protocol-level metadata for this request (SEP-1319)
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
@ -1171,6 +1407,13 @@ pub struct SetLevelRequestParams {
pub level: LoggingLevel,
}
impl SetLevelRequestParams {
/// Create a new SetLevelRequestParams with the given logging level.
pub fn new(level: LoggingLevel) -> Self {
Self { meta: None, level }
}
}
impl RequestParamsMeta for SetLevelRequestParams {
fn meta(&self) -> Option<&Meta> {
self.meta.as_ref()
@ -1201,6 +1444,27 @@ pub struct LoggingMessageNotificationParam {
/// The actual log data
pub data: Value,
}
impl LoggingMessageNotificationParam {
/// Create a new LoggingMessageNotificationParam.
pub fn new(level: LoggingLevel, data: Value) -> Self {
Self {
level,
logger: None,
data,
}
}
/// Create with a logger name.
pub fn with_logger(level: LoggingLevel, logger: impl Into<String>, data: Value) -> Self {
Self {
level,
logger: Some(logger.into()),
data,
}
}
}
/// Notification containing a log message
pub type LoggingMessageNotification =
Notification<LoggingMessageNotificationMethod, LoggingMessageNotificationParam>;
@ -1249,6 +1513,7 @@ impl Default for ToolChoiceMode {
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct ToolChoice {
#[serde(skip_serializing_if = "Option::is_none")]
pub mode: Option<ToolChoiceMode>,
@ -1379,6 +1644,7 @@ impl<T> From<Vec<T>> for SamplingContent<T> {
/// for generating appropriate responses.
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct SamplingMessage {
/// The role of the message sender (User or Assistant)
pub role: Role,
@ -1539,9 +1805,10 @@ pub enum ContextInclusion {
///
/// This implements `TaskAugmentedRequestParamsMeta` as sampling requests can be
/// long-running and may benefit from task-based execution.
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[derive(Default, Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct CreateMessageRequestParams {
/// Protocol-level metadata for this request (SEP-1319)
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
@ -1598,6 +1865,72 @@ impl TaskAugmentedRequestParamsMeta for CreateMessageRequestParams {
}
impl CreateMessageRequestParams {
/// Create a new CreateMessageRequestParams with required fields.
pub fn new(messages: Vec<SamplingMessage>, max_tokens: u32) -> Self {
Self {
meta: None,
task: None,
messages,
model_preferences: None,
system_prompt: None,
include_context: None,
temperature: None,
max_tokens,
stop_sequences: None,
metadata: None,
tools: None,
tool_choice: None,
}
}
/// Set model preferences.
pub fn with_model_preferences(mut self, model_preferences: ModelPreferences) -> Self {
self.model_preferences = Some(model_preferences);
self
}
/// Set system prompt.
pub fn with_system_prompt(mut self, system_prompt: impl Into<String>) -> Self {
self.system_prompt = Some(system_prompt.into());
self
}
/// Set include context.
pub fn with_include_context(mut self, include_context: ContextInclusion) -> Self {
self.include_context = Some(include_context);
self
}
/// Set temperature.
pub fn with_temperature(mut self, temperature: f32) -> Self {
self.temperature = Some(temperature);
self
}
/// Set stop sequences.
pub fn with_stop_sequences(mut self, stop_sequences: Vec<String>) -> Self {
self.stop_sequences = Some(stop_sequences);
self
}
/// Set metadata.
pub fn with_metadata(mut self, metadata: Value) -> Self {
self.metadata = Some(metadata);
self
}
/// Set tools.
pub fn with_tools(mut self, tools: Vec<Tool>) -> Self {
self.tools = Some(tools);
self
}
/// Set tool choice.
pub fn with_tool_choice(mut self, tool_choice: ToolChoice) -> Self {
self.tool_choice = Some(tool_choice);
self
}
/// Validate the sampling request parameters per SEP-1577 spec requirements.
///
/// Checks:
@ -1688,6 +2021,7 @@ pub type CreateMessageRequestParam = CreateMessageRequestParams;
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct ModelPreferences {
/// Specific model names or families to prefer (e.g., "claude", "gpt")
#[serde(skip_serializing_if = "Option::is_none")]
@ -1703,18 +2037,70 @@ pub struct ModelPreferences {
pub intelligence_priority: Option<f32>,
}
impl ModelPreferences {
/// Create a new default ModelPreferences.
pub fn new() -> Self {
Self {
hints: None,
cost_priority: None,
speed_priority: None,
intelligence_priority: None,
}
}
/// Set hints for model selection.
pub fn with_hints(mut self, hints: Vec<ModelHint>) -> Self {
self.hints = Some(hints);
self
}
/// Set cost priority (0.0 to 1.0).
pub fn with_cost_priority(mut self, cost_priority: f32) -> Self {
self.cost_priority = Some(cost_priority);
self
}
/// Set speed priority (0.0 to 1.0).
pub fn with_speed_priority(mut self, speed_priority: f32) -> Self {
self.speed_priority = Some(speed_priority);
self
}
/// Set intelligence priority (0.0 to 1.0).
pub fn with_intelligence_priority(mut self, intelligence_priority: f32) -> Self {
self.intelligence_priority = Some(intelligence_priority);
self
}
}
impl Default for ModelPreferences {
fn default() -> Self {
Self::new()
}
}
/// A hint suggesting a preferred model name or family.
///
/// Model hints are advisory suggestions that help clients choose appropriate
/// models. They can be specific model names or general families like "claude" or "gpt".
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[derive(Default, Debug, Serialize, Deserialize, Clone, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct ModelHint {
/// The suggested model name or family identifier
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
}
impl ModelHint {
/// Create a new ModelHint with a name.
pub fn new(name: impl Into<String>) -> Self {
Self {
name: Some(name.into()),
}
}
}
// =============================================================================
// COMPLETION AND AUTOCOMPLETE
// =============================================================================
@ -1768,6 +2154,7 @@ impl CompletionContext {
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct CompleteRequestParams {
/// Protocol-level metadata for this request (SEP-1319)
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
@ -1779,6 +2166,24 @@ pub struct CompleteRequestParams {
pub context: Option<CompletionContext>,
}
impl CompleteRequestParams {
/// Create a new CompleteRequestParams with required fields.
pub fn new(r#ref: Reference, argument: ArgumentInfo) -> Self {
Self {
meta: None,
r#ref,
argument,
context: None,
}
}
/// Set the completion context
pub fn with_context(mut self, context: CompletionContext) -> Self {
self.context = Some(context);
self
}
}
impl RequestParamsMeta for CompleteRequestParams {
fn meta(&self) -> Option<&Meta> {
self.meta.as_ref()
@ -1875,10 +2280,18 @@ impl CompletionInfo {
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct CompleteResult {
pub completion: CompletionInfo,
}
impl CompleteResult {
/// Create a new CompleteResult with the given completion info.
pub fn new(completion: CompletionInfo) -> Self {
Self { completion }
}
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(tag = "type")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
@ -1939,6 +2352,7 @@ pub struct ResourceReference {
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct PromptReference {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
@ -1960,6 +2374,7 @@ pub struct ArgumentInfo {
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct Root {
pub uri: String,
#[serde(skip_serializing_if = "Option::is_none")]
@ -1972,6 +2387,7 @@ pub type ListRootsRequest = RequestNoParam<ListRootsRequestMethod>;
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct ListRootsResult {
pub roots: Vec<Root>,
}
@ -2180,18 +2596,45 @@ pub struct CreateElicitationResult {
pub content: Option<Value>,
}
impl CreateElicitationResult {
/// Create a new CreateElicitationResult.
pub fn new(action: ElicitationAction) -> Self {
Self {
action,
content: None,
}
}
/// Create with content.
pub fn with_content(action: ElicitationAction, content: Value) -> Self {
Self {
action,
content: Some(content),
}
}
}
/// Request type for creating an elicitation to gather user input
pub type CreateElicitationRequest =
Request<ElicitationCreateRequestMethod, CreateElicitationRequestParams>;
/// Notification parameters for an url elicitation completion notification.
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[derive(Default, Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct ElicitationResponseNotificationParam {
pub elicitation_id: String,
}
impl ElicitationResponseNotificationParam {
/// Create a new ElicitationResponseNotificationParam.
pub fn new(elicitation_id: impl Into<String>) -> Self {
Self {
elicitation_id: elicitation_id.into(),
}
}
}
/// Notification sent when an url elicitation process is completed.
pub type ElicitationCompletionNotification =
Notification<ElicitationCompletionNotificationMethod, ElicitationResponseNotificationParam>;
@ -2204,9 +2647,10 @@ pub type ElicitationCompletionNotification =
///
/// Contains the content returned by the tool execution and an optional
/// flag indicating whether the operation resulted in an error.
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[derive(Default, Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct CallToolResult {
/// The content returned by the tool (text, images, etc.)
pub content: Vec<Content>,
@ -2289,6 +2733,12 @@ impl CallToolResult {
}
}
/// Set the metadata on this result
pub fn with_meta(mut self, meta: Option<Meta>) -> Self {
self.meta = meta;
self
}
/// Convert the `structured_content` part of response into a certain type.
///
/// # About json schema validation
@ -2336,9 +2786,10 @@ const_string!(CallToolRequestMethod = "tools/call");
///
/// This implements `TaskAugmentedRequestParamsMeta` as tool calls can be
/// long-running and may benefit from task-based execution.
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[derive(Default, Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct CallToolRequestParams {
/// Protocol-level metadata for this request (SEP-1319)
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
@ -2353,6 +2804,30 @@ pub struct CallToolRequestParams {
pub task: Option<JsonObject>,
}
impl CallToolRequestParams {
/// Creates a new `CallToolRequestParams` with the given tool name.
pub fn new(name: impl Into<Cow<'static, str>>) -> Self {
Self {
meta: None,
name: name.into(),
arguments: None,
task: None,
}
}
/// Sets the arguments for this tool call.
pub fn with_arguments(mut self, arguments: JsonObject) -> Self {
self.arguments = Some(arguments);
self
}
/// Sets the task metadata for this tool call.
pub fn with_task(mut self, task: Option<JsonObject>) -> Self {
self.task = task;
self
}
}
impl RequestParamsMeta for CallToolRequestParams {
fn meta(&self) -> Option<&Meta> {
self.meta.as_ref()
@ -2386,6 +2861,7 @@ pub type CallToolRequest = Request<CallToolRequestMethod, CallToolRequestParams>
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct CreateMessageResult {
/// The identifier of the model that generated the response
pub model: String,
@ -2398,11 +2874,32 @@ pub struct CreateMessageResult {
}
impl CreateMessageResult {
/// Create a new CreateMessageResult with required fields.
pub fn new(message: SamplingMessage, model: String) -> Self {
Self {
message,
model,
stop_reason: None,
}
}
pub const STOP_REASON_END_TURN: &str = "endTurn";
pub const STOP_REASON_END_SEQUENCE: &str = "stopSequence";
pub const STOP_REASON_END_MAX_TOKEN: &str = "maxTokens";
pub const STOP_REASON_TOOL_USE: &str = "toolUse";
/// Set the stop reason.
pub fn with_stop_reason(mut self, stop_reason: Option<String>) -> Self {
self.stop_reason = stop_reason;
self
}
/// Set the model identifier.
pub fn with_model(mut self, model: impl Into<String>) -> Self {
self.model = model.into();
self
}
/// Validate the result per SEP-1577: role must be "assistant".
pub fn validate(&self) -> Result<(), String> {
if self.message.role != Role::Assistant {
@ -2412,15 +2909,32 @@ impl CreateMessageResult {
}
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[derive(Default, Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct GetPromptResult {
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
pub messages: Vec<PromptMessage>,
}
impl GetPromptResult {
/// Create a new GetPromptResult with required fields.
pub fn new(messages: Vec<PromptMessage>) -> Self {
Self {
description: None,
messages,
}
}
/// Set the description
pub fn with_description<D: Into<String>>(mut self, description: D) -> Self {
self.description = Some(description.into());
self
}
}
// =============================================================================
// TASK MANAGEMENT
// =============================================================================
@ -2512,6 +3026,7 @@ pub type GetTaskInfoResult = GetTaskResult;
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct ListTasksResult {
pub tasks: Vec<crate::model::Task>,
#[serde(skip_serializing_if = "Option::is_none")]
@ -2520,6 +3035,17 @@ pub struct ListTasksResult {
pub total: Option<u64>,
}
impl ListTasksResult {
/// Create a new ListTasksResult.
pub fn new(tasks: Vec<crate::model::Task>) -> Self {
Self {
tasks,
next_cursor: None,
total: None,
}
}
}
// =============================================================================
// MESSAGE TYPE UNIONS
// =============================================================================

View file

@ -11,6 +11,7 @@ use super::{
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct Annotations {
#[serde(skip_serializing_if = "Option::is_none")]
pub audience: Option<Vec<Role>>,

View file

@ -243,6 +243,7 @@ pub struct SamplingCapability {
/// ```
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct ClientCapabilities {
#[serde(skip_serializing_if = "Option::is_none")]
pub experimental: Option<ExperimentalCapabilities>,
@ -280,6 +281,7 @@ pub struct ClientCapabilities {
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct ServerCapabilities {
#[serde(skip_serializing_if = "Option::is_none")]
pub experimental: Option<ExperimentalCapabilities>,

View file

@ -38,6 +38,17 @@ pub struct RawEmbeddedResource {
pub meta: Option<super::Meta>,
pub resource: ResourceContents,
}
impl RawEmbeddedResource {
/// Create a new RawEmbeddedResource.
pub fn new(resource: ResourceContents) -> Self {
Self {
meta: None,
resource,
}
}
}
pub type EmbeddedResource = Annotated<RawEmbeddedResource>;
impl EmbeddedResource {
@ -63,6 +74,7 @@ pub type AudioContent = Annotated<RawAudioContent>;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct ToolUseContent {
/// Unique identifier for this tool call
pub id: String,
@ -79,6 +91,7 @@ pub struct ToolUseContent {
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct ToolResultContent {
/// Optional metadata
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]

View file

@ -89,6 +89,7 @@ pub enum StringFormat {
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct StringSchema {
/// Type discriminator
#[serde(rename = "type")]
@ -237,6 +238,7 @@ impl StringSchema {
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct NumberSchema {
/// Type discriminator
#[serde(rename = "type")]
@ -444,6 +446,7 @@ impl IntegerSchema {
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct BooleanSchema {
/// Type discriminator
#[serde(rename = "type")]
@ -513,6 +516,16 @@ pub struct ConstTitle {
pub title: String,
}
impl ConstTitle {
/// Create a new ConstTitle.
pub fn new(const_: impl Into<String>, title: impl Into<String>) -> Self {
Self {
const_: const_.into(),
title: title.into(),
}
}
}
/// Legacy enum schema, keep for backward compatibility
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
@ -533,6 +546,7 @@ pub struct LegacyEnumSchema {
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct UntitledSingleSelectEnumSchema {
#[serde(rename = "type")]
pub type_: StringTypeConst,
@ -550,6 +564,7 @@ pub struct UntitledSingleSelectEnumSchema {
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct TitledSingleSelectEnumSchema {
#[serde(rename = "type")]
pub type_: StringTypeConst,
@ -563,6 +578,19 @@ pub struct TitledSingleSelectEnumSchema {
pub default: Option<String>,
}
impl TitledSingleSelectEnumSchema {
/// Create a new TitledSingleSelectEnumSchema.
pub fn new(one_of: Vec<ConstTitle>) -> Self {
Self {
type_: StringTypeConst,
title: None,
description: None,
one_of,
default: None,
}
}
}
/// Combined single-select
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
@ -592,10 +620,18 @@ pub struct TitledItems {
pub any_of: Vec<ConstTitle>,
}
impl TitledItems {
/// Create a new TitledItems.
pub fn new(any_of: Vec<ConstTitle>) -> Self {
Self { any_of }
}
}
/// Multi-select untitled options
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct UntitledMultiSelectEnumSchema {
#[serde(rename = "type")]
pub type_: ArrayTypeConst,
@ -616,6 +652,7 @@ pub struct UntitledMultiSelectEnumSchema {
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct TitledMultiSelectEnumSchema {
#[serde(rename = "type")]
pub type_: ArrayTypeConst,
@ -632,6 +669,51 @@ pub struct TitledMultiSelectEnumSchema {
pub default: Option<Vec<String>>,
}
impl TitledMultiSelectEnumSchema {
/// Create a new TitledMultiSelectEnumSchema.
pub fn new(items: TitledItems) -> Self {
Self {
type_: ArrayTypeConst,
title: None,
description: None,
min_items: None,
max_items: None,
items,
default: None,
}
}
/// Set the title.
pub fn with_title(mut self, title: impl Into<Cow<'static, str>>) -> Self {
self.title = Some(title.into());
self
}
/// Set the description.
pub fn with_description(mut self, description: impl Into<Cow<'static, str>>) -> Self {
self.description = Some(description.into());
self
}
/// Set the minimum number of items.
pub fn with_min_items(mut self, min_items: u64) -> Self {
self.min_items = Some(min_items);
self
}
/// Set the maximum number of items.
pub fn with_max_items(mut self, max_items: u64) -> Self {
self.max_items = Some(max_items);
self
}
/// Set the default values.
pub fn with_default(mut self, default: Vec<String>) -> Self {
self.default = Some(default);
self
}
}
/// Multi-select enum options
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]

View file

@ -7,9 +7,10 @@ use super::{
};
/// A prompt that can be used to generate text from a model
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct Prompt {
/// The name of the prompt
pub name: String,
@ -49,11 +50,46 @@ impl Prompt {
meta: None,
}
}
/// Create a new prompt from raw fields (used by the macro)
pub fn from_raw(
name: impl Into<String>,
description: Option<impl Into<String>>,
arguments: Option<Vec<PromptArgument>>,
) -> Self {
Prompt {
name: name.into(),
title: None,
description: description.map(Into::into),
arguments,
icons: None,
meta: None,
}
}
/// Set the human-readable title
pub fn with_title(mut self, title: Option<String>) -> Self {
self.title = title;
self
}
/// Set the icons
pub fn with_icons(mut self, icons: Option<Vec<Icon>>) -> Self {
self.icons = icons;
self
}
/// Set the metadata
pub fn with_meta(mut self, meta: Option<Meta>) -> Self {
self.meta = meta;
self
}
}
/// Represents a prompt argument that can be passed to customize the prompt
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct PromptArgument {
/// The name of the argument
pub name: String,
@ -68,6 +104,36 @@ pub struct PromptArgument {
pub required: Option<bool>,
}
impl PromptArgument {
/// Create a new prompt argument
pub fn new<N: Into<String>>(name: N) -> Self {
PromptArgument {
name: name.into(),
title: None,
description: None,
required: None,
}
}
/// Set the title
pub fn with_title<T: Into<String>>(mut self, title: T) -> Self {
self.title = Some(title.into());
self
}
/// Set the description
pub fn with_description<D: Into<String>>(mut self, description: D) -> Self {
self.description = Some(description.into());
self
}
/// Set the required flag
pub fn with_required(mut self, required: bool) -> Self {
self.required = Some(required);
self
}
}
/// Represents the role of a message sender in a prompt conversation
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
@ -112,6 +178,7 @@ impl PromptMessageContent {
/// A message in a prompt conversation
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct PromptMessage {
/// The role of the message sender
pub role: PromptMessageRole,
@ -120,6 +187,11 @@ pub struct PromptMessage {
}
impl PromptMessage {
/// Create a new prompt message with the given role and content
pub fn new(role: PromptMessageRole, content: PromptMessageContent) -> Self {
Self { role, content }
}
/// Create a new text message with the given role and text content
pub fn new_text<S: Into<String>>(role: PromptMessageRole, text: S) -> Self {
Self {

View file

@ -80,6 +80,7 @@ pub enum ResourceContents {
}
impl ResourceContents {
/// Create text resource contents.
pub fn text(text: impl Into<String>, uri: impl Into<String>) -> Self {
Self::TextResourceContents {
uri: uri.into(),
@ -88,6 +89,34 @@ impl ResourceContents {
meta: None,
}
}
/// Create blob resource contents.
pub fn blob(blob: impl Into<String>, uri: impl Into<String>) -> Self {
Self::BlobResourceContents {
uri: uri.into(),
mime_type: None,
blob: blob.into(),
meta: None,
}
}
/// Set the MIME type on this resource contents.
pub fn with_mime_type(mut self, mime_type: impl Into<String>) -> Self {
match &mut self {
Self::TextResourceContents { mime_type: mt, .. } => *mt = Some(mime_type.into()),
Self::BlobResourceContents { mime_type: mt, .. } => *mt = Some(mime_type.into()),
}
self
}
/// Set the metadata on this resource contents.
pub fn with_meta(mut self, meta: Meta) -> Self {
match &mut self {
Self::TextResourceContents { meta: m, .. } => *m = Some(meta),
Self::BlobResourceContents { meta: m, .. } => *m = Some(meta),
}
self
}
}
impl RawResource {
@ -104,6 +133,80 @@ impl RawResource {
meta: None,
}
}
/// Set the human-readable title.
pub fn with_title(mut self, title: impl Into<String>) -> Self {
self.title = Some(title.into());
self
}
/// Set the description.
pub fn with_description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
/// Set the MIME type.
pub fn with_mime_type(mut self, mime_type: impl Into<String>) -> Self {
self.mime_type = Some(mime_type.into());
self
}
/// Set the size in bytes.
pub fn with_size(mut self, size: u32) -> Self {
self.size = Some(size);
self
}
/// Set the icons.
pub fn with_icons(mut self, icons: Vec<Icon>) -> Self {
self.icons = Some(icons);
self
}
/// Set the metadata.
pub fn with_meta(mut self, meta: Meta) -> Self {
self.meta = Some(meta);
self
}
}
impl RawResourceTemplate {
/// Creates a new RawResourceTemplate with a URI template and name.
pub fn new(uri_template: impl Into<String>, name: impl Into<String>) -> Self {
Self {
uri_template: uri_template.into(),
name: name.into(),
title: None,
description: None,
mime_type: None,
icons: None,
}
}
/// Set the human-readable title.
pub fn with_title(mut self, title: impl Into<String>) -> Self {
self.title = Some(title.into());
self
}
/// Set the description.
pub fn with_description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
/// Set the MIME type.
pub fn with_mime_type(mut self, mime_type: impl Into<String>) -> Self {
self.mime_type = Some(mime_type.into());
self
}
/// Set the icons.
pub fn with_icons(mut self, icons: Vec<Icon>) -> Self {
self.icons = Some(icons);
self
}
}
#[cfg(test)]

View file

@ -28,6 +28,7 @@ pub enum TaskStatus {
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct Task {
/// Unique task identifier generated by the receiver.
pub task_id: String,
@ -48,14 +49,60 @@ pub struct Task {
pub poll_interval: Option<u64>,
}
impl Task {
/// Create a new Task with required fields.
pub fn new(
task_id: String,
status: TaskStatus,
created_at: String,
last_updated_at: String,
) -> Self {
Self {
task_id,
status,
status_message: None,
created_at,
last_updated_at,
ttl: None,
poll_interval: None,
}
}
/// Set the status message.
pub fn with_status_message(mut self, status_message: impl Into<String>) -> Self {
self.status_message = Some(status_message.into());
self
}
/// Set the TTL in milliseconds. `None` means unlimited retention.
pub fn with_ttl(mut self, ttl: u64) -> Self {
self.ttl = Some(ttl);
self
}
/// Set the poll interval in milliseconds.
pub fn with_poll_interval(mut self, poll_interval: u64) -> Self {
self.poll_interval = Some(poll_interval);
self
}
}
/// Wrapper returned by task-augmented requests (CreateTaskResult in SEP-1686).
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct CreateTaskResult {
pub task: Task,
}
impl CreateTaskResult {
/// Create a new CreateTaskResult.
pub fn new(task: Task) -> Self {
Self { task }
}
}
/// Response to a `tasks/get` request.
///
/// Per spec, `GetTaskResult = allOf[Result, Task]` — the Task fields are
@ -78,8 +125,16 @@ pub struct GetTaskResult {
/// serialized as a JSON value.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct GetTaskPayloadResult(pub Value);
impl GetTaskPayloadResult {
/// Create a new GetTaskPayloadResult with the given value.
pub fn new(value: Value) -> Self {
Self(value)
}
}
/// Response to a `tasks/cancel` request.
///
/// Per spec, `CancelTaskResult = allOf[Result, Task]` — same shape as `GetTaskResult`.
@ -104,3 +159,14 @@ pub struct TaskList {
#[serde(skip_serializing_if = "Option::is_none")]
pub total: Option<u64>,
}
impl TaskList {
/// Create a new TaskList.
pub fn new(tasks: Vec<Task>) -> Self {
Self {
tasks,
next_cursor: None,
total: None,
}
}
}

View file

@ -10,9 +10,10 @@ use serde_json::Value;
use super::{Icon, JsonObject, Meta};
/// A tool that can be used by a model.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct Tool {
/// The name of the tool
pub name: Cow<'static, str>,
@ -67,6 +68,7 @@ pub enum TaskSupport {
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct ToolExecution {
/// Indicates whether this tool supports task-based invocation.
///
@ -83,6 +85,11 @@ impl ToolExecution {
Self::default()
}
/// Create a ToolExecution from raw optional fields.
pub fn from_raw(task_support: Option<TaskSupport>) -> Self {
Self { task_support }
}
/// Set the task support mode.
pub fn with_task_support(mut self, task_support: TaskSupport) -> Self {
self.task_support = Some(task_support);
@ -101,6 +108,7 @@ impl ToolExecution {
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct ToolAnnotations {
/// A human-readable title for the tool.
#[serde(skip_serializing_if = "Option::is_none")]
@ -145,6 +153,24 @@ impl ToolAnnotations {
pub fn new() -> Self {
Self::default()
}
/// Create a new ToolAnnotations with all fields specified
pub fn from_raw(
title: Option<String>,
read_only_hint: Option<bool>,
destructive_hint: Option<bool>,
idempotent_hint: Option<bool>,
open_world_hint: Option<bool>,
) -> Self {
ToolAnnotations {
title,
read_only_hint,
destructive_hint,
idempotent_hint,
open_world_hint,
}
}
pub fn with_title<T>(title: T) -> Self
where
T: Into<String>,
@ -211,6 +237,59 @@ impl Tool {
}
}
/// Create a new tool with just a name and input schema (no description)
pub fn new_with_raw<N, S>(
name: N,
description: Option<Cow<'static, str>>,
input_schema: S,
) -> Self
where
N: Into<Cow<'static, str>>,
S: Into<Arc<JsonObject>>,
{
Tool {
name: name.into(),
title: None,
description,
input_schema: input_schema.into(),
output_schema: None,
annotations: None,
execution: None,
icons: None,
meta: None,
}
}
/// Set the human-readable title
pub fn with_title(mut self, title: Option<String>) -> Self {
self.title = title;
self
}
/// Set the output schema from a raw value
pub fn with_raw_output_schema(mut self, output_schema: Option<Arc<JsonObject>>) -> Self {
self.output_schema = output_schema;
self
}
/// Set the annotations
pub fn with_annotations(mut self, annotations: Option<ToolAnnotations>) -> Self {
self.annotations = annotations;
self
}
/// Set the icons
pub fn with_icons(mut self, icons: Option<Vec<Icon>>) -> Self {
self.icons = icons;
self
}
/// Set the metadata
pub fn with_meta(mut self, meta: Option<Meta>) -> Self {
self.meta = meta;
self
}
pub fn annotate(self, annotations: ToolAnnotations) -> Self {
Tool {
annotations: Some(annotations),
@ -219,11 +298,9 @@ impl Tool {
}
/// Set the execution configuration for this tool.
pub fn with_execution(self, execution: ToolExecution) -> Self {
Tool {
execution: Some(execution),
..self
}
pub fn with_execution(mut self, execution: Option<ToolExecution>) -> Self {
self.execution = execution;
self
}
/// Returns the task support mode for this tool.

View file

@ -566,6 +566,7 @@ impl RunningServiceCancellationToken {
}
#[derive(Debug)]
#[non_exhaustive]
pub enum QuitReason {
Cancelled,
Closed,
@ -584,6 +585,19 @@ pub struct RequestContext<R: ServiceRole> {
pub peer: Peer<R>,
}
impl<R: ServiceRole> RequestContext<R> {
/// Create a new RequestContext.
pub fn new(id: RequestId, peer: Peer<R>) -> Self {
Self {
ct: CancellationToken::new(),
id,
meta: Meta::default(),
extensions: Extensions::default(),
peer,
}
}
}
/// Request execution context
#[derive(Debug, Clone)]
pub struct NotificationContext<R: ServiceRole> {

View file

@ -25,6 +25,7 @@ use crate::{
///
/// if you want to handle the error, you can use `serve_client_with_ct` or `serve_client` with `Result<RunningService<RoleClient, S>, ClientError>`
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum ClientInitializeError {
#[error("expect initialized response, but received: {0:?}")]
ExpectedInitResponse(Option<ServerJsonRpcMessage>),

View file

@ -47,6 +47,7 @@ impl ServiceRole for RoleServer {
///
/// if you want to handle the error, you can use `serve_server_with_ct` or `serve_server` with `Result<RunningService<RoleServer, S>, ServerError>`
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum ServerInitializeError {
#[error("expect initialized request, but received: {0:?}")]
ExpectedInitializeRequest(Option<ClientJsonRpcMessage>),
@ -457,6 +458,7 @@ impl Peer<RoleServer> {
/// Errors that can occur during typed elicitation operations
#[cfg(feature = "elicitation")]
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum ElicitationError {
/// The elicitation request failed at the service level
#[error("Service error: {0}")]
@ -808,6 +810,7 @@ impl Peer<RoleServer> {
/// ElicitationAction::Cancel => {
/// println!("User cancelled/dismissed the request");
/// }
/// _ => {}
/// }
/// Ok(())
/// }
@ -858,6 +861,7 @@ impl Peer<RoleServer> {
/// ElicitationAction::Cancel => {
/// println!("User cancelled/dismissed the request");
/// }
/// _ => {}
/// }
/// Ok(())
/// }

View file

@ -266,6 +266,7 @@ impl<C> AuthClient<C> {
/// Auth error
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum AuthError {
#[error("OAuth authorization required")]
AuthorizationRequired,

View file

@ -44,6 +44,7 @@ impl InsufficientScopeError {
}
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum StreamableHttpError<E: std::error::Error + Send + Sync + 'static> {
#[error("SSE error: {0}")]
Sse(#[from] SseError),
@ -81,12 +82,14 @@ pub enum StreamableHttpError<E: std::error::Error + Send + Sync + 'static> {
}
#[derive(Debug, Clone, Error)]
#[non_exhaustive]
pub enum StreamableHttpProtocolError {
#[error("Missing session id in response")]
MissingSessionIdInResponse,
}
#[allow(clippy::large_enum_variant)]
#[non_exhaustive]
pub enum StreamableHttpPostResponse {
Accepted,
Json(ServerJsonRpcMessage, Option<String>),

View file

@ -688,6 +688,7 @@ pub enum SessionEvent {
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum SessionQuitReason {
ServiceTerminated,
ClientTerminated,
@ -880,6 +881,7 @@ pub type SessionTransport = WorkerTransport<LocalSessionWorker>;
#[allow(clippy::large_enum_variant)]
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum LocalSessionWorkerError {
#[error("transport terminated")]
TransportTerminated,

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