diff --git a/crates/goose-cli/src/commands/configure.rs b/crates/goose-cli/src/commands/configure.rs index 5c8d6c6068b7..3a8e792ee5e2 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 6090f6d587fc..d6d281adc407 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 3344905d9ad4..814a16b97d34 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 + .as_ref() + .map_or(false, |desc| desc + .contains("the `old_str` needs to exactly match one"))); 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("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 + .as_ref() + .map_or(false, |desc| desc.contains("edit_file"))); assert!(!text_editor_tool .description - .contains("work out how to place old_str with it intelligently")); + .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 4ad8aeb27c83..b3f235dd6ade 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 102e21a5b423..fee7b117b034 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 588242bd3096..b15e3dca8ac0 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 a896cdb2e6d7..1023b22dbbaf 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 d0ddb7ccf3db..17e006754a88 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 24a1f7eb104d..5e1733143906 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 49fc083c73d3..a021c7349f71 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 c67ae89e5789..270fd5804333 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 93d9ff99fefe..37dc58548973 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 732401a021e8..2ee529148bb5 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 0c2e779b152f..8c2f2b969d87 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 841c18d43f9e..777b054d972d 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 08866258e743..55e11b6fe1c8 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 a283ab2bf7a0..66da9ca22d2e 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 64bd196d4074..cd5f217d3c52 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 52da661a95c4..a75c648d844f 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 bb3b2ad0e06a..a4440f66bfb4 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 98431f5bb11d..314c3c41b616 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 d07030ab424c..f9cf85d64e74 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 e06da4061566..2527d558b8a5 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 10054d167233..8cb2c8fbadfb 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 cd12e09f96f9..3f9054361b95 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 5b7d049df171..cd09f4fcf726 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 6a452e24a5a4..cb1b4d7483b8 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 58b4c299b75f..508fdd3cb9d3 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 1ffdf4ed5419..916215ddf34c 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 260e1f22f581..9525486821b9 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 31e6cf8b4363..22d05fd6a18c 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 380979459fba..c98862c11c0f 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 77adf5569968..4b88cf5f6c86 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 091425530df4..27545c3a2191 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 17f525753811..bc0f715ec1ce 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 ae8840f27fbf..e947f347bae9 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 91992eaa98dd..0d19f8346456 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 ab3399651db2..e96a693b67c7 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 5a4f0b8452e2..50c45ac3c513 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 params1 = object!({ + "properties": { + "param1": { + "type": "string", + "description": "A parameter", + "field_does_not_accept": ["value1", "value2"] + } } }); - let params2 = json!({ - "param2": { - "type": "string", - "description": "B parameter", + let params2 = object!({ + "properties": { + "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." + 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)" + } }, - "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" + "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 4e32706bfc73..2a02ae6e4a49 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 7592d93c2ab8..270973024bb1 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 82463fa2848b..2544a1356953 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 8f1f123a08c8..d1c60517ae2f 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 245b14801a6d..096cec5892a3 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 2e807bc684ed..6967128af7fd 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 c508d290df33..5e3019f8cfb7 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 5d993b525b04..7909d5059e94 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 028731a4a2b6..303e3aaeb542 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 46754a91a3bb..4001ee857315 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 2ff225cd1c62..ff6b65253c93 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 21fd1e5e3b3d..e36f3f406918 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 d5da10583e89..9421d75b2e34 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 0ab6be58588b..a19bbd11445d 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 9016bdcf9612..7667109f4126 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 e1bec2239f40..1eb43db2d23e 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 5d1eab5eb831..75cb31145b50 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 9904531a3e01..6d24e087b3b6 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 50e06c24bbaa..9d879ac58fe5 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 6c60d3310dba..b5fc56bca149 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 497ebcaab715..016e9576ebea 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 0225c88a733e..f0ac979edcc2 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 ea0f326690ef..031a880f221c 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 a9757c7e8dd1..78f712b14ef1 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 4d6186f3eac7..aeea7bf1b702 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,