Skip to content

Commit 19c20f8

Browse files
committed
chore: MCP protocol compaction and handler updates
1 parent 0947efb commit 19c20f8

3 files changed

Lines changed: 223 additions & 9 deletions

File tree

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
//! Compact facade layer — groups 14 tools into 3 compact facades.
2+
//!
3+
//! Env var: `AEVOLVE_MCP_TOOL_SURFACE` (fallback `MCP_TOOL_SURFACE`).
4+
//! Set to "compact" to enable facade mode.
5+
6+
use serde_json::Value;
7+
8+
use crate::types::ToolDefinition;
9+
10+
/// A facade grouping that maps one compact tool to multiple underlying operations.
11+
struct FacadeGroup {
12+
name: &'static str,
13+
description: &'static str,
14+
operations: &'static [&'static str],
15+
}
16+
17+
const FACADES: &[FacadeGroup] = &[
18+
FacadeGroup {
19+
name: "evolve_patterns",
20+
description: "Store, retrieve, delete, search, and list evolution patterns",
21+
operations: &[
22+
"pattern_store",
23+
"pattern_get",
24+
"pattern_delete",
25+
"pattern_search",
26+
"pattern_list",
27+
],
28+
},
29+
FacadeGroup {
30+
name: "evolve_matching",
31+
description: "Match patterns by signature or context, crystallize, compose, and retrieve bodies",
32+
operations: &[
33+
"match_signature",
34+
"match_context",
35+
"crystallize",
36+
"compose",
37+
"get_body",
38+
],
39+
},
40+
FacadeGroup {
41+
name: "evolve_analytics",
42+
description: "Check coverage and confidence, update usage stats, and optimize the pattern library",
43+
operations: &[
44+
"coverage",
45+
"confidence",
46+
"update_usage",
47+
"optimize",
48+
],
49+
},
50+
];
51+
52+
/// Check whether compact tool mode is enabled via environment variable.
53+
pub fn is_compact_mode() -> bool {
54+
let val = std::env::var("AEVOLVE_MCP_TOOL_SURFACE")
55+
.or_else(|_| std::env::var("MCP_TOOL_SURFACE"))
56+
.unwrap_or_default();
57+
val.eq_ignore_ascii_case("compact")
58+
}
59+
60+
/// Build the compact facade tool definitions for tools/list.
61+
pub fn compact_tool_definitions() -> Vec<ToolDefinition> {
62+
FACADES
63+
.iter()
64+
.map(|f| {
65+
let ops_enum: Vec<Value> = f
66+
.operations
67+
.iter()
68+
.map(|o| Value::String(o.to_string()))
69+
.collect();
70+
71+
ToolDefinition {
72+
name: f.name.to_string(),
73+
description: Some(f.description.to_string()),
74+
input_schema: serde_json::json!({
75+
"type": "object",
76+
"properties": {
77+
"operation": {
78+
"type": "string",
79+
"enum": ops_enum,
80+
"description": "Operation to perform"
81+
},
82+
"params": {
83+
"type": "object",
84+
"description": "Parameters for the operation"
85+
}
86+
},
87+
"required": ["operation"]
88+
}),
89+
}
90+
})
91+
.collect()
92+
}
93+
94+
/// Normalize a compact facade call into the underlying tool name and arguments.
95+
///
96+
/// Mapping: facade "evolve_patterns" + operation "pattern_store"
97+
/// -> tool "evolve_pattern_store"
98+
pub fn normalize_compact_call(
99+
facade_name: &str,
100+
arguments: &Option<Value>,
101+
) -> Option<(String, Option<Value>)> {
102+
let facade = FACADES.iter().find(|f| f.name == facade_name)?;
103+
104+
let args = arguments.as_ref().unwrap_or(&Value::Null);
105+
let operation = args.get("operation").and_then(|v| v.as_str())?;
106+
107+
if !facade.operations.contains(&operation) {
108+
return None;
109+
}
110+
111+
let real_name = format!("evolve_{}", operation);
112+
let params = args.get("params").cloned();
113+
114+
Some((real_name, params))
115+
}
116+
117+
/// Check if a tool name is a compact facade name.
118+
pub fn is_compact_facade(name: &str) -> bool {
119+
FACADES.iter().any(|f| f.name == name)
120+
}
121+
122+
#[cfg(test)]
123+
mod tests {
124+
use super::*;
125+
126+
#[test]
127+
fn compact_definitions_count() {
128+
let defs = compact_tool_definitions();
129+
assert_eq!(defs.len(), 3);
130+
assert_eq!(defs[0].name, "evolve_patterns");
131+
assert_eq!(defs[1].name, "evolve_matching");
132+
assert_eq!(defs[2].name, "evolve_analytics");
133+
}
134+
135+
#[test]
136+
fn normalize_patterns_facade() {
137+
let args = Some(serde_json::json!({
138+
"operation": "pattern_store",
139+
"params": { "name": "test", "domain": "web" }
140+
}));
141+
let (name, params) = normalize_compact_call("evolve_patterns", &args).unwrap();
142+
assert_eq!(name, "evolve_pattern_store");
143+
assert_eq!(
144+
params.unwrap().get("name").unwrap().as_str().unwrap(),
145+
"test"
146+
);
147+
}
148+
149+
#[test]
150+
fn normalize_matching_facade() {
151+
let args = Some(serde_json::json!({
152+
"operation": "crystallize",
153+
"params": { "pattern_id": "p1" }
154+
}));
155+
let (name, _) = normalize_compact_call("evolve_matching", &args).unwrap();
156+
assert_eq!(name, "evolve_crystallize");
157+
}
158+
159+
#[test]
160+
fn normalize_analytics_facade() {
161+
let args = Some(serde_json::json!({
162+
"operation": "coverage",
163+
"params": {}
164+
}));
165+
let (name, _) = normalize_compact_call("evolve_analytics", &args).unwrap();
166+
assert_eq!(name, "evolve_coverage");
167+
}
168+
169+
#[test]
170+
fn normalize_unknown_facade_returns_none() {
171+
let args = Some(serde_json::json!({ "operation": "pattern_store" }));
172+
assert!(normalize_compact_call("evolve_unknown", &args).is_none());
173+
}
174+
175+
#[test]
176+
fn normalize_invalid_operation_returns_none() {
177+
let args = Some(serde_json::json!({ "operation": "nonexistent" }));
178+
assert!(normalize_compact_call("evolve_patterns", &args).is_none());
179+
}
180+
181+
#[test]
182+
fn is_compact_facade_checks() {
183+
assert!(is_compact_facade("evolve_patterns"));
184+
assert!(is_compact_facade("evolve_matching"));
185+
assert!(is_compact_facade("evolve_analytics"));
186+
assert!(!is_compact_facade("evolve_pattern_store"));
187+
}
188+
189+
#[test]
190+
fn compact_mode_off_by_default() {
191+
// Without env var set, should be false
192+
// (This test is valid as long as the env var isn't set in CI)
193+
assert!(!is_compact_mode());
194+
}
195+
}

crates/agentic-evolve-mcp/src/protocol/handler.rs

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use crate::session::SessionManager;
1010
use crate::tools::ToolRegistry;
1111
use crate::types::*;
1212

13+
use super::compact;
1314
use super::negotiation::NegotiatedCapabilities;
1415
use super::validator::validate_request;
1516

@@ -111,7 +112,7 @@ impl ProtocolHandler {
111112

112113
async fn handle_notification(&self, notification: JsonRpcNotification) {
113114
match notification.method.as_str() {
114-
"initialized" => {
115+
"initialized" | "notifications/initialized" => {
115116
let mut caps = self.capabilities.lock().await;
116117
if let Err(e) = caps.mark_initialized() {
117118
tracing::error!("Failed to mark initialized: {e}");
@@ -146,8 +147,13 @@ impl ProtocolHandler {
146147
}
147148

148149
async fn handle_tools_list(&self) -> McpResult<Value> {
150+
let tools = if compact::is_compact_mode() {
151+
compact::compact_tool_definitions()
152+
} else {
153+
ToolRegistry::list_tools()
154+
};
149155
let result = ToolListResult {
150-
tools: ToolRegistry::list_tools(),
156+
tools,
151157
next_cursor: None,
152158
};
153159
serde_json::to_value(result).map_err(|e| McpError::InternalError(e.to_string()))
@@ -160,15 +166,27 @@ impl ProtocolHandler {
160166
.map_err(|e| McpError::InvalidParams(e.to_string()))?
161167
.ok_or_else(|| McpError::InvalidParams("Tool call params required".to_string()))?;
162168

169+
// Normalize compact facade calls to underlying tool names.
170+
let (tool_name, arguments) = if compact::is_compact_facade(&call_params.name) {
171+
match compact::normalize_compact_call(&call_params.name, &call_params.arguments) {
172+
Some((real_name, real_args)) => (real_name, real_args),
173+
None => {
174+
return Err(McpError::InvalidParams(
175+
"Invalid operation for compact facade".to_string(),
176+
));
177+
}
178+
}
179+
} else {
180+
(call_params.name, call_params.arguments)
181+
};
182+
163183
// Classify errors: protocol errors (ToolNotFound etc.) become JSON-RPC errors;
164184
// tool execution errors become isError: true.
165-
let result =
166-
match ToolRegistry::call(&call_params.name, call_params.arguments, &self.session).await
167-
{
168-
Ok(r) => r,
169-
Err(e) if e.is_protocol_error() => return Err(e),
170-
Err(e) => ToolCallResult::error(e.to_string()),
171-
};
185+
let result = match ToolRegistry::call(&tool_name, arguments, &self.session).await {
186+
Ok(r) => r,
187+
Err(e) if e.is_protocol_error() => return Err(e),
188+
Err(e) => ToolCallResult::error(e.to_string()),
189+
};
172190

173191
serde_json::to_value(result).map_err(|e| McpError::InternalError(e.to_string()))
174192
}

crates/agentic-evolve-mcp/src/protocol/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//! MCP protocol layer — message handling, validation, and capability negotiation.
22
3+
pub mod compact;
34
pub mod handler;
45
pub mod negotiation;
56
pub mod validator;

0 commit comments

Comments
 (0)