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:
Stanley Horwood 2025-05-15 17:56:31 +02:00 committed by GitHub
parent 5d92061ece
commit e9a5ae9901
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 2790 additions and 1 deletions

View file

@ -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

View file

@ -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"

View file

@ -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),)*
}

View file

@ -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,

View file

@ -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>,

View file

@ -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),

View file

@ -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,

View file

@ -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,

View file

@ -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>,

View 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