feat: add title field for data types (#410)
* feat(model): add title field to various structs * fix(test): fix test json schema * fix(model): allow boxed type in `ts_union` macro
This commit is contained in:
parent
fdd6bc0565
commit
1b70f5b032
8 changed files with 219 additions and 73 deletions
|
|
@ -16,6 +16,7 @@ impl<H: ClientHandler> Service<RoleClient> for H {
|
|||
ServerRequest::CreateMessageRequest(request) => self
|
||||
.create_message(request.params, context)
|
||||
.await
|
||||
.map(Box::new)
|
||||
.map(ClientResult::CreateMessageResult),
|
||||
ServerRequest::ListRootsRequest(_) => self
|
||||
.list_roots(context)
|
||||
|
|
|
|||
|
|
@ -350,6 +350,7 @@ pub fn cached_arguments_from_schema<T: schemars::JsonSchema + std::any::Any>()
|
|||
|
||||
arguments.push(crate::model::PromptArgument {
|
||||
name: name.clone(),
|
||||
title: None,
|
||||
description,
|
||||
required: Some(required.contains(name.as_str())),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -504,7 +504,7 @@ impl ErrorData {
|
|||
/// Represents any JSON-RPC message that can be sent or received.
|
||||
///
|
||||
/// This enum covers all possible message types in the JSON-RPC protocol:
|
||||
/// individual requests/responses, notifications, batch operations, and errors.
|
||||
/// individual requests/responses, notifications, and errors.
|
||||
/// It serves as the top-level message container for MCP communication.
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[serde(untagged)]
|
||||
|
|
@ -697,6 +697,8 @@ impl Default for ClientInfo {
|
|||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct Implementation {
|
||||
pub name: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub title: Option<String>,
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
|
|
@ -710,6 +712,7 @@ impl Implementation {
|
|||
pub fn from_build_env() -> Self {
|
||||
Implementation {
|
||||
name: env!("CARGO_CRATE_NAME").to_owned(),
|
||||
title: None,
|
||||
version: env!("CARGO_PKG_VERSION").to_owned(),
|
||||
}
|
||||
}
|
||||
|
|
@ -1115,6 +1118,8 @@ pub struct ResourceReference {
|
|||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct PromptReference {
|
||||
pub name: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub title: Option<String>,
|
||||
}
|
||||
|
||||
const_string!(CompleteRequestMethod = "completion/complete");
|
||||
|
|
@ -1449,24 +1454,50 @@ pub struct GetPromptResult {
|
|||
|
||||
macro_rules! ts_union {
|
||||
(
|
||||
export type $U: ident =
|
||||
$(|)?$($V: ident)|*;
|
||||
export type $U:ident =
|
||||
$($rest:tt)*
|
||||
) => {
|
||||
ts_union!(@declare $U { $($rest)* });
|
||||
ts_union!(@impl_from $U { $($rest)* });
|
||||
};
|
||||
(@declare $U:ident { $($variant:tt)* }) => {
|
||||
ts_union!(@declare_variant $U { } {$($variant)*} );
|
||||
};
|
||||
(@declare_variant $U:ident { $($declared:tt)* } {$(|)? box $V:ident $($rest:tt)*}) => {
|
||||
ts_union!(@declare_variant $U { $($declared)* $V(Box<$V>), } {$($rest)*});
|
||||
};
|
||||
(@declare_variant $U:ident { $($declared:tt)* } {$(|)? $V:ident $($rest:tt)*}) => {
|
||||
ts_union!(@declare_variant $U { $($declared)* $V($V), } {$($rest)*});
|
||||
};
|
||||
(@declare_variant $U:ident { $($declared:tt)* } { ; }) => {
|
||||
ts_union!(@declare_end $U { $($declared)* } );
|
||||
};
|
||||
(@declare_end $U:ident { $($declared:tt)* }) => {
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[serde(untagged)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum $U {
|
||||
$($V($V),)*
|
||||
$($declared)*
|
||||
}
|
||||
|
||||
$(
|
||||
impl From<$V> for $U {
|
||||
fn from(value: $V) -> Self {
|
||||
$U::$V(value)
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
(@impl_from $U: ident {$(|)? box $V:ident $($rest:tt)*}) => {
|
||||
impl From<$V> for $U {
|
||||
fn from(value: $V) -> Self {
|
||||
$U::$V(Box::new(value))
|
||||
}
|
||||
}
|
||||
ts_union!(@impl_from $U {$($rest)*});
|
||||
};
|
||||
(@impl_from $U: ident {$(|)? $V:ident $($rest:tt)*}) => {
|
||||
impl From<$V> for $U {
|
||||
fn from(value: $V) -> Self {
|
||||
$U::$V(value)
|
||||
}
|
||||
}
|
||||
ts_union!(@impl_from $U {$($rest)*});
|
||||
};
|
||||
(@impl_from $U: ident { ; }) => {};
|
||||
(@impl_from $U: ident { }) => {};
|
||||
}
|
||||
|
||||
ts_union!(
|
||||
|
|
@ -1515,7 +1546,7 @@ ts_union!(
|
|||
);
|
||||
|
||||
ts_union!(
|
||||
export type ClientResult = CreateMessageResult | ListRootsResult | CreateElicitationResult | EmptyResult;
|
||||
export type ClientResult = box CreateMessageResult | ListRootsResult | CreateElicitationResult | EmptyResult;
|
||||
);
|
||||
|
||||
impl ClientResult {
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ pub enum RawContent {
|
|||
Text(RawTextContent),
|
||||
Image(RawImageContent),
|
||||
Resource(RawEmbeddedResource),
|
||||
Audio(AudioContent),
|
||||
Audio(RawAudioContent),
|
||||
ResourceLink(super::resource::RawResource),
|
||||
}
|
||||
|
||||
|
|
@ -253,6 +253,7 @@ mod tests {
|
|||
let resource_link = RawContent::ResourceLink(RawResource {
|
||||
uri: "file:///test.txt".to_string(),
|
||||
name: "test.txt".to_string(),
|
||||
title: None,
|
||||
description: Some("A test file".to_string()),
|
||||
mime_type: Some("text/plain".to_string()),
|
||||
size: Some(100),
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ use super::{
|
|||
pub struct Prompt {
|
||||
/// The name of the prompt
|
||||
pub name: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub title: Option<String>,
|
||||
/// Optional description of what the prompt does
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub description: Option<String>,
|
||||
|
|
@ -35,6 +37,7 @@ impl Prompt {
|
|||
{
|
||||
Prompt {
|
||||
name: name.into(),
|
||||
title: None,
|
||||
description: description.map(Into::into),
|
||||
arguments,
|
||||
}
|
||||
|
|
@ -47,6 +50,9 @@ impl Prompt {
|
|||
pub struct PromptArgument {
|
||||
/// The name of the argument
|
||||
pub name: String,
|
||||
/// A human-readable title for the argument
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub title: Option<String>,
|
||||
/// A description of what the argument is used for
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub description: Option<String>,
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@ pub struct RawResource {
|
|||
pub uri: String,
|
||||
/// Name of the resource
|
||||
pub name: String,
|
||||
/// Human-readable title of the resource
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub title: Option<String>,
|
||||
/// Optional description of the resource
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub description: Option<String>,
|
||||
|
|
@ -34,6 +37,8 @@ pub struct RawResourceTemplate {
|
|||
pub uri_template: String,
|
||||
pub name: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub title: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub description: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub mime_type: Option<String>,
|
||||
|
|
@ -82,6 +87,7 @@ impl RawResource {
|
|||
Self {
|
||||
uri: uri.into(),
|
||||
name: name.into(),
|
||||
title: None,
|
||||
description: None,
|
||||
mime_type: None,
|
||||
size: None,
|
||||
|
|
@ -99,6 +105,7 @@ mod tests {
|
|||
fn test_resource_serialization() {
|
||||
let resource = RawResource {
|
||||
uri: "file:///test.txt".to_string(),
|
||||
title: None,
|
||||
name: "test".to_string(),
|
||||
description: Some("Test resource".to_string()),
|
||||
mime_type: Some("text/plain".to_string()),
|
||||
|
|
|
|||
|
|
@ -15,6 +15,9 @@ use super::JsonObject;
|
|||
pub struct Tool {
|
||||
/// The name of the tool
|
||||
pub name: Cow<'static, str>,
|
||||
/// A human-readable title for the tool
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub title: Option<String>,
|
||||
/// A description of what the tool does
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub description: Option<Cow<'static, str>>,
|
||||
|
|
@ -138,6 +141,7 @@ impl Tool {
|
|||
{
|
||||
Tool {
|
||||
name: name.into(),
|
||||
title: None,
|
||||
description: Some(description.into()),
|
||||
input_schema: input_schema.into(),
|
||||
output_schema: None,
|
||||
|
|
|
|||
|
|
@ -384,7 +384,22 @@ macro_rules! method {
|
|||
}
|
||||
|
||||
impl Peer<RoleServer> {
|
||||
method!(peer_req create_message CreateMessageRequest(CreateMessageRequestParam) => CreateMessageResult);
|
||||
pub async fn create_message(
|
||||
&self,
|
||||
params: CreateMessageRequestParam,
|
||||
) -> Result<CreateMessageResult, ServiceError> {
|
||||
let result = self
|
||||
.send_request(ServerRequest::CreateMessageRequest(CreateMessageRequest {
|
||||
method: Default::default(),
|
||||
params,
|
||||
extensions: Default::default(),
|
||||
}))
|
||||
.await?;
|
||||
match result {
|
||||
ClientResult::CreateMessageResult(result) => Ok(*result),
|
||||
_ => Err(ServiceError::UnexpectedResponse),
|
||||
}
|
||||
}
|
||||
method!(peer_req list_roots ListRootsRequest() => ListRootsResult);
|
||||
#[cfg(feature = "elicitation")]
|
||||
method!(peer_req create_elicitation CreateElicitationRequest(CreateElicitationRequestParam) => CreateElicitationResult);
|
||||
|
|
|
|||
Loading…
Reference in a new issue