-
Notifications
You must be signed in to change notification settings - Fork 409
Description
Describe the bug
According to the MCP schema (version 2025-06-18), a tool’s outputSchema must have a root schema of type "object".
The Rust SDK currently serializes any structured output into a JSON schema without ensuring that the root type is "object". This behavior is non-compliant with the specification.
To Reproduce
- Implement a tool handler whose return type is
Json<non-object>orResult<Json<non-object>, E>.
An example of this exists in this very repository calculator.rs#L47.
Here is a minimal file with that tool:
use rmcp::{
Json, ServerHandler, ServiceExt,
handler::server::{tool::ToolRouter, wrapper::Parameters},
model::{ServerCapabilities, ServerInfo},
schemars, tool, tool_handler, tool_router,
transport::stdio,
};
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
pub struct SubRequest {
#[schemars(description = "the left hand side number")]
pub a: i32,
#[schemars(description = "the right hand side number")]
pub b: i32,
}
#[derive(Debug, Clone)]
pub struct Calculator {
tool_router: ToolRouter<Self>,
}
#[tool_router]
impl Calculator {
pub fn new() -> Self {
Self {
tool_router: Self::tool_router(),
}
}
#[tool(description = "Calculate the difference of two numbers")]
fn sub(&self, Parameters(SubRequest { a, b }): Parameters<SubRequest>) -> Json<i32> {
Json(a - b)
}
}
#[tool_handler]
impl ServerHandler for Calculator {
fn get_info(&self) -> ServerInfo {
ServerInfo {
instructions: Some("A simple calculator".into()),
capabilities: ServerCapabilities::builder().enable_tools().build(),
..Default::default()
}
}
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let service: rmcp::service::RunningService<rmcp::RoleServer, Calculator> =
Calculator::new().serve(stdio()).await.inspect_err(|e| {
tracing::error!("serving error: {:?}", e);
})?;
service.waiting().await?;
Ok(())
}- Query the available tools.
{"jsonrpc": "2.0","id": 1,"method": "initialize","params": {"protocolVersion": "2025-06-18","capabilities": {"elicitation": {}},"clientInfo": {"name": "example-client","version": "1.0.0"}}}{"jsonrpc": "2.0", "id": 2, "method": "notifications/initialized"}{"jsonrpc": "2.0", "id": 3, "method": "tools/list"}
- Observe the schema in the response - the root of
outputSchemais of type"integer", when it should be"object"
{"outputSchema":{"$schema":"http://json-schema.org/draft-07/schema#","format":"int32","title":"int32","type":"integer"}}Expected behavior
The generated outputSchema should comply with the specification by having a root type of "object", and all serialized outputs should match this schema.
Logs
Here are the full logs from running my example program.
{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2025-03-26","capabilities":{"tools":{}},"serverInfo":{"name":"rmcp","version":"0.8.5"},"instructions":"A simple calculator"}}
{"jsonrpc":"2.0","id":3,"result":{"tools":[{"name":"sub","description":"Calculate the difference of two numbers","inputSchema":{"$schema":"http://json-schema.org/draft-07/schema#","properties":{"a":{"description":"the left hand side number","format":"int32","type":"integer"},"b":{"description":"the right hand side number","format":"int32","type":"integer"}},"required":["a","b"],"title":"SubRequest","type":"object"},"outputSchema":{"$schema":"http://json-schema.org/draft-07/schema#","format":"int32","title":"int32","type":"integer"}}]}}Additional context
The team behind this SDK has previously acknowledged incomplete compatibility with the 2025-06-18 schema version (#496 (comment)). This report highlights a specific instance which I have noticed.
This issue also appears to underlie #491, as the MCP schema expects outputSchema definitions with root type "object", rather than an empty definition {} or one where the root is of a primitive type.
As a temporary workaround for anyone interested, wrapping non-object outputs resolves the issue:
use serde::{Serialize, Deserialize};
use rmcp::{schemars, schemars::JsonSchema};
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
// Useful in situations where the result is not of type object by itself
pub struct Wrapper<T> {
pub result: T,
}
impl<T> Wrapper<T> {
pub fn new(result: T) -> Self {
Self { result }
}
}