Skip to content
Closed
Show file tree
Hide file tree
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
29 changes: 28 additions & 1 deletion src/mcp/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,9 +161,36 @@ pub struct SemanticSearchWithContextRequest {
pub lang: Option<String>,
}

#[derive(Debug, Deserialize, Serialize, schemars::JsonSchema)]
#[derive(Debug, Deserialize, Serialize)]
pub struct GetIndexInfoRequest {}

impl schemars::JsonSchema for GetIndexInfoRequest {
fn schema_name() -> std::borrow::Cow<'static, str> {
std::borrow::Cow::Borrowed("GetIndexInfoRequest")
}

fn schema_id() -> std::borrow::Cow<'static, str> {
std::borrow::Cow::Borrowed(concat!(module_path!(), "::GetIndexInfoRequest"))
}

fn json_schema(_generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
// MCP spec recommends `{"type":"object","additionalProperties":false}` for
// no-parameter tools. We also include an empty `properties` map because
// OpenAI's strict function-calling validation rejects object schemas that
// lack `properties` entirely.
schemars::Schema::from(
serde_json::json!({
"type": "object",
"properties": {},
"additionalProperties": false
})
.as_object()
.unwrap()
.clone(),
)
}
}

#[derive(Debug, Deserialize, Serialize, schemars::JsonSchema)]
pub struct SearchDocumentsRequest {
/// Natural language search query
Expand Down
32 changes: 31 additions & 1 deletion tests/integration/test_mcp_schema.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
//! Test to verify MCP schema generation for usize fields

use codanna::mcp::{AnalyzeImpactRequest, SearchSymbolsRequest, SemanticSearchRequest};
use codanna::mcp::{
AnalyzeImpactRequest, GetIndexInfoRequest, SearchSymbolsRequest, SemanticSearchRequest,
};

#[test]
fn test_mcp_schema_uint_format() {
Expand Down Expand Up @@ -60,3 +62,31 @@ fn test_mcp_schema_uint_format() {
println!("✅ No 'uint' format found in schemas.");
}
}

/// Regression test: `get_index_info` is a no-parameter tool whose inputSchema must satisfy
/// both MCP spec (recommends `additionalProperties: false`) and OpenAI's strict
/// function-calling validation (requires `properties` field).
#[test]
fn test_get_index_info_schema_has_properties() {
let schema = rmcp::schemars::schema_for!(GetIndexInfoRequest);
let json = serde_json::to_string_pretty(&schema).unwrap();
println!("GetIndexInfoRequest schema:\n{json}");

let root: serde_json::Value = serde_json::from_str(&json).unwrap();

assert_eq!(
root.get("type").and_then(|v| v.as_str()),
Some("object"),
"schema must have type=object\nGot:\n{json}"
);
assert!(
root.get("properties").is_some(),
"schema must contain 'properties' for OpenAI compatibility\nGot:\n{json}"
);
assert_eq!(
root.get("additionalProperties").and_then(|v| v.as_bool()),
Some(false),
"schema should set additionalProperties=false per MCP spec\nGot:\n{json}"
);
println!("✅ GetIndexInfoRequest schema is MCP-spec compliant and OpenAI-compatible.");
}
Loading