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
2 changed files with 2253 additions and 137 deletions

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