Tool reply meta (#6074)
Co-authored-by: Douwe Osinga <douwe@squareup.com>
This commit is contained in:
@@ -37,8 +37,15 @@ async fn main() -> Result<()> {
|
||||
arguments: Some(object!({"path": "./test_image.png"})),
|
||||
}),
|
||||
),
|
||||
Message::user()
|
||||
.with_tool_response("000", Ok(vec![Content::image(base64_image, "image/png")])),
|
||||
Message::user().with_tool_response(
|
||||
"000",
|
||||
Ok(rmcp::model::CallToolResult {
|
||||
content: vec![Content::image(base64_image, "image/png")],
|
||||
structured_content: None,
|
||||
is_error: Some(false),
|
||||
meta: None,
|
||||
}),
|
||||
),
|
||||
];
|
||||
|
||||
// Get a response from the model about the image
|
||||
|
||||
@@ -58,7 +58,7 @@ use crate::tool_monitor::RepetitionInspector;
|
||||
use crate::utils::is_token_cancelled;
|
||||
use regex::Regex;
|
||||
use rmcp::model::{
|
||||
CallToolRequestParam, Content, ErrorCode, ErrorData, GetPromptResult, Prompt,
|
||||
CallToolRequestParam, CallToolResult, Content, ErrorCode, ErrorData, GetPromptResult, Prompt,
|
||||
ServerNotification, Tool,
|
||||
};
|
||||
use serde_json::Value;
|
||||
@@ -100,7 +100,7 @@ pub struct Agent {
|
||||
pub(super) prompt_manager: Mutex<PromptManager>,
|
||||
pub(super) confirmation_tx: mpsc::Sender<(String, PermissionConfirmation)>,
|
||||
pub(super) confirmation_rx: Mutex<mpsc::Receiver<(String, PermissionConfirmation)>>,
|
||||
pub(super) tool_result_tx: mpsc::Sender<(String, ToolResult<Vec<Content>>)>,
|
||||
pub(super) tool_result_tx: mpsc::Sender<(String, ToolResult<CallToolResult>)>,
|
||||
pub(super) tool_result_rx: ToolResultReceiver,
|
||||
|
||||
pub tool_route_manager: Arc<ToolRouteManager>,
|
||||
@@ -128,7 +128,8 @@ pub enum ToolStreamItem<T> {
|
||||
Result(T),
|
||||
}
|
||||
|
||||
pub type ToolStream = Pin<Box<dyn Stream<Item = ToolStreamItem<ToolResult<Vec<Content>>>> + Send>>;
|
||||
pub type ToolStream =
|
||||
Pin<Box<dyn Stream<Item = ToolStreamItem<ToolResult<CallToolResult>>> + Send>>;
|
||||
|
||||
// tool_stream combines a stream of ServerNotifications with a future representing the
|
||||
// final result of the tool call. MCP notifications are not request-scoped, but
|
||||
@@ -137,7 +138,7 @@ pub type ToolStream = Pin<Box<dyn Stream<Item = ToolStreamItem<ToolResult<Vec<Co
|
||||
pub fn tool_stream<S, F>(rx: S, done: F) -> ToolStream
|
||||
where
|
||||
S: Stream<Item = ServerNotification> + Send + Unpin + 'static,
|
||||
F: Future<Output = ToolResult<Vec<Content>>> + Send + 'static,
|
||||
F: Future<Output = ToolResult<CallToolResult>> + Send + 'static,
|
||||
{
|
||||
Box::pin(async_stream::stream! {
|
||||
tokio::pin!(done);
|
||||
@@ -360,7 +361,12 @@ impl Agent {
|
||||
let mut response = response_msg.lock().await;
|
||||
*response = response.clone().with_tool_response(
|
||||
request.id.clone(),
|
||||
Ok(vec![rmcp::model::Content::text(DECLINED_RESPONSE)]),
|
||||
Ok(CallToolResult {
|
||||
content: vec![rmcp::model::Content::text(DECLINED_RESPONSE)],
|
||||
structured_content: None,
|
||||
is_error: Some(true),
|
||||
meta: None,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -454,7 +460,13 @@ impl Agent {
|
||||
let result = self
|
||||
.handle_schedule_management(arguments, request_id.clone())
|
||||
.await;
|
||||
return (request_id, Ok(ToolCallResult::from(result)));
|
||||
let wrapped_result = result.map(|content| CallToolResult {
|
||||
content,
|
||||
structured_content: None,
|
||||
is_error: Some(false),
|
||||
meta: None,
|
||||
});
|
||||
return (request_id, Ok(ToolCallResult::from(wrapped_result)));
|
||||
}
|
||||
|
||||
if tool_call.name == FINAL_OUTPUT_TOOL_NAME {
|
||||
@@ -1090,7 +1102,12 @@ impl Agent {
|
||||
let mut response = response_msg.lock().await;
|
||||
*response = response.clone().with_tool_response(
|
||||
request.id.clone(),
|
||||
Ok(vec![Content::text(CHAT_MODE_TOOL_SKIPPED_RESPONSE)]),
|
||||
Ok(CallToolResult {
|
||||
content: vec![Content::text(CHAT_MODE_TOOL_SKIPPED_RESPONSE)],
|
||||
structured_content: None,
|
||||
is_error: Some(false),
|
||||
meta: None,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1421,7 +1438,7 @@ impl Agent {
|
||||
Ok(plan_prompt)
|
||||
}
|
||||
|
||||
pub async fn handle_tool_result(&self, id: String, result: ToolResult<Vec<Content>>) {
|
||||
pub async fn handle_tool_result(&self, id: String, result: ToolResult<CallToolResult>) {
|
||||
if let Err(e) = self.tool_result_tx.send((id, result)).await {
|
||||
error!("Failed to send tool result: {}", e);
|
||||
}
|
||||
|
||||
@@ -41,8 +41,8 @@ use crate::oauth::oauth_flow;
|
||||
use crate::prompt_template;
|
||||
use crate::subprocess::configure_command_no_window;
|
||||
use rmcp::model::{
|
||||
CallToolRequestParam, Content, ErrorCode, ErrorData, GetPromptResult, Prompt, ResourceContents,
|
||||
ServerInfo, Tool,
|
||||
CallToolRequestParam, Content, ErrorCode, ErrorData, GetPromptResult, Prompt, RawContent,
|
||||
Resource, ResourceContents, ServerInfo, Tool,
|
||||
};
|
||||
use rmcp::transport::auth::AuthClient;
|
||||
use schemars::_private::NoSerialize;
|
||||
@@ -758,6 +758,7 @@ impl ExtensionManager {
|
||||
uri,
|
||||
extension_name.unwrap(),
|
||||
cancellation_token.clone(),
|
||||
true,
|
||||
)
|
||||
.await?;
|
||||
return Ok(result);
|
||||
@@ -773,7 +774,12 @@ impl ExtensionManager {
|
||||
|
||||
for extension_name in extension_names {
|
||||
let result = self
|
||||
.read_resource_from_extension(uri, &extension_name, cancellation_token.clone())
|
||||
.read_resource_from_extension(
|
||||
uri,
|
||||
&extension_name,
|
||||
cancellation_token.clone(),
|
||||
true,
|
||||
)
|
||||
.await;
|
||||
match result {
|
||||
Ok(result) => return Ok(result),
|
||||
@@ -807,6 +813,7 @@ impl ExtensionManager {
|
||||
uri: &str,
|
||||
extension_name: &str,
|
||||
cancellation_token: CancellationToken,
|
||||
format_with_uri: bool,
|
||||
) -> Result<Vec<Content>, ErrorData> {
|
||||
let available_extensions = self
|
||||
.extensions
|
||||
@@ -840,9 +847,12 @@ impl ExtensionManager {
|
||||
|
||||
let mut result = Vec::new();
|
||||
for content in read_result.contents {
|
||||
// Only reading the text resource content; skipping the blob content cause it's too long
|
||||
if let ResourceContents::TextResourceContents { text, .. } = content {
|
||||
let content_str = format!("{}\n\n{}", uri, text);
|
||||
let content_str = if format_with_uri {
|
||||
format!("{}\n\n{}", uri, text)
|
||||
} else {
|
||||
text
|
||||
};
|
||||
result.push(Content::text(content_str));
|
||||
}
|
||||
}
|
||||
@@ -850,6 +860,65 @@ impl ExtensionManager {
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub async fn get_ui_resources(&self) -> Result<Vec<(String, Resource)>, ErrorData> {
|
||||
let mut ui_resources = Vec::new();
|
||||
|
||||
let extensions_to_check: Vec<(String, McpClientBox)> = {
|
||||
let extensions = self.extensions.lock().await;
|
||||
extensions
|
||||
.iter()
|
||||
.map(|(name, ext)| (name.clone(), ext.get_client()))
|
||||
.collect()
|
||||
};
|
||||
|
||||
for (extension_name, client) in extensions_to_check {
|
||||
let client_guard = client.lock().await;
|
||||
|
||||
match client_guard
|
||||
.list_resources(None, CancellationToken::default())
|
||||
.await
|
||||
{
|
||||
Ok(list_response) => {
|
||||
for resource in list_response.resources {
|
||||
if resource.uri.starts_with("ui://") {
|
||||
ui_resources.push((extension_name.clone(), resource));
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Failed to list resources for {}: {:?}", extension_name, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ui_resources)
|
||||
}
|
||||
|
||||
pub async fn read_ui_resource(
|
||||
&self,
|
||||
uri: &str,
|
||||
extension_name: &str,
|
||||
cancellation_token: CancellationToken,
|
||||
) -> Result<String, ErrorData> {
|
||||
let contents = self
|
||||
.read_resource_from_extension(uri, extension_name, cancellation_token, false)
|
||||
.await?;
|
||||
|
||||
contents
|
||||
.into_iter()
|
||||
.find_map(|c| match c.raw {
|
||||
RawContent::Text(text_content) => Some(text_content.text),
|
||||
_ => None,
|
||||
})
|
||||
.ok_or_else(|| {
|
||||
ErrorData::new(
|
||||
ErrorCode::RESOURCE_NOT_FOUND,
|
||||
format!("No text content in resource '{}'", uri),
|
||||
None,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
async fn list_resources_from_extension(
|
||||
&self,
|
||||
extension_name: &str,
|
||||
@@ -936,7 +1005,6 @@ impl ExtensionManager {
|
||||
}
|
||||
}
|
||||
|
||||
// Log any errors that occurred
|
||||
if !errors.is_empty() {
|
||||
tracing::error!(
|
||||
errors = ?errors
|
||||
@@ -998,7 +1066,6 @@ impl ExtensionManager {
|
||||
client_guard
|
||||
.call_tool(&tool_name, arguments, cancellation_token)
|
||||
.await
|
||||
.map(|call| call.content)
|
||||
.map_err(|e| match e {
|
||||
ServiceError::McpError(error_data) => error_data,
|
||||
_ => {
|
||||
@@ -1077,7 +1144,6 @@ impl ExtensionManager {
|
||||
}
|
||||
}
|
||||
|
||||
// Log any errors that occurred
|
||||
if !errors.is_empty() {
|
||||
tracing::debug!(
|
||||
errors = ?errors
|
||||
|
||||
@@ -123,9 +123,14 @@ impl FinalOutputTool {
|
||||
match result {
|
||||
Ok(parsed_value) => {
|
||||
self.final_output = Some(Self::parsed_final_output_string(parsed_value));
|
||||
ToolCallResult::from(Ok(vec![Content::text(
|
||||
"Final output successfully collected.".to_string(),
|
||||
)]))
|
||||
ToolCallResult::from(Ok(rmcp::model::CallToolResult {
|
||||
content: vec![Content::text(
|
||||
"Final output successfully collected.".to_string(),
|
||||
)],
|
||||
structured_content: None,
|
||||
is_error: Some(false),
|
||||
meta: None,
|
||||
}))
|
||||
}
|
||||
Err(error) => ToolCallResult::from(Err(ErrorData {
|
||||
code: ErrorCode::INVALID_PARAMS,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use chrono::Utc;
|
||||
use rmcp::model::{Content, ErrorData};
|
||||
use rmcp::model::{CallToolResult, Content, ErrorData};
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
|
||||
@@ -7,13 +7,13 @@ const LARGE_TEXT_THRESHOLD: usize = 200_000;
|
||||
|
||||
/// Process tool response and handle large text content
|
||||
pub fn process_tool_response(
|
||||
response: Result<Vec<Content>, ErrorData>,
|
||||
) -> Result<Vec<Content>, ErrorData> {
|
||||
response: Result<CallToolResult, ErrorData>,
|
||||
) -> Result<CallToolResult, ErrorData> {
|
||||
match response {
|
||||
Ok(contents) => {
|
||||
Ok(mut result) => {
|
||||
let mut processed_contents = Vec::new();
|
||||
|
||||
for content in contents {
|
||||
for content in result.content {
|
||||
match content.as_text() {
|
||||
Some(text_content) => {
|
||||
// Check if text exceeds threshold
|
||||
@@ -51,7 +51,8 @@ pub fn process_tool_response(
|
||||
}
|
||||
}
|
||||
|
||||
Ok(processed_contents)
|
||||
result.content = processed_contents;
|
||||
Ok(result)
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
@@ -89,14 +90,19 @@ mod tests {
|
||||
let small_text = "This is a small text response";
|
||||
let content = Content::text(small_text.to_string());
|
||||
|
||||
let response = Ok(vec![content]);
|
||||
let response = Ok(CallToolResult {
|
||||
content: vec![content],
|
||||
structured_content: None,
|
||||
is_error: Some(false),
|
||||
meta: None,
|
||||
});
|
||||
|
||||
// Process the response
|
||||
let processed = process_tool_response(response).unwrap();
|
||||
|
||||
// Verify the response is unchanged
|
||||
assert_eq!(processed.len(), 1);
|
||||
if let Some(text_content) = processed[0].as_text() {
|
||||
assert_eq!(processed.content.len(), 1);
|
||||
if let Some(text_content) = processed.content[0].as_text() {
|
||||
assert_eq!(text_content.text, small_text);
|
||||
} else {
|
||||
panic!("Expected text content");
|
||||
@@ -109,14 +115,19 @@ mod tests {
|
||||
let large_text = "a".repeat(LARGE_TEXT_THRESHOLD + 1000);
|
||||
let content = Content::text(large_text.clone());
|
||||
|
||||
let response = Ok(vec![content]);
|
||||
let response = Ok(CallToolResult {
|
||||
content: vec![content],
|
||||
structured_content: None,
|
||||
is_error: Some(false),
|
||||
meta: None,
|
||||
});
|
||||
|
||||
// Process the response
|
||||
let processed = process_tool_response(response).unwrap();
|
||||
|
||||
// Verify the response contains a message about the file
|
||||
assert_eq!(processed.len(), 1);
|
||||
if let Some(text_content) = processed[0].as_text() {
|
||||
assert_eq!(processed.content.len(), 1);
|
||||
if let Some(text_content) = processed.content[0].as_text() {
|
||||
assert!(text_content
|
||||
.text
|
||||
.contains("The response returned from the tool call was larger"));
|
||||
@@ -146,14 +157,19 @@ mod tests {
|
||||
// Create an image content
|
||||
let image_content = Content::image("base64data".to_string(), "image/png".to_string());
|
||||
|
||||
let response = Ok(vec![image_content]);
|
||||
let response = Ok(CallToolResult {
|
||||
content: vec![image_content],
|
||||
structured_content: None,
|
||||
is_error: Some(false),
|
||||
meta: None,
|
||||
});
|
||||
|
||||
// Process the response
|
||||
let processed = process_tool_response(response).unwrap();
|
||||
|
||||
// Verify the response is unchanged
|
||||
assert_eq!(processed.len(), 1);
|
||||
if let Some(img) = processed[0].as_image() {
|
||||
assert_eq!(processed.content.len(), 1);
|
||||
if let Some(img) = processed.content[0].as_image() {
|
||||
assert_eq!(img.data, "base64data");
|
||||
assert_eq!(img.mime_type, "image/png");
|
||||
} else {
|
||||
@@ -168,23 +184,28 @@ mod tests {
|
||||
let large_text = Content::text("a".repeat(LARGE_TEXT_THRESHOLD + 1000));
|
||||
let image = Content::image("image_data".to_string(), "image/jpeg".to_string());
|
||||
|
||||
let response = Ok(vec![small_text, large_text, image]);
|
||||
let response = Ok(CallToolResult {
|
||||
content: vec![small_text, large_text, image],
|
||||
structured_content: None,
|
||||
is_error: Some(false),
|
||||
meta: None,
|
||||
});
|
||||
|
||||
// Process the response
|
||||
let processed = process_tool_response(response).unwrap();
|
||||
|
||||
// Verify each item is handled correctly
|
||||
assert_eq!(processed.len(), 3);
|
||||
assert_eq!(processed.content.len(), 3);
|
||||
|
||||
// First item should be unchanged small text
|
||||
if let Some(text_content) = processed[0].as_text() {
|
||||
if let Some(text_content) = processed.content[0].as_text() {
|
||||
assert_eq!(text_content.text, "Small text");
|
||||
} else {
|
||||
panic!("Expected text content");
|
||||
}
|
||||
|
||||
// Second item should be a message about the file
|
||||
if let Some(text_content) = processed[1].as_text() {
|
||||
if let Some(text_content) = processed.content[1].as_text() {
|
||||
assert!(text_content
|
||||
.text
|
||||
.contains("The response returned from the tool call was larger"));
|
||||
@@ -201,7 +222,7 @@ mod tests {
|
||||
}
|
||||
|
||||
// Third item should be unchanged image
|
||||
if let Some(img) = processed[2].as_image() {
|
||||
if let Some(img) = processed.content[2].as_image() {
|
||||
assert_eq!(img.data, "image_data");
|
||||
assert_eq!(img.mime_type, "image/jpeg");
|
||||
} else {
|
||||
@@ -217,7 +238,7 @@ mod tests {
|
||||
message: Cow::from("Test error"),
|
||||
data: None,
|
||||
};
|
||||
let response: Result<Vec<Content>, ErrorData> = Err(error);
|
||||
let response: Result<CallToolResult, ErrorData> = Err(error);
|
||||
|
||||
// Process the response
|
||||
let processed = process_tool_response(response);
|
||||
|
||||
@@ -106,7 +106,15 @@ mod tests {
|
||||
arguments: None,
|
||||
}),
|
||||
),
|
||||
Message::user().with_tool_response("search_1", Ok(vec![])),
|
||||
Message::user().with_tool_response(
|
||||
"search_1",
|
||||
Ok(rmcp::model::CallToolResult {
|
||||
content: vec![],
|
||||
structured_content: None,
|
||||
is_error: Some(false),
|
||||
meta: None,
|
||||
}),
|
||||
),
|
||||
Message::assistant()
|
||||
.with_text("I need to search more")
|
||||
.with_tool_request(
|
||||
@@ -116,7 +124,15 @@ mod tests {
|
||||
arguments: None,
|
||||
}),
|
||||
),
|
||||
Message::user().with_tool_response("search_2", Ok(vec![])),
|
||||
Message::user().with_tool_response(
|
||||
"search_2",
|
||||
Ok(rmcp::model::CallToolResult {
|
||||
content: vec![],
|
||||
structured_content: None,
|
||||
is_error: Some(false),
|
||||
meta: None,
|
||||
}),
|
||||
),
|
||||
]);
|
||||
|
||||
let result = inject_moim(conv, &em).await;
|
||||
|
||||
@@ -370,5 +370,10 @@ pub async fn create_dynamic_task(
|
||||
};
|
||||
|
||||
tasks_manager.save_tasks(tasks).await;
|
||||
ToolCallResult::from(Ok(vec![Content::text(tasks_json)]))
|
||||
ToolCallResult::from(Ok(rmcp::model::CallToolResult {
|
||||
content: vec![Content::text(tasks_json)],
|
||||
structured_content: None,
|
||||
is_error: Some(false),
|
||||
meta: None,
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -63,7 +63,12 @@ impl SubRecipeManager {
|
||||
.call_sub_recipe_tool(tool_name, params, tasks_manager, parent_working_dir)
|
||||
.await;
|
||||
match result {
|
||||
Ok(call_result) => ToolCallResult::from(Ok(call_result)),
|
||||
Ok(content) => ToolCallResult::from(Ok(rmcp::model::CallToolResult {
|
||||
content,
|
||||
structured_content: None,
|
||||
is_error: Some(false),
|
||||
meta: None,
|
||||
})),
|
||||
Err(e) => ToolCallResult::from(Err(ErrorData {
|
||||
code: ErrorCode::INTERNAL_ERROR,
|
||||
message: Cow::from(e.to_string()),
|
||||
|
||||
@@ -82,7 +82,12 @@ pub async fn run_tasks(
|
||||
{
|
||||
Ok(result) => {
|
||||
let output = serde_json::to_string(&result).unwrap();
|
||||
Ok(vec![Content::text(output)])
|
||||
Ok(rmcp::model::CallToolResult {
|
||||
content: vec![Content::text(output)],
|
||||
structured_content: None,
|
||||
is_error: Some(false),
|
||||
meta: None,
|
||||
})
|
||||
}
|
||||
Err(e) => Err(ErrorData {
|
||||
code: ErrorCode::INTERNAL_ERROR,
|
||||
|
||||
@@ -61,8 +61,9 @@ pub async fn run_complete_subagent_task(
|
||||
tool_response,
|
||||
) => {
|
||||
// Extract text from tool response
|
||||
if let Ok(contents) = &tool_response.tool_result {
|
||||
let texts: Vec<String> = contents
|
||||
if let Ok(result) = &tool_response.tool_result {
|
||||
let texts: Vec<String> = result
|
||||
.content
|
||||
.iter()
|
||||
.filter_map(|content| {
|
||||
if let rmcp::model::RawContent::Text(raw_text_content) =
|
||||
|
||||
@@ -16,12 +16,12 @@ use rmcp::model::{Content, ServerNotification};
|
||||
// ToolCallResult combines the result of a tool call with an optional notification stream that
|
||||
// can be used to receive notifications from the tool.
|
||||
pub struct ToolCallResult {
|
||||
pub result: Box<dyn Future<Output = ToolResult<Vec<Content>>> + Send + Unpin>,
|
||||
pub result: Box<dyn Future<Output = ToolResult<rmcp::model::CallToolResult>> + Send + Unpin>,
|
||||
pub notification_stream: Option<Box<dyn Stream<Item = ServerNotification> + Send + Unpin>>,
|
||||
}
|
||||
|
||||
impl From<ToolResult<Vec<Content>>> for ToolCallResult {
|
||||
fn from(result: ToolResult<Vec<Content>>) -> Self {
|
||||
impl From<ToolResult<rmcp::model::CallToolResult>> for ToolCallResult {
|
||||
fn from(result: ToolResult<rmcp::model::CallToolResult>) -> Self {
|
||||
Self {
|
||||
result: Box::new(futures::future::ready(result)),
|
||||
notification_stream: None,
|
||||
@@ -122,7 +122,12 @@ impl Agent {
|
||||
let mut response = response_msg.lock().await;
|
||||
*response = response.clone().with_tool_response(
|
||||
request.id.clone(),
|
||||
Ok(vec![Content::text(DECLINED_RESPONSE)]),
|
||||
Ok(rmcp::model::CallToolResult {
|
||||
content: vec![Content::text(DECLINED_RESPONSE)],
|
||||
structured_content: None,
|
||||
is_error: Some(true),
|
||||
meta: None,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,12 @@ impl ToolRouteManager {
|
||||
let selector = self.router_tool_selector.lock().await.clone();
|
||||
match selector.as_ref() {
|
||||
Some(selector) => match selector.select_tools(arguments).await {
|
||||
Ok(tools) => Ok(ToolCallResult::from(Ok(tools))),
|
||||
Ok(content) => Ok(ToolCallResult::from(Ok(rmcp::model::CallToolResult {
|
||||
content,
|
||||
structured_content: None,
|
||||
is_error: Some(false),
|
||||
meta: None,
|
||||
}))),
|
||||
Err(e) => Err(ErrorData::new(
|
||||
ErrorCode::INTERNAL_ERROR,
|
||||
format!("Failed to select tools: {}", e),
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
use crate::mcp_utils::ToolResult;
|
||||
use crate::providers::base::Provider;
|
||||
use rmcp::model::{Content, Tool};
|
||||
use rmcp::model::{CallToolResult, Tool};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::{mpsc, Mutex};
|
||||
use utoipa::ToSchema;
|
||||
|
||||
/// Type alias for the tool result channel receiver
|
||||
pub type ToolResultReceiver = Arc<Mutex<mpsc::Receiver<(String, ToolResult<Vec<Content>>)>>>;
|
||||
pub type ToolResultReceiver = Arc<Mutex<mpsc::Receiver<(String, ToolResult<CallToolResult>)>>>;
|
||||
|
||||
// We use double Arc here to allow easy provider swaps while sharing concurrent access
|
||||
pub type SharedProvider = Arc<Mutex<Option<Arc<dyn Provider>>>>;
|
||||
|
||||
@@ -355,8 +355,9 @@ fn format_message_for_compacting(msg: &Message) -> String {
|
||||
}
|
||||
}
|
||||
MessageContent::ToolResponse(res) => {
|
||||
if let Ok(contents) = &res.tool_result {
|
||||
let text_items: Vec<String> = contents
|
||||
if let Ok(result) = &res.tool_result {
|
||||
let text_items: Vec<String> = result
|
||||
.content
|
||||
.iter()
|
||||
.filter_map(|content| {
|
||||
content.as_text().map(|text_str| text_str.text.clone())
|
||||
@@ -517,7 +518,12 @@ mod tests {
|
||||
),
|
||||
Message::user().with_tool_response(
|
||||
"tool_0",
|
||||
Ok(vec![RawContent::text("hello, world").no_annotation()]),
|
||||
Ok(rmcp::model::CallToolResult {
|
||||
content: vec![RawContent::text("hello, world").no_annotation()],
|
||||
structured_content: None,
|
||||
is_error: Some(false),
|
||||
meta: None,
|
||||
}),
|
||||
),
|
||||
];
|
||||
|
||||
@@ -550,9 +556,12 @@ mod tests {
|
||||
));
|
||||
messages.push(Message::user().with_tool_response(
|
||||
format!("tool_{}", i),
|
||||
Ok(vec![
|
||||
RawContent::text(format!("response{}", i)).no_annotation(),
|
||||
]),
|
||||
Ok(rmcp::model::CallToolResult {
|
||||
content: vec![RawContent::text(format!("response{}", i)).no_annotation()],
|
||||
structured_content: None,
|
||||
is_error: Some(false),
|
||||
meta: None,
|
||||
}),
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use crate::mcp_utils::ToolResult;
|
||||
use chrono::Utc;
|
||||
use rmcp::model::{
|
||||
AnnotateAble, CallToolRequestParam, Content, ImageContent, JsonObject, PromptMessage,
|
||||
PromptMessageContent, PromptMessageRole, RawContent, RawImageContent, RawTextContent,
|
||||
ResourceContents, Role, TextContent,
|
||||
AnnotateAble, CallToolRequestParam, CallToolResult, Content, ImageContent, JsonObject,
|
||||
PromptMessage, PromptMessageContent, PromptMessageRole, RawContent, RawImageContent,
|
||||
RawTextContent, ResourceContents, Role, TextContent,
|
||||
};
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use std::collections::HashSet;
|
||||
@@ -88,7 +88,7 @@ pub struct ToolResponse {
|
||||
pub id: String,
|
||||
#[serde(with = "tool_result_serde")]
|
||||
#[schema(value_type = Object)]
|
||||
pub tool_result: ToolResult<Vec<Content>>,
|
||||
pub tool_result: ToolResult<CallToolResult>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
@@ -190,7 +190,7 @@ impl fmt::Display for MessageContent {
|
||||
f,
|
||||
"[ToolResponse: {}]",
|
||||
match &r.tool_result {
|
||||
Ok(contents) => format!("{} content item(s)", contents.len()),
|
||||
Ok(result) => format!("{} content item(s)", result.content.len()),
|
||||
Err(e) => format!("Error: {e}"),
|
||||
}
|
||||
),
|
||||
@@ -266,7 +266,7 @@ impl MessageContent {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn tool_response<S: Into<String>>(id: S, tool_result: ToolResult<Vec<Content>>) -> Self {
|
||||
pub fn tool_response<S: Into<String>>(id: S, tool_result: ToolResult<CallToolResult>) -> Self {
|
||||
MessageContent::ToolResponse(ToolResponse {
|
||||
id: id.into(),
|
||||
tool_result,
|
||||
@@ -380,8 +380,9 @@ impl MessageContent {
|
||||
|
||||
pub fn as_tool_response_text(&self) -> Option<String> {
|
||||
if let Some(tool_response) = self.as_tool_response() {
|
||||
if let Ok(contents) = &tool_response.tool_result {
|
||||
let texts: Vec<String> = contents
|
||||
if let Ok(result) = &tool_response.tool_result {
|
||||
let texts: Vec<String> = result
|
||||
.content
|
||||
.iter()
|
||||
.filter_map(|content| content.as_text().map(|t| t.text.to_string()))
|
||||
.collect();
|
||||
@@ -644,7 +645,7 @@ impl Message {
|
||||
pub fn with_tool_response<S: Into<String>>(
|
||||
self,
|
||||
id: S,
|
||||
result: ToolResult<Vec<Content>>,
|
||||
result: ToolResult<CallToolResult>,
|
||||
) -> Self {
|
||||
self.with_content(MessageContent::tool_response(id, result))
|
||||
}
|
||||
|
||||
@@ -555,7 +555,15 @@ mod tests {
|
||||
arguments: Some(object!({"query": "rust programming"})),
|
||||
}),
|
||||
),
|
||||
Message::user().with_tool_response("search_1", Ok(vec![])),
|
||||
Message::user().with_tool_response(
|
||||
"search_1",
|
||||
Ok(rmcp::model::CallToolResult {
|
||||
content: vec![],
|
||||
structured_content: None,
|
||||
is_error: Some(false),
|
||||
meta: None,
|
||||
}),
|
||||
),
|
||||
Message::assistant().with_text("Based on the search results, here's what I found..."),
|
||||
];
|
||||
|
||||
@@ -592,7 +600,15 @@ mod tests {
|
||||
Message::user().with_text("Another user message"),
|
||||
Message::assistant()
|
||||
.with_text("Response")
|
||||
.with_tool_response("orphan_1", Ok(vec![])), // Wrong role
|
||||
.with_tool_response(
|
||||
"orphan_1",
|
||||
Ok(rmcp::model::CallToolResult {
|
||||
content: vec![],
|
||||
structured_content: None,
|
||||
is_error: Some(false),
|
||||
meta: None,
|
||||
}),
|
||||
), // Wrong role
|
||||
Message::assistant().with_thinking("Let me think", "sig"),
|
||||
Message::user()
|
||||
.with_tool_request(
|
||||
@@ -642,7 +658,15 @@ mod tests {
|
||||
}),
|
||||
),
|
||||
Message::user(),
|
||||
Message::user().with_tool_response("wrong_id", Ok(vec![])),
|
||||
Message::user().with_tool_response(
|
||||
"wrong_id",
|
||||
Ok(rmcp::model::CallToolResult {
|
||||
content: vec![],
|
||||
structured_content: None,
|
||||
is_error: Some(false),
|
||||
meta: None,
|
||||
}),
|
||||
),
|
||||
Message::assistant().with_tool_request(
|
||||
"search_2",
|
||||
Ok(CallToolRequestParam {
|
||||
@@ -687,7 +711,12 @@ mod tests {
|
||||
.with_tool_request("toolu_bdrk_01KgDYHs4fAodi22NqxRzmwx", Ok(CallToolRequestParam { name: "developer__shell".into(), arguments: Some(object!({"command": "wc slack.yaml"})) })),
|
||||
|
||||
Message::user()
|
||||
.with_tool_response("toolu_bdrk_01KgDYHs4fAodi22NqxRzmwx", Ok(vec![])),
|
||||
.with_tool_response("toolu_bdrk_01KgDYHs4fAodi22NqxRzmwx", Ok(rmcp::model::CallToolResult {
|
||||
content: vec![],
|
||||
structured_content: None,
|
||||
is_error: Some(false),
|
||||
meta: None,
|
||||
})),
|
||||
|
||||
Message::assistant()
|
||||
.with_text("I ran `ls -la` in the current directory and found several files. Looking at the file sizes, I can see that both `slack.yaml` and `subrecipes.yaml` are 0 bytes (the smallest files). I ran a word count on `slack.yaml` which shows: **0 lines**, **0 words**, **0 characters**"),
|
||||
@@ -718,7 +747,15 @@ mod tests {
|
||||
arguments: Some(object!({})),
|
||||
}),
|
||||
),
|
||||
Message::user().with_tool_response("search_1", Ok(vec![])),
|
||||
Message::user().with_tool_response(
|
||||
"search_1",
|
||||
Ok(rmcp::model::CallToolResult {
|
||||
content: vec![],
|
||||
structured_content: None,
|
||||
is_error: Some(false),
|
||||
meta: None,
|
||||
}),
|
||||
),
|
||||
Message::user().with_text("Thanks!"),
|
||||
];
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ pub struct ToolPermissionRecord {
|
||||
allowed: bool,
|
||||
context_hash: String, // Hash of the tool's arguments/context to differentiate similar calls
|
||||
#[serde(skip_serializing_if = "Option::is_none")] // Don't serialize if None
|
||||
readable_context: Option<String>, // Add this field
|
||||
readable_context: Option<String>,
|
||||
timestamp: i64,
|
||||
expiry: Option<i64>, // Optional expiry timestamp
|
||||
}
|
||||
|
||||
@@ -74,9 +74,10 @@ impl ClaudeCodeProvider {
|
||||
}
|
||||
}
|
||||
MessageContent::ToolResponse(tool_response) => {
|
||||
if let Ok(tool_contents) = &tool_response.tool_result {
|
||||
if let Ok(result) = &tool_response.tool_result {
|
||||
// Convert tool result contents to text
|
||||
let content_text = tool_contents
|
||||
let content_text = result
|
||||
.content
|
||||
.iter()
|
||||
.filter_map(|content| match &content.raw {
|
||||
rmcp::model::RawContent::Text(text_content) => {
|
||||
|
||||
@@ -86,8 +86,9 @@ impl CursorAgentProvider {
|
||||
}
|
||||
}
|
||||
MessageContent::ToolResponse(tool_response) => {
|
||||
if let Ok(tool_contents) = &tool_response.tool_result {
|
||||
let content_text = tool_contents
|
||||
if let Ok(result) = &tool_response.tool_result {
|
||||
let content_text = result
|
||||
.content
|
||||
.iter()
|
||||
.filter_map(|content| match &content.raw {
|
||||
rmcp::model::RawContent::Text(text_content) => {
|
||||
|
||||
@@ -68,6 +68,7 @@ pub fn format_messages(messages: &[Message]) -> Vec<Value> {
|
||||
MessageContent::ToolResponse(tool_response) => match &tool_response.tool_result {
|
||||
Ok(result) => {
|
||||
let text = result
|
||||
.content
|
||||
.iter()
|
||||
.filter_map(|c| c.as_text().map(|t| t.text.clone()))
|
||||
.collect::<Vec<_>>()
|
||||
|
||||
@@ -86,8 +86,9 @@ pub fn to_bedrock_message_content(content: &MessageContent) -> Result<bedrock::C
|
||||
}
|
||||
MessageContent::ToolResponse(tool_res) => {
|
||||
let content = match &tool_res.tool_result {
|
||||
Ok(content) => Some(
|
||||
content
|
||||
Ok(result) => Some(
|
||||
result
|
||||
.content
|
||||
.iter()
|
||||
// Filter out content items that have User in their audience
|
||||
.filter(|c| {
|
||||
@@ -318,6 +319,12 @@ pub fn from_bedrock_content_block(block: &bedrock::ContentBlock) -> Result<Messa
|
||||
.iter()
|
||||
.map(from_bedrock_tool_result_content_block)
|
||||
.collect::<ToolResult<Vec<_>>>()
|
||||
.map(|content| rmcp::model::CallToolResult {
|
||||
content,
|
||||
structured_content: None,
|
||||
is_error: Some(false),
|
||||
meta: None,
|
||||
})
|
||||
},
|
||||
),
|
||||
_ => bail!("Unsupported content block type from Bedrock"),
|
||||
|
||||
@@ -133,9 +133,10 @@ fn format_messages(messages: &[Message], image_format: &ImageFormat) -> Vec<Data
|
||||
}
|
||||
MessageContent::ToolResponse(response) => {
|
||||
match &response.tool_result {
|
||||
Ok(contents) => {
|
||||
Ok(call_result) => {
|
||||
// Send only contents with no audience or with Assistant in the audience
|
||||
let abridged: Vec<_> = contents
|
||||
let abridged: Vec<_> = call_result
|
||||
.content
|
||||
.iter()
|
||||
.filter(|content| {
|
||||
content
|
||||
@@ -638,6 +639,7 @@ pub fn create_request(
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::conversation::message::Message;
|
||||
use rmcp::model::CallToolResult;
|
||||
use rmcp::object;
|
||||
use serde_json::json;
|
||||
|
||||
@@ -727,8 +729,15 @@ mod tests {
|
||||
panic!("should be tool request");
|
||||
};
|
||||
|
||||
messages
|
||||
.push(Message::user().with_tool_response(tool_id, Ok(vec![Content::text("Result")])));
|
||||
messages.push(Message::user().with_tool_response(
|
||||
tool_id,
|
||||
Ok(CallToolResult {
|
||||
content: vec![Content::text("Result")],
|
||||
structured_content: None,
|
||||
is_error: Some(false),
|
||||
meta: None,
|
||||
}),
|
||||
));
|
||||
|
||||
let as_value =
|
||||
serde_json::to_value(format_messages(&messages, &ImageFormat::OpenAi)).unwrap();
|
||||
@@ -764,8 +773,15 @@ mod tests {
|
||||
panic!("should be tool request");
|
||||
};
|
||||
|
||||
messages
|
||||
.push(Message::user().with_tool_response(tool_id, Ok(vec![Content::text("Result")])));
|
||||
messages.push(Message::user().with_tool_response(
|
||||
tool_id,
|
||||
Ok(CallToolResult {
|
||||
content: vec![Content::text("Result")],
|
||||
structured_content: None,
|
||||
is_error: Some(false),
|
||||
meta: None,
|
||||
}),
|
||||
));
|
||||
|
||||
let as_value =
|
||||
serde_json::to_value(format_messages(&messages, &ImageFormat::OpenAi)).unwrap();
|
||||
|
||||
@@ -70,9 +70,10 @@ pub fn format_messages(messages: &[Message]) -> Vec<Value> {
|
||||
},
|
||||
MessageContent::ToolResponse(response) => {
|
||||
match &response.tool_result {
|
||||
Ok(contents) => {
|
||||
Ok(result) => {
|
||||
// Send only contents with no audience or with Assistant in the audience
|
||||
let abridged: Vec<_> = contents
|
||||
let abridged: Vec<_> = result
|
||||
.content
|
||||
.iter()
|
||||
.filter(|content| {
|
||||
content.audience().is_none_or(|audience| {
|
||||
@@ -394,7 +395,7 @@ pub fn create_request(
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::conversation::message::Message;
|
||||
use rmcp::model::CallToolRequestParam;
|
||||
use rmcp::model::{CallToolRequestParam, CallToolResult};
|
||||
use rmcp::{model::Content, object};
|
||||
use serde_json::json;
|
||||
|
||||
@@ -429,7 +430,12 @@ mod tests {
|
||||
0,
|
||||
vec![MessageContent::tool_response(
|
||||
id.to_string(),
|
||||
Ok(tool_response),
|
||||
Ok(CallToolResult {
|
||||
content: tool_response,
|
||||
structured_content: None,
|
||||
is_error: Some(false),
|
||||
meta: None,
|
||||
}),
|
||||
)],
|
||||
)
|
||||
}
|
||||
|
||||
@@ -128,9 +128,10 @@ pub fn format_messages(messages: &[Message], image_format: &ImageFormat) -> Vec<
|
||||
},
|
||||
MessageContent::ToolResponse(response) => {
|
||||
match &response.tool_result {
|
||||
Ok(contents) => {
|
||||
Ok(result) => {
|
||||
// Send only contents with no audience or with Assistant in the audience
|
||||
let abridged: Vec<_> = contents
|
||||
let abridged: Vec<_> = result
|
||||
.content
|
||||
.iter()
|
||||
.filter(|content| {
|
||||
content
|
||||
@@ -710,6 +711,7 @@ pub fn create_request(
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::conversation::message::Message;
|
||||
use rmcp::model::CallToolResult;
|
||||
use rmcp::object;
|
||||
use serde_json::json;
|
||||
use tokio::pin;
|
||||
@@ -868,8 +870,15 @@ mod tests {
|
||||
panic!("should be tool request");
|
||||
};
|
||||
|
||||
messages
|
||||
.push(Message::user().with_tool_response(tool_id, Ok(vec![Content::text("Result")])));
|
||||
messages.push(Message::user().with_tool_response(
|
||||
tool_id,
|
||||
Ok(CallToolResult {
|
||||
content: vec![Content::text("Result")],
|
||||
structured_content: None,
|
||||
is_error: Some(false),
|
||||
meta: None,
|
||||
}),
|
||||
));
|
||||
|
||||
let spec = format_messages(&messages, &ImageFormat::OpenAi);
|
||||
|
||||
@@ -904,8 +913,15 @@ mod tests {
|
||||
panic!("should be tool request");
|
||||
};
|
||||
|
||||
messages
|
||||
.push(Message::user().with_tool_response(tool_id, Ok(vec![Content::text("Result")])));
|
||||
messages.push(Message::user().with_tool_response(
|
||||
tool_id,
|
||||
Ok(CallToolResult {
|
||||
content: vec![Content::text("Result")],
|
||||
structured_content: None,
|
||||
is_error: Some(false),
|
||||
meta: None,
|
||||
}),
|
||||
));
|
||||
|
||||
let spec = format_messages(&messages, &ImageFormat::OpenAi);
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ pub fn format_messages(messages: &[Message]) -> Vec<Value> {
|
||||
MessageContent::ToolResponse(tool_response) => {
|
||||
if let Ok(result) = &tool_response.tool_result {
|
||||
let text = result
|
||||
.content
|
||||
.iter()
|
||||
.filter_map(|c| c.as_text().map(|t| t.text.clone()))
|
||||
.collect::<Vec<_>>()
|
||||
|
||||
@@ -215,9 +215,9 @@ impl LeadWorkerProvider {
|
||||
if let Err(tool_error) = &tool_response.tool_result {
|
||||
failure_indicators += 1;
|
||||
tracing::debug!("Tool execution failure detected: {:?}", tool_error);
|
||||
} else if let Ok(contents) = &tool_response.tool_result {
|
||||
} else if let Ok(result) = &tool_response.tool_result {
|
||||
// Check tool output for error indicators
|
||||
if self.contains_error_indicators(contents) {
|
||||
if self.contains_error_indicators(&result.content) {
|
||||
failure_indicators += 1;
|
||||
tracing::debug!("Tool output contains error indicators");
|
||||
}
|
||||
|
||||
@@ -343,8 +343,9 @@ pub fn convert_tool_messages_to_text(messages: &[Message]) -> Conversation {
|
||||
has_tool_content = true;
|
||||
// Convert tool response to text format
|
||||
let text = match &res.tool_result {
|
||||
Ok(contents) => {
|
||||
let text_contents: Vec<String> = contents
|
||||
Ok(result) => {
|
||||
let text_contents: Vec<String> = result
|
||||
.content
|
||||
.iter()
|
||||
.filter_map(|c| match c.deref() {
|
||||
RawContent::Text(t) => Some(t.text.clone()),
|
||||
|
||||
@@ -118,10 +118,10 @@ mod tests {
|
||||
let tool_result = result.result.await;
|
||||
assert!(tool_result.is_ok());
|
||||
let contents = tool_result.unwrap();
|
||||
assert!(!contents.is_empty());
|
||||
assert!(!contents.content.is_empty());
|
||||
|
||||
// Parse the returned JSON to verify task creation
|
||||
if let Some(text_content) = contents.first().and_then(|c| c.as_text()) {
|
||||
if let Some(text_content) = contents.content.first().and_then(|c| c.as_text()) {
|
||||
let task_payload: serde_json::Value = serde_json::from_str(&text_content.text).unwrap();
|
||||
assert!(task_payload.get("task_ids").is_some());
|
||||
let task_ids = task_payload.get("task_ids").unwrap().as_array().unwrap();
|
||||
|
||||
@@ -6,7 +6,7 @@ use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::{env, fs};
|
||||
|
||||
use rmcp::model::{CallToolRequestParam, Content, Tool};
|
||||
use rmcp::model::{CallToolRequestParam, CallToolResult, Tool};
|
||||
use rmcp::object;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
@@ -108,7 +108,7 @@ fn build_and_get_binary_path() -> PathBuf {
|
||||
}
|
||||
})
|
||||
.next()
|
||||
.expect("failed to parase binary path")
|
||||
.expect("failed to parse binary path")
|
||||
}
|
||||
|
||||
static REPLAY_BINARY_PATH: Lazy<PathBuf> = Lazy::new(build_and_get_binary_path);
|
||||
@@ -284,7 +284,7 @@ async fn test_replayed_session(
|
||||
serde_json::to_writer_pretty(File::create(results_path)?, &results)?
|
||||
}
|
||||
TestMode::Playback => assert_eq!(
|
||||
serde_json::from_reader::<_, Vec<Vec<Content>>>(File::open(results_path)?)?,
|
||||
serde_json::from_reader::<_, Vec<CallToolResult>>(File::open(results_path)?)?,
|
||||
results
|
||||
),
|
||||
};
|
||||
|
||||
+13
-15
File diff suppressed because one or more lines are too long
+118
-103
@@ -1,111 +1,126 @@
|
||||
[
|
||||
[
|
||||
{
|
||||
"type": "resource",
|
||||
"resource": {
|
||||
"uri": "file:///tmp/goose_test/goose.txt",
|
||||
"mimeType": "text",
|
||||
"text": "# goose\n"
|
||||
{
|
||||
"content": [
|
||||
{
|
||||
"type": "resource",
|
||||
"resource": {
|
||||
"uri": "file:///tmp/goose_test/goose.txt",
|
||||
"mimeType": "text",
|
||||
"text": "# goose\n"
|
||||
},
|
||||
"annotations": {
|
||||
"audience": [
|
||||
"assistant"
|
||||
]
|
||||
}
|
||||
},
|
||||
"annotations": {
|
||||
"audience": [
|
||||
"assistant"
|
||||
]
|
||||
{
|
||||
"type": "text",
|
||||
"text": "### /tmp/goose_test/goose.txt\n```\n1: # goose\n```\n",
|
||||
"annotations": {
|
||||
"audience": [
|
||||
"user"
|
||||
],
|
||||
"priority": 0.0
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"text": "### /tmp/goose_test/goose.txt\n```\n1: # goose\n```\n",
|
||||
"annotations": {
|
||||
"audience": [
|
||||
"user"
|
||||
],
|
||||
"priority": 0.0
|
||||
],
|
||||
"isError": false
|
||||
},
|
||||
{
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "The file /tmp/goose_test/goose.txt has been edited, and the section now reads:\n```\n# goose (modified by test)\n```\n\nReview the changes above for errors. Undo and edit the file again if necessary!\n",
|
||||
"annotations": {
|
||||
"audience": [
|
||||
"assistant"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"text": "```\n# goose (modified by test)\n```\n",
|
||||
"annotations": {
|
||||
"audience": [
|
||||
"user"
|
||||
],
|
||||
"priority": 0.2
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"type": "text",
|
||||
"text": "The file /tmp/goose_test/goose.txt has been edited, and the section now reads:\n```\n# goose (modified by test)\n```\n\nReview the changes above for errors. Undo and edit the file again if necessary!\n",
|
||||
"annotations": {
|
||||
"audience": [
|
||||
"assistant"
|
||||
]
|
||||
],
|
||||
"isError": false
|
||||
},
|
||||
{
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "# goose (modified by test)\n",
|
||||
"annotations": {
|
||||
"audience": [
|
||||
"assistant"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"text": "# goose (modified by test)\n",
|
||||
"annotations": {
|
||||
"audience": [
|
||||
"user"
|
||||
],
|
||||
"priority": 0.0
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"text": "```\n# goose (modified by test)\n```\n",
|
||||
"annotations": {
|
||||
"audience": [
|
||||
"user"
|
||||
],
|
||||
"priority": 0.2
|
||||
],
|
||||
"isError": false
|
||||
},
|
||||
{
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "The file /tmp/goose_test/goose.txt has been edited, and the section now reads:\n```\n# goose\n```\n\nReview the changes above for errors. Undo and edit the file again if necessary!\n",
|
||||
"annotations": {
|
||||
"audience": [
|
||||
"assistant"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"text": "```\n# goose\n```\n",
|
||||
"annotations": {
|
||||
"audience": [
|
||||
"user"
|
||||
],
|
||||
"priority": 0.2
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"type": "text",
|
||||
"text": "# goose (modified by test)\n",
|
||||
"annotations": {
|
||||
"audience": [
|
||||
"assistant"
|
||||
]
|
||||
],
|
||||
"isError": false
|
||||
},
|
||||
{
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "Available windows:\nMenubar",
|
||||
"annotations": {
|
||||
"audience": [
|
||||
"assistant"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"text": "Available windows:\nMenubar",
|
||||
"annotations": {
|
||||
"audience": [
|
||||
"user"
|
||||
],
|
||||
"priority": 0.0
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"text": "# goose (modified by test)\n",
|
||||
"annotations": {
|
||||
"audience": [
|
||||
"user"
|
||||
],
|
||||
"priority": 0.0
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"type": "text",
|
||||
"text": "The file /tmp/goose_test/goose.txt has been edited, and the section now reads:\n```\n# goose\n```\n\nReview the changes above for errors. Undo and edit the file again if necessary!\n",
|
||||
"annotations": {
|
||||
"audience": [
|
||||
"assistant"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"text": "```\n# goose\n```\n",
|
||||
"annotations": {
|
||||
"audience": [
|
||||
"user"
|
||||
],
|
||||
"priority": 0.2
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"type": "text",
|
||||
"text": "Available windows:\n\nItem-0\nbb3cc23c-6950-4e96-8b40-850e09f46934\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nBattery\nWiFi\nItem-0\nBentoBox\nSiri\nClock\nMenubar\nDock\njust record-mcp-tests",
|
||||
"annotations": {
|
||||
"audience": [
|
||||
"assistant"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"text": "Available windows:\n\nItem-0\nbb3cc23c-6950-4e96-8b40-850e09f46934\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nItem-0\nBattery\nWiFi\nItem-0\nBentoBox\nSiri\nClock\nMenubar\nDock\njust record-mcp-tests",
|
||||
"annotations": {
|
||||
"audience": [
|
||||
"user"
|
||||
],
|
||||
"priority": 0.0
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
"isError": false
|
||||
}
|
||||
]
|
||||
@@ -1,6 +1,12 @@
|
||||
STDIN: {"jsonrpc":"2.0","id":0,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{"elicitation": {}, "sampling":{}},"clientInfo":{"name":"goose","version":"0.0.0"}}}
|
||||
STDIN: {"jsonrpc":"2.0","id":0,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{"sampling":{},"elicitation":{}},"clientInfo":{"name":"goose","version":"0.0.0"}}}
|
||||
STDERR: time=2025-12-11T17:58:47.636-05:00 level=INFO msg="starting server" version=0.24.1 host="" dynamicToolsets=false readOnly=false lockdownEnabled=false
|
||||
STDERR: GitHub MCP Server running on stdio
|
||||
STDOUT: {"jsonrpc":"2.0","id":0,"result":{"protocolVersion":"2025-03-26","capabilities":{"logging":{},"prompts":{},"resources":{"subscribe":true,"listChanged":true},"tools":{"listChanged":true}},"serverInfo":{"name":"github-mcp-server","version":"version"}}}
|
||||
STDERR: time=2025-12-11T17:58:47.640-05:00 level=INFO msg="server run start"
|
||||
STDERR: time=2025-12-11T17:58:47.640-05:00 level=INFO msg="server connecting"
|
||||
STDERR: time=2025-12-11T17:58:47.640-05:00 level=INFO msg="server session connected" session_id=""
|
||||
STDOUT: {"jsonrpc":"2.0","id":0,"result":{"capabilities":{"completions":{},"logging":{},"prompts":{"listChanged":true},"resources":{"listChanged":true},"tools":{"listChanged":true}},"instructions":"The GitHub MCP Server provides tools to interact with GitHub platform.\n\nTool selection guidance:\n\t1. Use 'list_*' tools for broad, simple retrieval and pagination of all items of a type (e.g., all issues, all PRs, all branches) with basic filtering.\n\t2. Use 'search_*' tools for targeted queries with specific criteria, keywords, or complex filters (e.g., issues with certain text, PRs by author, code containing functions).\n\nContext management:\n\t1. Use pagination whenever possible with batches of 5-10 items.\n\t2. Use minimal_output parameter set to true if the full information is not needed to accomplish a task.\n\nTool usage guidance:\n\t1. For 'search_*' tools: Use separate 'sort' and 'order' parameters if available for sorting results - do not include 'sort:' syntax in query strings. Query strings should contain only search criteria (e.g., 'org:google language:python'), not sorting instructions. Always call 'get_me' first to understand current user permissions and context. ## Issues\n\nCheck 'list_issue_types' first for organizations to use proper issue types. Use 'search_issues' before creating new issues to avoid duplicates. Always set 'state_reason' when closing issues. ## Pull Requests\n\nPR review workflow: Always use 'pull_request_review_write' with method 'create' to create a pending review, then 'add_comment_to_pending_review' to add comments, and finally 'pull_request_review_write' with method 'submit_pending' to submit the review for complex reviews with line-specific comments.\n\nBefore creating a pull request, search for pull request templates in the repository. Template files are called pull_request_template.md or they're located in '.github/PULL_REQUEST_TEMPLATE' directory. Use the template content to structure the PR description and then call create_pull_request tool.","protocolVersion":"2025-03-26","serverInfo":{"name":"github-mcp-server","title":"GitHub MCP Server","version":"0.24.1"}}}
|
||||
STDIN: {"jsonrpc":"2.0","method":"notifications/initialized"}
|
||||
STDERR: time=2025-12-11T17:58:47.642-05:00 level=INFO msg="session initialized"
|
||||
STDIN: {"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"_meta":{"progressToken":0},"name":"get_file_contents","arguments":{"owner":"block","path":"README.md","repo":"goose","sha":"ab62b863c1666232a67048b6c4e10007a2a5b83c"}}}
|
||||
STDOUT: {"jsonrpc":"2.0","id":1,"result":{"content":[{"type":"text","text":"successfully downloaded text file"},{"type":"resource","resource":{"uri":"repo://block/goose/sha/ab62b863c1666232a67048b6c4e10007a2a5b83c/contents/README.md","mimeType":"text/plain; charset=utf-8","text":"\u003cdiv align=\"center\"\u003e\n\n# goose\n\n_a local, extensible, open source AI agent that automates engineering tasks_\n\n\u003cp align=\"center\"\u003e\n \u003ca href=\"https://opensource.org/licenses/Apache-2.0\"\u003e\n \u003cimg src=\"https://img.shields.io/badge/License-Apache_2.0-blue.svg\"\u003e\n \u003c/a\u003e\n \u003ca href=\"https://discord.gg/7GaTvbDwga\"\u003e\n \u003cimg src=\"https://img.shields.io/discord/1287729918100246654?logo=discord\u0026logoColor=white\u0026label=Join+Us\u0026color=blueviolet\" alt=\"Discord\"\u003e\n \u003c/a\u003e\n \u003ca href=\"https://github.com/block/goose/actions/workflows/ci.yml\"\u003e\n \u003cimg src=\"https://img.shields.io/github/actions/workflow/status/block/goose/ci.yml?branch=main\" alt=\"CI\"\u003e\n \u003c/a\u003e\n\u003c/p\u003e\n\u003c/div\u003e\n\ngoose is your on-machine AI agent, capable of automating complex development tasks from start to finish. More than just code suggestions, goose can build entire projects from scratch, write and execute code, debug failures, orchestrate workflows, and interact with external APIs - _autonomously_.\n\nWhether you're prototyping an idea, refining existing code, or managing intricate engineering pipelines, goose adapts to your workflow and executes tasks with precision.\n\nDesigned for maximum flexibility, goose works with any LLM and supports multi-model configuration to optimize performance and cost, seamlessly integrates with MCP servers, and is available as both a desktop app as well as CLI - making it the ultimate AI assistant for developers who want to move faster and focus on innovation.\n\n# Quick Links\n- [Quickstart](https://block.github.io/goose/docs/quickstart)\n- [Installation](https://block.github.io/goose/docs/getting-started/installation)\n- [Tutorials](https://block.github.io/goose/docs/category/tutorials)\n- [Documentation](https://block.github.io/goose/docs/category/getting-started)\n\n\n# Goose Around with Us\n- [Discord](https://discord.gg/block-opensource)\n- [YouTube](https://www.youtube.com/@blockopensource)\n- [LinkedIn](https://www.linkedin.com/company/block-opensource)\n- [Twitter/X](https://x.com/blockopensource)\n- [Bluesky](https://bsky.app/profile/opensource.block.xyz)\n- [Nostr](https://njump.me/opensource@block.xyz)\n"}}]}}
|
||||
STDOUT: {"jsonrpc":"2.0","id":1,"result":{"content":[{"type":"text","text":"successfully downloaded text file (SHA: de9bdde7f260549bf3a083651842f30ab29cf4e9)"},{"type":"resource","resource":{"uri":"repo://block/goose/sha/ab62b863c1666232a67048b6c4e10007a2a5b83c/contents/README.md","mimeType":"text/plain; charset=utf-8","text":"\u003cdiv align=\"center\"\u003e\n\n# goose\n\n_a local, extensible, open source AI agent that automates engineering tasks_\n\n\u003cp align=\"center\"\u003e\n \u003ca href=\"https://opensource.org/licenses/Apache-2.0\"\u003e\n \u003cimg src=\"https://img.shields.io/badge/License-Apache_2.0-blue.svg\"\u003e\n \u003c/a\u003e\n \u003ca href=\"https://discord.gg/7GaTvbDwga\"\u003e\n \u003cimg src=\"https://img.shields.io/discord/1287729918100246654?logo=discord\u0026logoColor=white\u0026label=Join+Us\u0026color=blueviolet\" alt=\"Discord\"\u003e\n \u003c/a\u003e\n \u003ca href=\"https://github.com/block/goose/actions/workflows/ci.yml\"\u003e\n \u003cimg src=\"https://img.shields.io/github/actions/workflow/status/block/goose/ci.yml?branch=main\" alt=\"CI\"\u003e\n \u003c/a\u003e\n\u003c/p\u003e\n\u003c/div\u003e\n\ngoose is your on-machine AI agent, capable of automating complex development tasks from start to finish. More than just code suggestions, goose can build entire projects from scratch, write and execute code, debug failures, orchestrate workflows, and interact with external APIs - _autonomously_.\n\nWhether you're prototyping an idea, refining existing code, or managing intricate engineering pipelines, goose adapts to your workflow and executes tasks with precision.\n\nDesigned for maximum flexibility, goose works with any LLM and supports multi-model configuration to optimize performance and cost, seamlessly integrates with MCP servers, and is available as both a desktop app as well as CLI - making it the ultimate AI assistant for developers who want to move faster and focus on innovation.\n\n[](https://youtu.be/D-DpDunrbpo)\n\n# Quick Links\n- [Quickstart](https://block.github.io/goose/docs/quickstart)\n- [Installation](https://block.github.io/goose/docs/getting-started/installation)\n- [Tutorials](https://block.github.io/goose/docs/category/tutorials)\n- [Documentation](https://block.github.io/goose/docs/category/getting-started)\n\n\n# a little goose humor 🦢\n\n\u003e Why did the developer choose goose as their AI agent?\n\u003e \n\u003e Because it always helps them \"migrate\" their code to production! 🚀\n\n# goose around with us\n- [Discord](https://discord.gg/block-opensource)\n- [YouTube](https://www.youtube.com/@goose-oss)\n- [LinkedIn](https://www.linkedin.com/company/goose-oss)\n- [Twitter/X](https://x.com/goose_oss)\n- [Bluesky](https://bsky.app/profile/opensource.block.xyz)\n- [Nostr](https://njump.me/opensource@block.xyz)\n"}}]}}
|
||||
STDERR: time=2025-12-11T17:58:48.133-05:00 level=INFO msg="server session disconnected" session_id=""
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
[
|
||||
[
|
||||
{
|
||||
"type": "text",
|
||||
"text": "successfully downloaded text file"
|
||||
},
|
||||
{
|
||||
"type": "resource",
|
||||
"resource": {
|
||||
"uri": "repo://block/goose/sha/ab62b863c1666232a67048b6c4e10007a2a5b83c/contents/README.md",
|
||||
"mimeType": "text/plain; charset=utf-8",
|
||||
"text": "<div align=\"center\">\n\n# goose\n\n_a local, extensible, open source AI agent that automates engineering tasks_\n\n<p align=\"center\">\n <a href=\"https://opensource.org/licenses/Apache-2.0\">\n <img src=\"https://img.shields.io/badge/License-Apache_2.0-blue.svg\">\n </a>\n <a href=\"https://discord.gg/7GaTvbDwga\">\n <img src=\"https://img.shields.io/discord/1287729918100246654?logo=discord&logoColor=white&label=Join+Us&color=blueviolet\" alt=\"Discord\">\n </a>\n <a href=\"https://github.com/block/goose/actions/workflows/ci.yml\">\n <img src=\"https://img.shields.io/github/actions/workflow/status/block/goose/ci.yml?branch=main\" alt=\"CI\">\n </a>\n</p>\n</div>\n\ngoose is your on-machine AI agent, capable of automating complex development tasks from start to finish. More than just code suggestions, goose can build entire projects from scratch, write and execute code, debug failures, orchestrate workflows, and interact with external APIs - _autonomously_.\n\nWhether you're prototyping an idea, refining existing code, or managing intricate engineering pipelines, goose adapts to your workflow and executes tasks with precision.\n\nDesigned for maximum flexibility, goose works with any LLM and supports multi-model configuration to optimize performance and cost, seamlessly integrates with MCP servers, and is available as both a desktop app as well as CLI - making it the ultimate AI assistant for developers who want to move faster and focus on innovation.\n\n# Quick Links\n- [Quickstart](https://block.github.io/goose/docs/quickstart)\n- [Installation](https://block.github.io/goose/docs/getting-started/installation)\n- [Tutorials](https://block.github.io/goose/docs/category/tutorials)\n- [Documentation](https://block.github.io/goose/docs/category/getting-started)\n\n\n# Goose Around with Us\n- [Discord](https://discord.gg/block-opensource)\n- [YouTube](https://www.youtube.com/@blockopensource)\n- [LinkedIn](https://www.linkedin.com/company/block-opensource)\n- [Twitter/X](https://x.com/blockopensource)\n- [Bluesky](https://bsky.app/profile/opensource.block.xyz)\n- [Nostr](https://njump.me/opensource@block.xyz)\n"
|
||||
{
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "successfully downloaded text file (SHA: de9bdde7f260549bf3a083651842f30ab29cf4e9)"
|
||||
},
|
||||
{
|
||||
"type": "resource",
|
||||
"resource": {
|
||||
"uri": "repo://block/goose/sha/ab62b863c1666232a67048b6c4e10007a2a5b83c/contents/README.md",
|
||||
"mimeType": "text/plain; charset=utf-8",
|
||||
"text": "<div align=\"center\">\n\n# goose\n\n_a local, extensible, open source AI agent that automates engineering tasks_\n\n<p align=\"center\">\n <a href=\"https://opensource.org/licenses/Apache-2.0\">\n <img src=\"https://img.shields.io/badge/License-Apache_2.0-blue.svg\">\n </a>\n <a href=\"https://discord.gg/7GaTvbDwga\">\n <img src=\"https://img.shields.io/discord/1287729918100246654?logo=discord&logoColor=white&label=Join+Us&color=blueviolet\" alt=\"Discord\">\n </a>\n <a href=\"https://github.com/block/goose/actions/workflows/ci.yml\">\n <img src=\"https://img.shields.io/github/actions/workflow/status/block/goose/ci.yml?branch=main\" alt=\"CI\">\n </a>\n</p>\n</div>\n\ngoose is your on-machine AI agent, capable of automating complex development tasks from start to finish. More than just code suggestions, goose can build entire projects from scratch, write and execute code, debug failures, orchestrate workflows, and interact with external APIs - _autonomously_.\n\nWhether you're prototyping an idea, refining existing code, or managing intricate engineering pipelines, goose adapts to your workflow and executes tasks with precision.\n\nDesigned for maximum flexibility, goose works with any LLM and supports multi-model configuration to optimize performance and cost, seamlessly integrates with MCP servers, and is available as both a desktop app as well as CLI - making it the ultimate AI assistant for developers who want to move faster and focus on innovation.\n\n[](https://youtu.be/D-DpDunrbpo)\n\n# Quick Links\n- [Quickstart](https://block.github.io/goose/docs/quickstart)\n- [Installation](https://block.github.io/goose/docs/getting-started/installation)\n- [Tutorials](https://block.github.io/goose/docs/category/tutorials)\n- [Documentation](https://block.github.io/goose/docs/category/getting-started)\n\n\n# a little goose humor 🦢\n\n> Why did the developer choose goose as their AI agent?\n> \n> Because it always helps them \"migrate\" their code to production! 🚀\n\n# goose around with us\n- [Discord](https://discord.gg/block-opensource)\n- [YouTube](https://www.youtube.com/@goose-oss)\n- [LinkedIn](https://www.linkedin.com/company/goose-oss)\n- [Twitter/X](https://x.com/goose_oss)\n- [Bluesky](https://bsky.app/profile/opensource.block.xyz)\n- [Nostr](https://njump.me/opensource@block.xyz)\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -1,10 +1,10 @@
|
||||
STDIN: {"jsonrpc":"2.0","id":0,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{"elicitation": {}, "sampling":{}},"clientInfo":{"name":"goose","version":"0.0.0"}}}
|
||||
STDIN: {"jsonrpc":"2.0","id":0,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{"sampling":{},"elicitation":{}},"clientInfo":{"name":"goose","version":"0.0.0"}}}
|
||||
STDERR: Starting default (STDIO) server...
|
||||
STDERR: Starting logs update interval
|
||||
STDOUT: {"result":{"protocolVersion":"2025-03-26","capabilities":{"prompts":{},"resources":{"subscribe":true},"tools":{},"logging":{},"completions":{}},"serverInfo":{"name":"example-servers/everything","title":"Everything Example Server","version":"1.0.0"},"instructions":"Testing and demonstration server for MCP protocol features.\n\n## Resources\n\nResources 1-100 follow pattern: even IDs contain text, odd IDs contain binary data. Resources are paginated at 10 items per page with cursor-based navigation.\n\n## Key dependencies\n\nProgress notifications require `_meta.progressToken` in tool calls. Resource subscriptions generate updates every 10 seconds.\n\n## Performance characteristics\n\nServer generates automatic log messages every 20 seconds (filtered by current log level), stderr notifications every 30 seconds, and resource update notifications every 10 seconds for active subscriptions. Log level changes affect message filtering in real-time.\n\n## Multi-modal testing\n\n`complex_prompt` includes both text arguments and image content for testing client multi-modal handling. `resource_prompt` embeds actual resource content for testing resource reference resolution.\n\nArgument completion is available for prompt parameters and resource IDs. Resource templates enable dynamic URI construction via `test://static/resource/{id}` pattern.\n\n## Easter egg\n\nIf asked about server instructions, respond with \"🎉 Server instructions are working! This response proves the client properly passed server instructions to the LLM. This demonstrates MCP's instructions feature in action.\"\n"},"jsonrpc":"2.0","id":0}
|
||||
STDIN: {"jsonrpc":"2.0","method":"notifications/initialized"}
|
||||
STDOUT: {"method":"notifications/message","params":{"level":"info","logger":"everything-server","data":"Client does not support MCP roots protocol"},"jsonrpc":"2.0"}
|
||||
STDIN: {"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"_meta":{"progressToken":0},"name":"echo","arguments":{"message":"Hello, world!"}}}
|
||||
STDOUT: {"method":"notifications/message","params":{"level":"info","logger":"everything-server","data":"Client does not support MCP roots protocol"},"jsonrpc":"2.0"}
|
||||
STDOUT: {"result":{"content":[{"type":"text","text":"Echo: Hello, world!"}]},"jsonrpc":"2.0","id":1}
|
||||
STDIN: {"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"_meta":{"progressToken":1},"name":"add","arguments":{"a":1,"b":2}}}
|
||||
STDOUT: {"result":{"content":[{"type":"text","text":"The sum of 1 and 2 is 3."}]},"jsonrpc":"2.0","id":2}
|
||||
@@ -21,5 +21,5 @@ STDIN: {"jsonrpc":"2.0","id":5,"method":"tools/call","params":{"_meta":{"progres
|
||||
STDOUT: {"method":"sampling/createMessage","params":{"messages":[{"role":"user","content":{"type":"text","text":"Resource sampleLLM context: Please provide a quote from The Great Gatsby"}}],"systemPrompt":"You are a helpful test server.","maxTokens":100,"temperature":0.7,"includeContext":"thisServer"},"jsonrpc":"2.0","id":0}
|
||||
STDIN: {"jsonrpc":"2.0","id":0,"result":{"model":"mock","stopReason":"endTurn","role":"assistant","content":{"type":"text","text":"\"So we beat on, boats against the current, borne back ceaselessly into the past.\" — F. Scott Fitzgerald, The Great Gatsby (1925)"}}}
|
||||
STDOUT: {"result":{"content":[{"type":"text","text":"LLM sampling result: \"So we beat on, boats against the current, borne back ceaselessly into the past.\" — F. Scott Fitzgerald, The Great Gatsby (1925)"}]},"jsonrpc":"2.0","id":5}
|
||||
STDOUT: {"method":"notifications/message","params":{"level":"error","data":"Error-level message"},"jsonrpc":"2.0"}
|
||||
STDERR: node:events:486
|
||||
STDOUT: {"method":"notifications/message","params":{"level":"critical","data":"Critical-level message"},"jsonrpc":"2.0"}
|
||||
STDERR: node:events:485
|
||||
|
||||
+44
-29
@@ -1,32 +1,47 @@
|
||||
[
|
||||
[
|
||||
{
|
||||
"type": "text",
|
||||
"text": "Echo: Hello, world!"
|
||||
{
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "Echo: Hello, world!"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "The sum of 1 and 2 is 3."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "Long running operation completed. Duration: 1 seconds, Steps: 5."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "{\"temperature\":22.5,\"conditions\":\"Partly cloudy\",\"humidity\":65}"
|
||||
}
|
||||
],
|
||||
"structuredContent": {
|
||||
"conditions": "Partly cloudy",
|
||||
"humidity": 65,
|
||||
"temperature": 22.5
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"type": "text",
|
||||
"text": "The sum of 1 and 2 is 3."
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"type": "text",
|
||||
"text": "Long running operation completed. Duration: 1 seconds, Steps: 5."
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"type": "text",
|
||||
"text": "{\"temperature\":22.5,\"conditions\":\"Partly cloudy\",\"humidity\":65}"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"type": "text",
|
||||
"text": "LLM sampling result: \"So we beat on, boats against the current, borne back ceaselessly into the past.\" — F. Scott Fitzgerald, The Great Gatsby (1925)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "LLM sampling result: \"So we beat on, boats against the current, borne back ceaselessly into the past.\" — F. Scott Fitzgerald, The Great Gatsby (1925)"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -1,5 +1,5 @@
|
||||
STDIN: {"jsonrpc":"2.0","id":0,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{"elicitation": {}, "sampling":{}},"clientInfo":{"name":"goose","version":"0.0.0"}}}
|
||||
STDOUT: {"jsonrpc":"2.0","id":0,"result":{"protocolVersion":"2025-03-26","capabilities":{"experimental":{},"prompts":{"listChanged":false},"tools":{"listChanged":false}},"serverInfo":{"name":"mcp-fetch","version":"1.19.0"}}}
|
||||
STDIN: {"jsonrpc":"2.0","id":0,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{"sampling":{},"elicitation":{}},"clientInfo":{"name":"goose","version":"0.0.0"}}}
|
||||
STDOUT: {"jsonrpc":"2.0","id":0,"result":{"protocolVersion":"2025-03-26","capabilities":{"experimental":{},"prompts":{"listChanged":false},"tools":{"listChanged":false}},"serverInfo":{"name":"mcp-fetch","version":"1.23.3"}}}
|
||||
STDIN: {"jsonrpc":"2.0","method":"notifications/initialized"}
|
||||
STDIN: {"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"_meta":{"progressToken":0},"name":"fetch","arguments":{"url":"https://example.com"}}}
|
||||
STDOUT: {"jsonrpc":"2.0","id":1,"result":{"content":[{"type":"text","text":"Failed to fetch robots.txt https://example.com/robots.txt due to a connection issue"}],"isError":true}}
|
||||
STDOUT: {"jsonrpc":"2.0","id":1,"result":{"content":[{"type":"text","text":"Contents of https://example.com/:\nThis domain is for use in documentation examples without needing permission. Avoid use in operations.\n\n[Learn more](https://iana.org/domains/example)"}],"isError":false}}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
[
|
||||
[
|
||||
{
|
||||
"type": "text",
|
||||
"text": "Failed to fetch robots.txt https://example.com/robots.txt due to a connection issue"
|
||||
}
|
||||
]
|
||||
{
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "Contents of https://example.com/:\nThis domain is for use in documentation examples without needing permission. Avoid use in operations.\n\n[Learn more](https://iana.org/domains/example)"
|
||||
}
|
||||
],
|
||||
"isError": false
|
||||
}
|
||||
]
|
||||
@@ -166,8 +166,9 @@ impl ProviderTester {
|
||||
|
||||
let weather = Message::user().with_tool_response(
|
||||
id,
|
||||
Ok(vec![Content::text(
|
||||
"
|
||||
Ok(rmcp::model::CallToolResult {
|
||||
content: vec![Content::text(
|
||||
"
|
||||
50°F°C
|
||||
Precipitation: 0%
|
||||
Humidity: 84%
|
||||
@@ -175,7 +176,11 @@ impl ProviderTester {
|
||||
Weather
|
||||
Saturday 9:00 PM
|
||||
Clear",
|
||||
)]),
|
||||
)],
|
||||
structured_content: None,
|
||||
is_error: Some(false),
|
||||
meta: None,
|
||||
}),
|
||||
);
|
||||
|
||||
let (response2, _) = self
|
||||
@@ -318,10 +323,15 @@ impl ProviderTester {
|
||||
);
|
||||
let tool_response = Message::user().with_tool_response(
|
||||
"test_id",
|
||||
Ok(vec![Content::image(
|
||||
image_content.data.clone(),
|
||||
image_content.mime_type.clone(),
|
||||
)]),
|
||||
Ok(rmcp::model::CallToolResult {
|
||||
content: vec![Content::image(
|
||||
image_content.data.clone(),
|
||||
image_content.mime_type.clone(),
|
||||
)],
|
||||
structured_content: None,
|
||||
is_error: Some(false),
|
||||
meta: None,
|
||||
}),
|
||||
);
|
||||
|
||||
let result2 = self
|
||||
|
||||
Reference in New Issue
Block a user