Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4566,6 +4566,20 @@ fn rewrite_tools_list(body: &str, creds: Option<&Credentials>) -> String {

// Strip `token` and `encryption_secret` from every tool's inputSchema
if let Some(schema) = tool.get_mut("inputSchema").and_then(|s| s.as_object_mut()) {
// Normalize no-arg object schemas so OpenAI-style function conversion
// always gets an explicit `properties` object.
if schema.get("type").and_then(|t| t.as_str()) == Some("object") {
schema
.entry("properties".to_string())
.or_insert_with(|| json!({}));
schema
.entry("required".to_string())
.or_insert_with(|| json!([]));
schema
.entry("additionalProperties".to_string())
.or_insert_with(|| json!(false));
}

// Add proxy-only confirmation flags / validation hints
if let Some(ref name) = tool_name {
if name == "forward_email" {
Expand Down Expand Up @@ -7531,6 +7545,71 @@ mod tests {
);
}

#[test]
fn test_rewrite_tools_list_normalizes_no_arg_schema() {
let body = serde_json::to_string(&json!({
"jsonrpc": "2.0",
"id": 1,
"result": {
"tools": [{
"name": "help",
"description": "Help",
"inputSchema": {"type": "object"}
}]
}
}))
.unwrap();

let result = rewrite_tools_list(&body, None);
let parsed: Value = serde_json::from_str(&result).unwrap();
let input = &parsed["result"]["tools"][0]["inputSchema"];
assert_eq!(
input["type"], "object",
"inputSchema type should remain object"
);
assert_eq!(
input["properties"],
json!({}),
"no-arg object schema should gain empty properties"
);
assert_eq!(
input["required"],
json!([]),
"no-arg object schema should gain empty required array"
);
assert_eq!(
input["additionalProperties"], false,
"no-arg object schema should default additionalProperties to false"
);
}

#[test]
fn test_rewrite_tools_list_keeps_existing_additional_properties() {
let body = serde_json::to_string(&json!({
"jsonrpc": "2.0",
"id": 1,
"result": {
"tools": [{
"name": "help",
"description": "Help",
"inputSchema": {
"type": "object",
"properties": {},
"additionalProperties": true
}
}]
}
}))
.unwrap();

let result = rewrite_tools_list(&body, None);
let parsed: Value = serde_json::from_str(&result).unwrap();
assert_eq!(
parsed["result"]["tools"][0]["inputSchema"]["additionalProperties"], true,
"existing additionalProperties value should be preserved"
);
Comment on lines +7607 to +7610
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Per the repository style guide (line 74), assertions should include descriptive messages. Please add a message to this assertion to clarify what is being tested.

assert_eq!(
    parsed["result"]["tools"][0]["inputSchema"]["additionalProperties"],
    true,
    "An existing 'additionalProperties' value should be preserved and not overwritten by the default"
);
References
  1. All assertions must include descriptive messages to clarify their purpose, especially upon failure. (link)

}

// --- MCP annotations tests ---

#[test]
Expand Down
Loading