fix: strip and validate tool outputSchema and inputSchema (#860)

* fix: remove unnecessary fields from tools' outputSchema

* fix: validate input schema root type per MCP spec
This commit is contained in:
Dale Seo 2026-06-02 12:01:33 -04:00 • committed by GitHub
commit 254f04a764
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 158 additions and 65 deletions

View file

@ -1,6 +1,7 @@
#![cfg(all(feature = "server", feature = "macros", not(feature = "local")))]
use rmcp::{
Json,
handler::server::wrapper::Parameters,
model::{ListToolsResult, NumberOrString, ServerJsonRpcMessage, ServerResult},
};
@ -14,10 +15,17 @@ struct AddRequest {
b: f64,
}
/// Result of adding two numbers.
#[derive(Debug, serde::Serialize, schemars::JsonSchema)]
struct AddResult {
/// The sum of the two numbers.
sum: f64,
}
/// Add two numbers.
#[rmcp::tool]
fn add(Parameters(AddRequest { a, b }): Parameters<AddRequest>) -> String {
(a + b).to_string()
fn add(Parameters(AddRequest { a, b }): Parameters<AddRequest>) -> Json<AddResult> {
Json(AddResult { sum: a + b })
}
#[test]
@ -27,7 +35,7 @@ fn list_tools_result_matches_expected_json() {
let expected: serde_json::Value =
serde_json::from_slice(&expected_json).expect("invalid expected JSON fixture");
assert_eq!(add(Parameters(AddRequest { a: 1.0, b: 2.0 })), "3");
assert_eq!(add(Parameters(AddRequest { a: 1.0, b: 2.0 })).0.sum, 3.0);
let result = ListToolsResult::with_all_items(vec![add_tool_attr()]);
let response = ServerJsonRpcMessage::response(

View file

@ -23,6 +23,20 @@
"a",
"b"
]
},
"outputSchema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"sum": {
"description": "The sum of the two numbers.",
"format": "double",
"type": "number"
}
},
"required": [
"sum"
]
}
}
]

View file

@ -28,6 +28,16 @@ pub struct UserInfo {
pub age: u32,
}
#[derive(Serialize, Deserialize, JsonSchema)]
pub struct GreetingRequest {
pub name: String,
}
#[derive(Serialize, Deserialize, JsonSchema)]
pub struct GetUserRequest {
pub user_id: String,
}
#[tool_handler(router = self.tool_router)]
impl ServerHandler for TestServer {}
@ -64,14 +74,17 @@ impl TestServer {
/// Tool that returns regular string output
#[tool(name = "get-greeting", description = "Get a greeting")]
pub async fn get_greeting(&self, name: Parameters<String>) -> String {
format!("Hello, {}!", name.0)
pub async fn get_greeting(&self, params: Parameters<GreetingRequest>) -> String {
format!("Hello, {}!", params.0.name)
}
/// Tool that returns structured user info
#[tool(name = "get-user", description = "Get user info")]
pub async fn get_user(&self, user_id: Parameters<String>) -> Result<Json<UserInfo>, String> {
if user_id.0 == "123" {
pub async fn get_user(
&self,
params: Parameters<GetUserRequest>,
) -> Result<Json<UserInfo>, String> {
if params.0.user_id == "123" {
Ok(Json(UserInfo {
name: "Alice".to_string(),
age: 30,