From b4fc6f7cc87d275a215ed482aed885c686731693 Mon Sep 17 00:00:00 2001 From: Alex Hancock Date: Thu, 24 Jul 2025 14:58:14 -0400 Subject: [PATCH] alexhancock/rmcp-tools-annotations (#3617) --- crates/goose-cli/src/commands/configure.rs | 34 +-- .../goose-mcp/src/computercontroller/mod.rs | 59 ++-- crates/goose-mcp/src/developer/mod.rs | 130 ++++----- crates/goose-mcp/src/google_drive/mod.rs | 250 ++++++++--------- crates/goose-mcp/src/memory/mod.rs | 80 +++--- crates/goose-mcp/src/tutorial/mod.rs | 25 +- crates/goose-server/src/routes/agent.rs | 5 +- crates/goose-server/src/routes/extension.rs | 3 +- crates/goose-server/src/routes/reply.rs | 3 +- crates/goose/examples/image_tool.rs | 9 +- crates/goose/src/agents/agent.rs | 12 +- crates/goose/src/agents/extension.rs | 2 +- crates/goose/src/agents/extension_manager.rs | 23 +- crates/goose/src/agents/final_output_tool.rs | 29 +- crates/goose/src/agents/platform_tools.rs | 92 +++---- .../agents/recipe_tools/dynamic_task_tools.rs | 24 +- .../agents/recipe_tools/sub_recipe_tools.rs | 24 +- crates/goose/src/agents/reply_parts.rs | 8 +- .../goose/src/agents/router_tool_selector.rs | 25 +- crates/goose/src/agents/router_tools.rs | 42 ++- crates/goose/src/agents/sub_recipe_manager.rs | 3 +- crates/goose/src/agents/subagent.rs | 9 +- .../subagent_execute_task_tool.rs | 48 ++-- crates/goose/src/agents/types.rs | 4 +- crates/goose/src/context_mgmt/common.rs | 2 +- crates/goose/src/context_mgmt/summarize.rs | 2 +- .../goose/src/permission/permission_judge.rs | 35 +-- crates/goose/src/providers/anthropic.rs | 2 +- crates/goose/src/providers/azure.rs | 2 +- crates/goose/src/providers/base.rs | 2 +- crates/goose/src/providers/bedrock.rs | 2 +- crates/goose/src/providers/claude_code.rs | 2 +- crates/goose/src/providers/databricks.rs | 2 +- crates/goose/src/providers/factory.rs | 2 +- .../goose/src/providers/formats/anthropic.rs | 11 +- crates/goose/src/providers/formats/bedrock.rs | 13 +- .../goose/src/providers/formats/databricks.rs | 16 +- .../src/providers/formats/gcpvertexai.rs | 2 +- crates/goose/src/providers/formats/google.rs | 258 +++++++++--------- crates/goose/src/providers/formats/openai.rs | 16 +- .../goose/src/providers/formats/snowflake.rs | 17 +- crates/goose/src/providers/gcpvertexai.rs | 2 +- crates/goose/src/providers/gemini_cli.rs | 2 +- crates/goose/src/providers/githubcopilot.rs | 2 +- crates/goose/src/providers/google.rs | 2 +- crates/goose/src/providers/groq.rs | 2 +- crates/goose/src/providers/lead_worker.rs | 2 +- crates/goose/src/providers/litellm.rs | 2 +- crates/goose/src/providers/ollama.rs | 2 +- crates/goose/src/providers/openai.rs | 2 +- crates/goose/src/providers/openrouter.rs | 2 +- crates/goose/src/providers/sagemaker_tgi.rs | 2 +- crates/goose/src/providers/snowflake.rs | 2 +- crates/goose/src/providers/testprovider.rs | 2 +- crates/goose/src/providers/toolshim.rs | 6 +- crates/goose/src/providers/venice.rs | 4 +- crates/goose/src/providers/xai.rs | 2 +- crates/goose/src/scheduler.rs | 2 +- crates/goose/src/session/info.rs | 1 - crates/goose/src/token_counter.rs | 43 +-- crates/goose/tests/agent.rs | 13 +- crates/goose/tests/providers.rs | 9 +- crates/mcp-core/src/protocol.rs | 2 +- crates/mcp-server/src/main.rs | 62 +++-- crates/mcp-server/src/router.rs | 2 +- 65 files changed, 770 insertions(+), 731 deletions(-) diff --git a/crates/goose-cli/src/commands/configure.rs b/crates/goose-cli/src/commands/configure.rs index 5c8d6c606..3a8e792ee 100644 --- a/crates/goose-cli/src/commands/configure.rs +++ b/crates/goose-cli/src/commands/configure.rs @@ -15,9 +15,9 @@ use goose::config::{ }; use goose::message::Message; use goose::providers::{create, providers}; -use mcp_core::tool::ToolAnnotations; -use mcp_core::Tool; -use serde_json::{json, Value}; +use rmcp::model::{Tool, ToolAnnotations}; +use rmcp::object; +use serde_json::Value; use std::collections::HashMap; use std::error::Error; @@ -387,21 +387,21 @@ pub async fn configure_provider_dialog() -> Result> { let sample_tool = Tool::new( "get_weather".to_string(), "Get current temperature for a given location.".to_string(), - json!({ + object!({ "type": "object", "required": ["location"], "properties": { "location": {"type": "string"} } }), - Some(ToolAnnotations { - title: Some("Get weather".to_string()), - read_only_hint: true, - destructive_hint: false, - idempotent_hint: false, - open_world_hint: false, - }), - ); + ) + .annotate(ToolAnnotations { + title: Some("Get weather".to_string()), + read_only_hint: Some(true), + destructive_hint: Some(false), + idempotent_hint: Some(false), + open_world_hint: Some(false), + }); vec![sample_tool] } else { vec![] @@ -411,9 +411,8 @@ pub async fn configure_provider_dialog() -> Result> { .complete( "You are an AI agent called Goose. You use tools of connected extensions to solve problems.", &messages, - &tools - ) - .await; + &tools.into_iter().collect::>() + ).await; match result { Ok((_message, _usage)) => { @@ -1270,7 +1269,10 @@ pub async fn configure_tool_permissions_dialog() -> Result<(), Box> { .map(|tool| { ToolInfo::new( &tool.name, - &tool.description, + tool.description + .as_ref() + .map(|d| d.as_ref()) + .unwrap_or_default(), get_parameter_names(&tool), permission_manager.get_user_permission(&tool.name), ) diff --git a/crates/goose-mcp/src/computercontroller/mod.rs b/crates/goose-mcp/src/computercontroller/mod.rs index 6090f6d58..d6d281adc 100644 --- a/crates/goose-mcp/src/computercontroller/mod.rs +++ b/crates/goose-mcp/src/computercontroller/mod.rs @@ -2,7 +2,7 @@ use base64::Engine; use etcetera::{choose_app_strategy, AppStrategy}; use indoc::{formatdoc, indoc}; use reqwest::{Client, Url}; -use serde_json::{json, Value}; +use serde_json::Value; use std::{ collections::HashMap, fs, future::Future, path::PathBuf, pin::Pin, sync::Arc, sync::Mutex, }; @@ -14,11 +14,13 @@ use std::os::unix::fs::PermissionsExt; use mcp_core::{ handler::{PromptError, ResourceError, ToolError}, protocol::ServerCapabilities, - tool::{Tool, ToolAnnotations}, }; use mcp_server::router::CapabilitiesBuilder; use mcp_server::Router; -use rmcp::model::{AnnotateAble, Content, JsonRpcMessage, Prompt, RawResource, Resource}; +use rmcp::model::{ + AnnotateAble, Content, JsonRpcMessage, Prompt, RawResource, Resource, Tool, ToolAnnotations, +}; +use rmcp::object; mod docx_tool; mod pdf_tool; @@ -58,7 +60,7 @@ impl ComputerControllerRouter { The content is cached locally and can be accessed later using the cache_path returned in the response. "#}, - json!({ + object!({ "type": "object", "required": ["url"], "properties": { @@ -74,14 +76,14 @@ impl ComputerControllerRouter { } } }), - Some(ToolAnnotations { - title: Some("Web Scrape".to_string()), - read_only_hint: true, - destructive_hint: false, - idempotent_hint: false, - open_world_hint: true, - }), - ); + ) + .annotate(ToolAnnotations { + title: Some("Web Scrape".to_string()), + read_only_hint: Some(true), + destructive_hint: Some(false), + idempotent_hint: Some(false), + open_world_hint: Some(true), + }); let computer_control_desc = match std::env::consts::OS { "windows" => indoc! {r#" @@ -131,7 +133,7 @@ impl ComputerControllerRouter { let computer_control_tool = Tool::new( "computer_control", computer_control_desc.to_string(), - json!({ + object!({ "type": "object", "required": ["script"], "properties": { @@ -146,7 +148,6 @@ impl ComputerControllerRouter { } } }), - None, ); let quick_script_desc = match std::env::consts::OS { @@ -177,7 +178,7 @@ impl ComputerControllerRouter { let quick_script_tool = Tool::new( "automation_script", quick_script_desc.to_string(), - json!({ + object!({ "type": "object", "required": ["language", "script"], "properties": { @@ -197,7 +198,6 @@ impl ComputerControllerRouter { } } }), - None, ); let cache_tool = Tool::new( @@ -209,7 +209,7 @@ impl ComputerControllerRouter { - delete: Delete a cached file - clear: Clear all cached files "#}, - json!({ + object!({ "type": "object", "required": ["command"], "properties": { @@ -224,7 +224,6 @@ impl ComputerControllerRouter { } } }), - None, ); let pdf_tool = Tool::new( @@ -237,7 +236,7 @@ impl ComputerControllerRouter { Use this when there is a .pdf file or files that need to be processed. "#}, - json!({ + object!({ "type": "object", "required": ["path", "operation"], "properties": { @@ -252,14 +251,14 @@ impl ComputerControllerRouter { } } }), - Some(ToolAnnotations { - title: Some("PDF process".to_string()), - read_only_hint: true, - destructive_hint: false, - idempotent_hint: true, - open_world_hint: false, - }), - ); + ) + .annotate(ToolAnnotations { + title: Some("PDF process".to_string()), + read_only_hint: Some(true), + destructive_hint: Some(false), + idempotent_hint: Some(true), + open_world_hint: Some(false), + }); let docx_tool = Tool::new( "docx_tool", @@ -276,7 +275,7 @@ impl ComputerControllerRouter { Use this when there is a .docx file that needs to be processed or created. "#}, - json!({ + object!({ "type": "object", "required": ["path", "operation"], "properties": { @@ -357,7 +356,6 @@ impl ComputerControllerRouter { } } }), - None, ); let xlsx_tool = Tool::new( @@ -375,7 +373,7 @@ impl ComputerControllerRouter { Use this when working with Excel spreadsheets to analyze or modify data. "#}, - json!({ + object!({ "type": "object", "required": ["path", "operation"], "properties": { @@ -419,7 +417,6 @@ impl ComputerControllerRouter { } } }), - None, ); // choose_app_strategy().cache_dir() diff --git a/crates/goose-mcp/src/developer/mod.rs b/crates/goose-mcp/src/developer/mod.rs index 3344905d9..814a16b97 100644 --- a/crates/goose-mcp/src/developer/mod.rs +++ b/crates/goose-mcp/src/developer/mod.rs @@ -6,7 +6,7 @@ use anyhow::Result; use base64::Engine; use etcetera::{choose_app_strategy, AppStrategy}; use indoc::formatdoc; -use serde_json::{json, Value}; +use serde_json::Value; use std::{ collections::HashMap, future::Future, @@ -25,7 +25,6 @@ use include_dir::{include_dir, Dir}; use mcp_core::{ handler::{PromptError, ResourceError, ToolError}, protocol::ServerCapabilities, - tool::{Tool, ToolAnnotations}, }; use mcp_server::router::CapabilitiesBuilder; @@ -33,7 +32,7 @@ use mcp_server::Router; use rmcp::model::{ Content, JsonRpcMessage, JsonRpcNotification, JsonRpcVersion2_0, Notification, Prompt, - PromptArgument, PromptTemplate, Resource, Role, + PromptArgument, PromptTemplate, Resource, Role, Tool, ToolAnnotations, }; use rmcp::object; @@ -166,14 +165,13 @@ impl DeveloperRouter { let bash_tool = Tool::new( "shell".to_string(), shell_tool_desc.to_string(), - json!({ + object!({ "type": "object", "required": ["command"], "properties": { "command": {"type": "string"} } }), - None, ); let glob_tool = Tool::new( @@ -193,22 +191,21 @@ impl DeveloperRouter { Use this tool when you need to locate files by name patterns rather than content. "#}.to_string(), - json!({ + object!({ "type": "object", "required": ["pattern"], "properties": { "pattern": {"type": "string", "description": "The glob pattern to search for"}, "path": {"type": "string", "description": "The directory to search in (defaults to current directory)"} } - }), - Some(ToolAnnotations { - title: Some("Search files by pattern".to_string()), - read_only_hint: true, - destructive_hint: false, - idempotent_hint: true, - open_world_hint: false, - }), - ); + }) + ).annotate(ToolAnnotations { + title: Some("Search files by pattern".to_string()), + read_only_hint: Some(true), + destructive_hint: Some(false), + idempotent_hint: Some(true), + open_world_hint: Some(false), + }); let grep_tool = Tool::new( "grep".to_string(), @@ -242,21 +239,20 @@ impl DeveloperRouter { properly filters results to respect ignored files. "#} .to_string(), - json!({ + object!({ "type": "object", "required": ["command"], "properties": { "command": {"type": "string", "description": "The search command to execute (rg, grep, find, etc.)"} } - }), - Some(ToolAnnotations { - title: Some("Search file contents".to_string()), - read_only_hint: true, - destructive_hint: false, - idempotent_hint: true, - open_world_hint: false, - }), - ); + }) + ).annotate(ToolAnnotations { + title: Some("Search file contents".to_string()), + read_only_hint: Some(true), + destructive_hint: Some(false), + idempotent_hint: Some(true), + open_world_hint: Some(false), + }); // Create text editor tool with different descriptions based on editor API configuration let (text_editor_desc, str_replace_command) = if let Some(ref editor) = editor_model { @@ -307,7 +303,7 @@ impl DeveloperRouter { let text_editor_tool = Tool::new( "text_editor".to_string(), text_editor_desc.to_string(), - json!({ + object!({ "type": "object", "required": ["command", "path"], "properties": { @@ -336,7 +332,6 @@ impl DeveloperRouter { "file_text": {"type": "string"} } }), - None, ); let list_windows_tool = Tool::new( @@ -346,19 +341,19 @@ impl DeveloperRouter { Returns a list of window titles that can be used with the window_title parameter of the screen_capture tool. "#}, - json!({ + object!({ "type": "object", "required": [], "properties": {} }), - Some(ToolAnnotations { - title: Some("List available windows".to_string()), - read_only_hint: true, - destructive_hint: false, - idempotent_hint: false, - open_world_hint: false, - }), - ); + ) + .annotate(ToolAnnotations { + title: Some("List available windows".to_string()), + read_only_hint: Some(true), + destructive_hint: Some(false), + idempotent_hint: Some(false), + open_world_hint: Some(false), + }); let screen_capture_tool = Tool::new( "screen_capture", @@ -370,7 +365,7 @@ impl DeveloperRouter { Only one of display or window_title should be specified. "#}, - json!({ + object!({ "type": "object", "required": [], "properties": { @@ -385,15 +380,14 @@ impl DeveloperRouter { "description": "Optional: the exact title of the window to capture. use the list_windows tool to find the available windows." } } - }), - Some(ToolAnnotations { - title: Some("Capture a full screen".to_string()), - read_only_hint: true, - destructive_hint: false, - idempotent_hint: false, - open_world_hint: false, - }), - ); + }) + ).annotate(ToolAnnotations { + title: Some("Capture a full screen".to_string()), + read_only_hint: Some(true), + destructive_hint: Some(false), + idempotent_hint: Some(false), + open_world_hint: Some(false), + }); let image_processor_tool = Tool::new( "image_processor", @@ -405,7 +399,7 @@ impl DeveloperRouter { This allows processing image files for use in the conversation. "#}, - json!({ + object!({ "type": "object", "required": ["path"], "properties": { @@ -415,14 +409,14 @@ impl DeveloperRouter { } } }), - Some(ToolAnnotations { - title: Some("Process Image".to_string()), - read_only_hint: true, - destructive_hint: false, - idempotent_hint: true, - open_world_hint: false, - }), - ); + ) + .annotate(ToolAnnotations { + title: Some("Process Image".to_string()), + read_only_hint: Some(true), + destructive_hint: Some(false), + idempotent_hint: Some(true), + open_world_hint: Some(false), + }); // Get base instructions and working directory let cwd = std::env::current_dir().expect("should have a current working dir"); @@ -2342,20 +2336,34 @@ mod tests { // Should use traditional description with str_replace command assert!(text_editor_tool .description - .contains("Replace a string in a file with a new string")); + .as_ref() + .map_or(false, |desc| desc + .contains("Replace a string in a file with a new string"))); assert!(text_editor_tool .description - .contains("the `old_str` needs to exactly match one")); - assert!(text_editor_tool.description.contains("str_replace")); + .as_ref() + .map_or(false, |desc| desc + .contains("the `old_str` needs to exactly match one"))); + assert!(text_editor_tool + .description + .as_ref() + .map_or(false, |desc| desc.contains("str_replace"))); // Should not contain editor API description or edit_file command assert!(!text_editor_tool .description - .contains("Edit the file with the new content")); - assert!(!text_editor_tool.description.contains("edit_file")); + .as_ref() + .map_or(false, |desc| desc + .contains("Edit the file with the new content"))); assert!(!text_editor_tool .description - .contains("work out how to place old_str with it intelligently")); + .as_ref() + .map_or(false, |desc| desc.contains("edit_file"))); + assert!(!text_editor_tool + .description + .as_ref() + .map_or(false, |desc| desc + .contains("work out how to place old_str with it intelligently"))); temp_dir.close().unwrap(); } diff --git a/crates/goose-mcp/src/google_drive/mod.rs b/crates/goose-mcp/src/google_drive/mod.rs index 4ad8aeb27..b3f235dd6 100644 --- a/crates/goose-mcp/src/google_drive/mod.rs +++ b/crates/goose-mcp/src/google_drive/mod.rs @@ -7,17 +7,18 @@ use base64::Engine; use chrono::NaiveDate; use indoc::indoc; use lazy_static::lazy_static; -use mcp_core::tool::ToolAnnotations; use mcp_core::{ handler::{PromptError, ResourceError, ToolError}, protocol::ServerCapabilities, - tool::Tool, }; use mcp_server::router::CapabilitiesBuilder; use mcp_server::Router; use oauth_pkce::PkceOAuth2Client; use regex::Regex; -use rmcp::model::{AnnotateAble, Content, JsonRpcMessage, Prompt, RawResource, Resource}; +use rmcp::model::{ + AnnotateAble, Content, JsonRpcMessage, Prompt, RawResource, Resource, Tool, ToolAnnotations, +}; +use rmcp::object; use serde_json::{json, Value}; use std::io::Cursor; use std::{env, fs, future::Future, path::Path, pin::Pin, sync::Arc}; @@ -219,8 +220,8 @@ impl GoogleDriveRouter { indoc! {r#" List or search for files or labels in google drive by name, given an input search query. At least one of ('name', 'mimeType', or 'parent') are required for file searches. "#} - .to_string(), - json!({ + .to_string(), + object!({ "type": "object", "properties": { "driveType": { @@ -257,15 +258,14 @@ impl GoogleDriveRouter { } }, "required": ["driveType"], - }), - Some(ToolAnnotations { - title: Some("Search GDrive".to_string()), - read_only_hint: true, - destructive_hint: false, - idempotent_hint: false, - open_world_hint: false, - }), - ); + }) + ).annotate(ToolAnnotations { + title: Some("Search GDrive".to_string()), + read_only_hint: Some(true), + destructive_hint: Some(false), + idempotent_hint: Some(false), + open_world_hint: Some(false), + }); let read_tool = Tool::new( "read".to_string(), @@ -280,8 +280,8 @@ impl GoogleDriveRouter { Pass in "gdrive:///1QG8d8wtWe7ZfmG93sW-1h2WXDJDUkOi-9hDnvJLmWrc" Do not include any other path parameters when using URI. "#} - .to_string(), - json!({ + .to_string(), + object!({ "type": "object", "properties": { "uri": { @@ -297,23 +297,22 @@ impl GoogleDriveRouter { "description": "Whether or not to include images as base64 encoded strings, defaults to false", } }, - }), - Some(ToolAnnotations { - title: Some("Read GDrive".to_string()), - read_only_hint: true, - destructive_hint: false, - idempotent_hint: false, - open_world_hint: false, - }), - ); + }) + ).annotate(ToolAnnotations { + title: Some("Read GDrive".to_string()), + read_only_hint: Some(true), + destructive_hint: Some(false), + idempotent_hint: Some(false), + open_world_hint: Some(false) + }); let create_file_tool = Tool::new( "create_file".to_string(), indoc! {r#" Create a new file, including Document, Spreadsheet, Slides, folder, or shortcut, in Google Drive. "#} - .to_string(), - json!({ + .to_string(), + object!({ "type": "object", "properties": { "name": { @@ -346,23 +345,22 @@ impl GoogleDriveRouter { } }, "required": ["name", "mimeType"], - }), - Some(ToolAnnotations { - title: Some("Create new file in GDrive".to_string()), - read_only_hint: false, - destructive_hint: false, - idempotent_hint: false, - open_world_hint: false, - }), - ); + }) + ).annotate(ToolAnnotations { + title: Some("Create new file in GDrive".to_string()), + read_only_hint: Some(false), + destructive_hint: Some(false), + idempotent_hint: Some(false), + open_world_hint: Some(false), + }); let move_file_tool = Tool::new( "move_file".to_string(), indoc! {r#" Move a Google Drive file, folder, or shortcut to a new parent folder. You cannot move a folder to a different drive. "#} - .to_string(), - json!({ + .to_string(), + object!({ "type": "object", "properties": { "fileId": { @@ -379,23 +377,22 @@ impl GoogleDriveRouter { }, }, "required": ["fileId", "currentFolderId", "newFolderId"], - }), - Some(ToolAnnotations { - title: Some("Move file".to_string()), - read_only_hint: false, - destructive_hint: true, - idempotent_hint: false, - open_world_hint: false, - }), - ); + }) + ).annotate(ToolAnnotations { + title: Some("Move file".to_string()), + read_only_hint: Some(false), + destructive_hint: Some(true), + idempotent_hint: Some(false), + open_world_hint: Some(false), + }); let update_file_tool = Tool::new( "update_file".to_string(), indoc! {r#" Update an existing file in Google Drive with new content or edit the file's labels. "#} - .to_string(), - json!({ + .to_string(), + object!({ "type": "object", "properties": { "fileId": { @@ -486,15 +483,14 @@ impl GoogleDriveRouter { "body": ["mimeType"], "path": ["mimeType"] } - }), - Some(ToolAnnotations { - title: Some("Update a file's contents or labels".to_string()), - read_only_hint: false, - destructive_hint: true, - idempotent_hint: false, - open_world_hint: false, - }), - ); + }) + ).annotate(ToolAnnotations { + title: Some("Update a file's contents or labels".to_string()), + read_only_hint: Some(false), + destructive_hint: Some(true), + idempotent_hint: Some(false), + open_world_hint: Some(false), + }); let sheets_tool = Tool::new( "sheets_tool".to_string(), @@ -509,8 +505,8 @@ impl GoogleDriveRouter { - add_sheet: Add a new sheet (tab) to a spreadsheet - clear_values: Clear values from a range "#} - .to_string(), - json!({ + .to_string(), + object!({ "type": "object", "properties": { "spreadsheetId": { @@ -553,15 +549,14 @@ impl GoogleDriveRouter { } }, "required": ["spreadsheetId", "operation"], - }), - Some(ToolAnnotations { - title: Some("Work with Google Sheets data using various operations.".to_string()), - read_only_hint: false, - destructive_hint: true, - idempotent_hint: false, - open_world_hint: false, - }), - ); + }) + ).annotate(ToolAnnotations { + title: Some("Work with Google Sheets data using various operations.".to_string()), + read_only_hint: Some(false), + destructive_hint: Some(true), + idempotent_hint: Some(false), + open_world_hint: Some(false), + }); let docs_tool = Tool::new( "docs_tool".to_string(), @@ -575,8 +570,8 @@ impl GoogleDriveRouter { - create_paragraph: Create a new paragraph - delete_content: Delete content between positions "#} - .to_string(), - json!({ + .to_string(), + object!({ "type": "object", "properties": { "documentId": { @@ -610,15 +605,14 @@ impl GoogleDriveRouter { } }, "required": ["documentId", "operation"], - }), - Some(ToolAnnotations { - title: Some("Work with Google Docs data using various operations.".to_string()), - read_only_hint: false, - destructive_hint: true, - idempotent_hint: false, - open_world_hint: false, - }), - ); + }) + ).annotate(ToolAnnotations { + title: Some("Work with Google Docs data using various operations.".to_string()), + read_only_hint: Some(false), + destructive_hint: Some(true), + idempotent_hint: Some(false), + open_world_hint: Some(false), + }); let get_comments_tool = Tool::new( "get_comments".to_string(), @@ -626,7 +620,7 @@ impl GoogleDriveRouter { List comments for a file in google drive. "#} .to_string(), - json!({ + object!({ "type": "object", "properties": { "fileId": { @@ -636,14 +630,14 @@ impl GoogleDriveRouter { }, "required": ["fileId"], }), - Some(ToolAnnotations { - title: Some("List file comments".to_string()), - read_only_hint: true, - destructive_hint: false, - idempotent_hint: false, - open_world_hint: false, - }), - ); + ) + .annotate(ToolAnnotations { + title: Some("List file comments".to_string()), + read_only_hint: Some(true), + destructive_hint: Some(false), + idempotent_hint: Some(false), + open_world_hint: Some(false), + }); let manage_comment_tool = Tool::new( "manage_comment".to_string(), @@ -654,8 +648,8 @@ impl GoogleDriveRouter { - create: Create a comment for the latest revision of a Google Drive file. The Google Drive API only supports unanchored comments (they don't refer to a specific location in the file). - reply: Add a reply to a comment thread, or resolve a comment. "#} - .to_string(), - json!({ + .to_string(), + object!({ "type": "object", "properties": { "fileId": { @@ -681,15 +675,14 @@ impl GoogleDriveRouter { } }, "required": ["fileId", "operation", "content"], - }), - Some(ToolAnnotations { - title: Some("Manage file comment".to_string()), - read_only_hint: false, - destructive_hint: false, - idempotent_hint: false, - open_world_hint: false, - }), - ); + }) + ).annotate(ToolAnnotations { + title: Some("Manage file comment".to_string()), + read_only_hint: Some(false), + destructive_hint: Some(false), + idempotent_hint: Some(false), + open_world_hint: Some(false), + }); let list_drives_tool = Tool::new( "list_drives".to_string(), @@ -697,7 +690,7 @@ impl GoogleDriveRouter { List shared Google drives. "#} .to_string(), - json!({ + object!({ "type": "object", "properties": { "name_contains": { @@ -706,14 +699,14 @@ impl GoogleDriveRouter { } }, }), - Some(ToolAnnotations { - title: Some("List shared google drives".to_string()), - read_only_hint: true, - destructive_hint: false, - idempotent_hint: false, - open_world_hint: false, - }), - ); + ) + .annotate(ToolAnnotations { + title: Some("List shared google drives".to_string()), + read_only_hint: Some(true), + destructive_hint: Some(false), + idempotent_hint: Some(false), + open_world_hint: Some(false), + }); let get_permissions_tool = Tool::new( "get_permissions".to_string(), @@ -721,7 +714,7 @@ impl GoogleDriveRouter { List sharing permissions for a file, folder, or shared drive. "#} .to_string(), - json!({ + object!({ "type": "object", "properties": { "fileId": { @@ -731,14 +724,14 @@ impl GoogleDriveRouter { }, "required": ["fileId"], }), - Some(ToolAnnotations { - title: Some("List sharing permissions".to_string()), - read_only_hint: true, - destructive_hint: false, - idempotent_hint: false, - open_world_hint: false, - }), - ); + ) + .annotate(ToolAnnotations { + title: Some("List sharing permissions".to_string()), + read_only_hint: Some(true), + destructive_hint: Some(false), + idempotent_hint: Some(false), + open_world_hint: Some(false), + }); let sharing_tool = Tool::new( "sharing".to_string(), @@ -750,8 +743,8 @@ impl GoogleDriveRouter { - update: Update an existing permission to a different role. (You cannot change the type or to whom it is targeted). - delete: Delete an existing permission. "#} - .to_string(), - json!({ + .to_string(), + object!({ "type": "object", "properties": { "fileId": { @@ -787,15 +780,14 @@ impl GoogleDriveRouter { }, }, "required": ["fileId", "operation"], - }), - Some(ToolAnnotations { - title: Some("Manage file sharing".to_string()), - read_only_hint: false, - destructive_hint: false, - idempotent_hint: false, - open_world_hint: false, - }), - ); + }) + ).annotate(ToolAnnotations { + title: Some("Manage file sharing".to_string()), + read_only_hint: Some(false), + destructive_hint: Some(false), + idempotent_hint: Some(false), + open_world_hint: Some(false), + }); let instructions = indoc::formatdoc! {r#" Google Drive MCP Server Instructions diff --git a/crates/goose-mcp/src/memory/mod.rs b/crates/goose-mcp/src/memory/mod.rs index 102e21a5b..fee7b117b 100644 --- a/crates/goose-mcp/src/memory/mod.rs +++ b/crates/goose-mcp/src/memory/mod.rs @@ -4,13 +4,13 @@ use indoc::formatdoc; use mcp_core::{ handler::{PromptError, ResourceError, ToolError}, protocol::ServerCapabilities, - tool::{Tool, ToolAnnotations, ToolCall}, + tool::ToolCall, }; use mcp_server::router::CapabilitiesBuilder; use mcp_server::Router; -use rmcp::model::JsonRpcMessage; -use rmcp::model::{Content, Prompt, Resource}; -use serde_json::{json, Value}; +use rmcp::model::{Content, JsonRpcMessage, Prompt, Resource, Tool, ToolAnnotations}; +use rmcp::object; +use serde_json::Value; use std::{ collections::HashMap, fs, @@ -41,7 +41,7 @@ impl MemoryRouter { let remember_memory = Tool::new( "remember_memory", "Stores a memory with optional tags in a specified category", - json!({ + object!({ "type": "object", "properties": { "category": {"type": "string"}, @@ -51,19 +51,19 @@ impl MemoryRouter { }, "required": ["category", "data", "is_global"] }), - Some(ToolAnnotations { - title: Some("Remember Memory".to_string()), - read_only_hint: false, - destructive_hint: false, - idempotent_hint: true, - open_world_hint: false, - }), - ); + ) + .annotate(ToolAnnotations { + title: Some("Remember Memory".to_string()), + read_only_hint: Some(false), + destructive_hint: Some(false), + idempotent_hint: Some(true), + open_world_hint: Some(false), + }); let retrieve_memories = Tool::new( "retrieve_memories", "Retrieves all memories from a specified category", - json!({ + object!({ "type": "object", "properties": { "category": {"type": "string"}, @@ -71,19 +71,19 @@ impl MemoryRouter { }, "required": ["category", "is_global"] }), - Some(ToolAnnotations { - title: Some("Retrieve Memory".to_string()), - read_only_hint: true, - destructive_hint: false, - idempotent_hint: false, - open_world_hint: false, - }), - ); + ) + .annotate(ToolAnnotations { + title: Some("Retrieve Memory".to_string()), + read_only_hint: Some(true), + destructive_hint: Some(false), + idempotent_hint: Some(false), + open_world_hint: Some(false), + }); let remove_memory_category = Tool::new( "remove_memory_category", "Removes all memories within a specified category", - json!({ + object!({ "type": "object", "properties": { "category": {"type": "string"}, @@ -91,19 +91,19 @@ impl MemoryRouter { }, "required": ["category", "is_global"] }), - Some(ToolAnnotations { - title: Some("Remove Memory Category".to_string()), - read_only_hint: false, - destructive_hint: true, - idempotent_hint: false, - open_world_hint: false, - }), - ); + ) + .annotate(ToolAnnotations { + title: Some("Remove Memory Category".to_string()), + read_only_hint: Some(false), + destructive_hint: Some(true), + idempotent_hint: Some(false), + open_world_hint: Some(false), + }); let remove_specific_memory = Tool::new( "remove_specific_memory", "Removes a specific memory within a specified category", - json!({ + object!({ "type": "object", "properties": { "category": {"type": "string"}, @@ -112,14 +112,14 @@ impl MemoryRouter { }, "required": ["category", "memory_content", "is_global"] }), - Some(ToolAnnotations { - title: Some("Remove Specific Memory".to_string()), - read_only_hint: false, - destructive_hint: true, - idempotent_hint: false, - open_world_hint: false, - }), - ); + ) + .annotate(ToolAnnotations { + title: Some("Remove Specific Memory".to_string()), + read_only_hint: Some(false), + destructive_hint: Some(true), + idempotent_hint: Some(false), + open_world_hint: Some(false), + }); let instructions = formatdoc! {r#" This extension allows storage and retrieval of categorized information with tagging support. It's designed to help diff --git a/crates/goose-mcp/src/tutorial/mod.rs b/crates/goose-mcp/src/tutorial/mod.rs index 588242bd3..b15e3dca8 100644 --- a/crates/goose-mcp/src/tutorial/mod.rs +++ b/crates/goose-mcp/src/tutorial/mod.rs @@ -4,12 +4,12 @@ use indoc::formatdoc; use mcp_core::{ handler::{PromptError, ResourceError, ToolError}, protocol::ServerCapabilities, - tool::{Tool, ToolAnnotations}, }; use mcp_server::router::CapabilitiesBuilder; use mcp_server::Router; -use rmcp::model::{Content, JsonRpcMessage, Prompt, Resource, Role}; -use serde_json::{json, Value}; +use rmcp::model::{Content, JsonRpcMessage, Prompt, Resource, Role, Tool, ToolAnnotations}; +use rmcp::object; +use serde_json::Value; use std::{future::Future, pin::Pin}; use tokio::sync::mpsc; @@ -31,7 +31,7 @@ impl TutorialRouter { let load_tutorial = Tool::new( "load_tutorial".to_string(), "Load a specific tutorial by name. The tutorial will be returned as markdown content that provides step by step instructions.".to_string(), - json!({ + object!({ "type": "object", "required": ["name"], "properties": { @@ -40,15 +40,14 @@ impl TutorialRouter { "description": "Name of the tutorial to load, e.g. 'getting-started' or 'developer-mcp'" } } - }), - Some(ToolAnnotations { - title: Some("Load Tutorial".to_string()), - read_only_hint: true, - destructive_hint: false, - idempotent_hint: false, - open_world_hint: false, - }), - ); + }) + ).annotate(ToolAnnotations { + title: Some("Load Tutorial".to_string()), + read_only_hint: Some(true), + destructive_hint: Some(false), + idempotent_hint: Some(false), + open_world_hint: Some(false), + }); // Get base instructions and available tutorials let available_tutorials = Self::get_available_tutorials(); diff --git a/crates/goose-server/src/routes/agent.rs b/crates/goose-server/src/routes/agent.rs index a896cdb2e..1023b22db 100644 --- a/crates/goose-server/src/routes/agent.rs +++ b/crates/goose-server/src/routes/agent.rs @@ -207,7 +207,10 @@ async fn get_tools( ToolInfo::new( &tool.name, - &tool.description, + tool.description + .as_ref() + .map(|d| d.as_ref()) + .unwrap_or_default(), get_parameter_names(&tool), permission, ) diff --git a/crates/goose-server/src/routes/extension.rs b/crates/goose-server/src/routes/extension.rs index d0ddb7ccf..17e006754 100644 --- a/crates/goose-server/src/routes/extension.rs +++ b/crates/goose-server/src/routes/extension.rs @@ -8,6 +8,7 @@ use crate::state::AppState; use axum::{extract::State, routing::post, Json, Router}; use goose::agents::{extension::Envs, ExtensionConfig}; use http::{HeaderMap, StatusCode}; +use rmcp::model::Tool; use serde::{Deserialize, Serialize}; use tracing; @@ -80,7 +81,7 @@ enum ExtensionConfigRequest { /// The name to identify this extension name: String, /// The tools provided by this extension - tools: Vec, + tools: Vec, /// Optional instructions for using the tools instructions: Option, }, diff --git a/crates/goose-server/src/routes/reply.rs b/crates/goose-server/src/routes/reply.rs index 24a1f7eb1..5e1733143 100644 --- a/crates/goose-server/src/routes/reply.rs +++ b/crates/goose-server/src/routes/reply.rs @@ -404,7 +404,6 @@ mod tests { errors::ProviderError, }, }; - use mcp_core::tool::Tool; #[derive(Clone)] struct MockProvider { @@ -421,7 +420,7 @@ mod tests { &self, _system: &str, _messages: &[Message], - _tools: &[Tool], + _tools: &[rmcp::model::Tool], ) -> anyhow::Result<(Message, ProviderUsage), ProviderError> { Ok(( Message::assistant().with_text("Mock response"), diff --git a/crates/goose/examples/image_tool.rs b/crates/goose/examples/image_tool.rs index 49fc083c7..a021c7349 100644 --- a/crates/goose/examples/image_tool.rs +++ b/crates/goose/examples/image_tool.rs @@ -5,8 +5,9 @@ use goose::{ message::Message, providers::{bedrock::BedrockProvider, databricks::DatabricksProvider, openai::OpenAiProvider}, }; -use mcp_core::tool::{Tool, ToolCall}; -use rmcp::model::Content; +use mcp_core::tool::ToolCall; +use rmcp::model::{Content, Tool}; +use rmcp::object; use serde_json::json; use std::fs; @@ -42,7 +43,7 @@ async fn main() -> Result<()> { ]; // Get a response from the model about the image - let input_schema = json!({ + let input_schema = object!({ "type": "object", "required": ["path"], "properties": { @@ -57,7 +58,7 @@ async fn main() -> Result<()> { .complete( "You are a helpful assistant. Please describe any text you see in the image.", &messages, - &[Tool::new("view_image", "View an image", input_schema, None)], + &[Tool::new("view_image", "View an image", input_schema)], ) .await?; diff --git a/crates/goose/src/agents/agent.rs b/crates/goose/src/agents/agent.rs index c67ae89e5..270fd5804 100644 --- a/crates/goose/src/agents/agent.rs +++ b/crates/goose/src/agents/agent.rs @@ -42,8 +42,9 @@ use crate::providers::errors::ProviderError; use crate::recipe::{Author, Recipe, Response, Settings, SubRecipe}; use crate::scheduler_trait::SchedulerTrait; use crate::tool_monitor::{ToolCall, ToolMonitor}; -use mcp_core::{protocol::GetPromptResult, tool::Tool, ToolError, ToolResult}; +use mcp_core::{protocol::GetPromptResult, ToolError, ToolResult}; use regex::Regex; +use rmcp::model::Tool; use rmcp::model::{Content, JsonRpcMessage, Prompt}; use serde_json::Value; use tokio::sync::{mpsc, Mutex, RwLock}; @@ -538,10 +539,10 @@ impl Agent { let mut frontend_tools = self.frontend_tools.lock().await; for tool in tools { let frontend_tool = FrontendTool { - name: tool.name.clone(), + name: tool.name.to_string(), tool: tool.clone(), }; - frontend_tools.insert(tool.name.clone(), frontend_tool); + frontend_tools.insert(tool.name.to_string(), frontend_tool); } // Store instructions if provided, using "frontend" as the key let mut frontend_instructions = self.frontend_instructions.lock().await; @@ -1181,7 +1182,10 @@ impl Agent { .map(|tool| { ToolInfo::new( &tool.name, - &tool.description, + tool.description + .as_ref() + .map(|d| d.as_ref()) + .unwrap_or_default(), get_parameter_names(&tool), None, ) diff --git a/crates/goose/src/agents/extension.rs b/crates/goose/src/agents/extension.rs index 93d9ff99f..37dc58548 100644 --- a/crates/goose/src/agents/extension.rs +++ b/crates/goose/src/agents/extension.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use mcp_client::client::Error as ClientError; -use mcp_core::tool::Tool; +use rmcp::model::Tool; use serde::{Deserialize, Serialize}; use thiserror::Error; use tracing::warn; diff --git a/crates/goose/src/agents/extension_manager.rs b/crates/goose/src/agents/extension_manager.rs index 732401a02..2ee529148 100644 --- a/crates/goose/src/agents/extension_manager.rs +++ b/crates/goose/src/agents/extension_manager.rs @@ -19,8 +19,8 @@ use crate::config::{Config, ExtensionConfigManager}; use crate::prompt_template; use mcp_client::client::{ClientCapabilities, ClientInfo, McpClient, McpClientTrait}; use mcp_client::transport::{SseTransport, StdioTransport, StreamableHttpTransport, Transport}; -use mcp_core::{Tool, ToolCall, ToolError}; -use rmcp::model::{Content, Prompt, Resource, ResourceContents}; +use mcp_core::{ToolCall, ToolError}; +use rmcp::model::{Content, Prompt, Resource, ResourceContents, Tool}; use serde_json::Value; // By default, we set it to Jan 1, 2020 if the resource does not have a timestamp @@ -380,13 +380,18 @@ impl ExtensionManager { let mut client_tools = client_guard.list_tools(None).await?; loop { - for tool in client_tools.tools { - tools.push(Tool::new( - format!("{}__{}", name, tool.name), - &tool.description, - tool.input_schema, - tool.annotations, - )); + for client_tool in client_tools.tools { + let mut tool = Tool::new( + format!("{}__{}", name, client_tool.name), + client_tool.description.unwrap_or_default(), + client_tool.input_schema, + ); + + if tool.annotations.is_some() { + tool = tool.annotate(client_tool.annotations.unwrap()) + } + + tools.push(tool); } // Exit loop when there are no more pages diff --git a/crates/goose/src/agents/final_output_tool.rs b/crates/goose/src/agents/final_output_tool.rs index 0c2e779b1..8c2f2b969 100644 --- a/crates/goose/src/agents/final_output_tool.rs +++ b/crates/goose/src/agents/final_output_tool.rs @@ -1,11 +1,8 @@ use crate::agents::tool_execution::ToolCallResult; use crate::recipe::Response; use indoc::formatdoc; -use mcp_core::{ - tool::{Tool, ToolAnnotations}, - ToolCall, ToolError, -}; -use rmcp::model::Content; +use mcp_core::{ToolCall, ToolError}; +use rmcp::model::{Content, Tool, ToolAnnotations}; use serde_json::Value; pub const FINAL_OUTPUT_TOOL_NAME: &str = "recipe__final_output"; @@ -64,15 +61,21 @@ impl FinalOutputTool { Tool::new( FINAL_OUTPUT_TOOL_NAME.to_string(), instructions, - self.response.json_schema.as_ref().unwrap().clone(), - Some(ToolAnnotations { - title: Some("Final Output".to_string()), - read_only_hint: false, - destructive_hint: false, - idempotent_hint: true, - open_world_hint: false, - }), + self.response + .json_schema + .as_ref() + .unwrap() + .as_object() + .unwrap() + .clone(), ) + .annotate(ToolAnnotations { + title: Some("Final Output".to_string()), + read_only_hint: Some(false), + destructive_hint: Some(false), + idempotent_hint: Some(true), + open_world_hint: Some(false), + }) } pub fn system_prompt(&self) -> String { diff --git a/crates/goose/src/agents/platform_tools.rs b/crates/goose/src/agents/platform_tools.rs index 841c18d43..777b054d9 100644 --- a/crates/goose/src/agents/platform_tools.rs +++ b/crates/goose/src/agents/platform_tools.rs @@ -1,6 +1,6 @@ use indoc::indoc; -use mcp_core::tool::{Tool, ToolAnnotations}; -use serde_json::json; +use rmcp::model::{Tool, ToolAnnotations}; +use rmcp::object; pub const PLATFORM_READ_RESOURCE_TOOL_NAME: &str = "platform__read_resource"; pub const PLATFORM_LIST_RESOURCES_TOOL_NAME: &str = "platform__list_resources"; @@ -20,22 +20,21 @@ pub fn read_resource_tool() -> Tool { resource URI in the provided extension, and reads in the resource content. If no extension is provided, the tool will search all extensions for the resource. "#}.to_string(), - json!({ + object!({ "type": "object", "required": ["uri"], "properties": { "uri": {"type": "string", "description": "Resource URI"}, "extension_name": {"type": "string", "description": "Optional extension name"} } - }), - Some(ToolAnnotations { - title: Some("Read a resource".to_string()), - read_only_hint: true, - destructive_hint: false, - idempotent_hint: false, - open_world_hint: false, - }), - ) + }) + ).annotate(ToolAnnotations { + title: Some("Read a resource".to_string()), + read_only_hint: Some(true), + destructive_hint: Some(false), + idempotent_hint: Some(false), + open_world_hint: Some(false), + }) } pub fn list_resources_tool() -> Tool { @@ -50,20 +49,20 @@ pub fn list_resources_tool() -> Tool { is provided, the tool will search all extensions for the resource. "#} .to_string(), - json!({ + object!({ "type": "object", "properties": { "extension_name": {"type": "string", "description": "Optional extension name"} } }), - Some(ToolAnnotations { - title: Some("List resources".to_string()), - read_only_hint: true, - destructive_hint: false, - idempotent_hint: false, - open_world_hint: false, - }), ) + .annotate(ToolAnnotations { + title: Some("List resources".to_string()), + read_only_hint: Some(true), + destructive_hint: Some(false), + idempotent_hint: Some(false), + open_world_hint: Some(false), + }) } pub fn search_available_extensions_tool() -> Tool { @@ -73,19 +72,18 @@ pub fn search_available_extensions_tool() -> Tool { Use this tool when you're unable to find a specific feature or functionality you need to complete your task, or when standard approaches aren't working. These extensions might provide the exact tools needed to solve your problem. If you find a relevant one, consider using your tools to enable it.".to_string(), - json!({ + object!({ "type": "object", "required": [], "properties": {} - }), - Some(ToolAnnotations { - title: Some("Discover extensions".to_string()), - read_only_hint: true, - destructive_hint: false, - idempotent_hint: false, - open_world_hint: false, - }), - ) + }) + ).annotate(ToolAnnotations { + title: Some("Discover extensions".to_string()), + read_only_hint: Some(true), + destructive_hint: Some(false), + idempotent_hint: Some(false), + open_world_hint: Some(false), + }) } pub fn manage_extensions_tool() -> Tool { @@ -96,7 +94,7 @@ pub fn manage_extensions_tool() -> Tool { Enable or disable an extension by providing the extension name. " .to_string(), - json!({ + object!({ "type": "object", "required": ["action", "extension_name"], "properties": { @@ -104,14 +102,13 @@ pub fn manage_extensions_tool() -> Tool { "extension_name": {"type": "string", "description": "The name of the extension to enable"} } }), - Some(ToolAnnotations { - title: Some("Enable or disable an extension".to_string()), - read_only_hint: false, - destructive_hint: false, - idempotent_hint: false, - open_world_hint: false, - }), - ) + ).annotate(ToolAnnotations { + title: Some("Enable or disable an extension".to_string()), + read_only_hint: Some(false), + destructive_hint: Some(false), + idempotent_hint: Some(false), + open_world_hint: Some(false), + }) } pub fn manage_schedule_tool() -> Tool { @@ -133,7 +130,7 @@ pub fn manage_schedule_tool() -> Tool { - "session_content": Get the full content (messages) of a specific session "#} .to_string(), - json!({ + object!({ "type": "object", "required": ["action"], "properties": { @@ -149,12 +146,11 @@ pub fn manage_schedule_tool() -> Tool { "session_id": {"type": "string", "description": "Session identifier for session_content action"} } }), - Some(ToolAnnotations { - title: Some("Manage scheduled recipes".to_string()), - read_only_hint: false, - destructive_hint: true, // Can kill jobs - idempotent_hint: false, - open_world_hint: false, - }), - ) + ).annotate(ToolAnnotations { + title: Some("Manage scheduled recipes".to_string()), + read_only_hint: Some(false), + destructive_hint: Some(true), // Can kill jobs + idempotent_hint: Some(false), + open_world_hint: Some(false), + }) } diff --git a/crates/goose/src/agents/recipe_tools/dynamic_task_tools.rs b/crates/goose/src/agents/recipe_tools/dynamic_task_tools.rs index 08866258e..55e11b6fe 100644 --- a/crates/goose/src/agents/recipe_tools/dynamic_task_tools.rs +++ b/crates/goose/src/agents/recipe_tools/dynamic_task_tools.rs @@ -5,8 +5,9 @@ use crate::agents::subagent_execution_tool::tasks_manager::TasksManager; use crate::agents::subagent_execution_tool::{lib::ExecutionMode, task_types::Task}; use crate::agents::tool_execution::ToolCallResult; -use mcp_core::{tool::ToolAnnotations, Tool, ToolError}; -use rmcp::model::Content; +use mcp_core::ToolError; +use rmcp::model::{Content, Tool, ToolAnnotations}; +use rmcp::object; use serde_json::{json, Value}; pub const DYNAMIC_TASK_TOOL_NAME_PREFIX: &str = "dynamic_task__create_task"; @@ -35,7 +36,7 @@ pub fn create_dynamic_task_tool() -> Tool { text_instruction: Get weather for Los Angeles. text_instruction: Get weather for San Francisco. ".to_string(), - json!({ + object!({ "type": "object", "properties": { "task_parameters": { @@ -56,15 +57,14 @@ pub fn create_dynamic_task_tool() -> Tool { } } } - }), - Some(ToolAnnotations { - title: Some("Dynamic Task Creation".to_string()), - read_only_hint: false, - destructive_hint: true, - idempotent_hint: false, - open_world_hint: true, - }), - ) + }) + ).annotate(ToolAnnotations { + title: Some("Dynamic Task Creation".to_string()), + read_only_hint: Some(false), + destructive_hint: Some(true), + idempotent_hint: Some(false), + open_world_hint: Some(true), + }) } fn extract_task_parameters(params: &Value) -> Vec { diff --git a/crates/goose/src/agents/recipe_tools/sub_recipe_tools.rs b/crates/goose/src/agents/recipe_tools/sub_recipe_tools.rs index a283ab2bf..66da9ca22 100644 --- a/crates/goose/src/agents/recipe_tools/sub_recipe_tools.rs +++ b/crates/goose/src/agents/recipe_tools/sub_recipe_tools.rs @@ -1,8 +1,9 @@ use std::collections::HashSet; use std::fs; +use std::sync::Arc; use anyhow::Result; -use mcp_core::tool::{Tool, ToolAnnotations}; +use rmcp::model::{Tool, ToolAnnotations}; use serde_json::{json, Map, Value}; use crate::agents::subagent_execution_tool::lib::{ExecutionMode, Task}; @@ -15,6 +16,7 @@ pub const SUB_RECIPE_TASK_TOOL_NAME_PREFIX: &str = "subrecipe__create_task"; pub fn create_sub_recipe_task_tool(sub_recipe: &SubRecipe) -> Tool { let input_schema = get_input_schema(sub_recipe).unwrap(); + Tool::new( format!("{}_{}", SUB_RECIPE_TASK_TOOL_NAME_PREFIX, sub_recipe.name), format!( @@ -27,15 +29,17 @@ pub fn create_sub_recipe_task_tool(sub_recipe: &SubRecipe) -> Tool { After creating the tasks and execution_mode is provided, pass them to the task executor to run these tasks", sub_recipe.name ), - input_schema, - Some(ToolAnnotations { - title: Some(format!("create multiple sub recipe tasks for {}", sub_recipe.name)), - read_only_hint: false, - destructive_hint: true, - idempotent_hint: false, - open_world_hint: true, - }), - ) + Arc::new(input_schema.as_object().unwrap().clone()) + ).annotate(ToolAnnotations { + title: Some(format!( + "create multiple sub recipe tasks for {}", + sub_recipe.name + )), + read_only_hint: Some(false), + destructive_hint: Some(true), + idempotent_hint: Some(false), + open_world_hint: Some(true), + }) } fn extract_task_parameters(params: &Value) -> Vec { diff --git a/crates/goose/src/agents/reply_parts.rs b/crates/goose/src/agents/reply_parts.rs index 64bd196d4..cd5f217d3 100644 --- a/crates/goose/src/agents/reply_parts.rs +++ b/crates/goose/src/agents/reply_parts.rs @@ -15,7 +15,7 @@ use crate::providers::toolshim::{ modify_system_prompt_for_tool_json, OllamaInterpreter, }; use crate::session; -use mcp_core::tool::Tool; +use rmcp::model::Tool; use super::super::agents::Agent; @@ -110,11 +110,11 @@ impl Agent { .iter() .fold((HashSet::new(), HashSet::new()), |mut acc, tool| { match &tool.annotations { - Some(annotations) if annotations.read_only_hint => { - acc.0.insert(tool.name.clone()); + Some(annotations) if annotations.read_only_hint.unwrap_or(false) => { + acc.0.insert(tool.name.to_string()); } _ => { - acc.1.insert(tool.name.clone()); + acc.1.insert(tool.name.to_string()); } } acc diff --git a/crates/goose/src/agents/router_tool_selector.rs b/crates/goose/src/agents/router_tool_selector.rs index 52da661a9..a75c648d8 100644 --- a/crates/goose/src/agents/router_tool_selector.rs +++ b/crates/goose/src/agents/router_tool_selector.rs @@ -1,6 +1,6 @@ -use mcp_core::tool::Tool; use mcp_core::ToolError; use rmcp::model::Content; +use rmcp::model::Tool; use anyhow::{Context, Result}; use async_trait::async_trait; @@ -128,7 +128,15 @@ impl RouterToolSelector for VectorToolSelector { .map(|tool| { let schema_str = serde_json::to_string_pretty(&tool.input_schema) .unwrap_or_else(|_| "{}".to_string()); - format!("{} {} {}", tool.name, tool.description, schema_str) + format!( + "{} {} {}", + tool.name, + tool.description + .as_ref() + .map(|d| d.as_ref()) + .unwrap_or_default(), + schema_str + ) }) .collect(); @@ -154,8 +162,12 @@ impl RouterToolSelector for VectorToolSelector { let schema_str = serde_json::to_string_pretty(&tool.input_schema) .unwrap_or_else(|_| "{}".to_string()); crate::agents::tool_vectordb::ToolRecord { - tool_name: tool.name.clone(), - description: tool.description.clone(), + tool_name: tool.name.to_string(), + description: tool + .description + .as_ref() + .map(|d| d.to_string()) + .unwrap_or_default(), schema: schema_str, vector, extension_name: extension_name.to_string(), @@ -305,7 +317,10 @@ impl RouterToolSelector for LLMToolSelector { let tool_string = format!( "Tool: {}\nDescription: {}\nSchema: {}", tool.name, - tool.description, + tool.description + .as_ref() + .map(|d| d.as_ref()) + .unwrap_or_default(), serde_json::to_string_pretty(&tool.input_schema) .unwrap_or_else(|_| "{}".to_string()) ); diff --git a/crates/goose/src/agents/router_tools.rs b/crates/goose/src/agents/router_tools.rs index bb3b2ad0e..a4440f66b 100644 --- a/crates/goose/src/agents/router_tools.rs +++ b/crates/goose/src/agents/router_tools.rs @@ -3,8 +3,8 @@ use super::platform_tools::{ PLATFORM_READ_RESOURCE_TOOL_NAME, PLATFORM_SEARCH_AVAILABLE_EXTENSIONS_TOOL_NAME, }; use indoc::indoc; -use mcp_core::tool::{Tool, ToolAnnotations}; -use serde_json::json; +use rmcp::model::{Tool, ToolAnnotations}; +use rmcp::object; pub const ROUTER_VECTOR_SEARCH_TOOL_NAME: &str = "router__vector_search"; pub const ROUTER_LLM_SEARCH_TOOL_NAME: &str = "router__llm_search"; @@ -24,7 +24,7 @@ pub fn vector_search_tool() -> Tool { Extension name is not optional, it is required. "#} .to_string(), - json!({ + object!({ "type": "object", "required": ["query", "extension_name"], "properties": { @@ -32,15 +32,14 @@ pub fn vector_search_tool() -> Tool { "k": {"type": "integer", "description": "The number of tools to retrieve (defaults to 5)", "default": 5}, "extension_name": {"type": "string", "description": "Name of the extension to filter tools by"} } - }), - Some(ToolAnnotations { - title: Some("Vector search for relevant tools".to_string()), - read_only_hint: true, - destructive_hint: false, - idempotent_hint: false, - open_world_hint: false, - }), - ) + }) + ).annotate(ToolAnnotations { + title: Some("Vector search for relevant tools".to_string()), + read_only_hint: Some(true), + destructive_hint: Some(false), + idempotent_hint: Some(false), + open_world_hint: Some(false), + }) } pub fn vector_search_tool_prompt() -> String { @@ -81,7 +80,7 @@ pub fn llm_search_tool() -> Tool { The returned result will be a list of tool names, descriptions, and schemas from which you, the agent can select the most relevant tool to invoke. "#} .to_string(), - json!({ + object!({ "type": "object", "required": ["query", "extension_name"], "properties": { @@ -89,15 +88,14 @@ pub fn llm_search_tool() -> Tool { "query": {"type": "string", "description": "The query to search for the most relevant tools based on the user's messages"}, "k": {"type": "integer", "description": "The number of tools to retrieve (defaults to 5)", "default": 5} } - }), - Some(ToolAnnotations { - title: Some("LLM search for relevant tools".to_string()), - read_only_hint: true, - destructive_hint: false, - idempotent_hint: false, - open_world_hint: false, - }), - ) + }) + ).annotate(ToolAnnotations { + title: Some("LLM search for relevant tools".to_string()), + read_only_hint: Some(true), + destructive_hint: Some(false), + idempotent_hint: Some(false), + open_world_hint: Some(false), + }) } pub fn llm_search_tool_prompt() -> String { diff --git a/crates/goose/src/agents/sub_recipe_manager.rs b/crates/goose/src/agents/sub_recipe_manager.rs index 98431f5bb..314c3c41b 100644 --- a/crates/goose/src/agents/sub_recipe_manager.rs +++ b/crates/goose/src/agents/sub_recipe_manager.rs @@ -1,5 +1,6 @@ -use mcp_core::{Tool, ToolError}; +use mcp_core::ToolError; use rmcp::model::Content; +use rmcp::model::Tool; use serde_json::Value; use std::collections::HashMap; diff --git a/crates/goose/src/agents/subagent.rs b/crates/goose/src/agents/subagent.rs index d07030ab4..f9cf85d64 100644 --- a/crates/goose/src/agents/subagent.rs +++ b/crates/goose/src/agents/subagent.rs @@ -9,7 +9,8 @@ use crate::{ }; use anyhow::anyhow; use chrono::{DateTime, Utc}; -use mcp_core::{handler::ToolError, tool::Tool}; +use mcp_core::handler::ToolError; +use rmcp::model::Tool; use serde::{Deserialize, Serialize}; // use serde_json::{self}; use std::{collections::HashMap, sync::Arc}; @@ -336,10 +337,10 @@ impl SubAgent { let tools_with_descriptions: Vec = available_tools .iter() .map(|t| { - if t.description.is_empty() { - t.name.clone() + if let Some(description) = &t.description { + format!("{}: {}", t.name, description) } else { - format!("{}: {}", t.name, t.description) + t.name.to_string() } }) .collect(); diff --git a/crates/goose/src/agents/subagent_execution_tool/subagent_execute_task_tool.rs b/crates/goose/src/agents/subagent_execution_tool/subagent_execute_task_tool.rs index e06da4061..2527d558b 100644 --- a/crates/goose/src/agents/subagent_execution_tool/subagent_execute_task_tool.rs +++ b/crates/goose/src/agents/subagent_execution_tool/subagent_execute_task_tool.rs @@ -1,5 +1,5 @@ -use mcp_core::{tool::ToolAnnotations, Tool, ToolError}; -use rmcp::model::Content; +use mcp_core::ToolError; +use rmcp::model::{Content, Tool, ToolAnnotations}; use serde_json::Value; use crate::agents::subagent_task_config::TaskConfig; @@ -9,6 +9,7 @@ use crate::agents::{ subagent_execution_tool::tasks_manager::TasksManager, tool_execution::ToolCallResult, }; use rmcp::model::JsonRpcMessage; +use rmcp::object; use tokio::sync::mpsc; use tokio_stream; use tokio_util::sync::CancellationToken; @@ -18,21 +19,21 @@ pub fn create_subagent_execute_task_tool() -> Tool { Tool::new( SUBAGENT_EXECUTE_TASK_TOOL_NAME, "Only use the subagent__execute_task tool when you execute sub recipe task or dynamic task. -EXECUTION STRATEGY DECISION: -1. If the tasks are created with execution_mode, use the execution_mode. -2. Execute tasks sequentially unless user explicitly requests parallel execution. PARALLEL: User uses keywords like 'parallel', 'simultaneously', 'at the same time', 'concurrently' + EXECUTION STRATEGY DECISION: + 1. If the tasks are created with execution_mode, use the execution_mode. + 2. Execute tasks sequentially unless user explicitly requests parallel execution. PARALLEL: User uses keywords like 'parallel', 'simultaneously', 'at the same time', 'concurrently' -IMPLEMENTATION: -- Sequential execution: Call this tool multiple times, passing exactly ONE task per call -- Parallel execution: Call this tool once, passing an ARRAY of all tasks + IMPLEMENTATION: + - Sequential execution: Call this tool multiple times, passing exactly ONE task per call + - Parallel execution: Call this tool once, passing an ARRAY of all tasks -EXAMPLES: -User Intent Based: -- User: 'get weather and tell me a joke' → Sequential (2 separate tool calls, 1 task each) -- User: 'get weather and joke in parallel' → Parallel (1 tool call with array of 2 tasks) -- User: 'run these simultaneously' → Parallel (1 tool call with task array) -- User: 'do task A then task B' → Sequential (2 separate tool calls)", - serde_json::json!({ + EXAMPLES: + User Intent Based: + - User: 'get weather and tell me a joke' → Sequential (2 separate tool calls, 1 task each) + - User: 'get weather and joke in parallel' → Parallel (1 tool call with array of 2 tasks) + - User: 'run these simultaneously' → Parallel (1 tool call with task array) + - User: 'do task A then task B' → Sequential (2 separate tool calls)", + object!({ "type": "object", "properties": { "execution_mode": { @@ -50,15 +51,14 @@ User Intent Based: } }, "required": ["task_ids"] - }), - Some(ToolAnnotations { - title: Some("Run tasks in parallel".to_string()), - read_only_hint: false, - destructive_hint: true, - idempotent_hint: false, - open_world_hint: true, - }), - ) + }) + ).annotate(ToolAnnotations { + title: Some("Run tasks in parallel".to_string()), + read_only_hint: Some(false), + destructive_hint: Some(true), + idempotent_hint: Some(false), + open_world_hint: Some(true), + }) } pub async fn run_tasks( diff --git a/crates/goose/src/agents/types.rs b/crates/goose/src/agents/types.rs index 10054d167..8cb2c8fba 100644 --- a/crates/goose/src/agents/types.rs +++ b/crates/goose/src/agents/types.rs @@ -1,6 +1,6 @@ use crate::session; -use mcp_core::{Tool, ToolResult}; -use rmcp::model::Content; +use mcp_core::ToolResult; +use rmcp::model::{Content, Tool}; use serde::{Deserialize, Serialize}; use std::path::PathBuf; use std::sync::Arc; diff --git a/crates/goose/src/context_mgmt/common.rs b/crates/goose/src/context_mgmt/common.rs index cd12e09f9..3f9054361 100644 --- a/crates/goose/src/context_mgmt/common.rs +++ b/crates/goose/src/context_mgmt/common.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use mcp_core::Tool; +use rmcp::model::Tool; use crate::{ message::Message, diff --git a/crates/goose/src/context_mgmt/summarize.rs b/crates/goose/src/context_mgmt/summarize.rs index 5b7d049df..cd09f4fcf 100644 --- a/crates/goose/src/context_mgmt/summarize.rs +++ b/crates/goose/src/context_mgmt/summarize.rs @@ -220,9 +220,9 @@ mod tests { use crate::providers::base::{Provider, ProviderMetadata, ProviderUsage, Usage}; use crate::providers::errors::ProviderError; use chrono::Utc; - use mcp_core::tool::Tool; use mcp_core::ToolCall; use rmcp::model::Role; + use rmcp::model::Tool; use rmcp::model::{AnnotateAble, Content, RawTextContent}; use serde_json::json; use std::sync::Arc; diff --git a/crates/goose/src/permission/permission_judge.rs b/crates/goose/src/permission/permission_judge.rs index 6a452e24a..cb1b4d748 100644 --- a/crates/goose/src/permission/permission_judge.rs +++ b/crates/goose/src/permission/permission_judge.rs @@ -5,10 +5,10 @@ use crate::message::{Message, MessageContent, ToolRequest}; use crate::providers::base::Provider; use chrono::Utc; use indoc::indoc; -use mcp_core::tool::Tool; -use mcp_core::tool::ToolAnnotations; +use rmcp::model::{Tool, ToolAnnotations}; +use rmcp::object; use serde::{Deserialize, Serialize}; -use serde_json::{json, Value}; +use serde_json::Value; use std::collections::HashSet; use std::sync::Arc; @@ -45,7 +45,7 @@ fn create_read_only_tool() -> Tool { Use this analysis to generate the list of tools performing read-only operations from the provided tool requests. "#} .to_string(), - json!({ + object!({ "type": "object", "properties": { "read_only_tools": { @@ -57,15 +57,14 @@ fn create_read_only_tool() -> Tool { } }, "required": [] - }), - Some(ToolAnnotations { - title: Some("Check tool operation".to_string()), - read_only_hint: true, - destructive_hint: false, - idempotent_hint: false, - open_world_hint: false, - }), - ) + }) + ).annotate(ToolAnnotations { + title: Some("Check tool operation".to_string()), + read_only_hint: Some(true), + destructive_hint: Some(false), + idempotent_hint: Some(false), + open_world_hint: Some(false), + }) } /// Builds the message to be sent to the LLM for detecting read-only operations. @@ -266,9 +265,8 @@ mod tests { use crate::providers::base::{Provider, ProviderMetadata, ProviderUsage, Usage}; use crate::providers::errors::ProviderError; use chrono::Utc; - use mcp_core::ToolCall; - use mcp_core::{tool::Tool, ToolResult}; - use rmcp::model::Role; + use mcp_core::{ToolCall, ToolResult}; + use rmcp::model::{Role, Tool}; use serde_json::json; use tempfile::NamedTempFile; @@ -324,7 +322,10 @@ mod tests { async fn test_create_read_only_tool() { let tool = create_read_only_tool(); assert_eq!(tool.name, "platform__tool_by_tool_permission"); - assert!(tool.description.contains("read-only operation")); + assert!(tool + .description + .as_ref() + .map_or(false, |desc| desc.contains("read-only operation"))); } #[test] diff --git a/crates/goose/src/providers/anthropic.rs b/crates/goose/src/providers/anthropic.rs index 58b4c299b..508fdd3cb 100644 --- a/crates/goose/src/providers/anthropic.rs +++ b/crates/goose/src/providers/anthropic.rs @@ -19,7 +19,7 @@ use super::formats::anthropic::{ use super::utils::{emit_debug_trace, get_model}; use crate::message::Message; use crate::model::ModelConfig; -use mcp_core::tool::Tool; +use rmcp::model::Tool; pub const ANTHROPIC_DEFAULT_MODEL: &str = "claude-3-5-sonnet-latest"; pub const ANTHROPIC_KNOWN_MODELS: &[&str] = &[ diff --git a/crates/goose/src/providers/azure.rs b/crates/goose/src/providers/azure.rs index 1ffdf4ed5..916215ddf 100644 --- a/crates/goose/src/providers/azure.rs +++ b/crates/goose/src/providers/azure.rs @@ -13,7 +13,7 @@ use super::formats::openai::{create_request, get_usage, response_to_message}; use super::utils::{emit_debug_trace, get_model, handle_response_openai_compat, ImageFormat}; use crate::message::Message; use crate::model::ModelConfig; -use mcp_core::tool::Tool; +use rmcp::model::Tool; pub const AZURE_DEFAULT_MODEL: &str = "gpt-4o"; pub const AZURE_DOC_URL: &str = diff --git a/crates/goose/src/providers/base.rs b/crates/goose/src/providers/base.rs index 260e1f22f..952548682 100644 --- a/crates/goose/src/providers/base.rs +++ b/crates/goose/src/providers/base.rs @@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize}; use super::errors::ProviderError; use crate::message::Message; use crate::model::ModelConfig; -use mcp_core::tool::Tool; +use rmcp::model::Tool; use utoipa::ToSchema; use once_cell::sync::Lazy; diff --git a/crates/goose/src/providers/bedrock.rs b/crates/goose/src/providers/bedrock.rs index 31e6cf8b4..22d05fd6a 100644 --- a/crates/goose/src/providers/bedrock.rs +++ b/crates/goose/src/providers/bedrock.rs @@ -6,7 +6,7 @@ use async_trait::async_trait; use aws_sdk_bedrockruntime::config::ProvideCredentials; use aws_sdk_bedrockruntime::operation::converse::ConverseError; use aws_sdk_bedrockruntime::{types as bedrock, Client}; -use mcp_core::Tool; +use rmcp::model::Tool; use serde_json::Value; use tokio::time::sleep; diff --git a/crates/goose/src/providers/claude_code.rs b/crates/goose/src/providers/claude_code.rs index 380979459..c98862c11 100644 --- a/crates/goose/src/providers/claude_code.rs +++ b/crates/goose/src/providers/claude_code.rs @@ -13,7 +13,7 @@ use super::utils::emit_debug_trace; use crate::config::Config; use crate::message::{Message, MessageContent}; use crate::model::ModelConfig; -use mcp_core::tool::Tool; +use rmcp::model::Tool; pub const CLAUDE_CODE_DEFAULT_MODEL: &str = "claude-3-5-sonnet-latest"; pub const CLAUDE_CODE_KNOWN_MODELS: &[&str] = &["sonnet", "opus", "claude-3-5-sonnet-latest"]; diff --git a/crates/goose/src/providers/databricks.rs b/crates/goose/src/providers/databricks.rs index 77adf5569..4b88cf5f6 100644 --- a/crates/goose/src/providers/databricks.rs +++ b/crates/goose/src/providers/databricks.rs @@ -20,7 +20,7 @@ use crate::config::ConfigError; use crate::message::Message; use crate::model::ModelConfig; use crate::providers::formats::openai::{get_usage, response_to_streaming_message}; -use mcp_core::tool::Tool; +use rmcp::model::Tool; use serde_json::json; use tokio::time::sleep; use tokio_stream::StreamExt; diff --git a/crates/goose/src/providers/factory.rs b/crates/goose/src/providers/factory.rs index 091425530..27545c3a2 100644 --- a/crates/goose/src/providers/factory.rs +++ b/crates/goose/src/providers/factory.rs @@ -27,7 +27,7 @@ use anyhow::Result; #[cfg(test)] use super::errors::ProviderError; #[cfg(test)] -use mcp_core::tool::Tool; +use rmcp::model::Tool; fn default_lead_turns() -> usize { 3 diff --git a/crates/goose/src/providers/formats/anthropic.rs b/crates/goose/src/providers/formats/anthropic.rs index 17f525753..bc0f715ec 100644 --- a/crates/goose/src/providers/formats/anthropic.rs +++ b/crates/goose/src/providers/formats/anthropic.rs @@ -3,8 +3,8 @@ use crate::model::ModelConfig; use crate::providers::base::Usage; use crate::providers::errors::ProviderError; use anyhow::{anyhow, Result}; -use mcp_core::tool::{Tool, ToolCall}; -use rmcp::model::Role; +use mcp_core::tool::ToolCall; +use rmcp::model::{Role, Tool}; use serde_json::{json, Value}; use std::collections::HashSet; @@ -676,6 +676,7 @@ where #[cfg(test)] mod tests { use super::*; + use rmcp::object; use serde_json::json; #[test] @@ -858,7 +859,7 @@ mod tests { Tool::new( "calculator", "Calculate mathematical expressions", - json!({ + object!({ "type": "object", "properties": { "expression": { @@ -867,12 +868,11 @@ mod tests { } } }), - None, ), Tool::new( "weather", "Get weather information", - json!({ + object!({ "type": "object", "properties": { "location": { @@ -881,7 +881,6 @@ mod tests { } } }), - None, ), ]; diff --git a/crates/goose/src/providers/formats/bedrock.rs b/crates/goose/src/providers/formats/bedrock.rs index ae8840f27..e947f347b 100644 --- a/crates/goose/src/providers/formats/bedrock.rs +++ b/crates/goose/src/providers/formats/bedrock.rs @@ -6,8 +6,8 @@ use aws_sdk_bedrockruntime::types as bedrock; use aws_smithy_types::{Document, Number}; use base64::Engine; use chrono::Utc; -use mcp_core::{Tool, ToolCall, ToolError, ToolResult}; -use rmcp::model::{Content, RawContent, ResourceContents, Role}; +use mcp_core::{ToolCall, ToolError, ToolResult}; +use rmcp::model::{Content, RawContent, ResourceContents, Role, Tool}; use serde_json::Value; use super::super::base::Usage; @@ -184,9 +184,14 @@ pub fn to_bedrock_tool(tool: &Tool) -> Result { Ok(bedrock::Tool::ToolSpec( bedrock::ToolSpecification::builder() .name(tool.name.to_string()) - .description(tool.description.to_string()) + .description( + tool.description + .as_ref() + .map(|d| d.to_string()) + .unwrap_or_default(), + ) .input_schema(bedrock::ToolInputSchema::Json(to_bedrock_json( - &tool.input_schema, + &Value::Object(tool.input_schema.as_ref().clone()), ))) .build()?, )) diff --git a/crates/goose/src/providers/formats/databricks.rs b/crates/goose/src/providers/formats/databricks.rs index 91992eaa9..0d19f8346 100644 --- a/crates/goose/src/providers/formats/databricks.rs +++ b/crates/goose/src/providers/formats/databricks.rs @@ -5,10 +5,8 @@ use crate::providers::utils::{ sanitize_function_name, ImageFormat, }; use anyhow::{anyhow, Error}; -use mcp_core::ToolError; -use mcp_core::{Tool, ToolCall}; -use rmcp::model::Role; -use rmcp::model::{AnnotateAble, Content, RawContent, ResourceContents}; +use mcp_core::{ToolCall, ToolError}; +use rmcp::model::{AnnotateAble, Content, RawContent, ResourceContents, Role, Tool}; use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; @@ -589,6 +587,7 @@ pub fn create_request( #[cfg(test)] mod tests { use super::*; + use rmcp::object; use serde_json::json; #[test] @@ -703,7 +702,7 @@ mod tests { let tool = Tool::new( "test_tool", "A test tool", - json!({ + object!({ "type": "object", "properties": { "input": { @@ -713,7 +712,6 @@ mod tests { }, "required": ["input"] }), - None, ); let spec = format_tools(&[tool])?; @@ -795,7 +793,7 @@ mod tests { let tool1 = Tool::new( "test_tool", "Test tool", - json!({ + object!({ "type": "object", "properties": { "input": { @@ -805,13 +803,12 @@ mod tests { }, "required": ["input"] }), - None, ); let tool2 = Tool::new( "test_tool", "Test tool", - json!({ + object!({ "type": "object", "properties": { "input": { @@ -821,7 +818,6 @@ mod tests { }, "required": ["input"] }), - None, ); let result = format_tools(&[tool1, tool2]); diff --git a/crates/goose/src/providers/formats/gcpvertexai.rs b/crates/goose/src/providers/formats/gcpvertexai.rs index ab3399651..e96a693b6 100644 --- a/crates/goose/src/providers/formats/gcpvertexai.rs +++ b/crates/goose/src/providers/formats/gcpvertexai.rs @@ -3,7 +3,7 @@ use crate::message::Message; use crate::model::ModelConfig; use crate::providers::base::Usage; use anyhow::{Context, Result}; -use mcp_core::tool::Tool; +use rmcp::model::Tool; use serde_json::Value; use std::fmt; diff --git a/crates/goose/src/providers/formats/google.rs b/crates/goose/src/providers/formats/google.rs index 5a4f0b845..50c45ac3c 100644 --- a/crates/goose/src/providers/formats/google.rs +++ b/crates/goose/src/providers/formats/google.rs @@ -4,9 +4,10 @@ use crate::providers::base::Usage; use crate::providers::errors::ProviderError; use crate::providers::utils::{is_valid_function_name, sanitize_function_name}; use anyhow::Result; -use mcp_core::tool::{Tool, ToolCall}; +use mcp_core::tool::ToolCall; use rand::{distributions::Alphanumeric, Rng}; -use rmcp::model::{AnnotateAble, RawContent, Role}; +use rmcp::model::{AnnotateAble, RawContent, Role, Tool}; + use serde_json::{json, Map, Value}; use std::ops::Deref; @@ -132,18 +133,17 @@ pub fn format_tools(tools: &[Tool]) -> Vec { let mut parameters = Map::new(); parameters.insert("name".to_string(), json!(tool.name)); parameters.insert("description".to_string(), json!(tool.description)); - if let Some(tool_input_schema) = tool.input_schema.as_object() { - // Only add the parameters key if the tool schema has non-empty properties. - if tool_input_schema - .get("properties") - .and_then(|v| v.as_object()) - .is_some_and(|p| !p.is_empty()) - { - parameters.insert( - "parameters".to_string(), - process_map(tool_input_schema, None), - ); - } + let tool_input_schema = &tool.input_schema; + // Only add the parameters key if the tool schema has non-empty properties. + if tool_input_schema + .get("properties") + .and_then(|v| v.as_object()) + .is_some_and(|p| !p.is_empty()) + { + parameters.insert( + "parameters".to_string(), + process_map(tool_input_schema, None), + ); } json!(parameters) }) @@ -320,7 +320,7 @@ pub fn create_request( } let mut generation_config = Map::new(); if let Some(temp) = model_config.temperature { - generation_config.insert("temperature".to_string(), json!(temp)); + generation_config.insert("temperature".to_string(), json!(temp as f64)); } if let Some(tokens) = model_config.max_tokens { generation_config.insert("maxOutputTokens".to_string(), json!(tokens)); @@ -329,13 +329,14 @@ pub fn create_request( payload.insert("generationConfig".to_string(), json!(generation_config)); } - Ok(Value::Object(payload)) + Ok(json!(payload)) } #[cfg(test)] mod tests { use super::*; use rmcp::model::Content; + use rmcp::object; use serde_json::json; fn set_up_text_message(text: &str, role: Role) -> Message { @@ -374,17 +375,6 @@ mod tests { ) } - fn set_up_tool(name: &str, description: &str, params: Value) -> Tool { - Tool { - name: name.to_string(), - description: description.to_string(), - input_schema: json!({ - "properties": params - }), - annotations: None, - } - } - #[test] fn test_get_usage() { let data = json!({ @@ -420,8 +410,11 @@ mod tests { "param1": "value1" }); let messages = vec![ - set_up_tool_request_message("id", ToolCall::new("tool_name", json!(arguments))), - set_up_tool_confirmation_message("id2", ToolCall::new("tool_name_2", json!(arguments))), + set_up_tool_request_message("id", ToolCall::new("tool_name", arguments.clone())), + set_up_tool_confirmation_message( + "id2", + ToolCall::new("tool_name_2", arguments.clone()), + ), ]; let payload = format_messages(&messages); assert_eq!(payload.len(), 1); @@ -478,100 +471,106 @@ mod tests { #[test] fn test_tools_to_google_spec_with_valid_tools() { - let params1 = json!({ - "param1": { - "type": "string", - "description": "A parameter", - "field_does_not_accept": ["value1", "value2"] - } - }); - let params2 = json!({ - "param2": { - "type": "string", - "description": "B parameter", - } - }); - let params3 = json!({ - "body": { - "description": "Review comment text", - "type": "string" - }, - "comments": { - "description": "Line-specific comments array of objects to place comments on pull request changes. Requires path and body. For line comments use line or position. For multi-line comments use start_line and line with optional side parameters.", - "type": "array", - "items": { - "additionalProperties": false, - "properties": { - "body": { - "description": "comment body", - "type": "string" - }, - "line": { - "anyOf": [ - { "type": "number" }, - { "type": "null" } - ], - "description": "line number in the file to comment on. For multi-line comments, the end of the line range" - }, - "path": { - "description": "path to the file", - "type": "string" - }, - "position": { - "anyOf": [ - { "type": "number" }, - { "type": "null" } - ], - "description": "position of the comment in the diff" - }, - "side": { - "anyOf": [ - { "type": "string" }, - { "type": "null" } - ], - "description": "The side of the diff on which the line resides. For multi-line comments, this is the side for the end of the line range. (LEFT or RIGHT)" - }, - "start_line": { - "anyOf": [ - { "type": "number" }, - { "type": "null" } - ], - "description": "The first line of the range to which the comment refers. Required for multi-line comments." - }, - "start_side": { - "anyOf": [ - { "type": "string" }, - { "type": "null" } - ], - "description": "The side of the diff on which the start line resides for multi-line comments. (LEFT or RIGHT)" - } - }, - "required": ["path", "body", "position", "line", "side", "start_line", "start_side"], - "type": "object" + let params1 = object!({ + "properties": { + "param1": { + "type": "string", + "description": "A parameter", + "field_does_not_accept": ["value1", "value2"] + } + } + }); + let params2 = object!({ + "properties": { + "param2": { + "type": "string", + "description": "B parameter", + } + } + }); + let params3 = object!({ + "properties": { + "body": { + "description": "Review comment text", + "type": "string" + }, + "comments": { + "description": "Line-specific comments array of objects to place comments on pull request changes. Requires path and body. For line comments use line or position. For multi-line comments use start_line and line with optional side parameters.", + "type": "array", + "items": { + "additionalProperties": false, + "properties": { + "body": { + "description": "comment body", + "type": "string" + }, + "line": { + "anyOf": [ + { "type": "number" }, + { "type": "null" } + ], + "description": "line number in the file to comment on. For multi-line comments, the end of the line range" + }, + "path": { + "description": "path to the file", + "type": "string" + }, + "position": { + "anyOf": [ + { "type": "number" }, + { "type": "null" } + ], + "description": "position of the comment in the diff" + }, + "side": { + "anyOf": [ + { "type": "string" }, + { "type": "null" } + ], + "description": "The side of the diff on which the line resides. For multi-line comments, this is the side for the end of the line range. (LEFT or RIGHT)" + }, + "start_line": { + "anyOf": [ + { "type": "number" }, + { "type": "null" } + ], + "description": "The first line of the range to which the comment refers. Required for multi-line comments." + }, + "start_side": { + "anyOf": [ + { "type": "string" }, + { "type": "null" } + ], + "description": "The side of the diff on which the start line resides for multi-line comments. (LEFT or RIGHT)" + } + }, + "required": ["path", "body", "position", "line", "side", "start_line", "start_side"], + "type": "object" + } + }, + "commitId": { + "description": "SHA of commit to review", + "type": "string" + }, + "event": { + "description": "Review action to perform", + "enum": ["APPROVE", "REQUEST_CHANGES", "COMMENT"], + "type": "string" + }, + "owner": { + "description": "Repository owner", + "type": "string" + }, + "pullNumber": { + "description": "Pull request number", + "type": "number" } - }, - "commitId": { - "description": "SHA of commit to review", - "type": "string" - }, - "event": { - "description": "Review action to perform", - "enum": ["APPROVE", "REQUEST_CHANGES", "COMMENT"], - "type": "string" - }, - "owner": { - "description": "Repository owner", - "type": "string" - }, - "pullNumber": { - "description": "Pull request number", - "type": "number" } }); let tools = vec![ - set_up_tool("tool1", "description1", params1), - set_up_tool("tool2", "description2", params2), - set_up_tool("tool3", "description3", params3), + Tool::new("tool1", "description1", params1), + Tool::new("tool2", "description2", params2), + Tool::new("tool3", "description3", params3), ]; let result = format_tools(&tools); assert_eq!(result.len(), 3); @@ -681,14 +680,19 @@ mod tests { #[test] fn test_tools_to_google_spec_with_empty_properties() { - let tools = vec![Tool { - name: "tool1".to_string(), - description: "description1".to_string(), - input_schema: json!({ - "properties": {} - }), - annotations: None, - }]; + use rmcp::model::object; + use std::borrow::Cow; + use std::sync::Arc; + + let schema = json!({ + "properties": {} + }); + + let tools = vec![Tool::new( + Cow::Borrowed("tool1"), + Cow::Borrowed("description1"), + Arc::new(object(schema)), + )]; let result = format_tools(&tools); assert_eq!(result.len(), 1); assert_eq!(result[0]["name"], "tool1"); diff --git a/crates/goose/src/providers/formats/openai.rs b/crates/goose/src/providers/formats/openai.rs index 4e32706bf..2a02ae6e4 100644 --- a/crates/goose/src/providers/formats/openai.rs +++ b/crates/goose/src/providers/formats/openai.rs @@ -8,10 +8,8 @@ use crate::providers::utils::{ use anyhow::{anyhow, Error}; use async_stream::try_stream; use futures::Stream; -use mcp_core::ToolError; -use mcp_core::{Tool, ToolCall}; -use rmcp::model::Role; -use rmcp::model::{AnnotateAble, Content, RawContent, ResourceContents}; +use mcp_core::{ToolCall, ToolError}; +use rmcp::model::{AnnotateAble, Content, RawContent, ResourceContents, Role, Tool}; use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; use std::ops::Deref; @@ -635,6 +633,7 @@ pub fn create_request( #[cfg(test)] mod tests { use super::*; + use rmcp::object; use serde_json::json; use tokio::pin; use tokio_stream::{self, StreamExt}; @@ -751,7 +750,7 @@ mod tests { let tool = Tool::new( "test_tool", "A test tool", - json!({ + object!({ "type": "object", "properties": { "input": { @@ -761,7 +760,6 @@ mod tests { }, "required": ["input"] }), - None, ); let spec = format_tools(&[tool])?; @@ -843,7 +841,7 @@ mod tests { let tool1 = Tool::new( "test_tool", "Test tool", - json!({ + object!({ "type": "object", "properties": { "input": { @@ -853,13 +851,12 @@ mod tests { }, "required": ["input"] }), - None, ); let tool2 = Tool::new( "test_tool", "Test tool", - json!({ + object!({ "type": "object", "properties": { "input": { @@ -869,7 +866,6 @@ mod tests { }, "required": ["input"] }), - None, ); let result = format_tools(&[tool1, tool2]); diff --git a/crates/goose/src/providers/formats/snowflake.rs b/crates/goose/src/providers/formats/snowflake.rs index 7592d93c2..270973024 100644 --- a/crates/goose/src/providers/formats/snowflake.rs +++ b/crates/goose/src/providers/formats/snowflake.rs @@ -3,8 +3,8 @@ use crate::model::ModelConfig; use crate::providers::base::Usage; use crate::providers::errors::ProviderError; use anyhow::{anyhow, Result}; -use mcp_core::tool::{Tool, ToolCall}; -use rmcp::model::Role; +use mcp_core::tool::ToolCall; +use rmcp::model::{Role, Tool}; use serde_json::{json, Value}; use std::collections::HashSet; @@ -359,6 +359,7 @@ pub fn create_request( #[cfg(test)] mod tests { use super::*; + use rmcp::object; use serde_json::json; #[test] @@ -460,7 +461,7 @@ mod tests { Tool::new( "calculator", "Calculate mathematical expressions", - json!({ + object!({ "type": "object", "properties": { "expression": { @@ -469,12 +470,11 @@ mod tests { } } }), - None, ), Tool::new( "weather", "Get weather information", - json!({ + object!({ "type": "object", "properties": { "location": { @@ -483,7 +483,6 @@ mod tests { } } }), - None, ), ]; @@ -557,7 +556,7 @@ data: {"id":"a9537c2c-2017-4906-9817-2456168d89fa","model":"claude-3-5-sonnet"," let tools = vec![Tool::new( "get_stock_price", "Get stock price information", - json!({ + object!({ "type": "object", "properties": { "symbol": { @@ -567,7 +566,6 @@ data: {"id":"a9537c2c-2017-4906-9817-2456168d89fa","model":"claude-3-5-sonnet"," }, "required": ["symbol"] }), - None, )]; let request = create_request(&model_config, system, &messages, &tools)?; @@ -664,8 +662,7 @@ data: {"id":"a9537c2c-2017-4906-9817-2456168d89fa","model":"claude-3-5-sonnet"," let tools = vec![Tool::new( "test_tool", "Test tool", - json!({"type": "object", "properties": {}}), - None, + object!({"type": "object", "properties": {}}), )]; let request = create_request(&model_config, system, &messages, &tools)?; diff --git a/crates/goose/src/providers/gcpvertexai.rs b/crates/goose/src/providers/gcpvertexai.rs index 82463fa28..2544a1356 100644 --- a/crates/goose/src/providers/gcpvertexai.rs +++ b/crates/goose/src/providers/gcpvertexai.rs @@ -21,7 +21,7 @@ use crate::providers::formats::gcpvertexai::{ use crate::providers::formats::gcpvertexai::GcpLocation::Iowa; use crate::providers::gcpauth::GcpAuth; use crate::providers::utils::emit_debug_trace; -use mcp_core::tool::Tool; +use rmcp::model::Tool; /// Base URL for GCP Vertex AI documentation const GCP_VERTEX_AI_DOC_URL: &str = "https://cloud.google.com/vertex-ai"; diff --git a/crates/goose/src/providers/gemini_cli.rs b/crates/goose/src/providers/gemini_cli.rs index 8f1f123a0..d1c60517a 100644 --- a/crates/goose/src/providers/gemini_cli.rs +++ b/crates/goose/src/providers/gemini_cli.rs @@ -11,8 +11,8 @@ use super::errors::ProviderError; use super::utils::emit_debug_trace; use crate::message::{Message, MessageContent}; use crate::model::ModelConfig; -use mcp_core::tool::Tool; use rmcp::model::Role; +use rmcp::model::Tool; pub const GEMINI_CLI_DEFAULT_MODEL: &str = "gemini-2.5-pro"; pub const GEMINI_CLI_KNOWN_MODELS: &[&str] = &["gemini-2.5-pro"]; diff --git a/crates/goose/src/providers/githubcopilot.rs b/crates/goose/src/providers/githubcopilot.rs index 245b14801..096cec589 100644 --- a/crates/goose/src/providers/githubcopilot.rs +++ b/crates/goose/src/providers/githubcopilot.rs @@ -20,7 +20,7 @@ use crate::config::{Config, ConfigError}; use crate::message::Message; use crate::model::ModelConfig; use crate::providers::base::ConfigKey; -use mcp_core::tool::Tool; +use rmcp::model::Tool; pub const GITHUB_COPILOT_DEFAULT_MODEL: &str = "gpt-4o"; pub const GITHUB_COPILOT_KNOWN_MODELS: &[&str] = &[ diff --git a/crates/goose/src/providers/google.rs b/crates/goose/src/providers/google.rs index 2e807bc68..6967128af 100644 --- a/crates/goose/src/providers/google.rs +++ b/crates/goose/src/providers/google.rs @@ -9,8 +9,8 @@ use crate::providers::utils::{ use anyhow::Result; use async_trait::async_trait; use axum::http::HeaderMap; -use mcp_core::tool::Tool; use reqwest::Client; +use rmcp::model::Tool; use serde_json::Value; use std::time::Duration; use url::Url; diff --git a/crates/goose/src/providers/groq.rs b/crates/goose/src/providers/groq.rs index c508d290d..5e3019f8c 100644 --- a/crates/goose/src/providers/groq.rs +++ b/crates/goose/src/providers/groq.rs @@ -6,8 +6,8 @@ use crate::providers::formats::openai::{create_request, get_usage, response_to_m use crate::providers::utils::get_model; use anyhow::Result; use async_trait::async_trait; -use mcp_core::Tool; use reqwest::{Client, StatusCode}; +use rmcp::model::Tool; use serde_json::Value; use std::time::Duration; use url::Url; diff --git a/crates/goose/src/providers/lead_worker.rs b/crates/goose/src/providers/lead_worker.rs index 5d993b525..7909d5059 100644 --- a/crates/goose/src/providers/lead_worker.rs +++ b/crates/goose/src/providers/lead_worker.rs @@ -8,7 +8,7 @@ use super::base::{LeadWorkerProviderTrait, Provider, ProviderMetadata, ProviderU use super::errors::ProviderError; use crate::message::{Message, MessageContent}; use crate::model::ModelConfig; -use mcp_core::tool::Tool; +use rmcp::model::Tool; use rmcp::model::{Content, RawContent}; /// A provider that switches between a lead model and a worker model based on turn count diff --git a/crates/goose/src/providers/litellm.rs b/crates/goose/src/providers/litellm.rs index 028731a4a..303e3aaeb 100644 --- a/crates/goose/src/providers/litellm.rs +++ b/crates/goose/src/providers/litellm.rs @@ -12,7 +12,7 @@ use super::errors::ProviderError; use super::utils::{emit_debug_trace, get_model, handle_response_openai_compat, ImageFormat}; use crate::message::Message; use crate::model::ModelConfig; -use mcp_core::tool::Tool; +use rmcp::model::Tool; pub const LITELLM_DEFAULT_MODEL: &str = "gpt-4o-mini"; pub const LITELLM_DOC_URL: &str = "https://docs.litellm.ai/docs/"; diff --git a/crates/goose/src/providers/ollama.rs b/crates/goose/src/providers/ollama.rs index 46754a91a..4001ee857 100644 --- a/crates/goose/src/providers/ollama.rs +++ b/crates/goose/src/providers/ollama.rs @@ -6,8 +6,8 @@ use crate::model::ModelConfig; use crate::providers::formats::openai::{create_request, get_usage, response_to_message}; use anyhow::Result; use async_trait::async_trait; -use mcp_core::tool::Tool; use reqwest::Client; +use rmcp::model::Tool; use serde_json::Value; use std::time::Duration; use url::Url; diff --git a/crates/goose/src/providers/openai.rs b/crates/goose/src/providers/openai.rs index 2ff225cd1..ff6b65253 100644 --- a/crates/goose/src/providers/openai.rs +++ b/crates/goose/src/providers/openai.rs @@ -22,7 +22,7 @@ use crate::model::ModelConfig; use crate::providers::base::MessageStream; use crate::providers::formats::openai::response_to_streaming_message; use crate::providers::utils::handle_status_openai_compat; -use mcp_core::tool::Tool; +use rmcp::model::Tool; pub const OPEN_AI_DEFAULT_MODEL: &str = "gpt-4o"; pub const OPEN_AI_KNOWN_MODELS: &[&str] = &[ diff --git a/crates/goose/src/providers/openrouter.rs b/crates/goose/src/providers/openrouter.rs index 21fd1e5e3..e36f3f406 100644 --- a/crates/goose/src/providers/openrouter.rs +++ b/crates/goose/src/providers/openrouter.rs @@ -13,7 +13,7 @@ use super::utils::{ use crate::message::Message; use crate::model::ModelConfig; use crate::providers::formats::openai::{create_request, get_usage, response_to_message}; -use mcp_core::tool::Tool; +use rmcp::model::Tool; use url::Url; pub const OPENROUTER_DEFAULT_MODEL: &str = "anthropic/claude-3.5-sonnet"; diff --git a/crates/goose/src/providers/sagemaker_tgi.rs b/crates/goose/src/providers/sagemaker_tgi.rs index d5da10583..9421d75b2 100644 --- a/crates/goose/src/providers/sagemaker_tgi.rs +++ b/crates/goose/src/providers/sagemaker_tgi.rs @@ -6,7 +6,7 @@ use async_trait::async_trait; use aws_config; use aws_sdk_bedrockruntime::config::ProvideCredentials; use aws_sdk_sagemakerruntime::Client as SageMakerClient; -use mcp_core::Tool; +use rmcp::model::Tool; use serde_json::{json, Value}; use tokio::time::sleep; diff --git a/crates/goose/src/providers/snowflake.rs b/crates/goose/src/providers/snowflake.rs index 0ab6be585..a19bbd114 100644 --- a/crates/goose/src/providers/snowflake.rs +++ b/crates/goose/src/providers/snowflake.rs @@ -12,7 +12,7 @@ use super::utils::{get_model, ImageFormat}; use crate::config::ConfigError; use crate::message::Message; use crate::model::ModelConfig; -use mcp_core::tool::Tool; +use rmcp::model::Tool; use url::Url; pub const SNOWFLAKE_DEFAULT_MODEL: &str = "claude-3-7-sonnet"; diff --git a/crates/goose/src/providers/testprovider.rs b/crates/goose/src/providers/testprovider.rs index 9016bdcf9..7667109f4 100644 --- a/crates/goose/src/providers/testprovider.rs +++ b/crates/goose/src/providers/testprovider.rs @@ -11,7 +11,7 @@ use super::base::{Provider, ProviderMetadata, ProviderUsage}; use super::errors::ProviderError; use crate::message::Message; use crate::model::ModelConfig; -use mcp_core::tool::Tool; +use rmcp::model::Tool; #[derive(Debug, Clone, Serialize, Deserialize)] struct TestInput { diff --git a/crates/goose/src/providers/toolshim.rs b/crates/goose/src/providers/toolshim.rs index e1bec2239..1eb43db2d 100644 --- a/crates/goose/src/providers/toolshim.rs +++ b/crates/goose/src/providers/toolshim.rs @@ -37,9 +37,9 @@ use crate::message::{Message, MessageContent}; use crate::model::ModelConfig; use crate::providers::formats::openai::create_request; use anyhow::Result; -use mcp_core::tool::{Tool, ToolCall}; +use mcp_core::tool::ToolCall; use reqwest::Client; -use rmcp::model::RawContent; +use rmcp::model::{RawContent, Tool}; use serde_json::{json, Value}; use std::ops::Deref; use std::time::Duration; @@ -297,7 +297,7 @@ pub fn format_tool_info(tools: &[Tool]) -> String { let mut tool_info = String::new(); for tool in tools { tool_info.push_str(&format!( - "Tool Name: {}\nSchema: {}\nDescription: {}\n\n", + "Tool Name: {}\nSchema: {}\nDescription: {:?}\n\n", tool.name, serde_json::to_string_pretty(&tool.input_schema).unwrap_or_default(), tool.description diff --git a/crates/goose/src/providers/venice.rs b/crates/goose/src/providers/venice.rs index 5d1eab5eb..75cb31145 100644 --- a/crates/goose/src/providers/venice.rs +++ b/crates/goose/src/providers/venice.rs @@ -10,8 +10,8 @@ use super::base::{ConfigKey, Provider, ProviderMetadata, ProviderUsage, Usage}; use super::errors::ProviderError; use crate::message::{Message, MessageContent}; use crate::model::ModelConfig; -use mcp_core::{tool::Tool, ToolCall, ToolResult}; -use rmcp::model::Role; +use mcp_core::{ToolCall, ToolResult}; +use rmcp::model::{Role, Tool}; // ---------- Capability Flags ---------- #[derive(Debug)] diff --git a/crates/goose/src/providers/xai.rs b/crates/goose/src/providers/xai.rs index 9904531a3..6d24e087b 100644 --- a/crates/goose/src/providers/xai.rs +++ b/crates/goose/src/providers/xai.rs @@ -6,8 +6,8 @@ use crate::providers::formats::openai::{create_request, get_usage, response_to_m use crate::providers::utils::get_model; use anyhow::Result; use async_trait::async_trait; -use mcp_core::Tool; use reqwest::{Client, StatusCode}; +use rmcp::model::Tool; use serde_json::Value; use std::time::Duration; use url::Url; diff --git a/crates/goose/src/scheduler.rs b/crates/goose/src/scheduler.rs index 50e06c24b..9d879ac58 100644 --- a/crates/goose/src/scheduler.rs +++ b/crates/goose/src/scheduler.rs @@ -1332,7 +1332,7 @@ mod tests { providers::base::{ProviderMetadata, ProviderUsage, Usage}, providers::errors::ProviderError, }; - use mcp_core::tool::Tool; + use rmcp::model::Tool; use rmcp::model::{AnnotateAble, RawTextContent, Role}; // Removed: use crate::session::storage::{get_most_recent_session, read_metadata}; // `read_metadata` is still used by the test itself, so keep it or its module. diff --git a/crates/goose/src/session/info.rs b/crates/goose/src/session/info.rs index 6c60d3310..b5fc56bca 100644 --- a/crates/goose/src/session/info.rs +++ b/crates/goose/src/session/info.rs @@ -100,7 +100,6 @@ pub fn get_valid_sorted_sessions(sort_order: SortOrder) -> Result Result { mod tests { use super::*; use crate::message::{Message, MessageContent}; - use mcp_core::tool::Tool; - use rmcp::model::Role; - use serde_json::json; + use rmcp::model::{Role, Tool}; + use rmcp::object; #[test] fn test_token_counter_basic() { @@ -428,10 +437,10 @@ mod tests { ), ]; - let tools = vec![Tool { - name: "get_current_weather".to_string(), - description: "Get the current weather in a given location".to_string(), - input_schema: json!({ + let tools = vec![Tool::new( + "get_current_weather", + "Get the current weather in a given location", + object!({ "properties": { "location": { "type": "string", @@ -445,8 +454,7 @@ mod tests { }, "required": ["location"] }), - annotations: None, - }]; + )]; let token_count_without_tools = counter.count_chat_tokens(system_prompt, &messages, &[]); println!("Total tokens without tools: {}", token_count_without_tools); @@ -526,10 +534,10 @@ mod tests { ), ]; - let tools = vec![Tool { - name: "get_current_weather".to_string(), - description: "Get the current weather in a given location".to_string(), - input_schema: json!({ + let tools = vec![Tool::new( + "get_current_weather", + "Get the current weather in a given location", + object!({ "properties": { "location": { "type": "string", @@ -543,8 +551,7 @@ mod tests { }, "required": ["location"] }), - annotations: None, - }]; + )]; let token_count_without_tools = counter.count_chat_tokens(system_prompt, &messages, &[]); println!( diff --git a/crates/goose/tests/agent.rs b/crates/goose/tests/agent.rs index 497ebcaab..016e9576e 100644 --- a/crates/goose/tests/agent.rs +++ b/crates/goose/tests/agent.rs @@ -448,6 +448,8 @@ mod schedule_tool_tests { let tool = schedule_tool.unwrap(); assert!(tool .description + .clone() + .unwrap_or_default() .contains("Manage scheduled recipe execution")); } @@ -478,6 +480,8 @@ mod schedule_tool_tests { let tool = schedule_tool.unwrap(); assert!(tool .description + .clone() + .unwrap_or_default() .contains("Manage scheduled recipe execution")); // Verify the tool has the expected actions in its schema @@ -548,7 +552,7 @@ mod final_output_tool_tests { use goose::model::ModelConfig; use goose::providers::base::{Provider, ProviderUsage, Usage}; use goose::providers::errors::ProviderError; - use mcp_core::tool::Tool; + use rmcp::model::Tool; #[derive(Clone)] struct MockProvider { @@ -648,7 +652,7 @@ mod final_output_tool_tests { use goose::model::ModelConfig; use goose::providers::base::{Provider, ProviderUsage}; use goose::providers::errors::ProviderError; - use mcp_core::tool::Tool; + use rmcp::model::Tool; #[derive(Clone)] struct MockProvider { @@ -769,7 +773,7 @@ mod retry_tests { use goose::model::ModelConfig; use goose::providers::base::{Provider, ProviderUsage, Usage}; use goose::providers::errors::ProviderError; - use mcp_core::tool::Tool; + use rmcp::model::Tool; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; @@ -950,7 +954,8 @@ mod max_turns_tests { use goose::providers::base::{Provider, ProviderMetadata, ProviderUsage, Usage}; use goose::providers::errors::ProviderError; use goose::session::storage::Identifier; - use mcp_core::tool::{Tool, ToolCall}; + use mcp_core::tool::ToolCall; + use rmcp::model::Tool; use std::path::PathBuf; struct MockToolProvider {} diff --git a/crates/goose/tests/providers.rs b/crates/goose/tests/providers.rs index 0225c88a7..f0ac979ed 100644 --- a/crates/goose/tests/providers.rs +++ b/crates/goose/tests/providers.rs @@ -7,8 +7,9 @@ use goose::providers::{ anthropic, azure, bedrock, databricks, google, groq, litellm, ollama, openai, openrouter, snowflake, xai, }; -use mcp_core::tool::Tool; +use rmcp::model::Tool; use rmcp::model::{AnnotateAble, Content, RawImageContent}; +use rmcp::object; use std::collections::HashMap; use std::sync::Arc; use std::sync::Mutex; @@ -118,7 +119,7 @@ impl ProviderTester { let weather_tool = Tool::new( "get_weather", "Get the weather for a location", - serde_json::json!({ + object!({ "type": "object", "required": ["location"], "properties": { @@ -128,7 +129,6 @@ impl ProviderTester { } } }), - None, ); let message = Message::user().with_text("What's the weather like in San Francisco?"); @@ -309,11 +309,10 @@ impl ProviderTester { let screenshot_tool = Tool::new( "get_screenshot", "Get a screenshot of the current screen", - serde_json::json!({ + object!({ "type": "object", "properties": {} }), - None, ); let user_message = Message::user().with_text("Take a screenshot please"); diff --git a/crates/mcp-core/src/protocol.rs b/crates/mcp-core/src/protocol.rs index ea0f32669..031a880f2 100644 --- a/crates/mcp-core/src/protocol.rs +++ b/crates/mcp-core/src/protocol.rs @@ -1,5 +1,5 @@ /// The protocol messages exchanged between client and server -use crate::tool::Tool; +use rmcp::model::Tool; use rmcp::model::{Content, ErrorData, Prompt, PromptMessage, Resource, ResourceContents}; use serde::{Deserialize, Serialize}; use serde_json::Value; diff --git a/crates/mcp-server/src/main.rs b/crates/mcp-server/src/main.rs index a9757c7e8..78f712b14 100644 --- a/crates/mcp-server/src/main.rs +++ b/crates/mcp-server/src/main.rs @@ -1,10 +1,12 @@ use anyhow::Result; use mcp_core::handler::{PromptError, ResourceError}; -use mcp_core::tool::ToolAnnotations; -use mcp_core::{handler::ToolError, protocol::ServerCapabilities, tool::Tool}; +use mcp_core::{handler::ToolError, protocol::ServerCapabilities}; use mcp_server::router::{CapabilitiesBuilder, RouterService}; use mcp_server::{ByteTransport, Router, Server}; -use rmcp::model::{Content, JsonRpcMessage, Prompt, PromptArgument, RawResource, Resource}; +use rmcp::model::{ + Content, JsonRpcMessage, Prompt, PromptArgument, RawResource, Resource, Tool, ToolAnnotations, +}; +use rmcp::object; use serde_json::Value; use std::{future::Future, pin::Pin, sync::Arc}; use tokio::sync::mpsc; @@ -72,51 +74,51 @@ impl Router for CounterRouter { Tool::new( "increment".to_string(), "Increment the counter by 1".to_string(), - serde_json::json!({ + object!({ "type": "object", "properties": {}, "required": [] }), - Some(ToolAnnotations { - title: Some("Increment Tool".to_string()), - read_only_hint: false, - destructive_hint: false, - idempotent_hint: false, - open_world_hint: false, - }), - ), + ) + .annotate(ToolAnnotations { + title: Some("Increment Tool".to_string()), + read_only_hint: Some(false), + destructive_hint: Some(false), + idempotent_hint: Some(false), + open_world_hint: Some(false), + }), Tool::new( "decrement".to_string(), "Decrement the counter by 1".to_string(), - serde_json::json!({ + object!({ "type": "object", "properties": {}, "required": [] }), - Some(ToolAnnotations { - title: Some("Decrement Tool".to_string()), - read_only_hint: false, - destructive_hint: false, - idempotent_hint: false, - open_world_hint: false, - }), - ), + ) + .annotate(ToolAnnotations { + title: Some("Decrement Tool".to_string()), + read_only_hint: Some(false), + destructive_hint: Some(false), + idempotent_hint: Some(false), + open_world_hint: Some(false), + }), Tool::new( "get_value".to_string(), "Get the current counter value".to_string(), - serde_json::json!({ + object!({ "type": "object", "properties": {}, "required": [] }), - Some(ToolAnnotations { - title: Some("Get Value Tool".to_string()), - read_only_hint: true, - destructive_hint: false, - idempotent_hint: false, - open_world_hint: false, - }), - ), + ) + .annotate(ToolAnnotations { + title: Some("Get Value Tool".to_string()), + read_only_hint: Some(true), + destructive_hint: Some(false), + idempotent_hint: Some(false), + open_world_hint: Some(false), + }), ] } diff --git a/crates/mcp-server/src/router.rs b/crates/mcp-server/src/router.rs index 4d6186f3e..aeea7bf1b 100644 --- a/crates/mcp-server/src/router.rs +++ b/crates/mcp-server/src/router.rs @@ -86,7 +86,7 @@ pub trait Router: Send + Sync + 'static { // in the protocol, instructions are optional but we make it required fn instructions(&self) -> String; fn capabilities(&self) -> ServerCapabilities; - fn list_tools(&self) -> Vec; + fn list_tools(&self) -> Vec; fn call_tool( &self, tool_name: &str,