1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
//! Port of `lib/ruby_llm/mcp/result.rb`.
use serde_json::Value;
use super::content;
use crate::attachment::Attachment;
use crate::tool::ToolResult;
/// `RubyLLM::MCP::Result`: what an MCP tool returned. Text, files, and structured data each have
/// their own reader.
#[derive(Debug, Clone, PartialEq)]
pub struct McpResult {
/// The text of the result, with text blocks joined by blank lines.
pub text: String,
/// Images, audio, and embedded files.
pub attachments: Vec<Attachment>,
/// The structured content, or `None`.
pub structured: Option<Value>,
data: Value,
}
impl McpResult {
pub fn new(data: Value) -> McpResult {
let blocks = data
.get("content")
.and_then(Value::as_array)
.cloned()
.unwrap_or_default();
let (text, attachments) = content::read(&blocks);
McpResult {
text,
attachments,
structured: data.get("structuredContent").cloned(),
data,
}
}
/// `error?`: whether the tool reported a failure.
pub fn is_error(&self) -> bool {
self.data.get("isError") == Some(&Value::Bool(true))
}
/// `content`: what a chat sends to the model, the text or, when there is none, the structured
/// content as JSON, followed by any attachments.
pub fn content(&self) -> ToolResult {
let body = match &self.structured {
Some(structured) if self.text.is_empty() => structured.to_string(),
_ => self.text.clone(),
};
ToolResult::with_attachments(body, self.attachments.clone())
}
/// `to_h`: the result as the server sent it.
pub fn to_h(&self) -> &Value {
&self.data
}
}