feat(mcp): switch to YAML serialization for tool outputs

- Replaced serde_json with serde_yaml in all handler endpoints.

- This drastically reduces token consumption for large list/graph responses by removing JSON braces and quotes.
This commit is contained in:
Riz Ashraf committed 2026-10-08 23:46:32 +01:00
1 parent 410b0c42ca
commit cb0c55775a
12 files changed
+65 -37

No files matched your search

Generated
+20
View File
@@ -2654,6 +2654,7 @@ dependencies = [
"schemars", "schemars",
"serde", "serde",
"serde_json", "serde_json",
"serde_yaml",
"tantivy", "tantivy",
"tempfile", "tempfile",
"thiserror 2.0.20", "thiserror 2.0.20",
@@ -4342,6 +4343,19 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "serde_yaml"
version = "0.9.34+deprecated"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
dependencies = [
"indexmap",
"itoa",
"ryu",
"serde",
"unsafe-libyaml",
]
[[package]] [[package]]
name = "sha1" name = "sha1"
version = "0.10.7" version = "0.10.7"
@@ -5438,6 +5452,12 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
[[package]]
name = "unsafe-libyaml"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
[[package]] [[package]]
name = "untrusted" name = "untrusted"
version = "0.9.0" version = "0.9.0"
+1
View File
@@ -51,6 +51,7 @@ tokio-stream = { version = "0.1", features = ["sync"] }
chrono = { version = "0.4.45", features = ["serde"] } chrono = { version = "0.4.45", features = ["serde"] }
ocrs = "0.13.1" ocrs = "0.13.1"
rten = "0.26.0" rten = "0.26.0"
serde_yaml = "0.9.34"
[build-dependencies] [build-dependencies]
chrono = "0.4.45" chrono = "0.4.45"
+7
View File
@@ -40,6 +40,9 @@ pub enum AppError {
#[error("I/O Error: {0}")] #[error("I/O Error: {0}")]
IoError(#[from] std::io::Error), IoError(#[from] std::io::Error),
#[error("YAML Serialization Error: {0}")]
YamlError(#[from] serde_yaml::Error),
} }
impl From<AppError> for String { impl From<AppError> for String {
@@ -81,6 +84,10 @@ impl IntoResponse for AppError {
StatusCode::INTERNAL_SERVER_ERROR, StatusCode::INTERNAL_SERVER_ERROR,
format!("I/O Error: {}", e), format!("I/O Error: {}", e),
), ),
AppError::YamlError(e) => (
StatusCode::INTERNAL_SERVER_ERROR,
format!("YAML Serialization Error: {}", e),
),
}; };
let body = Json(json!({ let body = Json(json!({
+3 -3
View File
@@ -504,7 +504,7 @@ impl McpTool for FindSymbolReferencesHandler {
matches.extend(disk_matches); matches.extend(disk_matches);
} }
Ok(serde_json::to_string_pretty(&matches)?) Ok(serde_yaml::to_string(&matches)?)
} }
} }
@@ -560,7 +560,7 @@ impl McpTool for GetCallersHandler {
callers.extend(disk_callers); callers.extend(disk_callers);
} }
Ok(serde_json::to_string_pretty(&callers)?) Ok(serde_yaml::to_string(&callers)?)
} }
} }
@@ -649,7 +649,7 @@ impl McpTool for AnalyzeImpactHandler {
} }
}); });
Ok(serde_json::to_string_pretty(&result)?) Ok(serde_yaml::to_string(&result)?)
} }
} }
+2 -2
View File
@@ -70,7 +70,7 @@ impl McpTool for EnvironmentHandler {
.env_fingerprints .env_fingerprints
.read_with(|fps| fps.get(&ns).cloned()); .read_with(|fps| fps.get(&ns).cloned());
if let Some(fp) = data { if let Some(fp) = data {
let data = Ok::<String, crate::error::AppError>(serde_json::to_string(&fp)?)?; let data = Ok::<String, crate::error::AppError>(serde_yaml::to_string(&fp)?)?;
Ok(data) Ok(data)
} else { } else {
Ok("{}".to_string()) Ok("{}".to_string())
@@ -118,7 +118,7 @@ impl McpTool for EnvironmentHandler {
EnvAction::GetDetails => { EnvAction::GetDetails => {
let data = state.env.environments.read_with(|envs| { let data = state.env.environments.read_with(|envs| {
let filtered: Vec<_> = envs.iter().filter(|e| e.namespace == ns).collect(); let filtered: Vec<_> = envs.iter().filter(|e| e.namespace == ns).collect();
Ok::<String, crate::error::AppError>(serde_json::to_string(&filtered)?) Ok::<String, crate::error::AppError>(serde_yaml::to_string(&filtered)?)
})?; })?;
Ok(data) Ok(data)
} }
+2 -2
View File
@@ -87,7 +87,7 @@ impl McpTool for GetActiveWorktreeContextHandler {
.await .await
.map_err(|e| crate::error::AppError::Internal(format!("Task panic: {}", e)))??; .map_err(|e| crate::error::AppError::Internal(format!("Task panic: {}", e)))??;
Ok::<String, crate::error::AppError>(serde_json::to_string_pretty(&result)?) Ok::<String, crate::error::AppError>(serde_yaml::to_string(&result)?)
} }
} }
@@ -168,7 +168,7 @@ impl McpTool for QueryGitDiffsHandler {
matches.extend(git_matches); matches.extend(git_matches);
} }
Ok(serde_json::to_string_pretty(&matches)?) Ok(serde_yaml::to_string(&matches)?)
} }
} }
+8 -8
View File
@@ -542,7 +542,7 @@ impl McpTool for ReadGraphHandler {
}) })
}; };
let mut result_json = serde_json::to_string(&serialized)?; let mut result_json = serde_yaml::to_string(&serialized)?;
if let Some(max_tok) = max_tokens { if let Some(max_tok) = max_tokens {
let max_chars = max_tok * 4; let max_chars = max_tok * 4;
@@ -579,7 +579,7 @@ impl McpTool for ReadGraphHandler {
obj.insert("has_more".to_string(), serde_json::Value::Bool(true)); obj.insert("has_more".to_string(), serde_json::Value::Bool(true));
} }
if let Ok(trunc_str) = serde_json::to_string(&truncated_val) if let Ok(trunc_str) = serde_yaml::to_string(&truncated_val)
&& (trunc_str.len() <= max_chars || keep_count == 1) { && (trunc_str.len() <= max_chars || keep_count == 1) {
result_json = trunc_str; result_json = trunc_str;
break; break;
@@ -638,7 +638,7 @@ impl McpTool for SearchNodesHandler {
} }
} }
} }
Ok::<String, crate::error::AppError>(serde_json::to_string(&matched_entities)?) Ok::<String, crate::error::AppError>(serde_yaml::to_string(&matched_entities)?)
})?; })?;
Ok(data) Ok(data)
} }
@@ -679,7 +679,7 @@ impl McpTool for OpenNodesHandler {
result.entities.insert(name, e); result.entities.insert(name, e);
} }
} }
Ok::<String, crate::error::AppError>(serde_json::to_string(&result)?) Ok::<String, crate::error::AppError>(serde_yaml::to_string(&result)?)
})?; })?;
Ok(data) Ok(data)
} }
@@ -954,7 +954,7 @@ impl McpTool for FindOrphansHandler {
.cloned() .cloned()
.collect::<Vec<String>>() .collect::<Vec<String>>()
}); });
let data = Ok::<String, crate::error::AppError>(serde_json::to_string(&orphans)?)?; let data = Ok::<String, crate::error::AppError>(serde_yaml::to_string(&orphans)?)?;
Ok(data) Ok(data)
} }
} }
@@ -1047,7 +1047,7 @@ impl McpTool for GetSubgraphHandler {
"relations": matched_relations, "relations": matched_relations,
}); });
Ok(serde_json::to_string_pretty(&result)?) Ok(serde_yaml::to_string(&result)?)
} }
} }
@@ -1146,7 +1146,7 @@ impl McpTool for SweepGraphHealthHandler {
"health_score": if orphans.is_empty() && duplicates.is_empty() && stale_entities.is_empty() { "100%" } else { "Needs Maintenance" } "health_score": if orphans.is_empty() && duplicates.is_empty() && stale_entities.is_empty() { "100%" } else { "Needs Maintenance" }
}); });
Ok(serde_json::to_string_pretty(&report)?) Ok(serde_yaml::to_string(&report)?)
} }
} }
@@ -1203,7 +1203,7 @@ impl McpTool for ResolveStaleSymbolsHandler {
"status": "success" "status": "success"
}); });
Ok(serde_json::to_string_pretty(&report)?) Ok(serde_yaml::to_string(&report)?)
} }
} }
+12 -12
View File
@@ -142,7 +142,7 @@ impl McpTool for SearchErrorFixesHandler {
}) })
.collect(); .collect();
Ok::<String, crate::error::AppError>(serde_json::to_string_pretty(&suggestions)?) Ok::<String, crate::error::AppError>(serde_yaml::to_string(&suggestions)?)
})?; })?;
return Ok(data); return Ok(data);
} }
@@ -169,7 +169,7 @@ impl McpTool for SearchErrorFixesHandler {
} }
}) })
.collect(); .collect();
Ok::<String, crate::error::AppError>(serde_json::to_string_pretty(&filtered)?) Ok::<String, crate::error::AppError>(serde_yaml::to_string(&filtered)?)
})?; })?;
Ok(data) Ok(data)
} }
@@ -358,7 +358,7 @@ impl McpTool for QueryRecentChangesHandler {
.take(limit) .take(limit)
.cloned() .cloned()
.collect(); .collect();
Ok::<String, crate::error::AppError>(serde_json::to_string(&filtered)?) Ok::<String, crate::error::AppError>(serde_yaml::to_string(&filtered)?)
})?; })?;
Ok(data) Ok(data)
} }
@@ -575,7 +575,7 @@ impl McpTool for DecisionsHandler {
.collect(); .collect();
if include_body { if include_body {
Ok::<String, crate::error::AppError>(serde_json::to_string(&filtered)?) Ok::<String, crate::error::AppError>(serde_yaml::to_string(&filtered)?)
} else { } else {
let compact: Vec<_> = filtered let compact: Vec<_> = filtered
.iter() .iter()
@@ -592,7 +592,7 @@ impl McpTool for DecisionsHandler {
}) })
}) })
.collect(); .collect();
Ok::<String, crate::error::AppError>(serde_json::to_string(&compact)?) Ok::<String, crate::error::AppError>(serde_yaml::to_string(&compact)?)
} }
})?; })?;
Ok(data) Ok(data)
@@ -738,7 +738,7 @@ impl McpTool for TechDebtHandler {
}), }),
}) })
.collect(); .collect();
let mut json_str = serde_json::to_string(&filtered)?; let mut json_str = serde_yaml::to_string(&filtered)?;
if let Some(max_t) = req.max_tokens { if let Some(max_t) = req.max_tokens {
let char_limit = max_t * 4; let char_limit = max_t * 4;
if json_str.len() > char_limit { if json_str.len() > char_limit {
@@ -1305,7 +1305,7 @@ impl McpTool for ManageCheckpointHandler {
for s in snaps { for s in snaps {
list.push(serde_json::json!({"type": "snapshot", "id": s.id, "description": s.description, "namespace": s.namespace})); list.push(serde_json::json!({"type": "snapshot", "id": s.id, "description": s.description, "namespace": s.namespace}));
} }
Ok(serde_json::to_string_pretty(&list)?) Ok(serde_yaml::to_string(&list)?)
} }
CheckpointAction::Delete => { CheckpointAction::Delete => {
let name = req.name_or_id.ok_or_else(|| { let name = req.name_or_id.ok_or_else(|| {
@@ -1427,7 +1427,7 @@ impl McpTool for QueryLineageHandler {
"timeline": timeline "timeline": timeline
}); });
Ok(serde_json::to_string_pretty(&res)?) Ok(serde_yaml::to_string(&res)?)
} }
} }
@@ -1486,7 +1486,7 @@ impl McpTool for GetNextActionableTasksHandler {
"tasks": actionable "tasks": actionable
}); });
Ok(serde_json::to_string_pretty(&res)?) Ok(serde_yaml::to_string(&res)?)
} }
} }
@@ -1555,7 +1555,7 @@ impl McpTool for HypothesesHandler {
}) })
.collect(); .collect();
Ok(serde_json::to_string_pretty(&filtered)?) Ok(serde_yaml::to_string(&filtered)?)
} }
} }
} }
@@ -1634,7 +1634,7 @@ impl McpTool for GetPreflightContextHandler {
"recent_activities": recent_activities "recent_activities": recent_activities
}); });
Ok(serde_json::to_string_pretty(&preflight)?) Ok(serde_yaml::to_string(&preflight)?)
} }
} }
@@ -1742,7 +1742,7 @@ impl McpTool for AgentSignalsHandler {
.collect::<Vec<_>>() .collect::<Vec<_>>()
}); });
Ok(serde_json::to_string_pretty(&filtered)?) Ok(serde_yaml::to_string(&filtered)?)
} }
} }
} }
+1 -1
View File
@@ -60,7 +60,7 @@ impl McpTool for HandoffMemosHandler {
} }
}) })
.collect(); .collect();
Ok::<String, crate::error::AppError>(serde_json::to_string(&filtered)?) Ok::<String, crate::error::AppError>(serde_yaml::to_string(&filtered)?)
})?; })?;
Ok(data) Ok(data)
} }
+3 -3
View File
@@ -297,12 +297,12 @@ impl McpTool for TasksHandler {
_ => serde_json::json!({ "id": t.id, "title": t.title, "status": t.status, "description": t.description, "git_branch": t.git_branch }), _ => serde_json::json!({ "id": t.id, "title": t.title, "status": t.status, "description": t.description, "git_branch": t.git_branch }),
}) })
.collect(); .collect();
let mut json_str = serde_json::to_string(&filtered)?; let mut json_str = serde_yaml::to_string(&filtered)?;
if let Some(max_t) = req.max_tokens { if let Some(max_t) = req.max_tokens {
let char_limit = max_t * 4; let char_limit = max_t * 4;
while json_str.len() > char_limit && !filtered.is_empty() { while json_str.len() > char_limit && !filtered.is_empty() {
filtered.pop(); filtered.pop();
json_str = serde_json::to_string(&filtered)?; json_str = serde_yaml::to_string(&filtered)?;
} }
} }
Ok::<String, crate::error::AppError>(json_str) Ok::<String, crate::error::AppError>(json_str)
@@ -514,7 +514,7 @@ impl McpTool for MilestonesHandler {
} }
}) })
.collect(); .collect();
Ok::<String, crate::error::AppError>(serde_json::to_string(&filtered)?) Ok::<String, crate::error::AppError>(serde_yaml::to_string(&filtered)?)
})?; })?;
Ok(data) Ok(data)
} }
+4 -4
View File
@@ -758,7 +758,7 @@ impl McpTool for ClipboardHandler {
); );
if let Some(img) = item { if let Some(img) = item {
Ok(serde_json::to_string_pretty(&img)?) Ok(serde_yaml::to_string(&img)?)
} else { } else {
Ok(json!({ Ok(json!({
"status": "no_image_in_clipboard", "status": "no_image_in_clipboard",
@@ -786,7 +786,7 @@ impl McpTool for ClipboardHandler {
state.record_activity("clipboard", "Retrieved text from clipboard cache", None); state.record_activity("clipboard", "Retrieved text from clipboard cache", None);
if let Some(txt) = item { if let Some(txt) = item {
Ok(serde_json::to_string_pretty(&txt)?) Ok(serde_yaml::to_string(&txt)?)
} else { } else {
Ok(json!({ Ok(json!({
"text": "", "text": "",
@@ -818,7 +818,7 @@ impl McpTool for ClipboardHandler {
}; };
state.record_activity("clipboard", "Viewed clipboard history ring buffer", None); state.record_activity("clipboard", "Viewed clipboard history ring buffer", None);
Ok(serde_json::to_string_pretty(&history)?) Ok(serde_yaml::to_string(&history)?)
} }
ClipboardAction::Clear => { ClipboardAction::Clear => {
tokio::task::spawn_blocking(|| { tokio::task::spawn_blocking(|| {
@@ -905,7 +905,7 @@ impl McpTool for ClipboardHandler {
.map_err(|e| crate::error::AppError::Internal(format!("Task panic: {}", e)))??; .map_err(|e| crate::error::AppError::Internal(format!("Task panic: {}", e)))??;
state.record_activity("clipboard", "Read contents from OS clipboard", None); state.record_activity("clipboard", "Read contents from OS clipboard", None);
Ok::<String, crate::error::AppError>(serde_json::to_string_pretty(&Value::Object( Ok::<String, crate::error::AppError>(serde_yaml::to_string(&Value::Object(
out, out,
))?) ))?)
} }
+2 -2
View File
@@ -122,7 +122,7 @@ impl McpTool for SnippetsHandler {
.total_cmp(&a["score"].as_f64().unwrap_or(0.0)) .total_cmp(&a["score"].as_f64().unwrap_or(0.0))
}); });
scored.truncate(10); scored.truncate(10);
Ok(serde_json::to_string_pretty(&scored)?) Ok(serde_yaml::to_string(&scored)?)
} else { } else {
let data = state.code.snippets.read_with(|snippets| { let data = state.code.snippets.read_with(|snippets| {
let results: Vec<_> = snippets let results: Vec<_> = snippets
@@ -153,7 +153,7 @@ impl McpTool for SnippetsHandler {
}) })
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
serde_json::to_string_pretty(&results) serde_yaml::to_string(&results)
})?; })?;
Ok(data) Ok(data)
} }