-
Notifications
You must be signed in to change notification settings - Fork 364
Description
In the rust-sdk MCP implementation, when using schemars to generate OpenAPI/JSON schemas for structs with optional fields (e.g., Option<String>
, Option<i64>
, etc.), the default output is type: ["T", "null"]
(where T
is the underlying type). While this is technically correct per the OpenAPI spec, it leads to incompatibility with some clients and tools, such as Cursor and Windsurf, which expect a different representation for optional fields.
To work around this, I had to combine #[serde(default)]
with a custom schema using #[schemars(schema_with = "nullable_string_schema", description = "A new short description of the task")]
to ensure compatibility. This workaround adds boilerplate and is not ideal for maintainability.
What doesn't work (default approach):
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct UpdateTaskRequest {
#[schemars(description = "A new short description of the task")]
pub description: Option<String>,
}
Generated schema:
{
"description": "A new short description of the task",
"type": ["string", "null"]
}
Problem:
Some clients (e.g., Cursor, Windsurf) do not accept this representation for optional fields.
What works (workaround):
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct UpdateTaskRequest {
#[schemars(
schema_with = "nullable_string_schema",
description = "A new short description of the task"
)]
#[serde(default)]
pub description: Option<String>,
}
// Custom schema function
pub fn nullable_string_schema(_gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
serde_json::from_value(serde_json::json!({
"type": "string",
"nullable": true,
"description": "A new short description of the task"
})).unwrap()
}
Generated schema:
{
"type": "string",
"nullable": true,
"description": "A new short description of the task"
}
This is accepted by more clients.
Steps to Reproduce
- In the rust-sdk MCP implementation, define a struct with an
Option<T>
field and deriveJsonSchema
. - Generate the schema using schemars.
- Observe that the field is represented as
type: ["T", "null"]
. - Attempt to use the schema with clients like Cursor or Windsurf and note the incompatibility.
Expected Behavior
Schemas for optional fields should be compatible with a wide range of clients, or there should be clear documentation or configuration options for this scenario.
Actual Behavior
The default schema output is not accepted by some clients, requiring custom workarounds.
Request
- Investigate the compatibility issue with
type: ["T", "null"]
in popular clients for the rust-sdk MCP implementation. - Consider providing a built-in, ergonomic way to generate schemas for optional fields that maximizes compatibility.
- Update documentation to clarify best practices for optional fields and client compatibility in the context of rust-sdk MCP.
Thank you!