swarm repositories / source
aboutsummaryrefslogtreecommitdiff
path: root/crates/fidget-spinner-cli/src/mcp/output.rs
diff options
context:
space:
mode:
authormain <main@swarm.moe>2026-03-19 11:19:42 -0400
committermain <main@swarm.moe>2026-03-19 11:19:42 -0400
commiteb6b1af642f5829d5dc08aa61138d893b91b60b2 (patch)
tree0f1f5a81424f2a98ea08a8743995303769763e32 /crates/fidget-spinner-cli/src/mcp/output.rs
parent7b9bd8b42883f82b090718175b8316296ef18236 (diff)
downloadfidget_spinner-eb6b1af642f5829d5dc08aa61138d893b91b60b2.zip
Retrofit MCP host onto libmcp
Diffstat (limited to 'crates/fidget-spinner-cli/src/mcp/output.rs')
-rw-r--r--crates/fidget-spinner-cli/src/mcp/output.rs88
1 files changed, 88 insertions, 0 deletions
diff --git a/crates/fidget-spinner-cli/src/mcp/output.rs b/crates/fidget-spinner-cli/src/mcp/output.rs
new file mode 100644
index 0000000..58f7eb4
--- /dev/null
+++ b/crates/fidget-spinner-cli/src/mcp/output.rs
@@ -0,0 +1,88 @@
+use libmcp::{JsonPorcelainConfig, RenderMode, render_json_porcelain};
+use serde::Serialize;
+use serde_json::{Map, Value, json};
+
+use crate::mcp::fault::{FaultKind, FaultRecord, FaultStage};
+
+pub(crate) fn split_render_mode(
+ arguments: Value,
+ operation: &str,
+ stage: FaultStage,
+) -> Result<(RenderMode, Value), FaultRecord> {
+ let Value::Object(mut object) = arguments else {
+ return Ok((RenderMode::Porcelain, arguments));
+ };
+ let render = object
+ .remove("render")
+ .map(|value| {
+ serde_json::from_value::<RenderMode>(value).map_err(|error| {
+ FaultRecord::new(
+ FaultKind::InvalidInput,
+ stage,
+ operation,
+ format!("invalid render mode: {error}"),
+ )
+ })
+ })
+ .transpose()?
+ .unwrap_or(RenderMode::Porcelain);
+ Ok((render, Value::Object(object)))
+}
+
+pub(crate) fn tool_success(
+ value: &impl Serialize,
+ render: RenderMode,
+ stage: FaultStage,
+ operation: &str,
+) -> Result<Value, FaultRecord> {
+ let structured = serde_json::to_value(value).map_err(|error| {
+ FaultRecord::new(FaultKind::Internal, stage, operation, error.to_string())
+ })?;
+ tool_success_from_value(structured, render, stage, operation)
+}
+
+pub(crate) fn tool_success_from_value(
+ structured: Value,
+ render: RenderMode,
+ stage: FaultStage,
+ operation: &str,
+) -> Result<Value, FaultRecord> {
+ let text = match render {
+ RenderMode::Porcelain => render_json_porcelain(&structured, JsonPorcelainConfig::default()),
+ RenderMode::Json => crate::to_pretty_json(&structured).map_err(|error| {
+ FaultRecord::new(FaultKind::Internal, stage, operation, error.to_string())
+ })?,
+ };
+ Ok(json!({
+ "content": [{
+ "type": "text",
+ "text": text,
+ }],
+ "structuredContent": structured,
+ "isError": false,
+ }))
+}
+
+pub(crate) fn with_render_property(schema: Value) -> Value {
+ let Value::Object(mut object) = schema else {
+ return schema;
+ };
+
+ let properties = object
+ .entry("properties".to_owned())
+ .or_insert_with(|| Value::Object(Map::new()));
+ if let Value::Object(properties) = properties {
+ let _ = properties.insert(
+ "render".to_owned(),
+ json!({
+ "type": "string",
+ "enum": ["porcelain", "json"],
+ "description": "Output mode. Defaults to porcelain for model-friendly summaries."
+ }),
+ );
+ }
+ let _ = object
+ .entry("additionalProperties".to_owned())
+ .or_insert(Value::Bool(false));
+ Value::Object(object)
+}