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
1 changed files with 1280 additions and 229 deletions
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue