feat: add type-safe elicitation schema support (#465) (#466)

* feat: add type-safe elicitation schema support (#465)

Implement type-safe schema definitions for MCP elicitation requests,
replacing generic `JsonObject` with strongly-typed primitive schemas
per the [MCP 2025-06-18 specification](https://spec.modelcontextprotocol.io/specification/2025-06-18/server/elicitation/).

Features:
- Type-safe schema hierarchy (`StringSchema`, `NumberSchema`, `IntegerSchema`, `BooleanSchema`)
- Builder pattern with fluent API and 20+ convenience methods
- Build-time validation ensuring required fields exist in properties
- Private fields enforcing invariants through validated constructors
- Comprehensive validation support (range, length, format, enums)
- Typed property methods for cleaner schema construction

Benefits:
- Compile-time type safety prevents invalid schema construction
- 60-70% reduction in boilerplate through convenience methods
- Enforces MCP specification requirement for primitive-only properties
- Better IDE autocomplete and type inference
- Runtime validation catches schema errors early

Breaking changes:
- `CreateElicitationRequestParam.requested_schema` changed from `JsonObject` to `ElicitationSchema`
- `ElicitationSchemaBuilder::build()` now returns `Result` instead of direct value

Fixes #465

* fix: fix RMCP compliance

* feat: add conversion methods to ElicitationSchema

Add from_json_schema() and from_type() methods to ElicitationSchema
for easier type-to-schema conversion. This addresses feedback about
improving ergonomics when working with generated schemas.

Also make all struct fields public for better flexibility.

* chore: change `StringFormat` to enum
This commit is contained in:
sktrpathi 2025-10-15 15:25:10 +05:30 • committed by GitHub
commit 11093bc830
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 2253 additions and 137 deletions

View file

@ -2,6 +2,7 @@ use std::{borrow::Cow, sync::Arc};
mod annotated;
mod capabilities;
mod content;
mod elicitation_schema;
mod extension;
mod meta;
mod prompt;
@ -11,6 +12,7 @@ mod tool;
pub use annotated::*;
pub use capabilities::*;
pub use content::*;
pub use elicitation_schema::*;
pub use extension::*;
pub use meta::*;
pub use prompt::*;
@ -1377,7 +1379,21 @@ pub enum ElicitationAction {
///
/// This structure contains everything needed to request interactive input from a user:
/// - A human-readable message explaining what information is needed
/// - A JSON schema defining the expected structure of the response
/// - A type-safe schema defining the expected structure of the response
///
/// # Example
///
/// ```rust
/// use rmcp::model::*;
///
/// let params = CreateElicitationRequestParam {
/// message: "Please provide your email".to_string(),
/// requested_schema: ElicitationSchema::builder()
/// .required_email("email")
/// .build()
/// .unwrap(),
/// };
/// ```
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
@ -1387,10 +1403,10 @@ pub struct CreateElicitationRequestParam {
/// what information they need to provide.
pub message: String,
/// JSON Schema defining the expected structure and validation rules for the user's response.
/// This allows clients to validate input and provide appropriate UI controls.
/// Must be a valid JSON Schema Draft 2020-12 object.
pub requested_schema: JsonObject,
/// Type-safe schema defining the expected structure and validation rules for the user's response.
/// This enforces the MCP 2025-06-18 specification that elicitation schemas must be objects
/// with primitive-typed properties.
pub requested_schema: ElicitationSchema,
}
/// The result returned by a client in response to an elicitation request.

File diff suppressed because it is too large Load diff

View file

@ -677,7 +677,16 @@ impl Peer<RoleServer> {
}
// Generate schema automatically from type
let schema = crate::handler::server::tool::schema_for_type::<T>();
let schema = crate::model::ElicitationSchema::from_type::<T>().map_err(|e| {
ElicitationError::Service(ServiceError::McpError(crate::ErrorData::invalid_params(
format!(
"Invalid schema for type {}: {}",
std::any::type_name::<T>(),
e
),
None,
)))
})?;
let response = self
.create_elicitation_with_timeout(

View file

@ -39,23 +39,14 @@ async fn test_elicitation_serialization() {
/// Test CreateElicitationRequestParam structure serialization/deserialization
#[tokio::test]
async fn test_elicitation_request_param_serialization() {
let schema_object = json!({
"type": "object",
"properties": {
"email": {
"type": "string",
"format": "email"
}
},
"required": ["email"]
})
.as_object()
.unwrap()
.clone();
let schema = ElicitationSchema::builder()
.required_property("email", PrimitiveSchema::String(StringSchema::email()))
.build()
.unwrap();
let request_param = CreateElicitationRequestParam {
message: "Please provide your email address".to_string(),
requested_schema: schema_object,
requested_schema: schema,
};
// Test serialization
@ -123,16 +114,13 @@ async fn test_elicitation_result_serialization() {
/// Test that elicitation requests can be created and handled through the JSON-RPC protocol
#[tokio::test]
async fn test_elicitation_json_rpc_protocol() {
let schema = json!({
"type": "object",
"properties": {
"confirmation": {"type": "boolean"}
},
"required": ["confirmation"]
})
.as_object()
.unwrap()
.clone();
let schema = ElicitationSchema::builder()
.required_property(
"confirmation",
PrimitiveSchema::Boolean(BooleanSchema::new()),
)
.build()
.unwrap();
// Create a complete JSON-RPC request for elicitation
let request = JsonRpcRequest {
@ -223,19 +211,22 @@ async fn test_elicitation_spec_compliance() {
/// Test error handling and edge cases for elicitation
#[tokio::test]
async fn test_elicitation_error_handling() {
// Test invalid JSON schema handling
let invalid_schema_request = CreateElicitationRequestParam {
// Test minimal schema handling (empty properties is technically valid)
let minimal_schema_request = CreateElicitationRequestParam {
message: "Test message".to_string(),
requested_schema: serde_json::Map::new(), // Empty schema is technically valid
requested_schema: ElicitationSchema::builder().build().unwrap(),
};
// Should serialize without error
let _json = serde_json::to_value(&invalid_schema_request).unwrap();
let _json = serde_json::to_value(&minimal_schema_request).unwrap();
// Test empty message
let empty_message_request = CreateElicitationRequestParam {
message: "".to_string(),
requested_schema: json!({"type": "string"}).as_object().unwrap().clone(),
requested_schema: ElicitationSchema::builder()
.property("value", PrimitiveSchema::String(StringSchema::new()))
.build()
.unwrap(),
};
// Should serialize without error (validation is up to the implementation)
@ -250,15 +241,10 @@ async fn test_elicitation_error_handling() {
/// Benchmark-style test for elicitation performance
#[tokio::test]
async fn test_elicitation_performance() {
let schema = json!({
"type": "object",
"properties": {
"data": {"type": "string"}
}
})
.as_object()
.unwrap()
.clone();
let schema = ElicitationSchema::builder()
.property("data", PrimitiveSchema::String(StringSchema::new()))
.build()
.unwrap();
let request = CreateElicitationRequestParam {
message: "Performance test message".to_string(),
@ -383,51 +369,52 @@ async fn test_elicitation_convenience_methods() {
.contains("Option A")
);
// Test that CreateElicitationRequestParam can be created with these schemas
// Test that CreateElicitationRequestParam can be created with type-safe schemas
let confirmation_request = CreateElicitationRequestParam {
message: "Test confirmation".to_string(),
requested_schema: confirmation_schema.as_object().unwrap().clone(),
requested_schema: ElicitationSchema::builder()
.property(
"confirmed",
PrimitiveSchema::Boolean(
BooleanSchema::new()
.description("User confirmation (true for yes, false for no)"),
),
)
.build()
.unwrap(),
};
// Test serialization of convenience method request
let json = serde_json::to_value(&confirmation_request).unwrap();
assert_eq!(json["message"], "Test confirmation");
assert_eq!(json["requestedSchema"]["type"], "boolean");
assert_eq!(json["requestedSchema"]["type"], "object");
assert_eq!(
json["requestedSchema"]["properties"]["confirmed"]["type"],
"boolean"
);
}
/// Test structured input with complex schemas
/// Ensures that complex JSON schemas work correctly with elicitation
/// Test structured input with multiple primitive properties
/// Ensures that schemas with multiple primitive properties work correctly with elicitation
#[tokio::test]
async fn test_elicitation_structured_schemas() {
// Test complex nested object schema
let complex_schema = json!({
"type": "object",
"properties": {
"user": {
"type": "object",
"properties": {
"name": {"type": "string"},
"email": {"type": "string", "format": "email"},
"preferences": {
"type": "object",
"properties": {
"theme": {"type": "string", "enum": ["light", "dark"]},
"notifications": {"type": "boolean"}
}
}
}
},
"metadata": {
"type": "array",
"items": {"type": "string"}
}
},
"required": ["user"]
});
// Test schema with multiple primitive properties
let schema = ElicitationSchema::builder()
.required_string_with("name", |s| s.length(1, 100))
.required_email("email")
.required_integer("age", 0, 150)
.optional_bool("newsletter", false)
.required_enum(
"country",
vec!["US".to_string(), "UK".to_string(), "CA".to_string()],
)
.description("User registration information")
.build()
.unwrap();
let request = CreateElicitationRequestParam {
message: "Please provide your user information".to_string(),
requested_schema: complex_schema.as_object().unwrap().clone(),
requested_schema: schema,
};
// Test that complex schemas serialize/deserialize correctly
@ -435,36 +422,41 @@ async fn test_elicitation_structured_schemas() {
let deserialized: CreateElicitationRequestParam = serde_json::from_value(json).unwrap();
assert_eq!(deserialized.message, "Please provide your user information");
assert_eq!(deserialized.requested_schema.properties.len(), 5);
assert!(
deserialized
.requested_schema
.properties
.contains_key("name")
);
assert!(
deserialized
.requested_schema
.properties
.contains_key("email")
);
assert!(deserialized.requested_schema.properties.contains_key("age"));
assert!(
deserialized
.requested_schema
.properties
.contains_key("newsletter")
);
assert!(
deserialized
.requested_schema
.properties
.contains_key("country")
);
assert_eq!(
deserialized.requested_schema["properties"]["user"]["properties"]["name"]["type"],
"string"
deserialized.requested_schema.required,
Some(vec![
"name".to_string(),
"email".to_string(),
"age".to_string(),
"country".to_string()
])
);
// Test array schema
let array_schema = json!({
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {"type": "integer"},
"name": {"type": "string"}
},
"required": ["id", "name"]
},
"minItems": 1,
"maxItems": 10
});
let array_request = CreateElicitationRequestParam {
message: "Please provide a list of items".to_string(),
requested_schema: array_schema.as_object().unwrap().clone(),
};
// Verify array schema
let json = serde_json::to_value(&array_request).unwrap();
assert_eq!(json["requestedSchema"]["type"], "array");
assert_eq!(json["requestedSchema"]["minItems"], 1);
assert_eq!(json["requestedSchema"]["maxItems"], 10);
}
// Typed elicitation tests using the API with schemars
@ -649,13 +641,13 @@ async fn test_elicitation_direction_server_to_client() {
use serde_json::json;
// Test that server can create elicitation requests
let schema = json!({
"type": "string",
"description": "Enter your name"
})
.as_object()
.unwrap()
.clone();
let schema = ElicitationSchema::builder()
.property(
"name",
PrimitiveSchema::String(StringSchema::new().description("Enter your name")),
)
.build()
.unwrap();
let elicitation_request = CreateElicitationRequestParam {
message: "Please enter your name".to_string(),
@ -665,7 +657,7 @@ async fn test_elicitation_direction_server_to_client() {
// Verify request can be serialized
let serialized = serde_json::to_value(&elicitation_request).unwrap();
assert_eq!(serialized["message"], "Please enter your name");
assert_eq!(serialized["requestedSchema"]["type"], "string");
assert_eq!(serialized["requestedSchema"]["type"], "object");
// Test that elicitation requests are part of ServerRequest
let _server_request = ServerRequest::CreateElicitationRequest(CreateElicitationRequest {
@ -697,13 +689,13 @@ async fn test_elicitation_json_rpc_direction() {
use rmcp::model::*;
use serde_json::json;
let schema = json!({
"type": "boolean",
"description": "Do you want to continue?"
})
.as_object()
.unwrap()
.clone();
let schema = ElicitationSchema::builder()
.property(
"continue",
PrimitiveSchema::Boolean(BooleanSchema::new().description("Do you want to continue?")),
)
.build()
.unwrap();
// 1. Server creates elicitation request
let server_request = ServerJsonRpcMessage::request(
@ -1143,17 +1135,11 @@ async fn test_create_elicitation_with_timeout_basic() {
use std::time::Duration;
// This test verifies that the method accepts timeout parameter
let schema = json!({
"type": "object",
"properties": {
"name": {"type": "string"},
"email": {"type": "string"}
},
"required": ["name", "email"]
})
.as_object()
.unwrap()
.clone();
let schema = ElicitationSchema::builder()
.required_property("name", PrimitiveSchema::String(StringSchema::new()))
.required_property("email", PrimitiveSchema::String(StringSchema::new()))
.build()
.unwrap();
let _params = CreateElicitationRequestParam {
message: "Enter your details".to_string(),
@ -1473,3 +1459,146 @@ async fn test_elicitation_examples_compile() {
_assert_safe::<UserProfile>();
}
}
// =============================================================================
// BUILD-TIME VALIDATION TESTS
// =============================================================================
/// Test that build() validates required fields exist in properties
#[tokio::test]
async fn test_build_validation_required_field_not_in_properties() {
// Try to mark a field as required that doesn't exist in properties
let result = ElicitationSchema::builder()
.property("email", PrimitiveSchema::String(StringSchema::email()))
.mark_required("nonexistent_field")
.build();
// Should return an error
assert!(result.is_err());
assert_eq!(
result.unwrap_err(),
"Required field does not exist in properties"
);
}
/// Test that build() succeeds when all required fields exist
#[tokio::test]
async fn test_build_validation_required_field_exists() {
let result = ElicitationSchema::builder()
.property("email", PrimitiveSchema::String(StringSchema::email()))
.property("name", PrimitiveSchema::String(StringSchema::new()))
.mark_required("email")
.mark_required("name")
.build();
// Should succeed
assert!(result.is_ok());
let schema = result.unwrap();
assert_eq!(schema.properties.len(), 2);
assert_eq!(
schema.required,
Some(vec!["email".to_string(), "name".to_string()])
);
}
/// Test that build_unchecked() panics on validation errors
#[tokio::test]
#[should_panic(expected = "Invalid elicitation schema")]
async fn test_build_unchecked_panics_on_invalid() {
// build_unchecked validates but panics instead of returning Result
let _schema = ElicitationSchema::builder()
.property("email", PrimitiveSchema::String(StringSchema::email()))
.mark_required("nonexistent_field")
.build_unchecked();
}
/// Test convenience methods handle validation correctly
#[tokio::test]
async fn test_convenience_methods_validation() {
// required_string_property should add both property and mark as required
let result = ElicitationSchema::builder()
.required_string_property("name", |s| s)
.required_email("email")
.build();
assert!(result.is_ok());
let schema = result.unwrap();
assert_eq!(schema.properties.len(), 2);
assert!(
schema
.required
.as_ref()
.unwrap()
.contains(&"name".to_string())
);
assert!(
schema
.required
.as_ref()
.unwrap()
.contains(&"email".to_string())
);
}
/// Test typed property methods work correctly
#[tokio::test]
async fn test_typed_property_methods() {
let result = ElicitationSchema::builder()
.string_property("name", |s| s.length(1, 100))
.number_property("price", |n| n.range(0.0, 1000.0))
.integer_property("quantity", |i| i.range(1, 100))
.bool_property("in_stock", |b| b.with_default(true))
.build();
assert!(result.is_ok());
let schema = result.unwrap();
assert_eq!(schema.properties.len(), 4);
// Verify types are correct
if let Some(PrimitiveSchema::String(_)) = schema.properties.get("name") {
// Expected
} else {
panic!("name should be StringSchema");
}
if let Some(PrimitiveSchema::Number(_)) = schema.properties.get("price") {
// Expected
} else {
panic!("price should be NumberSchema");
}
if let Some(PrimitiveSchema::Integer(_)) = schema.properties.get("quantity") {
// Expected
} else {
panic!("quantity should be IntegerSchema");
}
if let Some(PrimitiveSchema::Boolean(_)) = schema.properties.get("in_stock") {
// Expected
} else {
panic!("in_stock should be BooleanSchema");
}
}
/// Test required typed property methods
#[tokio::test]
async fn test_required_typed_property_methods() {
let result = ElicitationSchema::builder()
.required_string_property("name", |s| s)
.required_number_property("price", |n| n)
.required_integer_property("age", |i| i)
.required_bool_property("active", |b| b)
.build();
assert!(result.is_ok());
let schema = result.unwrap();
assert_eq!(schema.properties.len(), 4);
assert_eq!(schema.required.as_ref().unwrap().len(), 4);
// All should be marked as required
let required = schema.required.as_ref().unwrap();
assert!(required.contains(&"name".to_string()));
assert!(required.contains(&"price".to_string()));
assert!(required.contains(&"age".to_string()));
assert!(required.contains(&"active".to_string()));
}

View file

@ -307,6 +307,49 @@
}
}
},
"BooleanSchema": {
"description": "Schema definition for boolean properties.",
"type": "object",
"properties": {
"default": {
"description": "Default value",
"type": [
"boolean",
"null"
]
},
"description": {
"description": "Human-readable description",
"type": [
"string",
"null"
]
},
"title": {
"description": "Optional title for the schema",
"type": [
"string",
"null"
]
},
"type": {
"description": "Type discriminator",
"allOf": [
{
"$ref": "#/definitions/BooleanTypeConst"
}
]
}
},
"required": [
"type"
]
},
"BooleanTypeConst": {
"type": "string",
"format": "const",
"const": "boolean"
},
"CallToolResult": {
"description": "The result of a tool call operation.\n\nContains the content returned by the tool execution and an optional\nflag indicating whether the operation resulted in an error.",
"type": "object",
@ -423,7 +466,7 @@
]
},
"CreateElicitationRequestParam": {
"description": "Parameters for creating an elicitation request to gather user input.\n\nThis structure contains everything needed to request interactive input from a user:\n- A human-readable message explaining what information is needed\n- A JSON schema defining the expected structure of the response",
"description": "Parameters for creating an elicitation request to gather user input.\n\nThis structure contains everything needed to request interactive input from a user:\n- A human-readable message explaining what information is needed\n- A type-safe schema defining the expected structure of the response\n\n# Example\n\n```rust\nuse rmcp::model::*;\n\nlet params = CreateElicitationRequestParam {\n message: \"Please provide your email\".to_string(),\n requested_schema: ElicitationSchema::builder()\n .required_email(\"email\")\n .build()\n .unwrap(),\n};\n```",
"type": "object",
"properties": {
"message": {
@ -431,9 +474,12 @@
"type": "string"
},
"requestedSchema": {
"description": "JSON Schema defining the expected structure and validation rules for the user's response.\nThis allows clients to validate input and provide appropriate UI controls.\nMust be a valid JSON Schema Draft 2020-12 object.",
"type": "object",
"additionalProperties": true
"description": "Type-safe schema defining the expected structure and validation rules for the user's response.\nThis enforces the MCP 2025-06-18 specification that elicitation schemas must be objects\nwith primitive-typed properties.",
"allOf": [
{
"$ref": "#/definitions/ElicitationSchema"
}
]
}
},
"required": [
@ -564,10 +610,108 @@
"format": "const",
"const": "elicitation/create"
},
"ElicitationSchema": {
"description": "Type-safe elicitation schema for requesting structured user input.\n\nThis enforces the MCP 2025-06-18 specification that elicitation schemas\nmust be objects with primitive-typed properties.\n\n# Example\n\n```rust\nuse rmcp::model::*;\n\nlet schema = ElicitationSchema::builder()\n .required_email(\"email\")\n .required_integer(\"age\", 0, 150)\n .optional_bool(\"newsletter\", false)\n .build();\n```",
"type": "object",
"properties": {
"description": {
"description": "Optional description of what this schema represents",
"type": [
"string",
"null"
]
},
"properties": {
"description": "Property definitions (must be primitive types)",
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/PrimitiveSchema"
}
},
"required": {
"description": "List of required property names",
"type": [
"array",
"null"
],
"items": {
"type": "string"
}
},
"title": {
"description": "Optional title for the schema",
"type": [
"string",
"null"
]
},
"type": {
"description": "Always \"object\" for elicitation schemas",
"allOf": [
{
"$ref": "#/definitions/ObjectTypeConst"
}
]
}
},
"required": [
"type",
"properties"
]
},
"EmptyObject": {
"description": "This is commonly used for representing empty objects in MCP messages.\n\nwithout returning any specific data.",
"type": "object"
},
"EnumSchema": {
"description": "Schema definition for enum properties.\n\nCompliant with MCP 2025-06-18 specification for elicitation schemas.\nEnums must have string type and can optionally include human-readable names.",
"type": "object",
"properties": {
"description": {
"description": "Human-readable description",
"type": [
"string",
"null"
]
},
"enum": {
"description": "Allowed enum values (string values only per MCP spec)",
"type": "array",
"items": {
"type": "string"
}
},
"enumNames": {
"description": "Optional human-readable names for each enum value",
"type": [
"array",
"null"
],
"items": {
"type": "string"
}
},
"title": {
"description": "Optional title for the schema",
"type": [
"string",
"null"
]
},
"type": {
"description": "Type discriminator (always \"string\" for enums)",
"allOf": [
{
"$ref": "#/definitions/StringTypeConst"
}
]
}
},
"required": [
"type",
"enum"
]
},
"ErrorCode": {
"description": "Standard JSON-RPC error codes used throughout the MCP protocol.\n\nThese codes follow the JSON-RPC 2.0 specification and provide\nstandardized error reporting across all MCP implementations.",
"type": "integer",
@ -726,6 +870,58 @@
"serverInfo"
]
},
"IntegerSchema": {
"description": "Schema definition for integer properties.\n\nCompliant with MCP 2025-06-18 specification for elicitation schemas.\nSupports only the fields allowed by the MCP spec.",
"type": "object",
"properties": {
"description": {
"description": "Human-readable description",
"type": [
"string",
"null"
]
},
"maximum": {
"description": "Maximum value (inclusive)",
"type": [
"integer",
"null"
],
"format": "int64"
},
"minimum": {
"description": "Minimum value (inclusive)",
"type": [
"integer",
"null"
],
"format": "int64"
},
"title": {
"description": "Optional title for the schema",
"type": [
"string",
"null"
]
},
"type": {
"description": "Type discriminator",
"allOf": [
{
"$ref": "#/definitions/IntegerTypeConst"
}
]
}
},
"required": [
"type"
]
},
"IntegerTypeConst": {
"type": "string",
"format": "const",
"const": "integer"
},
"JsonRpcError": {
"type": "object",
"properties": {
@ -1120,11 +1316,113 @@
}
]
},
"NumberSchema": {
"description": "Schema definition for number properties (floating-point).\n\nCompliant with MCP 2025-06-18 specification for elicitation schemas.\nSupports only the fields allowed by the MCP spec.",
"type": "object",
"properties": {
"description": {
"description": "Human-readable description",
"type": [
"string",
"null"
]
},
"maximum": {
"description": "Maximum value (inclusive)",
"type": [
"number",
"null"
],
"format": "double"
},
"minimum": {
"description": "Minimum value (inclusive)",
"type": [
"number",
"null"
],
"format": "double"
},
"title": {
"description": "Optional title for the schema",
"type": [
"string",
"null"
]
},
"type": {
"description": "Type discriminator",
"allOf": [
{
"$ref": "#/definitions/NumberTypeConst"
}
]
}
},
"required": [
"type"
]
},
"NumberTypeConst": {
"type": "string",
"format": "const",
"const": "number"
},
"ObjectTypeConst": {
"type": "string",
"format": "const",
"const": "object"
},
"PingRequestMethod": {
"type": "string",
"format": "const",
"const": "ping"
},
"PrimitiveSchema": {
"description": "Primitive schema definition for elicitation properties.\n\nAccording to MCP 2025-06-18 specification, elicitation schemas must have\nproperties of primitive types only (string, number, integer, boolean, enum).",
"anyOf": [
{
"description": "String property (with optional enum constraint)",
"allOf": [
{
"$ref": "#/definitions/StringSchema"
}
]
},
{
"description": "Number property (with optional enum constraint)",
"allOf": [
{
"$ref": "#/definitions/NumberSchema"
}
]
},
{
"description": "Integer property (with optional enum constraint)",
"allOf": [
{
"$ref": "#/definitions/IntegerSchema"
}
]
},
{
"description": "Boolean property",
"allOf": [
{
"$ref": "#/definitions/BooleanSchema"
}
]
},
{
"description": "Enum property (explicit enum schema)",
"allOf": [
{
"$ref": "#/definitions/EnumSchema"
}
]
}
]
},
"ProgressNotificationMethod": {
"type": "string",
"format": "const",
@ -1900,6 +2198,96 @@
}
]
},
"StringFormat": {
"description": "String format types allowed by the MCP specification.",
"oneOf": [
{
"description": "Email address format",
"type": "string",
"const": "email"
},
{
"description": "URI format",
"type": "string",
"const": "uri"
},
{
"description": "Date format (YYYY-MM-DD)",
"type": "string",
"const": "date"
},
{
"description": "Date-time format (ISO 8601)",
"type": "string",
"const": "date-time"
}
]
},
"StringSchema": {
"description": "Schema definition for string properties.\n\nCompliant with MCP 2025-06-18 specification for elicitation schemas.\nSupports only the fields allowed by the MCP spec:\n- format limited to: \"email\", \"uri\", \"date\", \"date-time\"",
"type": "object",
"properties": {
"description": {
"description": "Human-readable description",
"type": [
"string",
"null"
]
},
"format": {
"description": "String format - limited to: \"email\", \"uri\", \"date\", \"date-time\"",
"anyOf": [
{
"$ref": "#/definitions/StringFormat"
},
{
"type": "null"
}
]
},
"maxLength": {
"description": "Maximum string length",
"type": [
"integer",
"null"
],
"format": "uint32",
"minimum": 0
},
"minLength": {
"description": "Minimum string length",
"type": [
"integer",
"null"
],
"format": "uint32",
"minimum": 0
},
"title": {
"description": "Optional title for the schema",
"type": [
"string",
"null"
]
},
"type": {
"description": "Type discriminator",
"allOf": [
{
"$ref": "#/definitions/StringTypeConst"
}
]
}
},
"required": [
"type"
]
},
"StringTypeConst": {
"type": "string",
"format": "const",
"const": "string"
},
"Tool": {
"description": "A tool that can be used by a model.",
"type": "object",

View file

@ -307,6 +307,49 @@
}
}
},
"BooleanSchema": {
"description": "Schema definition for boolean properties.",
"type": "object",
"properties": {
"default": {
"description": "Default value",
"type": [
"boolean",
"null"
]
},
"description": {
"description": "Human-readable description",
"type": [
"string",
"null"
]
},
"title": {
"description": "Optional title for the schema",
"type": [
"string",
"null"
]
},
"type": {
"description": "Type discriminator",
"allOf": [
{
"$ref": "#/definitions/BooleanTypeConst"
}
]
}
},
"required": [
"type"
]
},
"BooleanTypeConst": {
"type": "string",
"format": "const",
"const": "boolean"
},
"CallToolResult": {
"description": "The result of a tool call operation.\n\nContains the content returned by the tool execution and an optional\nflag indicating whether the operation resulted in an error.",
"type": "object",
@ -423,7 +466,7 @@
]
},
"CreateElicitationRequestParam": {
"description": "Parameters for creating an elicitation request to gather user input.\n\nThis structure contains everything needed to request interactive input from a user:\n- A human-readable message explaining what information is needed\n- A JSON schema defining the expected structure of the response",
"description": "Parameters for creating an elicitation request to gather user input.\n\nThis structure contains everything needed to request interactive input from a user:\n- A human-readable message explaining what information is needed\n- A type-safe schema defining the expected structure of the response\n\n# Example\n\n```rust\nuse rmcp::model::*;\n\nlet params = CreateElicitationRequestParam {\n message: \"Please provide your email\".to_string(),\n requested_schema: ElicitationSchema::builder()\n .required_email(\"email\")\n .build()\n .unwrap(),\n};\n```",
"type": "object",
"properties": {
"message": {
@ -431,9 +474,12 @@
"type": "string"
},
"requestedSchema": {
"description": "JSON Schema defining the expected structure and validation rules for the user's response.\nThis allows clients to validate input and provide appropriate UI controls.\nMust be a valid JSON Schema Draft 2020-12 object.",
"type": "object",
"additionalProperties": true
"description": "Type-safe schema defining the expected structure and validation rules for the user's response.\nThis enforces the MCP 2025-06-18 specification that elicitation schemas must be objects\nwith primitive-typed properties.",
"allOf": [
{
"$ref": "#/definitions/ElicitationSchema"
}
]
}
},
"required": [
@ -564,10 +610,108 @@
"format": "const",
"const": "elicitation/create"
},
"ElicitationSchema": {
"description": "Type-safe elicitation schema for requesting structured user input.\n\nThis enforces the MCP 2025-06-18 specification that elicitation schemas\nmust be objects with primitive-typed properties.\n\n# Example\n\n```rust\nuse rmcp::model::*;\n\nlet schema = ElicitationSchema::builder()\n .required_email(\"email\")\n .required_integer(\"age\", 0, 150)\n .optional_bool(\"newsletter\", false)\n .build();\n```",
"type": "object",
"properties": {
"description": {
"description": "Optional description of what this schema represents",
"type": [
"string",
"null"
]
},
"properties": {
"description": "Property definitions (must be primitive types)",
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/PrimitiveSchema"
}
},
"required": {
"description": "List of required property names",
"type": [
"array",
"null"
],
"items": {
"type": "string"
}
},
"title": {
"description": "Optional title for the schema",
"type": [
"string",
"null"
]
},
"type": {
"description": "Always \"object\" for elicitation schemas",
"allOf": [
{
"$ref": "#/definitions/ObjectTypeConst"
}
]
}
},
"required": [
"type",
"properties"
]
},
"EmptyObject": {
"description": "This is commonly used for representing empty objects in MCP messages.\n\nwithout returning any specific data.",
"type": "object"
},
"EnumSchema": {
"description": "Schema definition for enum properties.\n\nCompliant with MCP 2025-06-18 specification for elicitation schemas.\nEnums must have string type and can optionally include human-readable names.",
"type": "object",
"properties": {
"description": {
"description": "Human-readable description",
"type": [
"string",
"null"
]
},
"enum": {
"description": "Allowed enum values (string values only per MCP spec)",
"type": "array",
"items": {
"type": "string"
}
},
"enumNames": {
"description": "Optional human-readable names for each enum value",
"type": [
"array",
"null"
],
"items": {
"type": "string"
}
},
"title": {
"description": "Optional title for the schema",
"type": [
"string",
"null"
]
},
"type": {
"description": "Type discriminator (always \"string\" for enums)",
"allOf": [
{
"$ref": "#/definitions/StringTypeConst"
}
]
}
},
"required": [
"type",
"enum"
]
},
"ErrorCode": {
"description": "Standard JSON-RPC error codes used throughout the MCP protocol.\n\nThese codes follow the JSON-RPC 2.0 specification and provide\nstandardized error reporting across all MCP implementations.",
"type": "integer",
@ -726,6 +870,58 @@
"serverInfo"
]
},
"IntegerSchema": {
"description": "Schema definition for integer properties.\n\nCompliant with MCP 2025-06-18 specification for elicitation schemas.\nSupports only the fields allowed by the MCP spec.",
"type": "object",
"properties": {
"description": {
"description": "Human-readable description",
"type": [
"string",
"null"
]
},
"maximum": {
"description": "Maximum value (inclusive)",
"type": [
"integer",
"null"
],
"format": "int64"
},
"minimum": {
"description": "Minimum value (inclusive)",
"type": [
"integer",
"null"
],
"format": "int64"
},
"title": {
"description": "Optional title for the schema",
"type": [
"string",
"null"
]
},
"type": {
"description": "Type discriminator",
"allOf": [
{
"$ref": "#/definitions/IntegerTypeConst"
}
]
}
},
"required": [
"type"
]
},
"IntegerTypeConst": {
"type": "string",
"format": "const",
"const": "integer"
},
"JsonRpcError": {
"type": "object",
"properties": {
@ -1120,11 +1316,113 @@
}
]
},
"NumberSchema": {
"description": "Schema definition for number properties (floating-point).\n\nCompliant with MCP 2025-06-18 specification for elicitation schemas.\nSupports only the fields allowed by the MCP spec.",
"type": "object",
"properties": {
"description": {
"description": "Human-readable description",
"type": [
"string",
"null"
]
},
"maximum": {
"description": "Maximum value (inclusive)",
"type": [
"number",
"null"
],
"format": "double"
},
"minimum": {
"description": "Minimum value (inclusive)",
"type": [
"number",
"null"
],
"format": "double"
},
"title": {
"description": "Optional title for the schema",
"type": [
"string",
"null"
]
},
"type": {
"description": "Type discriminator",
"allOf": [
{
"$ref": "#/definitions/NumberTypeConst"
}
]
}
},
"required": [
"type"
]
},
"NumberTypeConst": {
"type": "string",
"format": "const",
"const": "number"
},
"ObjectTypeConst": {
"type": "string",
"format": "const",
"const": "object"
},
"PingRequestMethod": {
"type": "string",
"format": "const",
"const": "ping"
},
"PrimitiveSchema": {
"description": "Primitive schema definition for elicitation properties.\n\nAccording to MCP 2025-06-18 specification, elicitation schemas must have\nproperties of primitive types only (string, number, integer, boolean, enum).",
"anyOf": [
{
"description": "String property (with optional enum constraint)",
"allOf": [
{
"$ref": "#/definitions/StringSchema"
}
]
},
{
"description": "Number property (with optional enum constraint)",
"allOf": [
{
"$ref": "#/definitions/NumberSchema"
}
]
},
{
"description": "Integer property (with optional enum constraint)",
"allOf": [
{
"$ref": "#/definitions/IntegerSchema"
}
]
},
{
"description": "Boolean property",
"allOf": [
{
"$ref": "#/definitions/BooleanSchema"
}
]
},
{
"description": "Enum property (explicit enum schema)",
"allOf": [
{
"$ref": "#/definitions/EnumSchema"
}
]
}
]
},
"ProgressNotificationMethod": {
"type": "string",
"format": "const",
@ -1900,6 +2198,96 @@
}
]
},
"StringFormat": {
"description": "String format types allowed by the MCP specification.",
"oneOf": [
{
"description": "Email address format",
"type": "string",
"const": "email"
},
{
"description": "URI format",
"type": "string",
"const": "uri"
},
{
"description": "Date format (YYYY-MM-DD)",
"type": "string",
"const": "date"
},
{
"description": "Date-time format (ISO 8601)",
"type": "string",
"const": "date-time"
}
]
},
"StringSchema": {
"description": "Schema definition for string properties.\n\nCompliant with MCP 2025-06-18 specification for elicitation schemas.\nSupports only the fields allowed by the MCP spec:\n- format limited to: \"email\", \"uri\", \"date\", \"date-time\"",
"type": "object",
"properties": {
"description": {
"description": "Human-readable description",
"type": [
"string",
"null"
]
},
"format": {
"description": "String format - limited to: \"email\", \"uri\", \"date\", \"date-time\"",
"anyOf": [
{
"$ref": "#/definitions/StringFormat"
},
{
"type": "null"
}
]
},
"maxLength": {
"description": "Maximum string length",
"type": [
"integer",
"null"
],
"format": "uint32",
"minimum": 0
},
"minLength": {
"description": "Minimum string length",
"type": [
"integer",
"null"
],
"format": "uint32",
"minimum": 0
},
"title": {
"description": "Optional title for the schema",
"type": [
"string",
"null"
]
},
"type": {
"description": "Type discriminator",
"allOf": [
{
"$ref": "#/definitions/StringTypeConst"
}
]
}
},
"required": [
"type"
]
},
"StringTypeConst": {
"type": "string",
"format": "const",
"const": "string"
},
"Tool": {
"description": "A tool that can be used by a model.",
"type": "object",

View file

@ -29,6 +29,12 @@ pub struct UserInfo {
// Mark as safe for elicitation
elicit_safe!(UserInfo);
/// Simple greeting message
#[derive(Debug, Serialize, Deserialize)]
pub struct GreetingMessage {
pub text: String,
}
/// Simple tool request
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GreetRequest {