feat: revision-2025-03-26 without streamable http (#84)
1. Suppot revision 2025-03-26 data types 2. Support meta, progress tokne, and extensions in request/notification, 3. Remove `Message`, use `JsonRpcMessage` directly
This commit is contained in:
parent
cced5e9c2f
commit
68375460df
15 changed files with 1280 additions and 229 deletions
|
|
@ -349,6 +349,7 @@ pub(crate) fn tool_fn_item(attr: TokenStream, mut input_fn: ItemFn) -> syn::Resu
|
|||
name: #name.into(),
|
||||
description: Some(#description.into()),
|
||||
input_schema: #schema.into(),
|
||||
annotations: None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -413,10 +414,10 @@ pub(crate) fn tool_fn_item(attr: TokenStream, mut input_fn: ItemFn) -> syn::Resu
|
|||
Some(line)
|
||||
}
|
||||
});
|
||||
let trivial_argrextraction_part = quote! {
|
||||
let trivial_arg_extraction_part = quote! {
|
||||
#(#trivial_args)*
|
||||
};
|
||||
let processed_argrextraction_part = match &mut tool_macro_attrs.params {
|
||||
let processed_arg_extraction_part = match &mut tool_macro_attrs.params {
|
||||
ToolParams::Aggregated { rust_type } => {
|
||||
let PatType { pat, ty, .. } = rust_type;
|
||||
quote! {
|
||||
|
|
@ -487,8 +488,8 @@ pub(crate) fn tool_fn_item(attr: TokenStream, mut input_fn: ItemFn) -> syn::Resu
|
|||
#raw_fn_vis async fn #tool_call_fn_ident(context: rmcp::handler::server::tool::ToolCallContext<'_, Self>)
|
||||
-> std::result::Result<rmcp::model::CallToolResult, rmcp::Error> {
|
||||
use rmcp::handler::server::tool::*;
|
||||
#trivial_argrextraction_part
|
||||
#processed_argrextraction_part
|
||||
#trivial_arg_extraction_part
|
||||
#processed_arg_extraction_part
|
||||
#call
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -141,21 +141,21 @@ pub trait ServerHandler: Sized + Send + Sync + 'static {
|
|||
}
|
||||
fn list_prompts(
|
||||
&self,
|
||||
request: PaginatedRequestParam,
|
||||
request: Option<PaginatedRequestParam>,
|
||||
context: RequestContext<RoleServer>,
|
||||
) -> impl Future<Output = Result<ListPromptsResult, McpError>> + Send + '_ {
|
||||
std::future::ready(Ok(ListPromptsResult::default()))
|
||||
}
|
||||
fn list_resources(
|
||||
&self,
|
||||
request: PaginatedRequestParam,
|
||||
request: Option<PaginatedRequestParam>,
|
||||
context: RequestContext<RoleServer>,
|
||||
) -> impl Future<Output = Result<ListResourcesResult, McpError>> + Send + '_ {
|
||||
std::future::ready(Ok(ListResourcesResult::default()))
|
||||
}
|
||||
fn list_resource_templates(
|
||||
&self,
|
||||
request: PaginatedRequestParam,
|
||||
request: Option<PaginatedRequestParam>,
|
||||
context: RequestContext<RoleServer>,
|
||||
) -> impl Future<Output = Result<ListResourceTemplatesResult, McpError>> + Send + '_ {
|
||||
std::future::ready(Ok(ListResourceTemplatesResult::default()))
|
||||
|
|
@ -192,7 +192,7 @@ pub trait ServerHandler: Sized + Send + Sync + 'static {
|
|||
}
|
||||
fn list_tools(
|
||||
&self,
|
||||
request: PaginatedRequestParam,
|
||||
request: Option<PaginatedRequestParam>,
|
||||
context: RequestContext<RoleServer>,
|
||||
) -> impl Future<Output = Result<ListToolsResult, McpError>> + Send + '_ {
|
||||
std::future::ready(Ok(ListToolsResult::default()))
|
||||
|
|
|
|||
|
|
@ -453,7 +453,7 @@ macro_rules! tool_box {
|
|||
(@derive $tool_box:ident) => {
|
||||
async fn list_tools(
|
||||
&self,
|
||||
_: $crate::model::PaginatedRequestParam,
|
||||
_: Option<$crate::model::PaginatedRequestParam>,
|
||||
_: $crate::service::RequestContext<$crate::service::RoleServer>,
|
||||
) -> Result<$crate::model::ListToolsResult, $crate::Error> {
|
||||
Ok($crate::model::ListToolsResult {
|
||||
|
|
|
|||
|
|
@ -2,18 +2,24 @@ use std::{borrow::Cow, sync::Arc};
|
|||
mod annotated;
|
||||
mod capabilities;
|
||||
mod content;
|
||||
mod extension;
|
||||
mod meta;
|
||||
mod prompt;
|
||||
mod resource;
|
||||
mod serde_impl;
|
||||
mod tool;
|
||||
|
||||
pub use annotated::*;
|
||||
pub use capabilities::*;
|
||||
pub use content::*;
|
||||
pub use extension::*;
|
||||
pub use meta::*;
|
||||
pub use prompt::*;
|
||||
pub use resource::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
pub use tool::*;
|
||||
|
||||
/// You can use [`crate::object!`] or [`crate::model::object`] to create a json object quickly.
|
||||
pub type JsonObject<F = Value> = serde_json::Map<String, F>;
|
||||
|
||||
/// unwrap the JsonObject under [`serde_json::Value`]
|
||||
|
|
@ -28,6 +34,7 @@ pub fn object(value: serde_json::Value) -> JsonObject {
|
|||
}
|
||||
}
|
||||
|
||||
/// Use this macro just like [`serde_json::json!`]
|
||||
#[cfg(feature = "macros")]
|
||||
#[macro_export]
|
||||
macro_rules! object {
|
||||
|
|
@ -85,7 +92,7 @@ macro_rules! const_string {
|
|||
|
||||
const_string!(JsonRpcVersion2_0 = "2.0");
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash, PartialOrd)]
|
||||
pub struct ProtocolVersion(Cow<'static, str>);
|
||||
|
||||
impl Default for ProtocolVersion {
|
||||
|
|
@ -94,8 +101,9 @@ impl Default for ProtocolVersion {
|
|||
}
|
||||
}
|
||||
impl ProtocolVersion {
|
||||
pub const LATEST: Self = Self(Cow::Borrowed("2024-11-05"));
|
||||
pub const V_2024_11_05: Self = Self::LATEST;
|
||||
pub const V_2025_03_26: Self = Self(Cow::Borrowed("2025-03-26"));
|
||||
pub const V_2024_11_05: Self = Self(Cow::Borrowed("2024-11-05"));
|
||||
pub const LATEST: Self = Self::V_2025_03_26;
|
||||
}
|
||||
|
||||
impl Serialize for ProtocolVersion {
|
||||
|
|
@ -116,17 +124,28 @@ impl<'de> Deserialize<'de> for ProtocolVersion {
|
|||
#[allow(clippy::single_match)]
|
||||
match s.as_str() {
|
||||
"2024-11-05" => return Ok(ProtocolVersion::V_2024_11_05),
|
||||
"2025-03-26" => return Ok(ProtocolVersion::V_2025_03_26),
|
||||
_ => {}
|
||||
}
|
||||
Ok(ProtocolVersion(Cow::Owned(s)))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
pub enum NumberOrString {
|
||||
Number(u32),
|
||||
String(Arc<str>),
|
||||
}
|
||||
|
||||
impl NumberOrString {
|
||||
pub fn into_json_value(self) -> Value {
|
||||
match self {
|
||||
NumberOrString::Number(n) => Value::Number(serde_json::Number::from(n)),
|
||||
NumberOrString::String(s) => Value::String(s.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for NumberOrString {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
|
|
@ -166,41 +185,58 @@ impl<'de> Deserialize<'de> for NumberOrString {
|
|||
}
|
||||
|
||||
pub type RequestId = NumberOrString;
|
||||
pub type ProgressToken = NumberOrString;
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
pub struct WithMeta<P = JsonObject, M = ()> {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub _meta: Option<M>,
|
||||
#[serde(flatten)]
|
||||
pub inner: P,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RequestMeta {
|
||||
progress_token: ProgressToken,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
pub struct Request<M = String, P = Option<WithMeta<JsonObject, RequestMeta>>> {
|
||||
#[serde(transparent)]
|
||||
pub struct ProgressToken(pub NumberOrString);
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Request<M = String, P = JsonObject> {
|
||||
pub method: M,
|
||||
// #[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub params: P,
|
||||
/// extensions will carry anything possible in the context, including [`Meta`]
|
||||
///
|
||||
/// this is similar with the Extensions in `http` crate
|
||||
pub extensions: Extensions,
|
||||
}
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RequestOptionalParam<M = String, P = JsonObject> {
|
||||
pub method: M,
|
||||
// #[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub params: Option<P>,
|
||||
/// extensions will carry anything possible in the context, including [`Meta`]
|
||||
///
|
||||
/// this is similar with the Extensions in `http` crate
|
||||
pub extensions: Extensions,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RequestNoParam<M = String> {
|
||||
pub method: M,
|
||||
/// extensions will carry anything possible in the context, including [`Meta`]
|
||||
///
|
||||
/// this is similar with the Extensions in `http` crate
|
||||
pub extensions: Extensions,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
pub struct Notification<M = String, P = Option<WithMeta<JsonObject, JsonObject>>> {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Notification<M = String, P = JsonObject> {
|
||||
pub method: M,
|
||||
pub params: P,
|
||||
/// extensions will carry anything possible in the context, including [`Meta`]
|
||||
///
|
||||
/// this is similar with the Extensions in `http` crate
|
||||
pub extensions: Extensions,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NotificationNoParam<M = String> {
|
||||
pub method: M,
|
||||
/// extensions will carry anything possible in the context, including [`Meta`]
|
||||
///
|
||||
/// this is similar with the Extensions in `http` crate
|
||||
pub extensions: Extensions,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
|
|
@ -210,9 +246,9 @@ pub struct JsonRpcRequest<R = Request> {
|
|||
#[serde(flatten)]
|
||||
pub request: R,
|
||||
}
|
||||
type DefaultResponse = WithMeta<JsonObject, JsonObject>;
|
||||
type DefaultResponse = JsonObject;
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
pub struct JsonRpcResponse<R = DefaultResponse> {
|
||||
pub struct JsonRpcResponse<R = JsonObject> {
|
||||
pub jsonrpc: JsonRpcVersion2_0,
|
||||
pub id: RequestId,
|
||||
pub result: R,
|
||||
|
|
@ -295,95 +331,111 @@ impl ErrorData {
|
|||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[serde(untagged)]
|
||||
pub enum JsonRpcMessage<Req = Request, Resp = DefaultResponse, Noti = Notification> {
|
||||
pub enum JsonRpcBatchRequestItem<Req, Not> {
|
||||
Request(JsonRpcRequest<Req>),
|
||||
Response(JsonRpcResponse<Resp>),
|
||||
Notification(JsonRpcNotification<Noti>),
|
||||
Error(JsonRpcError),
|
||||
Notification(JsonRpcNotification<Not>),
|
||||
}
|
||||
|
||||
impl<Req, Resp, Noti> JsonRpcMessage<Req, Resp, Noti> {
|
||||
pub fn into_message(self) -> Message<Req, Resp, Noti> {
|
||||
impl<Req, Not> JsonRpcBatchRequestItem<Req, Not> {
|
||||
pub fn into_non_batch_message<Resp>(self) -> JsonRpcMessage<Req, Resp, Not> {
|
||||
match self {
|
||||
JsonRpcMessage::Request(JsonRpcRequest { id, request, .. }) => {
|
||||
Message::Request(request, id)
|
||||
}
|
||||
JsonRpcMessage::Response(JsonRpcResponse { id, result, .. }) => {
|
||||
Message::Response(result, id)
|
||||
}
|
||||
JsonRpcMessage::Notification(JsonRpcNotification { notification, .. }) => {
|
||||
Message::Notification(notification)
|
||||
}
|
||||
JsonRpcMessage::Error(JsonRpcError { id, error, .. }) => Message::Error(error, id),
|
||||
JsonRpcBatchRequestItem::Request(r) => JsonRpcMessage::Request(r),
|
||||
JsonRpcBatchRequestItem::Notification(n) => JsonRpcMessage::Notification(n),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Message<Req = Request, Resp = DefaultResponse, Noti = Notification> {
|
||||
Request(Req, RequestId),
|
||||
Response(Resp, RequestId),
|
||||
Error(ErrorData, RequestId),
|
||||
Notification(Noti),
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[serde(untagged)]
|
||||
pub enum JsonRpcBatchResponseItem<Resp> {
|
||||
Response(JsonRpcResponse<Resp>),
|
||||
Error(JsonRpcError),
|
||||
}
|
||||
|
||||
impl<Req, Resp, Noti> Message<Req, Resp, Noti> {
|
||||
pub fn into_notification(self) -> Option<Noti> {
|
||||
impl<Resp> JsonRpcBatchResponseItem<Resp> {
|
||||
pub fn into_non_batch_message<Req, Not>(self) -> JsonRpcMessage<Req, Resp, Not> {
|
||||
match self {
|
||||
Message::Notification(notification) => Some(notification),
|
||||
JsonRpcBatchResponseItem::Response(r) => JsonRpcMessage::Response(r),
|
||||
JsonRpcBatchResponseItem::Error(e) => JsonRpcMessage::Error(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[serde(untagged)]
|
||||
pub enum JsonRpcMessage<Req = Request, Resp = DefaultResponse, Noti = Notification> {
|
||||
Request(JsonRpcRequest<Req>),
|
||||
Response(JsonRpcResponse<Resp>),
|
||||
Notification(JsonRpcNotification<Noti>),
|
||||
BatchRequest(Vec<JsonRpcBatchRequestItem<Req, Noti>>),
|
||||
BatchResponse(Vec<JsonRpcBatchResponseItem<Resp>>),
|
||||
Error(JsonRpcError),
|
||||
}
|
||||
|
||||
impl<Req, Resp, Not> JsonRpcMessage<Req, Resp, Not> {
|
||||
#[inline]
|
||||
pub const fn request(request: Req, id: RequestId) -> Self {
|
||||
JsonRpcMessage::Request(JsonRpcRequest {
|
||||
jsonrpc: JsonRpcVersion2_0,
|
||||
id,
|
||||
request,
|
||||
})
|
||||
}
|
||||
#[inline]
|
||||
pub const fn response(response: Resp, id: RequestId) -> Self {
|
||||
JsonRpcMessage::Response(JsonRpcResponse {
|
||||
jsonrpc: JsonRpcVersion2_0,
|
||||
id,
|
||||
result: response,
|
||||
})
|
||||
}
|
||||
#[inline]
|
||||
pub const fn error(error: ErrorData, id: RequestId) -> Self {
|
||||
JsonRpcMessage::Error(JsonRpcError {
|
||||
jsonrpc: JsonRpcVersion2_0,
|
||||
id,
|
||||
error,
|
||||
})
|
||||
}
|
||||
#[inline]
|
||||
pub const fn notification(notification: Not) -> Self {
|
||||
JsonRpcMessage::Notification(JsonRpcNotification {
|
||||
jsonrpc: JsonRpcVersion2_0,
|
||||
notification,
|
||||
})
|
||||
}
|
||||
pub fn into_request(self) -> Option<(Req, RequestId)> {
|
||||
match self {
|
||||
JsonRpcMessage::Request(r) => Some((r.request, r.id)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
pub fn into_response(self) -> Option<(Resp, RequestId)> {
|
||||
match self {
|
||||
Message::Response(result, id) => Some((result, id)),
|
||||
JsonRpcMessage::Response(r) => Some((r.result, r.id)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
pub fn into_request(self) -> Option<(Req, RequestId)> {
|
||||
pub fn into_notification(self) -> Option<Not> {
|
||||
match self {
|
||||
Message::Request(request, id) => Some((request, id)),
|
||||
JsonRpcMessage::Notification(n) => Some(n.notification),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
pub fn into_error(self) -> Option<(ErrorData, RequestId)> {
|
||||
match self {
|
||||
Message::Error(error, id) => Some((error, id)),
|
||||
JsonRpcMessage::Error(e) => Some((e.error, e.id)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
pub fn into_result(self) -> Option<(Result<Resp, ErrorData>, RequestId)> {
|
||||
match self {
|
||||
Message::Response(result, id) => Some((Ok(result), id)),
|
||||
Message::Error(error, id) => Some((Err(error), id)),
|
||||
JsonRpcMessage::Response(r) => Some((Ok(r.result), r.id)),
|
||||
JsonRpcMessage::Error(e) => Some((Err(e.error), e.id)),
|
||||
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
pub fn into_json_rpc_message(self) -> JsonRpcMessage<Req, Resp, Noti> {
|
||||
match self {
|
||||
Message::Request(request, id) => JsonRpcMessage::Request(JsonRpcRequest {
|
||||
jsonrpc: JsonRpcVersion2_0,
|
||||
id,
|
||||
request,
|
||||
}),
|
||||
Message::Response(result, id) => JsonRpcMessage::Response(JsonRpcResponse {
|
||||
jsonrpc: JsonRpcVersion2_0,
|
||||
id,
|
||||
result,
|
||||
}),
|
||||
Message::Error(error, id) => JsonRpcMessage::Error(JsonRpcError {
|
||||
jsonrpc: JsonRpcVersion2_0,
|
||||
id,
|
||||
error,
|
||||
}),
|
||||
Message::Notification(notification) => {
|
||||
JsonRpcMessage::Notification(JsonRpcNotification {
|
||||
jsonrpc: JsonRpcVersion2_0,
|
||||
notification,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// # Empty result
|
||||
|
|
@ -493,12 +545,10 @@ impl Implementation {
|
|||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PaginatedRequestParamInner {
|
||||
pub struct PaginatedRequestParam {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub cursor: Option<String>,
|
||||
}
|
||||
|
||||
pub type PaginatedRequestParam = Option<PaginatedRequestParamInner>;
|
||||
const_string!(PingRequestMethod = "ping");
|
||||
pub type PingRequest = RequestNoParam<PingRequestMethod>;
|
||||
|
||||
|
|
@ -512,6 +562,9 @@ pub struct ProgressNotificationParam {
|
|||
/// Total number of items to process (or total progress required), if known
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub total: Option<u32>,
|
||||
/// An optional message describing the current progress.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub message: Option<String>,
|
||||
}
|
||||
|
||||
pub type ProgressNotification = Notification<ProgressNotificationMethod, ProgressNotificationParam>;
|
||||
|
|
@ -533,14 +586,15 @@ macro_rules! paginated_result {
|
|||
}
|
||||
|
||||
const_string!(ListResourcesRequestMethod = "resources/list");
|
||||
pub type ListResourcesRequest = Request<ListResourcesRequestMethod, PaginatedRequestParam>;
|
||||
pub type ListResourcesRequest =
|
||||
RequestOptionalParam<ListResourcesRequestMethod, PaginatedRequestParam>;
|
||||
paginated_result!(ListResourcesResult {
|
||||
resources: Vec<Resource>
|
||||
});
|
||||
|
||||
const_string!(ListResourceTemplatesRequestMethod = "resources/templates/list");
|
||||
pub type ListResourceTemplatesRequest =
|
||||
Request<ListResourceTemplatesRequestMethod, PaginatedRequestParam>;
|
||||
RequestOptionalParam<ListResourceTemplatesRequestMethod, PaginatedRequestParam>;
|
||||
paginated_result!(ListResourceTemplatesResult {
|
||||
resource_templates: Vec<ResourceTemplate>
|
||||
});
|
||||
|
|
@ -589,7 +643,7 @@ pub type ResourceUpdatedNotification =
|
|||
Notification<ResourceUpdatedNotificationMethod, ResourceUpdatedNotificationParam>;
|
||||
|
||||
const_string!(ListPromptsRequestMethod = "prompts/list");
|
||||
pub type ListPromptsRequest = Request<ListPromptsRequestMethod, PaginatedRequestParam>;
|
||||
pub type ListPromptsRequest = RequestOptionalParam<ListPromptsRequestMethod, PaginatedRequestParam>;
|
||||
paginated_result!(ListPromptsResult {
|
||||
prompts: Vec<Prompt>
|
||||
});
|
||||
|
|
@ -793,7 +847,7 @@ impl CallToolResult {
|
|||
}
|
||||
|
||||
const_string!(ListToolsRequestMethod = "tools/list");
|
||||
pub type ListToolsRequest = Request<ListToolsRequestMethod, PaginatedRequestParam>;
|
||||
pub type ListToolsRequest = RequestOptionalParam<ListToolsRequestMethod, PaginatedRequestParam>;
|
||||
paginated_result!(
|
||||
ListToolsResult {
|
||||
tools: Vec<Tool>
|
||||
|
|
@ -840,7 +894,7 @@ macro_rules! ts_union {
|
|||
export type $U: ident =
|
||||
$(|)?$($V: ident)|*;
|
||||
) => {
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[serde(untagged)]
|
||||
pub enum $U {
|
||||
$($V($V),)*
|
||||
|
|
@ -884,7 +938,6 @@ impl ClientResult {
|
|||
}
|
||||
|
||||
pub type ClientJsonRpcMessage = JsonRpcMessage<ClientRequest, ClientResult, ClientNotification>;
|
||||
pub type ClientMessage = Message<ClientRequest, ClientResult, ClientNotification>;
|
||||
|
||||
ts_union!(
|
||||
export type ServerRequest =
|
||||
|
|
@ -926,7 +979,6 @@ impl ServerResult {
|
|||
}
|
||||
|
||||
pub type ServerJsonRpcMessage = JsonRpcMessage<ServerRequest, ServerResult, ServerNotification>;
|
||||
pub type ServerMessage = Message<ServerRequest, ServerResult, ServerNotification>;
|
||||
|
||||
impl TryInto<CancelledNotification> for ServerNotification {
|
||||
type Error = ServerNotification;
|
||||
|
|
@ -975,12 +1027,14 @@ mod tests {
|
|||
});
|
||||
let message: ClientJsonRpcMessage =
|
||||
serde_json::from_value(raw.clone()).expect("invalid notification");
|
||||
let message = message.into_message();
|
||||
match &message {
|
||||
ClientMessage::Notification(ClientNotification::InitializedNotification(_n)) => {}
|
||||
ClientJsonRpcMessage::Notification(JsonRpcNotification {
|
||||
notification: ClientNotification::InitializedNotification(_n),
|
||||
..
|
||||
}) => {}
|
||||
_ => panic!("Expected Notification"),
|
||||
}
|
||||
let json = serde_json::to_value(message.into_json_rpc_message()).expect("valid json");
|
||||
let json = serde_json::to_value(message).expect("valid json");
|
||||
assert_eq!(json, raw);
|
||||
}
|
||||
|
||||
|
|
@ -999,7 +1053,7 @@ mod tests {
|
|||
assert_eq!(r.id, RequestId::Number(1));
|
||||
assert_eq!(r.request.method, "request");
|
||||
assert_eq!(
|
||||
&r.request.params.as_ref().unwrap().inner,
|
||||
&r.request.params,
|
||||
json!({"key": "value"})
|
||||
.as_object()
|
||||
.expect("should be an object")
|
||||
|
|
@ -1057,10 +1111,7 @@ mod tests {
|
|||
});
|
||||
let request: ClientJsonRpcMessage =
|
||||
serde_json::from_value(request.clone()).expect("invalid request");
|
||||
let (request, id) = request
|
||||
.into_message()
|
||||
.into_request()
|
||||
.expect("expect request");
|
||||
let (request, id) = request.into_request().expect("should be a request");
|
||||
assert_eq!(id, RequestId::Number(1));
|
||||
match request {
|
||||
ClientRequest::InitializeRequest(Request {
|
||||
|
|
@ -1071,6 +1122,7 @@ mod tests {
|
|||
capabilities,
|
||||
client_info,
|
||||
},
|
||||
..
|
||||
}) => {
|
||||
assert_eq!(capabilities.roots.unwrap().list_changed, Some(true));
|
||||
assert_eq!(capabilities.sampling.unwrap().len(), 0);
|
||||
|
|
@ -1083,7 +1135,6 @@ mod tests {
|
|||
serde_json::from_value(raw_response_json.clone()).expect("invalid response");
|
||||
let (response, id) = server_response
|
||||
.clone()
|
||||
.into_message()
|
||||
.into_response()
|
||||
.expect("expect response");
|
||||
assert_eq!(id, RequestId::Number(1));
|
||||
|
|
@ -1113,4 +1164,11 @@ mod tests {
|
|||
|
||||
assert_eq!(server_response_json, raw_response_json);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_protocol_version_order() {
|
||||
let v1 = ProtocolVersion::V_2024_11_05;
|
||||
let v2 = ProtocolVersion::V_2025_03_26;
|
||||
assert!(v1 < v2);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ use chrono::{DateTime, Utc};
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::{
|
||||
RawContent, RawEmbeddedResource, RawImageContent, RawResource, RawResourceTemplate,
|
||||
RawTextContent, Role,
|
||||
RawAudioContent, RawContent, RawEmbeddedResource, RawImageContent, RawResource,
|
||||
RawResourceTemplate, RawTextContent, Role,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
|
||||
|
|
@ -162,6 +162,7 @@ macro_rules! annotate {
|
|||
annotate!(RawContent);
|
||||
annotate!(RawTextContent);
|
||||
annotate!(RawImageContent);
|
||||
annotate!(RawAudioContent);
|
||||
annotate!(RawEmbeddedResource);
|
||||
annotate!(RawResource);
|
||||
annotate!(RawResourceTemplate);
|
||||
|
|
|
|||
|
|
@ -76,6 +76,8 @@ pub struct ServerCapabilities {
|
|||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub logging: Option<JsonObject>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub completions: Option<JsonObject>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub prompts: Option<PromptsCapability>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub resources: Option<ResourcesCapability>,
|
||||
|
|
@ -192,14 +194,15 @@ builder! {
|
|||
ServerCapabilities {
|
||||
experimental: ExperimentalCapabilities,
|
||||
logging: JsonObject,
|
||||
completions: JsonObject,
|
||||
prompts: PromptsCapability,
|
||||
resources: ResourcesCapability,
|
||||
tools: ToolsCapability
|
||||
}
|
||||
}
|
||||
|
||||
impl<const E: bool, const L: bool, const P: bool, const R: bool>
|
||||
ServerCapabilitiesBuilder<ServerCapabilitiesBuilderState<E, L, P, R, true>>
|
||||
impl<const E: bool, const L: bool, const C: bool, const P: bool, const R: bool>
|
||||
ServerCapabilitiesBuilder<ServerCapabilitiesBuilderState<E, L, C, P, R, true>>
|
||||
{
|
||||
pub fn enable_tool_list_changed(mut self) -> Self {
|
||||
if let Some(c) = self.tools.as_mut() {
|
||||
|
|
@ -209,8 +212,8 @@ impl<const E: bool, const L: bool, const P: bool, const R: bool>
|
|||
}
|
||||
}
|
||||
|
||||
impl<const E: bool, const L: bool, const R: bool, const T: bool>
|
||||
ServerCapabilitiesBuilder<ServerCapabilitiesBuilderState<E, L, true, R, T>>
|
||||
impl<const E: bool, const L: bool, const C: bool, const R: bool, const T: bool>
|
||||
ServerCapabilitiesBuilder<ServerCapabilitiesBuilderState<E, L, C, true, R, T>>
|
||||
{
|
||||
pub fn enable_prompts_list_changed(mut self) -> Self {
|
||||
if let Some(c) = self.prompts.as_mut() {
|
||||
|
|
@ -220,8 +223,8 @@ impl<const E: bool, const L: bool, const R: bool, const T: bool>
|
|||
}
|
||||
}
|
||||
|
||||
impl<const E: bool, const L: bool, const P: bool, const T: bool>
|
||||
ServerCapabilitiesBuilder<ServerCapabilitiesBuilderState<E, L, P, true, T>>
|
||||
impl<const E: bool, const L: bool, const C: bool, const P: bool, const T: bool>
|
||||
ServerCapabilitiesBuilder<ServerCapabilitiesBuilderState<E, L, C, P, true, T>>
|
||||
{
|
||||
pub fn enable_resources_list_changed(mut self) -> Self {
|
||||
if let Some(c) = self.resources.as_mut() {
|
||||
|
|
|
|||
|
|
@ -37,12 +37,22 @@ impl EmbeddedResource {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RawAudioContent {
|
||||
pub data: String,
|
||||
pub mime_type: String,
|
||||
}
|
||||
|
||||
pub type AudioContent = Annotated<RawAudioContent>;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
pub enum RawContent {
|
||||
Text(RawTextContent),
|
||||
Image(RawImageContent),
|
||||
Resource(RawEmbeddedResource),
|
||||
Audio(AudioContent),
|
||||
}
|
||||
|
||||
pub type Content = Annotated<RawContent>;
|
||||
|
|
|
|||
337
crates/rmcp/src/model/extension.rs
Normal file
337
crates/rmcp/src/model/extension.rs
Normal file
|
|
@ -0,0 +1,337 @@
|
|||
//! A container for those extra data could be carried on request or notification.
|
||||
//!
|
||||
//! This file is copied and modified from crate [http](https://github.com/hyperium/http).
|
||||
//!
|
||||
//! - Original code license: <https://github.com/hyperium/http/blob/master/LICENSE-MIT>
|
||||
//! - Original code: <https://github.com/hyperium/http/blob/master/src/extensions.rs>
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
collections::HashMap,
|
||||
fmt,
|
||||
hash::{BuildHasherDefault, Hasher},
|
||||
};
|
||||
|
||||
type AnyMap = HashMap<TypeId, Box<dyn AnyClone + Send + Sync>, BuildHasherDefault<IdHasher>>;
|
||||
|
||||
// With TypeIds as keys, there's no need to hash them. They are already hashes
|
||||
// themselves, coming from the compiler. The IdHasher just holds the u64 of
|
||||
// the TypeId, and then returns it, instead of doing any bit fiddling.
|
||||
#[derive(Default)]
|
||||
struct IdHasher(u64);
|
||||
|
||||
impl Hasher for IdHasher {
|
||||
fn write(&mut self, _: &[u8]) {
|
||||
unreachable!("TypeId calls write_u64");
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn write_u64(&mut self, id: u64) {
|
||||
self.0 = id;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn finish(&self) -> u64 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// A type map of protocol extensions.
|
||||
///
|
||||
/// `Extensions` can be used by `Request` `Notification` and `Response` to store
|
||||
/// extra data derived from the underlying protocol.
|
||||
#[derive(Clone, Default)]
|
||||
pub struct Extensions {
|
||||
// If extensions are never used, no need to carry around an empty HashMap.
|
||||
// That's 3 words. Instead, this is only 1 word.
|
||||
map: Option<Box<AnyMap>>,
|
||||
}
|
||||
|
||||
impl Extensions {
|
||||
/// Create an empty `Extensions`.
|
||||
#[inline]
|
||||
pub fn new() -> Extensions {
|
||||
Extensions { map: None }
|
||||
}
|
||||
|
||||
/// Insert a type into this `Extensions`.
|
||||
///
|
||||
/// If a extension of this type already existed, it will
|
||||
/// be returned and replaced with the new one.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use rmcp::model::Extensions;
|
||||
/// let mut ext = Extensions::new();
|
||||
/// assert!(ext.insert(5i32).is_none());
|
||||
/// assert!(ext.insert(4u8).is_none());
|
||||
/// assert_eq!(ext.insert(9i32), Some(5i32));
|
||||
/// ```
|
||||
pub fn insert<T: Clone + Send + Sync + 'static>(&mut self, val: T) -> Option<T> {
|
||||
self.map
|
||||
.get_or_insert_with(Box::default)
|
||||
.insert(TypeId::of::<T>(), Box::new(val))
|
||||
.and_then(|boxed| boxed.into_any().downcast().ok().map(|boxed| *boxed))
|
||||
}
|
||||
|
||||
/// Get a reference to a type previously inserted on this `Extensions`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use rmcp::model::Extensions;
|
||||
/// let mut ext = Extensions::new();
|
||||
/// assert!(ext.get::<i32>().is_none());
|
||||
/// ext.insert(5i32);
|
||||
///
|
||||
/// assert_eq!(ext.get::<i32>(), Some(&5i32));
|
||||
/// ```
|
||||
pub fn get<T: Send + Sync + 'static>(&self) -> Option<&T> {
|
||||
self.map
|
||||
.as_ref()
|
||||
.and_then(|map| map.get(&TypeId::of::<T>()))
|
||||
.and_then(|boxed| (**boxed).as_any().downcast_ref())
|
||||
}
|
||||
|
||||
/// Get a mutable reference to a type previously inserted on this `Extensions`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use rmcp::model::Extensions;
|
||||
/// let mut ext = Extensions::new();
|
||||
/// ext.insert(String::from("Hello"));
|
||||
/// ext.get_mut::<String>().unwrap().push_str(" World");
|
||||
///
|
||||
/// assert_eq!(ext.get::<String>().unwrap(), "Hello World");
|
||||
/// ```
|
||||
pub fn get_mut<T: Send + Sync + 'static>(&mut self) -> Option<&mut T> {
|
||||
self.map
|
||||
.as_mut()
|
||||
.and_then(|map| map.get_mut(&TypeId::of::<T>()))
|
||||
.and_then(|boxed| (**boxed).as_any_mut().downcast_mut())
|
||||
}
|
||||
|
||||
/// Get a mutable reference to a type, inserting `value` if not already present on this
|
||||
/// `Extensions`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use rmcp::model::Extensions;
|
||||
/// let mut ext = Extensions::new();
|
||||
/// *ext.get_or_insert(1i32) += 2;
|
||||
///
|
||||
/// assert_eq!(*ext.get::<i32>().unwrap(), 3);
|
||||
/// ```
|
||||
pub fn get_or_insert<T: Clone + Send + Sync + 'static>(&mut self, value: T) -> &mut T {
|
||||
self.get_or_insert_with(|| value)
|
||||
}
|
||||
|
||||
/// Get a mutable reference to a type, inserting the value created by `f` if not already present
|
||||
/// on this `Extensions`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use rmcp::model::Extensions;
|
||||
/// let mut ext = Extensions::new();
|
||||
/// *ext.get_or_insert_with(|| 1i32) += 2;
|
||||
///
|
||||
/// assert_eq!(*ext.get::<i32>().unwrap(), 3);
|
||||
/// ```
|
||||
pub fn get_or_insert_with<T: Clone + Send + Sync + 'static, F: FnOnce() -> T>(
|
||||
&mut self,
|
||||
f: F,
|
||||
) -> &mut T {
|
||||
let out = self
|
||||
.map
|
||||
.get_or_insert_with(Box::default)
|
||||
.entry(TypeId::of::<T>())
|
||||
.or_insert_with(|| Box::new(f()));
|
||||
(**out).as_any_mut().downcast_mut().unwrap()
|
||||
}
|
||||
|
||||
/// Get a mutable reference to a type, inserting the type's default value if not already present
|
||||
/// on this `Extensions`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use rmcp::model::Extensions;
|
||||
/// let mut ext = Extensions::new();
|
||||
/// *ext.get_or_insert_default::<i32>() += 2;
|
||||
///
|
||||
/// assert_eq!(*ext.get::<i32>().unwrap(), 2);
|
||||
/// ```
|
||||
pub fn get_or_insert_default<T: Default + Clone + Send + Sync + 'static>(&mut self) -> &mut T {
|
||||
self.get_or_insert_with(T::default)
|
||||
}
|
||||
|
||||
/// Remove a type from this `Extensions`.
|
||||
///
|
||||
/// If a extension of this type existed, it will be returned.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use rmcp::model::Extensions;
|
||||
/// let mut ext = Extensions::new();
|
||||
/// ext.insert(5i32);
|
||||
/// assert_eq!(ext.remove::<i32>(), Some(5i32));
|
||||
/// assert!(ext.get::<i32>().is_none());
|
||||
/// ```
|
||||
pub fn remove<T: Send + Sync + 'static>(&mut self) -> Option<T> {
|
||||
self.map
|
||||
.as_mut()
|
||||
.and_then(|map| map.remove(&TypeId::of::<T>()))
|
||||
.and_then(|boxed| boxed.into_any().downcast().ok().map(|boxed| *boxed))
|
||||
}
|
||||
|
||||
/// Clear the `Extensions` of all inserted extensions.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use rmcp::model::Extensions;
|
||||
/// let mut ext = Extensions::new();
|
||||
/// ext.insert(5i32);
|
||||
/// ext.clear();
|
||||
///
|
||||
/// assert!(ext.get::<i32>().is_none());
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn clear(&mut self) {
|
||||
if let Some(ref mut map) = self.map {
|
||||
map.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// Check whether the extension set is empty or not.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use rmcp::model::Extensions;
|
||||
/// let mut ext = Extensions::new();
|
||||
/// assert!(ext.is_empty());
|
||||
/// ext.insert(5i32);
|
||||
/// assert!(!ext.is_empty());
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.map.as_ref().is_none_or(|map| map.is_empty())
|
||||
}
|
||||
|
||||
/// Get the number of extensions available.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use rmcp::model::Extensions;
|
||||
/// let mut ext = Extensions::new();
|
||||
/// assert_eq!(ext.len(), 0);
|
||||
/// ext.insert(5i32);
|
||||
/// assert_eq!(ext.len(), 1);
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn len(&self) -> usize {
|
||||
self.map.as_ref().map_or(0, |map| map.len())
|
||||
}
|
||||
|
||||
/// Extends `self` with another `Extensions`.
|
||||
///
|
||||
/// If an instance of a specific type exists in both, the one in `self` is overwritten with the
|
||||
/// one from `other`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use rmcp::model::Extensions;
|
||||
/// let mut ext_a = Extensions::new();
|
||||
/// ext_a.insert(8u8);
|
||||
/// ext_a.insert(16u16);
|
||||
///
|
||||
/// let mut ext_b = Extensions::new();
|
||||
/// ext_b.insert(4u8);
|
||||
/// ext_b.insert("hello");
|
||||
///
|
||||
/// ext_a.extend(ext_b);
|
||||
/// assert_eq!(ext_a.len(), 3);
|
||||
/// assert_eq!(ext_a.get::<u8>(), Some(&4u8));
|
||||
/// assert_eq!(ext_a.get::<u16>(), Some(&16u16));
|
||||
/// assert_eq!(ext_a.get::<&'static str>().copied(), Some("hello"));
|
||||
/// ```
|
||||
pub fn extend(&mut self, other: Self) {
|
||||
if let Some(other) = other.map {
|
||||
if let Some(map) = &mut self.map {
|
||||
map.extend(*other);
|
||||
} else {
|
||||
self.map = Some(other);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Extensions {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Extensions").finish()
|
||||
}
|
||||
}
|
||||
|
||||
trait AnyClone: Any {
|
||||
fn clone_box(&self) -> Box<dyn AnyClone + Send + Sync>;
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any;
|
||||
fn into_any(self: Box<Self>) -> Box<dyn Any>;
|
||||
}
|
||||
|
||||
impl<T: Clone + Send + Sync + 'static> AnyClone for T {
|
||||
fn clone_box(&self) -> Box<dyn AnyClone + Send + Sync> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn into_any(self: Box<Self>) -> Box<dyn Any> {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Box<dyn AnyClone + Send + Sync> {
|
||||
fn clone(&self) -> Self {
|
||||
(**self).clone_box()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extensions() {
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
struct MyType(i32);
|
||||
|
||||
let mut extensions = Extensions::new();
|
||||
|
||||
extensions.insert(5i32);
|
||||
extensions.insert(MyType(10));
|
||||
|
||||
assert_eq!(extensions.get(), Some(&5i32));
|
||||
assert_eq!(extensions.get_mut(), Some(&mut 5i32));
|
||||
|
||||
let ext2 = extensions.clone();
|
||||
|
||||
assert_eq!(extensions.remove::<i32>(), Some(5i32));
|
||||
assert!(extensions.get::<i32>().is_none());
|
||||
|
||||
// clone still has it
|
||||
assert_eq!(ext2.get(), Some(&5i32));
|
||||
assert_eq!(ext2.get(), Some(&MyType(10)));
|
||||
|
||||
assert_eq!(extensions.get::<bool>(), None);
|
||||
assert_eq!(extensions.get(), Some(&MyType(10)));
|
||||
}
|
||||
150
crates/rmcp/src/model/meta.rs
Normal file
150
crates/rmcp/src/model/meta.rs
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
|
||||
use super::{
|
||||
ClientNotification, ClientRequest, Extensions, JsonObject, NumberOrString, ProgressToken,
|
||||
ServerNotification, ServerRequest,
|
||||
};
|
||||
|
||||
pub trait GetMeta {
|
||||
fn get_meta_mut(&mut self) -> &mut Meta;
|
||||
fn get_meta(&self) -> &Meta;
|
||||
}
|
||||
|
||||
macro_rules! variant_extension {
|
||||
(
|
||||
$Enum: ident {
|
||||
$($variant: ident)*
|
||||
}
|
||||
) => {
|
||||
impl $Enum {
|
||||
pub fn extensions(&self) -> &Extensions {
|
||||
match self {
|
||||
$(
|
||||
$Enum::$variant(v) => &v.extensions,
|
||||
)*
|
||||
}
|
||||
}
|
||||
pub fn extensions_mut(&mut self) -> &mut Extensions {
|
||||
match self {
|
||||
$(
|
||||
$Enum::$variant(v) => &mut v.extensions,
|
||||
)*
|
||||
}
|
||||
}
|
||||
}
|
||||
impl GetMeta for $Enum {
|
||||
fn get_meta_mut(&mut self) -> &mut Meta {
|
||||
self.extensions_mut().get_or_insert_default()
|
||||
}
|
||||
fn get_meta(&self) -> &Meta {
|
||||
self.extensions().get::<Meta>().unwrap_or(Meta::static_empty())
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
variant_extension! {
|
||||
ClientRequest {
|
||||
PingRequest
|
||||
InitializeRequest
|
||||
CompleteRequest
|
||||
SetLevelRequest
|
||||
GetPromptRequest
|
||||
ListPromptsRequest
|
||||
ListResourcesRequest
|
||||
ListResourceTemplatesRequest
|
||||
ReadResourceRequest
|
||||
SubscribeRequest
|
||||
UnsubscribeRequest
|
||||
CallToolRequest
|
||||
ListToolsRequest
|
||||
}
|
||||
}
|
||||
|
||||
variant_extension! {
|
||||
ServerRequest {
|
||||
PingRequest
|
||||
CreateMessageRequest
|
||||
ListRootsRequest
|
||||
}
|
||||
}
|
||||
|
||||
variant_extension! {
|
||||
ClientNotification {
|
||||
CancelledNotification
|
||||
ProgressNotification
|
||||
InitializedNotification
|
||||
RootsListChangedNotification
|
||||
}
|
||||
}
|
||||
|
||||
variant_extension! {
|
||||
ServerNotification {
|
||||
CancelledNotification
|
||||
ProgressNotification
|
||||
LoggingMessageNotification
|
||||
ResourceUpdatedNotification
|
||||
ResourceListChangedNotification
|
||||
ToolListChangedNotification
|
||||
PromptListChangedNotification
|
||||
}
|
||||
}
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||
#[serde(transparent)]
|
||||
pub struct Meta(pub JsonObject);
|
||||
const PROGRESS_TOKEN_FIELD: &str = "progressToken";
|
||||
impl Meta {
|
||||
pub fn new() -> Self {
|
||||
Self(JsonObject::new())
|
||||
}
|
||||
|
||||
pub(crate) fn static_empty() -> &'static Self {
|
||||
static EMPTY: std::sync::OnceLock<Meta> = std::sync::OnceLock::new();
|
||||
EMPTY.get_or_init(Default::default)
|
||||
}
|
||||
|
||||
pub fn get_progress_token(&self) -> Option<ProgressToken> {
|
||||
self.0.get(PROGRESS_TOKEN_FIELD).and_then(|v| match v {
|
||||
Value::String(s) => Some(ProgressToken(NumberOrString::String(s.to_string().into()))),
|
||||
Value::Number(n) => n
|
||||
.as_u64()
|
||||
.map(|n| ProgressToken(NumberOrString::Number(n as u32))),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_progress_token(&mut self, token: ProgressToken) {
|
||||
match token.0 {
|
||||
NumberOrString::String(ref s) => self.0.insert(
|
||||
PROGRESS_TOKEN_FIELD.to_string(),
|
||||
Value::String(s.to_string()),
|
||||
),
|
||||
NumberOrString::Number(n) => self
|
||||
.0
|
||||
.insert(PROGRESS_TOKEN_FIELD.to_string(), Value::Number(n.into())),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn extend(&mut self, other: Meta) {
|
||||
for (k, v) in other.0.into_iter() {
|
||||
self.0.insert(k, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Meta {
|
||||
type Target = JsonObject;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for Meta {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
267
crates/rmcp/src/model/serde_impl.rs
Normal file
267
crates/rmcp/src/model/serde_impl.rs
Normal file
|
|
@ -0,0 +1,267 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::{
|
||||
Extensions, Meta, Notification, NotificationNoParam, Request, RequestNoParam,
|
||||
RequestOptionalParam,
|
||||
};
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct WithMeta<'a, P> {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
_meta: Option<Cow<'a, Meta>>,
|
||||
#[serde(flatten)]
|
||||
_rest: P,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct Proxy<'a, M, P> {
|
||||
method: M,
|
||||
params: WithMeta<'a, P>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct ProxyOptionalParam<'a, M, P> {
|
||||
method: M,
|
||||
params: Option<WithMeta<'a, P>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct ProxyNoParam<M> {
|
||||
method: M,
|
||||
}
|
||||
|
||||
impl<M, R> Serialize for Request<M, R>
|
||||
where
|
||||
M: Serialize,
|
||||
R: Serialize,
|
||||
{
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
let extensions = &self.extensions;
|
||||
let _meta = extensions.get::<Meta>().map(Cow::Borrowed);
|
||||
Proxy::serialize(
|
||||
&Proxy {
|
||||
method: &self.method,
|
||||
params: WithMeta {
|
||||
_rest: &self.params,
|
||||
_meta,
|
||||
},
|
||||
},
|
||||
serializer,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, M, R> Deserialize<'de> for Request<M, R>
|
||||
where
|
||||
M: Deserialize<'de>,
|
||||
R: Deserialize<'de>,
|
||||
{
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let body = Proxy::deserialize(deserializer)?;
|
||||
let _meta = body.params._meta.map(|m| m.into_owned());
|
||||
let mut extensions = Extensions::new();
|
||||
if let Some(meta) = _meta {
|
||||
extensions.insert(meta);
|
||||
}
|
||||
Ok(Request {
|
||||
extensions,
|
||||
method: body.method,
|
||||
params: body.params._rest,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<M, R> Serialize for RequestOptionalParam<M, R>
|
||||
where
|
||||
M: Serialize,
|
||||
R: Serialize,
|
||||
{
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
let extensions = &self.extensions;
|
||||
let _meta = extensions.get::<Meta>().map(Cow::Borrowed);
|
||||
Proxy::serialize(
|
||||
&Proxy {
|
||||
method: &self.method,
|
||||
params: WithMeta {
|
||||
_rest: &self.params,
|
||||
_meta,
|
||||
},
|
||||
},
|
||||
serializer,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, M, R> Deserialize<'de> for RequestOptionalParam<M, R>
|
||||
where
|
||||
M: Deserialize<'de>,
|
||||
R: Deserialize<'de>,
|
||||
{
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let body = ProxyOptionalParam::<'_, _, Option<R>>::deserialize(deserializer)?;
|
||||
let mut params = None;
|
||||
let mut _meta = None;
|
||||
if let Some(body_params) = body.params {
|
||||
params = body_params._rest;
|
||||
_meta = body_params._meta.map(|m| m.into_owned());
|
||||
}
|
||||
let mut extensions = Extensions::new();
|
||||
if let Some(meta) = _meta {
|
||||
extensions.insert(meta);
|
||||
}
|
||||
Ok(RequestOptionalParam {
|
||||
extensions,
|
||||
method: body.method,
|
||||
params,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<M> Serialize for RequestNoParam<M>
|
||||
where
|
||||
M: Serialize,
|
||||
{
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
let extensions = &self.extensions;
|
||||
let _meta = extensions.get::<Meta>().map(Cow::Borrowed);
|
||||
ProxyNoParam::serialize(
|
||||
&ProxyNoParam {
|
||||
method: &self.method,
|
||||
},
|
||||
serializer,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, M> Deserialize<'de> for RequestNoParam<M>
|
||||
where
|
||||
M: Deserialize<'de>,
|
||||
{
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let body = ProxyNoParam::<_>::deserialize(deserializer)?;
|
||||
let extensions = Extensions::new();
|
||||
Ok(RequestNoParam {
|
||||
extensions,
|
||||
method: body.method,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<M, R> Serialize for Notification<M, R>
|
||||
where
|
||||
M: Serialize,
|
||||
R: Serialize,
|
||||
{
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
let extensions = &self.extensions;
|
||||
let _meta = extensions.get::<Meta>().map(Cow::Borrowed);
|
||||
Proxy::serialize(
|
||||
&Proxy {
|
||||
method: &self.method,
|
||||
params: WithMeta {
|
||||
_rest: &self.params,
|
||||
_meta,
|
||||
},
|
||||
},
|
||||
serializer,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, M, R> Deserialize<'de> for Notification<M, R>
|
||||
where
|
||||
M: Deserialize<'de>,
|
||||
R: Deserialize<'de>,
|
||||
{
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let body = Proxy::deserialize(deserializer)?;
|
||||
let _meta = body.params._meta.map(|m| m.into_owned());
|
||||
let mut extensions = Extensions::new();
|
||||
if let Some(meta) = _meta {
|
||||
extensions.insert(meta);
|
||||
}
|
||||
Ok(Notification {
|
||||
extensions,
|
||||
method: body.method,
|
||||
params: body.params._rest,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<M> Serialize for NotificationNoParam<M>
|
||||
where
|
||||
M: Serialize,
|
||||
{
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
let extensions = &self.extensions;
|
||||
let _meta = extensions.get::<Meta>().map(Cow::Borrowed);
|
||||
ProxyNoParam::serialize(
|
||||
&ProxyNoParam {
|
||||
method: &self.method,
|
||||
},
|
||||
serializer,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, M> Deserialize<'de> for NotificationNoParam<M>
|
||||
where
|
||||
M: Deserialize<'de>,
|
||||
{
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let body = ProxyNoParam::<_>::deserialize(deserializer)?;
|
||||
let extensions = Extensions::new();
|
||||
Ok(NotificationNoParam {
|
||||
extensions,
|
||||
method: body.method,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use serde_json::json;
|
||||
|
||||
use crate::model::ListToolsRequest;
|
||||
|
||||
#[test]
|
||||
fn test_deserialize_lost_tools_request() {
|
||||
let _req: ListToolsRequest = serde_json::from_value(json!(
|
||||
{
|
||||
"method": "tools/list",
|
||||
}
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
|
@ -18,6 +18,102 @@ pub struct Tool {
|
|||
pub description: Option<Cow<'static, str>>,
|
||||
/// A JSON Schema object defining the expected parameters for the tool
|
||||
pub input_schema: Arc<JsonObject>,
|
||||
/// Optional additional tool information.
|
||||
pub annotations: Option<ToolAnnotations>,
|
||||
}
|
||||
|
||||
/// Additional properties describing a Tool to clients.
|
||||
///
|
||||
/// NOTE: all properties in ToolAnnotations are **hints**.
|
||||
/// They are not guaranteed to provide a faithful description of
|
||||
/// tool behavior (including descriptive properties like `title`).
|
||||
///
|
||||
/// Clients should never make tool use decisions based on ToolAnnotations
|
||||
/// received from untrusted servers.
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ToolAnnotations {
|
||||
/// A human-readable title for the tool.
|
||||
pub title: Option<String>,
|
||||
|
||||
/// If true, the tool does not modify its environment.
|
||||
///
|
||||
/// Default: false
|
||||
pub read_only_hint: Option<bool>,
|
||||
|
||||
/// If true, the tool may perform destructive updates to its environment.
|
||||
/// If false, the tool performs only additive updates.
|
||||
///
|
||||
/// (This property is meaningful only when `readOnlyHint == false`)
|
||||
///
|
||||
/// Default: true
|
||||
/// A human-readable description of the tool's purpose.
|
||||
pub destructive_hint: Option<bool>,
|
||||
|
||||
/// If true, calling the tool repeatedly with the same arguments
|
||||
/// will have no additional effect on the its environment.
|
||||
///
|
||||
/// (This property is meaningful only when `readOnlyHint == false`)
|
||||
///
|
||||
/// Default: false.
|
||||
pub idempotent_hint: Option<bool>,
|
||||
|
||||
/// If true, this tool may interact with an "open world" of external
|
||||
/// entities. If false, the tool's domain of interaction is closed.
|
||||
/// For example, the world of a web search tool is open, whereas that
|
||||
/// of a memory tool is not.
|
||||
///
|
||||
/// Default: true
|
||||
pub open_world_hint: Option<bool>,
|
||||
}
|
||||
|
||||
impl ToolAnnotations {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
pub fn with_title<T>(title: T) -> Self
|
||||
where
|
||||
T: Into<String>,
|
||||
{
|
||||
ToolAnnotations {
|
||||
title: Some(title.into()),
|
||||
..Self::default()
|
||||
}
|
||||
}
|
||||
pub fn read_only(self, read_only: bool) -> Self {
|
||||
ToolAnnotations {
|
||||
read_only_hint: Some(read_only),
|
||||
..self
|
||||
}
|
||||
}
|
||||
pub fn destructive(self, destructive: bool) -> Self {
|
||||
ToolAnnotations {
|
||||
destructive_hint: Some(destructive),
|
||||
..self
|
||||
}
|
||||
}
|
||||
pub fn idempotent(self, idempotent: bool) -> Self {
|
||||
ToolAnnotations {
|
||||
idempotent_hint: Some(idempotent),
|
||||
..self
|
||||
}
|
||||
}
|
||||
pub fn open_world(self, open_world: bool) -> Self {
|
||||
ToolAnnotations {
|
||||
open_world_hint: Some(open_world),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// If not set, defaults to true.
|
||||
pub fn is_destructive(&self) -> bool {
|
||||
self.destructive_hint.unwrap_or(true)
|
||||
}
|
||||
|
||||
/// If not set, defaults to false.
|
||||
pub fn is_idempotent(&self) -> bool {
|
||||
self.idempotent_hint.unwrap_or(false)
|
||||
}
|
||||
}
|
||||
|
||||
impl Tool {
|
||||
|
|
@ -32,6 +128,14 @@ impl Tool {
|
|||
name: name.into(),
|
||||
description: Some(description.into()),
|
||||
input_schema: input_schema.into(),
|
||||
annotations: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn annotate(self, annotations: ToolAnnotations) -> Self {
|
||||
Tool {
|
||||
annotations: Some(annotations),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,10 @@ use thiserror::Error;
|
|||
use crate::{
|
||||
error::Error as McpError,
|
||||
model::{
|
||||
CancelledNotification, CancelledNotificationParam, JsonRpcMessage, Message, RequestId,
|
||||
CancelledNotification, CancelledNotificationParam, GetMeta, JsonRpcBatchRequestItem,
|
||||
JsonRpcBatchResponseItem, JsonRpcError, JsonRpcMessage, JsonRpcNotification,
|
||||
JsonRpcRequest, JsonRpcResponse, Meta, NumberOrString, ProgressToken, RequestId,
|
||||
ServerJsonRpcMessage,
|
||||
},
|
||||
transport::IntoTransport,
|
||||
};
|
||||
|
|
@ -56,12 +59,12 @@ impl<T> TransferObject for T where
|
|||
|
||||
#[allow(private_bounds, reason = "there's no the third implementation")]
|
||||
pub trait ServiceRole: std::fmt::Debug + Send + Sync + 'static + Copy + Clone {
|
||||
type Req: TransferObject;
|
||||
type Req: TransferObject + GetMeta;
|
||||
type Resp: TransferObject;
|
||||
type Not: TryInto<CancelledNotification, Error = Self::Not>
|
||||
+ From<CancelledNotification>
|
||||
+ TransferObject;
|
||||
type PeerReq: TransferObject;
|
||||
type PeerReq: TransferObject + GetMeta;
|
||||
type PeerResp: TransferObject;
|
||||
type PeerNot: TryInto<CancelledNotification, Error = Self::PeerNot>
|
||||
+ From<CancelledNotification>
|
||||
|
|
@ -79,11 +82,6 @@ pub type RxJsonRpcMessage<R> = JsonRpcMessage<
|
|||
<R as ServiceRole>::PeerNot,
|
||||
>;
|
||||
|
||||
pub type TxMessage<R> =
|
||||
Message<<R as ServiceRole>::Req, <R as ServiceRole>::Resp, <R as ServiceRole>::Not>;
|
||||
pub type RxMessage<R> =
|
||||
Message<<R as ServiceRole>::PeerReq, <R as ServiceRole>::PeerResp, <R as ServiceRole>::PeerNot>;
|
||||
|
||||
pub trait Service<R: ServiceRole>: Send + Sync + 'static {
|
||||
fn handle_request(
|
||||
&self,
|
||||
|
|
@ -192,7 +190,7 @@ impl<R: ServiceRole, S: Service<R>> DynService<R> for S {
|
|||
}
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
collections::{HashMap, VecDeque},
|
||||
ops::Deref,
|
||||
sync::{Arc, atomic::AtomicU32},
|
||||
time::Duration,
|
||||
|
|
@ -204,17 +202,32 @@ pub trait RequestIdProvider: Send + Sync + 'static {
|
|||
fn next_request_id(&self) -> RequestId;
|
||||
}
|
||||
|
||||
pub trait ProgressTokenProvider: Send + Sync + 'static {
|
||||
fn next_progress_token(&self) -> ProgressToken;
|
||||
}
|
||||
|
||||
pub type AtomicU32RequestIdProvider = AtomicU32Provider;
|
||||
pub type AtomicU32ProgressTokenProvider = AtomicU32Provider;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct AtomicU32RequestIdProvider {
|
||||
pub struct AtomicU32Provider {
|
||||
id: AtomicU32,
|
||||
}
|
||||
|
||||
impl RequestIdProvider for AtomicU32RequestIdProvider {
|
||||
impl RequestIdProvider for AtomicU32Provider {
|
||||
fn next_request_id(&self) -> RequestId {
|
||||
RequestId::Number(self.id.fetch_add(1, std::sync::atomic::Ordering::SeqCst))
|
||||
}
|
||||
}
|
||||
|
||||
impl ProgressTokenProvider for AtomicU32Provider {
|
||||
fn next_progress_token(&self) -> ProgressToken {
|
||||
ProgressToken(NumberOrString::Number(
|
||||
self.id.fetch_add(1, std::sync::atomic::Ordering::SeqCst),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
type Responder<T> = tokio::sync::oneshot::Sender<T>;
|
||||
|
||||
/// A handle to a remote request
|
||||
|
|
@ -228,6 +241,7 @@ pub struct RequestHandle<R: ServiceRole> {
|
|||
pub options: PeerRequestOptions,
|
||||
pub peer: Peer<R>,
|
||||
pub id: RequestId,
|
||||
pub progress_token: ProgressToken,
|
||||
}
|
||||
|
||||
impl<R: ServiceRole> RequestHandle<R> {
|
||||
|
|
@ -251,6 +265,7 @@ impl<R: ServiceRole> RequestHandle<R> {
|
|||
reason: Some(Self::REQUEST_TIMEOUT_REASON.to_owned()),
|
||||
},
|
||||
method: crate::model::CancelledNotificationMethod,
|
||||
extensions: Default::default(),
|
||||
};
|
||||
let _ = self.peer.send_notification(notification.into()).await;
|
||||
error
|
||||
|
|
@ -271,6 +286,7 @@ impl<R: ServiceRole> RequestHandle<R> {
|
|||
reason,
|
||||
},
|
||||
method: crate::model::CancelledNotificationMethod,
|
||||
extensions: Default::default(),
|
||||
};
|
||||
self.peer.send_notification(notification.into()).await?;
|
||||
Ok(())
|
||||
|
|
@ -278,24 +294,28 @@ impl<R: ServiceRole> RequestHandle<R> {
|
|||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum PeerSinkMessage<R: ServiceRole> {
|
||||
Request(
|
||||
R::Req,
|
||||
RequestId,
|
||||
Responder<Result<R::PeerResp, ServiceError>>,
|
||||
),
|
||||
Notification(R::Not, Responder<Result<(), ServiceError>>),
|
||||
pub(crate) enum PeerSinkMessage<R: ServiceRole> {
|
||||
Request {
|
||||
request: R::Req,
|
||||
id: RequestId,
|
||||
responder: Responder<Result<R::PeerResp, ServiceError>>,
|
||||
},
|
||||
Notification {
|
||||
notification: R::Not,
|
||||
responder: Responder<Result<(), ServiceError>>,
|
||||
},
|
||||
}
|
||||
|
||||
/// An interface to fetch the remote client or server
|
||||
///
|
||||
/// For general purpose, call [`Peer::send_request`] or [`Peer::send_notification`] to send message to remote peer.
|
||||
///
|
||||
/// To create a cancellable request, call [`Peer::send_cancellable_request`].
|
||||
/// To create a cancellable request, call [`Peer::send_request_with_option`].
|
||||
#[derive(Clone)]
|
||||
pub struct Peer<R: ServiceRole> {
|
||||
tx: mpsc::Sender<PeerSinkMessage<R>>,
|
||||
request_id_provider: Arc<dyn RequestIdProvider>,
|
||||
progress_token_provider: Arc<dyn ProgressTokenProvider>,
|
||||
info: Arc<R::PeerInfo>,
|
||||
}
|
||||
|
||||
|
|
@ -312,7 +332,8 @@ type ProxyOutbound<R> = mpsc::Receiver<PeerSinkMessage<R>>;
|
|||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct PeerRequestOptions {
|
||||
timeout: Option<Duration>,
|
||||
pub timeout: Option<Duration>,
|
||||
pub meta: Option<Meta>,
|
||||
}
|
||||
|
||||
impl PeerRequestOptions {
|
||||
|
|
@ -323,7 +344,7 @@ impl PeerRequestOptions {
|
|||
|
||||
impl<R: ServiceRole> Peer<R> {
|
||||
const CLIENT_CHANNEL_BUFFER_SIZE: usize = 1024;
|
||||
pub fn new(
|
||||
pub(crate) fn new(
|
||||
request_id_provider: Arc<dyn RequestIdProvider>,
|
||||
peer_info: R::PeerInfo,
|
||||
) -> (Peer<R>, ProxyOutbound<R>) {
|
||||
|
|
@ -332,6 +353,7 @@ impl<R: ServiceRole> Peer<R> {
|
|||
Self {
|
||||
tx,
|
||||
request_id_provider,
|
||||
progress_token_provider: Arc::new(AtomicU32ProgressTokenProvider::default()),
|
||||
info: peer_info.into(),
|
||||
},
|
||||
rx,
|
||||
|
|
@ -340,7 +362,10 @@ impl<R: ServiceRole> Peer<R> {
|
|||
pub async fn send_notification(&self, notification: R::Not) -> Result<(), ServiceError> {
|
||||
let (responder, receiver) = tokio::sync::oneshot::channel();
|
||||
self.tx
|
||||
.send(PeerSinkMessage::Notification(notification, responder))
|
||||
.send(PeerSinkMessage::Notification {
|
||||
notification,
|
||||
responder,
|
||||
})
|
||||
.await
|
||||
.map_err(|_m| {
|
||||
ServiceError::Transport(std::io::Error::other("disconnected: receiver dropped"))
|
||||
|
|
@ -350,25 +375,46 @@ impl<R: ServiceRole> Peer<R> {
|
|||
})?
|
||||
}
|
||||
pub async fn send_request(&self, request: R::Req) -> Result<R::PeerResp, ServiceError> {
|
||||
self.send_cancellable_request(request, PeerRequestOptions::no_options())
|
||||
self.send_request_with_option(request, PeerRequestOptions::no_options())
|
||||
.await?
|
||||
.await_response()
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn send_cancellable_request(
|
||||
&self,
|
||||
request: R::Req,
|
||||
options: PeerRequestOptions,
|
||||
) -> Result<RequestHandle<R>, ServiceError> {
|
||||
self.send_request_with_option(request, options).await
|
||||
}
|
||||
|
||||
pub async fn send_request_with_option(
|
||||
&self,
|
||||
mut request: R::Req,
|
||||
options: PeerRequestOptions,
|
||||
) -> Result<RequestHandle<R>, ServiceError> {
|
||||
let id = self.request_id_provider.next_request_id();
|
||||
let progress_token = self.progress_token_provider.next_progress_token();
|
||||
request
|
||||
.get_meta_mut()
|
||||
.set_progress_token(progress_token.clone());
|
||||
if let Some(meta) = options.meta.clone() {
|
||||
request.get_meta_mut().extend(meta);
|
||||
}
|
||||
let (responder, receiver) = tokio::sync::oneshot::channel();
|
||||
self.tx
|
||||
.send(PeerSinkMessage::Request(request, id.clone(), responder))
|
||||
.send(PeerSinkMessage::Request {
|
||||
request,
|
||||
id: id.clone(),
|
||||
responder,
|
||||
})
|
||||
.await
|
||||
.map_err(|_m| ServiceError::Transport(std::io::Error::other("disconnected")))?;
|
||||
Ok(RequestHandle {
|
||||
id,
|
||||
rx: receiver,
|
||||
progress_token,
|
||||
options,
|
||||
peer: self.clone(),
|
||||
})
|
||||
|
|
@ -424,6 +470,7 @@ pub struct RequestContext<R: ServiceRole> {
|
|||
/// this token will be cancelled when the [`CancelledNotification`] is received.
|
||||
pub ct: CancellationToken,
|
||||
pub id: RequestId,
|
||||
pub meta: Meta,
|
||||
/// An interface to fetch the remote client or server
|
||||
pub peer: Peer<R>,
|
||||
}
|
||||
|
|
@ -464,7 +511,7 @@ async fn serve_inner<R, S, T, E, A>(
|
|||
mut service: S,
|
||||
transport: T,
|
||||
peer_info: R::PeerInfo,
|
||||
id_provider: Arc<AtomicU32RequestIdProvider>,
|
||||
id_provider: Arc<AtomicU32Provider>,
|
||||
ct: CancellationToken,
|
||||
) -> Result<RunningService<R, S>, E>
|
||||
where
|
||||
|
|
@ -475,9 +522,8 @@ where
|
|||
{
|
||||
use futures::{SinkExt, StreamExt};
|
||||
const SINK_PROXY_BUFFER_SIZE: usize = 64;
|
||||
let (sink_proxy_tx, mut sink_proxy_rx) = tokio::sync::mpsc::channel::<
|
||||
Message<<R as ServiceRole>::Req, <R as ServiceRole>::Resp, <R as ServiceRole>::Not>,
|
||||
>(SINK_PROXY_BUFFER_SIZE);
|
||||
let (sink_proxy_tx, mut sink_proxy_rx) =
|
||||
tokio::sync::mpsc::channel::<TxJsonRpcMessage<R>>(SINK_PROXY_BUFFER_SIZE);
|
||||
|
||||
if R::IS_CLIENT {
|
||||
tracing::info!(?peer_info, "Service initialized as client");
|
||||
|
|
@ -501,6 +547,7 @@ where
|
|||
let (mut sink, mut stream) = transport.into_transport();
|
||||
let mut sink = std::pin::pin!(sink);
|
||||
let mut stream = std::pin::pin!(stream);
|
||||
let mut batch_messages = VecDeque::<RxJsonRpcMessage<R>>::new();
|
||||
#[derive(Debug)]
|
||||
enum Event<P, R, T> {
|
||||
ProxyMessage(P),
|
||||
|
|
@ -508,57 +555,66 @@ where
|
|||
ToSink(T),
|
||||
}
|
||||
let quit_reason = loop {
|
||||
let evt = tokio::select! {
|
||||
m = sink_proxy_rx.recv() => {
|
||||
if let Some(m) = m {
|
||||
Event::ToSink(m)
|
||||
} else {
|
||||
continue
|
||||
let evt = if let Some(m) = batch_messages.pop_front() {
|
||||
Event::PeerMessage(m)
|
||||
} else {
|
||||
tokio::select! {
|
||||
m = sink_proxy_rx.recv() => {
|
||||
if let Some(m) = m {
|
||||
Event::ToSink(m)
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
m = stream.next() => {
|
||||
if let Some(m) = m {
|
||||
Event::PeerMessage(m.into_message())
|
||||
} else {
|
||||
// input stream closed
|
||||
tracing::info!("input stream terminated");
|
||||
break QuitReason::Closed
|
||||
m = stream.next() => {
|
||||
if let Some(m) = m {
|
||||
Event::PeerMessage(m)
|
||||
} else {
|
||||
// input stream closed
|
||||
tracing::info!("input stream terminated");
|
||||
break QuitReason::Closed
|
||||
}
|
||||
}
|
||||
}
|
||||
m = peer_proxy.recv() => {
|
||||
if let Some(m) = m {
|
||||
Event::ProxyMessage(m)
|
||||
} else {
|
||||
continue
|
||||
m = peer_proxy.recv() => {
|
||||
if let Some(m) = m {
|
||||
Event::ProxyMessage(m)
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
_ = serve_loop_ct.cancelled() => {
|
||||
tracing::info!("task cancelled");
|
||||
break QuitReason::Cancelled
|
||||
}
|
||||
}
|
||||
_ = serve_loop_ct.cancelled() => {
|
||||
tracing::info!("task cancelled");
|
||||
break QuitReason::Cancelled
|
||||
}
|
||||
};
|
||||
|
||||
tracing::debug!(?evt, "new event");
|
||||
match evt {
|
||||
// response and error
|
||||
Event::ToSink(e) => {
|
||||
if let Some(id) = match &e {
|
||||
Message::Response(_, id) => Some(id),
|
||||
Message::Error(_, id) => Some(id),
|
||||
Event::ToSink(m) => {
|
||||
if let Some(id) = match &m {
|
||||
JsonRpcMessage::Response(response) => Some(&response.id),
|
||||
JsonRpcMessage::Error(error) => Some(&error.id),
|
||||
_ => None,
|
||||
} {
|
||||
if let Some(ct) = local_ct_pool.remove(id) {
|
||||
ct.cancel();
|
||||
}
|
||||
let send_result = sink.send(e.into_json_rpc_message()).await;
|
||||
let send_result = sink.send(m).await;
|
||||
if let Err(error) = send_result {
|
||||
tracing::error!(%error, "fail to response message");
|
||||
}
|
||||
}
|
||||
}
|
||||
Event::ProxyMessage(PeerSinkMessage::Request(request, id, responder)) => {
|
||||
Event::ProxyMessage(PeerSinkMessage::Request {
|
||||
request,
|
||||
id,
|
||||
responder,
|
||||
}) => {
|
||||
local_responder_pool.insert(id.clone(), responder);
|
||||
let send_result = sink
|
||||
.send(Message::Request(request, id.clone()).into_json_rpc_message())
|
||||
.send(JsonRpcMessage::request(request, id.clone()))
|
||||
.await;
|
||||
if let Err(e) = send_result {
|
||||
if let Some(responder) = local_responder_pool.remove(&id) {
|
||||
|
|
@ -567,7 +623,10 @@ where
|
|||
}
|
||||
}
|
||||
}
|
||||
Event::ProxyMessage(PeerSinkMessage::Notification(notification, responder)) => {
|
||||
Event::ProxyMessage(PeerSinkMessage::Notification {
|
||||
notification,
|
||||
responder,
|
||||
}) => {
|
||||
// catch cancellation notification
|
||||
let mut cancellation_param = None;
|
||||
let notification = match notification.try_into() {
|
||||
|
|
@ -577,9 +636,7 @@ where
|
|||
}
|
||||
Err(notification) => notification,
|
||||
};
|
||||
let send_result = sink
|
||||
.send(Message::Notification(notification).into_json_rpc_message())
|
||||
.await;
|
||||
let send_result = sink.send(JsonRpcMessage::notification(notification)).await;
|
||||
let response = if let Err(e) = send_result {
|
||||
Err(ServiceError::Transport(std::io::Error::other(e)))
|
||||
} else {
|
||||
|
|
@ -595,7 +652,9 @@ where
|
|||
}
|
||||
}
|
||||
}
|
||||
Event::PeerMessage(Message::Request(request, id)) => {
|
||||
Event::PeerMessage(JsonRpcMessage::Request(JsonRpcRequest {
|
||||
id, request, ..
|
||||
})) => {
|
||||
tracing::info!(%id, ?request, "received request");
|
||||
{
|
||||
let service = shared_service.clone();
|
||||
|
|
@ -607,24 +666,28 @@ where
|
|||
ct: context_ct,
|
||||
id: id.clone(),
|
||||
peer: peer.clone(),
|
||||
meta: request.get_meta().clone(),
|
||||
};
|
||||
tokio::spawn(async move {
|
||||
let result = service.handle_request(request, context).await;
|
||||
let response = match result {
|
||||
Ok(result) => {
|
||||
tracing::info!(%id, ?result, "response message");
|
||||
Message::Response(result, id)
|
||||
JsonRpcMessage::response(result, id)
|
||||
}
|
||||
Err(error) => {
|
||||
tracing::warn!(%id, ?error, "response error");
|
||||
Message::Error(error, id)
|
||||
JsonRpcMessage::error(error, id)
|
||||
}
|
||||
};
|
||||
let _send_result = sink.send(response).await;
|
||||
});
|
||||
}
|
||||
}
|
||||
Event::PeerMessage(Message::Notification(notification)) => {
|
||||
Event::PeerMessage(JsonRpcMessage::Notification(JsonRpcNotification {
|
||||
notification,
|
||||
..
|
||||
})) => {
|
||||
tracing::info!(?notification, "received notification");
|
||||
// catch cancelled notification
|
||||
let notification = match notification.try_into() {
|
||||
|
|
@ -647,7 +710,11 @@ where
|
|||
});
|
||||
}
|
||||
}
|
||||
Event::PeerMessage(Message::Response(result, id)) => {
|
||||
Event::PeerMessage(JsonRpcMessage::Response(JsonRpcResponse {
|
||||
result,
|
||||
id,
|
||||
..
|
||||
})) => {
|
||||
if let Some(responder) = local_responder_pool.remove(&id) {
|
||||
let response_result = responder.send(Ok(result));
|
||||
if let Err(_error) = response_result {
|
||||
|
|
@ -655,7 +722,7 @@ where
|
|||
}
|
||||
}
|
||||
}
|
||||
Event::PeerMessage(Message::Error(error, id)) => {
|
||||
Event::PeerMessage(JsonRpcMessage::Error(JsonRpcError { error, id, .. })) => {
|
||||
if let Some(responder) = local_responder_pool.remove(&id) {
|
||||
let _response_result = responder.send(Err(ServiceError::McpError(error)));
|
||||
if let Err(_error) = _response_result {
|
||||
|
|
@ -663,6 +730,20 @@ where
|
|||
}
|
||||
}
|
||||
}
|
||||
Event::PeerMessage(JsonRpcMessage::BatchRequest(batch)) => {
|
||||
batch_messages.extend(
|
||||
batch
|
||||
.into_iter()
|
||||
.map(JsonRpcBatchRequestItem::into_non_batch_message),
|
||||
);
|
||||
}
|
||||
Event::PeerMessage(JsonRpcMessage::BatchResponse(batch)) => {
|
||||
batch_messages.extend(
|
||||
batch
|
||||
.into_iter()
|
||||
.map(JsonRpcBatchResponseItem::into_non_batch_message),
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
let sink_close_result = sink.close().await;
|
||||
|
|
|
|||
|
|
@ -4,16 +4,16 @@ use thiserror::Error;
|
|||
use super::*;
|
||||
use crate::model::{
|
||||
CallToolRequest, CallToolRequestParam, CallToolResult, CancelledNotification,
|
||||
CancelledNotificationParam, ClientInfo, ClientMessage, ClientNotification, ClientRequest,
|
||||
ClientResult, CompleteRequest, CompleteRequestParam, CompleteResult, GetPromptRequest,
|
||||
GetPromptRequestParam, GetPromptResult, InitializeRequest, InitializedNotification,
|
||||
JsonRpcResponse, ListPromptsRequest, ListPromptsResult, ListResourceTemplatesRequest,
|
||||
ListResourceTemplatesResult, ListResourcesRequest, ListResourcesResult, ListToolsRequest,
|
||||
ListToolsResult, PaginatedRequestParam, PaginatedRequestParamInner, ProgressNotification,
|
||||
ProgressNotificationParam, ReadResourceRequest, ReadResourceRequestParam, ReadResourceResult,
|
||||
RequestId, RootsListChangedNotification, ServerInfo, ServerJsonRpcMessage, ServerNotification,
|
||||
ServerRequest, ServerResult, SetLevelRequest, SetLevelRequestParam, SubscribeRequest,
|
||||
SubscribeRequestParam, UnsubscribeRequest, UnsubscribeRequestParam,
|
||||
CancelledNotificationParam, ClientInfo, ClientJsonRpcMessage, ClientNotification,
|
||||
ClientRequest, ClientResult, CompleteRequest, CompleteRequestParam, CompleteResult,
|
||||
GetPromptRequest, GetPromptRequestParam, GetPromptResult, InitializeRequest,
|
||||
InitializedNotification, JsonRpcResponse, ListPromptsRequest, ListPromptsResult,
|
||||
ListResourceTemplatesRequest, ListResourceTemplatesResult, ListResourcesRequest,
|
||||
ListResourcesResult, ListToolsRequest, ListToolsResult, PaginatedRequestParam,
|
||||
ProgressNotification, ProgressNotificationParam, ReadResourceRequest, ReadResourceRequestParam,
|
||||
ReadResourceResult, RequestId, RootsListChangedNotification, ServerInfo, ServerJsonRpcMessage,
|
||||
ServerNotification, ServerRequest, ServerResult, SetLevelRequest, SetLevelRequestParam,
|
||||
SubscribeRequest, SubscribeRequestParam, UnsubscribeRequest, UnsubscribeRequestParam,
|
||||
};
|
||||
|
||||
/// It represents the error that may occur when serving the client.
|
||||
|
|
@ -141,11 +141,12 @@ where
|
|||
let init_request = InitializeRequest {
|
||||
method: Default::default(),
|
||||
params: service.get_info(),
|
||||
extensions: Default::default(),
|
||||
};
|
||||
sink.send(
|
||||
ClientMessage::Request(ClientRequest::InitializeRequest(init_request), id.clone())
|
||||
.into_json_rpc_message(),
|
||||
)
|
||||
sink.send(ClientJsonRpcMessage::request(
|
||||
ClientRequest::InitializeRequest(init_request),
|
||||
id.clone(),
|
||||
))
|
||||
.await?;
|
||||
|
||||
let (response, response_id) = expect_response(&mut stream, "initialize response")
|
||||
|
|
@ -166,12 +167,13 @@ where
|
|||
};
|
||||
|
||||
// send notification
|
||||
let notification = ClientMessage::Notification(ClientNotification::InitializedNotification(
|
||||
InitializedNotification {
|
||||
let notification = ClientJsonRpcMessage::notification(
|
||||
ClientNotification::InitializedNotification(InitializedNotification {
|
||||
method: Default::default(),
|
||||
},
|
||||
));
|
||||
sink.send(notification.into_json_rpc_message()).await?;
|
||||
extensions: Default::default(),
|
||||
}),
|
||||
);
|
||||
sink.send(notification).await?;
|
||||
serve_inner(service, (sink, stream), initialize_result, id_provider, ct).await
|
||||
}
|
||||
|
||||
|
|
@ -195,6 +197,22 @@ macro_rules! method {
|
|||
.send_request(ClientRequest::$Req($Req {
|
||||
method: Default::default(),
|
||||
params,
|
||||
extensions: Default::default(),
|
||||
}))
|
||||
.await?;
|
||||
match result {
|
||||
ServerResult::$Resp(result) => Ok(result),
|
||||
_ => Err(ServiceError::UnexpectedResponse),
|
||||
}
|
||||
}
|
||||
};
|
||||
(peer_req $method:ident $Req:ident($Param: ident)? => $Resp: ident ) => {
|
||||
pub async fn $method(&self, params: Option<$Param>) -> Result<$Resp, ServiceError> {
|
||||
let result = self
|
||||
.send_request(ClientRequest::$Req($Req {
|
||||
method: Default::default(),
|
||||
params,
|
||||
extensions: Default::default(),
|
||||
}))
|
||||
.await?;
|
||||
match result {
|
||||
|
|
@ -209,6 +227,7 @@ macro_rules! method {
|
|||
.send_request(ClientRequest::$Req($Req {
|
||||
method: Default::default(),
|
||||
params,
|
||||
extensions: Default::default(),
|
||||
}))
|
||||
.await?;
|
||||
match result {
|
||||
|
|
@ -223,6 +242,7 @@ macro_rules! method {
|
|||
self.send_notification(ClientNotification::$Not($Not {
|
||||
method: Default::default(),
|
||||
params,
|
||||
extensions: Default::default(),
|
||||
}))
|
||||
.await?;
|
||||
Ok(())
|
||||
|
|
@ -232,6 +252,7 @@ macro_rules! method {
|
|||
pub async fn $method(&self) -> Result<(), ServiceError> {
|
||||
self.send_notification(ClientNotification::$Not($Not {
|
||||
method: Default::default(),
|
||||
extensions: Default::default(),
|
||||
}))
|
||||
.await?;
|
||||
Ok(())
|
||||
|
|
@ -243,14 +264,14 @@ impl Peer<RoleClient> {
|
|||
method!(peer_req complete CompleteRequest(CompleteRequestParam) => CompleteResult);
|
||||
method!(peer_req set_level SetLevelRequest(SetLevelRequestParam));
|
||||
method!(peer_req get_prompt GetPromptRequest(GetPromptRequestParam) => GetPromptResult);
|
||||
method!(peer_req list_prompts ListPromptsRequest(PaginatedRequestParam) => ListPromptsResult);
|
||||
method!(peer_req list_resources ListResourcesRequest(PaginatedRequestParam) => ListResourcesResult);
|
||||
method!(peer_req list_resource_templates ListResourceTemplatesRequest(PaginatedRequestParam) => ListResourceTemplatesResult);
|
||||
method!(peer_req list_prompts ListPromptsRequest(PaginatedRequestParam)? => ListPromptsResult);
|
||||
method!(peer_req list_resources ListResourcesRequest(PaginatedRequestParam)? => ListResourcesResult);
|
||||
method!(peer_req list_resource_templates ListResourceTemplatesRequest(PaginatedRequestParam)? => ListResourceTemplatesResult);
|
||||
method!(peer_req read_resource ReadResourceRequest(ReadResourceRequestParam) => ReadResourceResult);
|
||||
method!(peer_req subscribe SubscribeRequest(SubscribeRequestParam) );
|
||||
method!(peer_req unsubscribe UnsubscribeRequest(UnsubscribeRequestParam));
|
||||
method!(peer_req call_tool CallToolRequest(CallToolRequestParam) => CallToolResult);
|
||||
method!(peer_req list_tools ListToolsRequest(PaginatedRequestParam) => ListToolsResult);
|
||||
method!(peer_req list_tools ListToolsRequest(PaginatedRequestParam)? => ListToolsResult);
|
||||
|
||||
method!(peer_not notify_cancelled CancelledNotification(CancelledNotificationParam));
|
||||
method!(peer_not notify_progress ProgressNotification(ProgressNotificationParam));
|
||||
|
|
@ -267,7 +288,7 @@ impl Peer<RoleClient> {
|
|||
let mut cursor = None;
|
||||
loop {
|
||||
let result = self
|
||||
.list_tools(Some(PaginatedRequestParamInner { cursor }))
|
||||
.list_tools(Some(PaginatedRequestParam { cursor }))
|
||||
.await?;
|
||||
tools.extend(result.tools);
|
||||
cursor = result.next_cursor;
|
||||
|
|
@ -286,7 +307,7 @@ impl Peer<RoleClient> {
|
|||
let mut cursor = None;
|
||||
loop {
|
||||
let result = self
|
||||
.list_prompts(Some(PaginatedRequestParamInner { cursor }))
|
||||
.list_prompts(Some(PaginatedRequestParam { cursor }))
|
||||
.await?;
|
||||
prompts.extend(result.prompts);
|
||||
cursor = result.next_cursor;
|
||||
|
|
@ -305,7 +326,7 @@ impl Peer<RoleClient> {
|
|||
let mut cursor = None;
|
||||
loop {
|
||||
let result = self
|
||||
.list_resources(Some(PaginatedRequestParamInner { cursor }))
|
||||
.list_resources(Some(PaginatedRequestParam { cursor }))
|
||||
.await?;
|
||||
resources.extend(result.resources);
|
||||
cursor = result.next_cursor;
|
||||
|
|
@ -326,7 +347,7 @@ impl Peer<RoleClient> {
|
|||
let mut cursor = None;
|
||||
loop {
|
||||
let result = self
|
||||
.list_resource_templates(Some(PaginatedRequestParamInner { cursor }))
|
||||
.list_resource_templates(Some(PaginatedRequestParam { cursor }))
|
||||
.await?;
|
||||
resource_templates.extend(result.resource_templates);
|
||||
cursor = result.next_cursor;
|
||||
|
|
|
|||
|
|
@ -4,12 +4,12 @@ use thiserror::Error;
|
|||
use super::*;
|
||||
use crate::model::{
|
||||
CancelledNotification, CancelledNotificationParam, ClientInfo, ClientJsonRpcMessage,
|
||||
ClientMessage, ClientNotification, ClientRequest, ClientResult, CreateMessageRequest,
|
||||
ClientNotification, ClientRequest, ClientResult, CreateMessageRequest,
|
||||
CreateMessageRequestParam, CreateMessageResult, ListRootsRequest, ListRootsResult,
|
||||
LoggingMessageNotification, LoggingMessageNotificationParam, ProgressNotification,
|
||||
ProgressNotificationParam, PromptListChangedNotification, ResourceListChangedNotification,
|
||||
ResourceUpdatedNotification, ResourceUpdatedNotificationParam, ServerInfo, ServerMessage,
|
||||
ServerNotification, ServerRequest, ServerResult, ToolListChangedNotification,
|
||||
ResourceUpdatedNotification, ResourceUpdatedNotificationParam, ServerInfo, ServerNotification,
|
||||
ServerRequest, ServerResult, ToolListChangedNotification,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
|
|
@ -33,10 +33,10 @@ impl ServiceRole for RoleServer {
|
|||
#[derive(Error, Debug)]
|
||||
pub enum ServerError {
|
||||
#[error("expect initialized request, but received: {0:?}")]
|
||||
ExpectedInitRequest(Option<ClientMessage>),
|
||||
ExpectedInitRequest(Option<ClientJsonRpcMessage>),
|
||||
|
||||
#[error("expect initialized notification, but received: {0:?}")]
|
||||
ExpectedInitNotification(Option<ClientMessage>),
|
||||
ExpectedInitNotification(Option<ClientJsonRpcMessage>),
|
||||
|
||||
#[error("connection closed: {0}")]
|
||||
ConnectionClosed(String),
|
||||
|
|
@ -75,15 +75,17 @@ where
|
|||
}
|
||||
|
||||
/// Helper function to get the next message from the stream
|
||||
async fn expect_next_message<S>(stream: &mut S, context: &str) -> Result<ClientMessage, ServerError>
|
||||
async fn expect_next_message<S>(
|
||||
stream: &mut S,
|
||||
context: &str,
|
||||
) -> Result<ClientJsonRpcMessage, ServerError>
|
||||
where
|
||||
S: StreamExt<Item = ClientJsonRpcMessage> + Unpin,
|
||||
{
|
||||
Ok(stream
|
||||
stream
|
||||
.next()
|
||||
.await
|
||||
.ok_or_else(|| ServerError::ConnectionClosed(context.to_string()))?
|
||||
.into_message())
|
||||
.ok_or_else(|| ServerError::ConnectionClosed(context.to_string()))
|
||||
}
|
||||
|
||||
/// Helper function to expect a request from the stream
|
||||
|
|
@ -144,16 +146,28 @@ where
|
|||
|
||||
let ClientRequest::InitializeRequest(peer_info) = request else {
|
||||
return Err(handle_server_error(ServerError::ExpectedInitRequest(Some(
|
||||
ClientMessage::Request(request, id),
|
||||
ClientJsonRpcMessage::request(request, id),
|
||||
))));
|
||||
};
|
||||
|
||||
// Send initialize response
|
||||
let init_response = service.get_info();
|
||||
sink.send(
|
||||
ServerMessage::Response(ServerResult::InitializeResult(init_response), id)
|
||||
.into_json_rpc_message(),
|
||||
)
|
||||
let mut init_response = service.get_info();
|
||||
let protocol_version = match peer_info
|
||||
.params
|
||||
.protocol_version
|
||||
.partial_cmp(&init_response.protocol_version)
|
||||
.ok_or(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidData,
|
||||
"unsupported protocol version",
|
||||
))? {
|
||||
std::cmp::Ordering::Less => peer_info.params.protocol_version.clone(),
|
||||
_ => init_response.protocol_version,
|
||||
};
|
||||
init_response.protocol_version = protocol_version;
|
||||
sink.send(ServerJsonRpcMessage::response(
|
||||
ServerResult::InitializeResult(init_response),
|
||||
id,
|
||||
))
|
||||
.await?;
|
||||
|
||||
// Wait for initialize notification
|
||||
|
|
@ -163,7 +177,7 @@ where
|
|||
|
||||
let ClientNotification::InitializedNotification(_) = notification else {
|
||||
return Err(handle_server_error(ServerError::ExpectedInitNotification(
|
||||
Some(ClientMessage::Notification(notification)),
|
||||
Some(ClientJsonRpcMessage::notification(notification)),
|
||||
)));
|
||||
};
|
||||
|
||||
|
|
@ -177,6 +191,7 @@ macro_rules! method {
|
|||
let result = self
|
||||
.send_request(ServerRequest::$Req($Req {
|
||||
method: Default::default(),
|
||||
extensions: Default::default(),
|
||||
}))
|
||||
.await?;
|
||||
match result {
|
||||
|
|
@ -191,6 +206,7 @@ macro_rules! method {
|
|||
.send_request(ServerRequest::$Req($Req {
|
||||
method: Default::default(),
|
||||
params,
|
||||
extensions: Default::default(),
|
||||
}))
|
||||
.await?;
|
||||
match result {
|
||||
|
|
@ -224,6 +240,7 @@ macro_rules! method {
|
|||
self.send_notification(ServerNotification::$Not($Not {
|
||||
method: Default::default(),
|
||||
params,
|
||||
extensions: Default::default(),
|
||||
}))
|
||||
.await?;
|
||||
Ok(())
|
||||
|
|
@ -233,6 +250,7 @@ macro_rules! method {
|
|||
pub async fn $method(&self) -> Result<(), ServiceError> {
|
||||
self.send_notification(ServerNotification::$Not($Not {
|
||||
method: Default::default(),
|
||||
extensions: Default::default(),
|
||||
}))
|
||||
.await?;
|
||||
Ok(())
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ impl ServerHandler for Counter {
|
|||
|
||||
async fn list_resources(
|
||||
&self,
|
||||
_request: PaginatedRequestParam,
|
||||
_request: Option<PaginatedRequestParam>,
|
||||
_: RequestContext<RoleServer>,
|
||||
) -> Result<ListResourcesResult, McpError> {
|
||||
Ok(ListResourcesResult {
|
||||
|
|
@ -139,7 +139,7 @@ impl ServerHandler for Counter {
|
|||
|
||||
async fn list_prompts(
|
||||
&self,
|
||||
_request: PaginatedRequestParam,
|
||||
_request: Option<PaginatedRequestParam>,
|
||||
_: RequestContext<RoleServer>,
|
||||
) -> Result<ListPromptsResult, McpError> {
|
||||
Ok(ListPromptsResult {
|
||||
|
|
@ -185,7 +185,7 @@ impl ServerHandler for Counter {
|
|||
|
||||
async fn list_resource_templates(
|
||||
&self,
|
||||
_request: PaginatedRequestParam,
|
||||
_request: Option<PaginatedRequestParam>,
|
||||
_: RequestContext<RoleServer>,
|
||||
) -> Result<ListResourceTemplatesResult, McpError> {
|
||||
Ok(ListResourceTemplatesResult {
|
||||
|
|
|
|||
Loading…
Reference in a new issue