diff options
| author | main <main@swarm.moe> | 2026-03-19 11:19:42 -0400 |
|---|---|---|
| committer | main <main@swarm.moe> | 2026-03-19 11:19:42 -0400 |
| commit | eb6b1af642f5829d5dc08aa61138d893b91b60b2 (patch) | |
| tree | 0f1f5a81424f2a98ea08a8743995303769763e32 /crates/fidget-spinner-cli/src/mcp/output.rs | |
| parent | 7b9bd8b42883f82b090718175b8316296ef18236 (diff) | |
| download | fidget_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.rs | 88 |
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) +} |