feat(model): add json schema generation support for all model types (#176)
* feat(model): add json schema generation support for all model types This commit adds JSON Schema support for all model types by implementing `schemars::JsonSchema` trait. The feature is gated behind the new `schemars` feature flag. This enables automatic schema generation for API documentation and validation purposes. Added tests to verify schema generation for client and server JSON-RPC messages. * fix(model): add manual json schema implementation for `NumberOrString` This commit adds a manual implementation of `JsonSchema` trait for the `NumberOrString` enum to properly represent its union type nature in JSON Schema. The schema now correctly specifies that the type can be either a number or a string using the `oneOf` validation keyword. * fix(model): skip extensions field in json schema generation The `Extensions` type was incorrectly included in JSON schema generation, which could lead to confusing API documentation. This commit adds `#[schemars(skip)]` attribute to all `extensions` fields in request and notification structs, and removes the manual `JsonSchema` implementation for the `Extensions` type since it's an internal implementation detail that shouldn't be exposed in the schema.
This commit is contained in:
parent
5d92061ece
commit
e9a5ae9901
12 changed files with 2790 additions and 1 deletions
|
|
@ -192,6 +192,7 @@ See [examples](examples/README.md)
|
|||
- `client`: use client side sdk
|
||||
- `server`: use server side sdk
|
||||
- `macros`: macros default
|
||||
- `schemars`: implement `JsonSchema` for all model structs
|
||||
|
||||
### Transports
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ paste = { version = "1", optional = true }
|
|||
oauth2 = { version = "5.0", optional = true }
|
||||
|
||||
# for auto generate schema
|
||||
schemars = { version = "0.8", optional = true }
|
||||
schemars = { version = "0.8", optional = true, features = ["chrono"] }
|
||||
|
||||
# for image encoding
|
||||
base64 = { version = "0.21", optional = true }
|
||||
|
|
@ -89,6 +89,7 @@ tower = ["dep:tower-service"]
|
|||
__auth = ["dep:oauth2", "dep:reqwest", "dep:url"]
|
||||
auth = ["__auth", "reqwest?/rustls-tls"]
|
||||
auth-tls-no-provider = ["auth", "reqwest?/rustls-tls-no-provider"]
|
||||
schemars = ["dep:schemars"]
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
|
|
@ -131,3 +132,8 @@ name = "test_message_protocol"
|
|||
required-features = ["client"]
|
||||
path = "tests/test_message_protocol.rs"
|
||||
|
||||
[[test]]
|
||||
name = "test_message_schema"
|
||||
required-features = ["server", "client", "schemars"]
|
||||
path = "tests/test_message_schema.rs"
|
||||
|
||||
|
|
|
|||
|
|
@ -87,12 +87,30 @@ macro_rules! const_string {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "schemars")]
|
||||
impl schemars::JsonSchema for $name {
|
||||
fn schema_name() -> String {
|
||||
stringify!($name).to_string()
|
||||
}
|
||||
|
||||
fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::schema::Schema {
|
||||
// Create a schema for a constant value of type String
|
||||
schemars::schema::Schema::Object(schemars::schema::SchemaObject {
|
||||
instance_type: Some(schemars::schema::InstanceType::String.into()),
|
||||
format: Some("const".to_string()),
|
||||
const_value: Some(serde_json::Value::String($value.into())),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const_string!(JsonRpcVersion2_0 = "2.0");
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash, PartialOrd)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct ProtocolVersion(Cow<'static, str>);
|
||||
|
||||
impl Default for ProtocolVersion {
|
||||
|
|
@ -184,12 +202,40 @@ impl<'de> Deserialize<'de> for NumberOrString {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "schemars")]
|
||||
impl schemars::JsonSchema for NumberOrString {
|
||||
fn schema_name() -> String {
|
||||
"NumberOrString".to_string()
|
||||
}
|
||||
|
||||
fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::schema::Schema {
|
||||
schemars::schema::Schema::Object(schemars::schema::SchemaObject {
|
||||
subschemas: Some(Box::new(schemars::schema::SubschemaValidation {
|
||||
one_of: Some(vec![
|
||||
schemars::schema::Schema::Object(schemars::schema::SchemaObject {
|
||||
instance_type: Some(schemars::schema::InstanceType::Number.into()),
|
||||
..Default::default()
|
||||
}),
|
||||
schemars::schema::Schema::Object(schemars::schema::SchemaObject {
|
||||
instance_type: Some(schemars::schema::InstanceType::String.into()),
|
||||
..Default::default()
|
||||
}),
|
||||
]),
|
||||
..Default::default()
|
||||
})),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub type RequestId = NumberOrString;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Hash, Eq)]
|
||||
#[serde(transparent)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct ProgressToken(pub NumberOrString);
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct Request<M = String, P = JsonObject> {
|
||||
pub method: M,
|
||||
// #[serde(skip_serializing_if = "Option::is_none")]
|
||||
|
|
@ -197,6 +243,7 @@ pub struct Request<M = String, P = JsonObject> {
|
|||
/// extensions will carry anything possible in the context, including [`Meta`]
|
||||
///
|
||||
/// this is similar with the Extensions in `http` crate
|
||||
#[cfg_attr(feature = "schemars", schemars(skip))]
|
||||
pub extensions: Extensions,
|
||||
}
|
||||
|
||||
|
|
@ -210,6 +257,7 @@ impl<M, P> GetExtensions for Request<M, P> {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct RequestOptionalParam<M = String, P = JsonObject> {
|
||||
pub method: M,
|
||||
// #[serde(skip_serializing_if = "Option::is_none")]
|
||||
|
|
@ -217,15 +265,18 @@ pub struct RequestOptionalParam<M = String, P = JsonObject> {
|
|||
/// extensions will carry anything possible in the context, including [`Meta`]
|
||||
///
|
||||
/// this is similar with the Extensions in `http` crate
|
||||
#[cfg_attr(feature = "schemars", schemars(skip))]
|
||||
pub extensions: Extensions,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
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
|
||||
#[cfg_attr(feature = "schemars", schemars(skip))]
|
||||
pub extensions: Extensions,
|
||||
}
|
||||
|
||||
|
|
@ -238,33 +289,40 @@ impl<M> GetExtensions for RequestNoParam<M> {
|
|||
}
|
||||
}
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
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
|
||||
#[cfg_attr(feature = "schemars", schemars(skip))]
|
||||
pub extensions: Extensions,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
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
|
||||
#[cfg_attr(feature = "schemars", schemars(skip))]
|
||||
pub extensions: Extensions,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct JsonRpcRequest<R = Request> {
|
||||
pub jsonrpc: JsonRpcVersion2_0,
|
||||
pub id: RequestId,
|
||||
#[serde(flatten)]
|
||||
pub request: R,
|
||||
}
|
||||
|
||||
type DefaultResponse = JsonObject;
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct JsonRpcResponse<R = JsonObject> {
|
||||
pub jsonrpc: JsonRpcVersion2_0,
|
||||
pub id: RequestId,
|
||||
|
|
@ -272,6 +330,7 @@ pub struct JsonRpcResponse<R = JsonObject> {
|
|||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct JsonRpcError {
|
||||
pub jsonrpc: JsonRpcVersion2_0,
|
||||
pub id: RequestId,
|
||||
|
|
@ -279,6 +338,7 @@ pub struct JsonRpcError {
|
|||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct JsonRpcNotification<N = Notification> {
|
||||
pub jsonrpc: JsonRpcVersion2_0,
|
||||
#[serde(flatten)]
|
||||
|
|
@ -288,6 +348,7 @@ pub struct JsonRpcNotification<N = Notification> {
|
|||
// Standard JSON-RPC error codes
|
||||
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(transparent)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct ErrorCode(pub i32);
|
||||
|
||||
impl ErrorCode {
|
||||
|
|
@ -301,6 +362,7 @@ impl ErrorCode {
|
|||
|
||||
/// Error information for JSON-RPC error responses.
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct ErrorData {
|
||||
/// The error type that occurred.
|
||||
pub code: ErrorCode,
|
||||
|
|
@ -348,6 +410,7 @@ impl ErrorData {
|
|||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[serde(untagged)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum JsonRpcBatchRequestItem<Req, Not> {
|
||||
Request(JsonRpcRequest<Req>),
|
||||
Notification(JsonRpcNotification<Not>),
|
||||
|
|
@ -364,6 +427,7 @@ impl<Req, Not> JsonRpcBatchRequestItem<Req, Not> {
|
|||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[serde(untagged)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum JsonRpcBatchResponseItem<Resp> {
|
||||
Response(JsonRpcResponse<Resp>),
|
||||
Error(JsonRpcError),
|
||||
|
|
@ -380,6 +444,7 @@ impl<Resp> JsonRpcBatchResponseItem<Resp> {
|
|||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[serde(untagged)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum JsonRpcMessage<Req = Request, Resp = DefaultResponse, Noti = Notification> {
|
||||
Request(JsonRpcRequest<Req>),
|
||||
Response(JsonRpcResponse<Resp>),
|
||||
|
|
@ -471,6 +536,7 @@ impl From<EmptyResult> for () {
|
|||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct CancelledNotificationParam {
|
||||
pub request_id: RequestId,
|
||||
pub reason: Option<String>,
|
||||
|
|
@ -499,6 +565,7 @@ const_string!(InitializedNotificationMethod = "notifications/initialized");
|
|||
pub type InitializedNotification = NotificationNoParam<InitializedNotificationMethod>;
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct InitializeRequestParam {
|
||||
pub protocol_version: ProtocolVersion,
|
||||
pub capabilities: ClientCapabilities,
|
||||
|
|
@ -507,6 +574,7 @@ pub struct InitializeRequestParam {
|
|||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct InitializeResult {
|
||||
pub protocol_version: ProtocolVersion,
|
||||
pub capabilities: ServerCapabilities,
|
||||
|
|
@ -540,6 +608,7 @@ impl Default for ClientInfo {
|
|||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct Implementation {
|
||||
pub name: String,
|
||||
pub version: String,
|
||||
|
|
@ -562,6 +631,7 @@ impl Implementation {
|
|||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct PaginatedRequestParam {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub cursor: Option<String>,
|
||||
|
|
@ -572,6 +642,7 @@ pub type PingRequest = RequestNoParam<PingRequestMethod>;
|
|||
const_string!(ProgressNotificationMethod = "notifications/progress");
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct ProgressNotificationParam {
|
||||
pub progress_token: ProgressToken,
|
||||
/// The progress thus far. This should increase every time progress is made, even if the total is unknown.
|
||||
|
|
@ -594,6 +665,7 @@ macro_rules! paginated_result {
|
|||
}) => {
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct $t {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub next_cursor: Option<Cursor>,
|
||||
|
|
@ -619,11 +691,13 @@ paginated_result!(ListResourceTemplatesResult {
|
|||
const_string!(ReadResourceRequestMethod = "resources/read");
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct ReadResourceRequestParam {
|
||||
pub uri: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct ReadResourceResult {
|
||||
pub contents: Vec<ResourceContents>,
|
||||
}
|
||||
|
|
@ -637,6 +711,7 @@ pub type ResourceListChangedNotification =
|
|||
const_string!(SubscribeRequestMethod = "resources/subscribe");
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct SubscribeRequestParam {
|
||||
pub uri: String,
|
||||
}
|
||||
|
|
@ -645,6 +720,7 @@ pub type SubscribeRequest = Request<SubscribeRequestMethod, SubscribeRequestPara
|
|||
const_string!(UnsubscribeRequestMethod = "resources/unsubscribe");
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct UnsubscribeRequestParam {
|
||||
pub uri: String,
|
||||
}
|
||||
|
|
@ -653,6 +729,7 @@ pub type UnsubscribeRequest = Request<UnsubscribeRequestMethod, UnsubscribeReque
|
|||
const_string!(ResourceUpdatedNotificationMethod = "notifications/resources/updated");
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct ResourceUpdatedNotificationParam {
|
||||
pub uri: String,
|
||||
}
|
||||
|
|
@ -668,6 +745,7 @@ paginated_result!(ListPromptsResult {
|
|||
const_string!(GetPromptRequestMethod = "prompts/get");
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct GetPromptRequestParam {
|
||||
pub name: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
|
|
@ -683,6 +761,7 @@ pub type ToolListChangedNotification = NotificationNoParam<ToolListChangedNotifi
|
|||
// 日志相关
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Copy)]
|
||||
#[serde(rename_all = "lowercase")] //match spec
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum LoggingLevel {
|
||||
Debug,
|
||||
Info,
|
||||
|
|
@ -697,6 +776,7 @@ pub enum LoggingLevel {
|
|||
const_string!(SetLevelRequestMethod = "logging/setLevel");
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct SetLevelRequestParam {
|
||||
pub level: LoggingLevel,
|
||||
}
|
||||
|
|
@ -705,6 +785,7 @@ pub type SetLevelRequest = Request<SetLevelRequestMethod, SetLevelRequestParam>;
|
|||
const_string!(LoggingMessageNotificationMethod = "notifications/message");
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct LoggingMessageNotificationParam {
|
||||
pub level: LoggingLevel,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
|
|
@ -719,18 +800,21 @@ pub type CreateMessageRequest = Request<CreateMessageRequestMethod, CreateMessag
|
|||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum Role {
|
||||
User,
|
||||
Assistant,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct SamplingMessage {
|
||||
pub role: Role,
|
||||
pub content: Content,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum ContextInclusion {
|
||||
#[serde(rename = "allServers")]
|
||||
AllServers,
|
||||
|
|
@ -742,6 +826,7 @@ pub enum ContextInclusion {
|
|||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct CreateMessageRequestParam {
|
||||
pub messages: Vec<SamplingMessage>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
|
|
@ -761,6 +846,7 @@ pub struct CreateMessageRequestParam {
|
|||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct ModelPreferences {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub hints: Option<Vec<ModelHint>>,
|
||||
|
|
@ -773,6 +859,7 @@ pub struct ModelPreferences {
|
|||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct ModelHint {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub name: Option<String>,
|
||||
|
|
@ -780,6 +867,7 @@ pub struct ModelHint {
|
|||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct CompleteRequestParam {
|
||||
pub r#ref: Reference,
|
||||
pub argument: ArgumentInfo,
|
||||
|
|
@ -789,6 +877,7 @@ pub type CompleteRequest = Request<CompleteRequestMethod, CompleteRequestParam>;
|
|||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct CompletionInfo {
|
||||
pub values: Vec<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
|
|
@ -799,12 +888,14 @@ pub struct CompletionInfo {
|
|||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct CompleteResult {
|
||||
pub completion: CompletionInfo,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[serde(tag = "type")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum Reference {
|
||||
#[serde(rename = "ref/resource")]
|
||||
Resource(ResourceReference),
|
||||
|
|
@ -813,11 +904,13 @@ pub enum Reference {
|
|||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct ResourceReference {
|
||||
pub uri: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct PromptReference {
|
||||
pub name: String,
|
||||
}
|
||||
|
|
@ -825,6 +918,7 @@ pub struct PromptReference {
|
|||
const_string!(CompleteRequestMethod = "completion/complete");
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct ArgumentInfo {
|
||||
pub name: String,
|
||||
pub value: String,
|
||||
|
|
@ -832,6 +926,7 @@ pub struct ArgumentInfo {
|
|||
|
||||
// 根目录相关
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct Root {
|
||||
pub uri: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
|
|
@ -843,6 +938,7 @@ pub type ListRootsRequest = RequestNoParam<ListRootsRequestMethod>;
|
|||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct ListRootsResult {
|
||||
pub roots: Vec<Root>,
|
||||
}
|
||||
|
|
@ -852,6 +948,7 @@ pub type RootsListChangedNotification = NotificationNoParam<RootsListChangedNoti
|
|||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct CallToolResult {
|
||||
pub content: Vec<Content>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
|
|
@ -884,6 +981,7 @@ paginated_result!(
|
|||
const_string!(CallToolRequestMethod = "tools/call");
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct CallToolRequestParam {
|
||||
pub name: Cow<'static, str>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
|
|
@ -894,6 +992,7 @@ pub type CallToolRequest = Request<CallToolRequestMethod, CallToolRequestParam>;
|
|||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct CreateMessageResult {
|
||||
pub model: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
|
|
@ -910,6 +1009,7 @@ impl CreateMessageResult {
|
|||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct GetPromptResult {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub description: Option<String>,
|
||||
|
|
@ -923,6 +1023,7 @@ macro_rules! ts_union {
|
|||
) => {
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[serde(untagged)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum $U {
|
||||
$($V($V),)*
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ use super::{
|
|||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct Annotations {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub audience: Option<Vec<Role>>,
|
||||
|
|
@ -36,6 +37,7 @@ impl Annotations {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct Annotated<T: AnnotateAble> {
|
||||
#[serde(flatten)]
|
||||
pub raw: T,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ pub type ExperimentalCapabilities = BTreeMap<String, JsonObject>;
|
|||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct PromptsCapability {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub list_changed: Option<bool>,
|
||||
|
|
@ -15,6 +16,7 @@ pub struct PromptsCapability {
|
|||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct ResourcesCapability {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub subscribe: Option<bool>,
|
||||
|
|
@ -24,6 +26,7 @@ pub struct ResourcesCapability {
|
|||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct ToolsCapability {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub list_changed: Option<bool>,
|
||||
|
|
@ -31,6 +34,7 @@ pub struct ToolsCapability {
|
|||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct RootsCapabilities {
|
||||
pub list_changed: Option<bool>,
|
||||
}
|
||||
|
|
@ -46,6 +50,7 @@ pub struct RootsCapabilities {
|
|||
/// .build();
|
||||
/// ```
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct ClientCapabilities {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub experimental: Option<ExperimentalCapabilities>,
|
||||
|
|
@ -70,6 +75,7 @@ pub struct ClientCapabilities {
|
|||
/// ```
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct ServerCapabilities {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub experimental: Option<ExperimentalCapabilities>,
|
||||
|
|
|
|||
|
|
@ -8,12 +8,14 @@ use super::{AnnotateAble, Annotated, resource::ResourceContents};
|
|||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct RawTextContent {
|
||||
pub text: String,
|
||||
}
|
||||
pub type TextContent = Annotated<RawTextContent>;
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct RawImageContent {
|
||||
/// The base64-encoded image
|
||||
pub data: String,
|
||||
|
|
@ -23,6 +25,7 @@ pub struct RawImageContent {
|
|||
pub type ImageContent = Annotated<RawImageContent>;
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct RawEmbeddedResource {
|
||||
pub resource: ResourceContents,
|
||||
}
|
||||
|
|
@ -39,6 +42,7 @@ impl EmbeddedResource {
|
|||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct RawAudioContent {
|
||||
pub data: String,
|
||||
pub mime_type: String,
|
||||
|
|
@ -48,6 +52,7 @@ pub type AudioContent = Annotated<RawAudioContent>;
|
|||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum RawContent {
|
||||
Text(RawTextContent),
|
||||
Image(RawImageContent),
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ use super::{
|
|||
/// A prompt that can be used to generate text from a model
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct Prompt {
|
||||
/// The name of the prompt
|
||||
pub name: String,
|
||||
|
|
@ -42,6 +43,7 @@ impl Prompt {
|
|||
|
||||
/// Represents a prompt argument that can be passed to customize the prompt
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct PromptArgument {
|
||||
/// The name of the argument
|
||||
pub name: String,
|
||||
|
|
@ -56,6 +58,7 @@ pub struct PromptArgument {
|
|||
/// Represents the role of a message sender in a prompt conversation
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum PromptMessageRole {
|
||||
User,
|
||||
Assistant,
|
||||
|
|
@ -64,6 +67,7 @@ pub enum PromptMessageRole {
|
|||
/// Content types that can be included in prompt messages
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum PromptMessageContent {
|
||||
/// Plain text content
|
||||
Text { text: String },
|
||||
|
|
@ -84,6 +88,7 @@ impl PromptMessageContent {
|
|||
|
||||
/// A message in a prompt conversation
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct PromptMessage {
|
||||
/// The role of the message sender
|
||||
pub role: PromptMessageRole,
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ use super::Annotated;
|
|||
/// Represents a resource in the extension with metadata
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct RawResource {
|
||||
/// URI representing the resource location (e.g., "file:///path/to/file" or "str:///content")
|
||||
pub uri: String,
|
||||
|
|
@ -28,6 +29,7 @@ pub type Resource = Annotated<RawResource>;
|
|||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct RawResourceTemplate {
|
||||
pub uri_template: String,
|
||||
pub name: String,
|
||||
|
|
@ -41,6 +43,7 @@ pub type ResourceTemplate = Annotated<RawResourceTemplate>;
|
|||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[serde(rename_all = "camelCase", untagged)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum ResourceContents {
|
||||
TextResourceContents {
|
||||
uri: String,
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ use super::JsonObject;
|
|||
/// A tool that can be used by a model.
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct Tool {
|
||||
/// The name of the tool
|
||||
pub name: Cow<'static, str>,
|
||||
|
|
@ -33,6 +34,7 @@ pub struct Tool {
|
|||
/// received from untrusted servers.
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct ToolAnnotations {
|
||||
/// A human-readable title for the tool.
|
||||
pub title: Option<String>,
|
||||
|
|
|
|||
40
crates/rmcp/tests/test_message_schema.rs
Normal file
40
crates/rmcp/tests/test_message_schema.rs
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
mod tests {
|
||||
use rmcp::model::{ClientJsonRpcMessage, ServerJsonRpcMessage};
|
||||
use schemars::schema_for;
|
||||
|
||||
#[test]
|
||||
fn test_client_json_rpc_message_schema() {
|
||||
let schema = schema_for!(ClientJsonRpcMessage);
|
||||
let schema_str = serde_json::to_string_pretty(&schema).unwrap();
|
||||
let expected = std::fs::read_to_string(
|
||||
"tests/test_message_schema/client_json_rpc_message_schema.json",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Parse both strings to JSON values for more robust comparison
|
||||
let schema_json: serde_json::Value = serde_json::from_str(&schema_str).unwrap();
|
||||
let expected_json: serde_json::Value = serde_json::from_str(&expected).unwrap();
|
||||
assert_eq!(
|
||||
schema_json, expected_json,
|
||||
"Schema generation for ClientJsonRpcMessage should match expected output"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_server_json_rpc_message_schema() {
|
||||
let schema = schema_for!(ServerJsonRpcMessage);
|
||||
let schema_str = serde_json::to_string_pretty(&schema).unwrap();
|
||||
let expected = std::fs::read_to_string(
|
||||
"tests/test_message_schema/server_json_rpc_message_schema.json",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Parse both strings to JSON values for more robust comparison
|
||||
let schema_json: serde_json::Value = serde_json::from_str(&schema_str).unwrap();
|
||||
let expected_json: serde_json::Value = serde_json::from_str(&expected).unwrap();
|
||||
assert_eq!(
|
||||
schema_json, expected_json,
|
||||
"Schema generation for ServerJsonRpcMessage should match expected output"
|
||||
);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue