Skip to content

Optional Fields in rust-sdk MCP Schemas (type: ["T", "null"]) Cause Client Incompatibility #135

@aitoroses

Description

@aitoroses

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

  1. In the rust-sdk MCP implementation, define a struct with an Option<T> field and derive JsonSchema.
  2. Generate the schema using schemars.
  3. Observe that the field is represented as type: ["T", "null"].
  4. 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!

Metadata

Metadata

Assignees

No one assigned

    Labels

    T-bugBug fixes and error corrections

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions