diff --git a/crates/goose-cli/src/commands/configure.rs b/crates/goose-cli/src/commands/configure.rs index ec81b0b4..ce88ab01 100644 --- a/crates/goose-cli/src/commands/configure.rs +++ b/crates/goose-cli/src/commands/configure.rs @@ -5,6 +5,7 @@ use goose::config::extensions::name_to_key; use goose::config::{Config, ConfigError, ExperimentManager, ExtensionEntry, ExtensionManager}; use goose::message::Message; use goose::providers::{create, providers}; +use mcp_core::tool::ToolAnnotations; use mcp_core::Tool; use serde_json::{json, Value}; use std::collections::HashMap; @@ -349,6 +350,13 @@ pub async fn configure_provider_dialog() -> Result> { "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, + }), ); vec![sample_tool] } else { diff --git a/crates/goose-mcp/src/computercontroller/mod.rs b/crates/goose-mcp/src/computercontroller/mod.rs index 724be7fd..6b2b2038 100644 --- a/crates/goose-mcp/src/computercontroller/mod.rs +++ b/crates/goose-mcp/src/computercontroller/mod.rs @@ -13,7 +13,7 @@ use mcp_core::{ prompt::Prompt, protocol::ServerCapabilities, resource::Resource, - tool::Tool, + tool::{Tool, ToolAnnotations}, Content, }; use mcp_server::router::CapabilitiesBuilder; @@ -74,6 +74,13 @@ impl ComputerControllerRouter { } } }), + Some(ToolAnnotations { + title: Some("Web Scrape".to_string()), + read_only_hint: true, + destructive_hint: false, + idempotent_hint: false, + open_world_hint: true, + }), ); let computer_control_desc = match std::env::consts::OS { @@ -139,6 +146,7 @@ impl ComputerControllerRouter { } } }), + None, ); let quick_script_desc = match std::env::consts::OS { @@ -189,6 +197,7 @@ impl ComputerControllerRouter { } } }), + None, ); let cache_tool = Tool::new( @@ -215,6 +224,7 @@ impl ComputerControllerRouter { } } }), + None, ); let pdf_tool = Tool::new( @@ -242,6 +252,13 @@ impl ComputerControllerRouter { } } }), + Some(ToolAnnotations { + title: Some("PDF process".to_string()), + read_only_hint: true, + destructive_hint: false, + idempotent_hint: true, + open_world_hint: false, + }), ); let docx_tool = Tool::new( @@ -340,6 +357,7 @@ impl ComputerControllerRouter { } } }), + None, ); let make_presentation_tool = Tool::new( @@ -380,6 +398,7 @@ impl ComputerControllerRouter { } } }), + None, ); let xlsx_tool = Tool::new( @@ -441,6 +460,7 @@ 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 6d322e80..ca9815ce 100644 --- a/crates/goose-mcp/src/developer/mod.rs +++ b/crates/goose-mcp/src/developer/mod.rs @@ -17,13 +17,16 @@ use tokio::process::Command; use url::Url; use include_dir::{include_dir, Dir}; -use mcp_core::prompt::{Prompt, PromptArgument, PromptTemplate}; use mcp_core::{ handler::{PromptError, ResourceError, ToolError}, protocol::ServerCapabilities, resource::Resource, tool::Tool, }; +use mcp_core::{ + prompt::{Prompt, PromptArgument, PromptTemplate}, + tool::ToolAnnotations, +}; use mcp_server::router::CapabilitiesBuilder; use mcp_server::Router; @@ -161,6 +164,7 @@ impl DeveloperRouter { "command": {"type": "string"} } }), + None, ); let text_editor_tool = Tool::new( @@ -199,6 +203,7 @@ impl DeveloperRouter { "file_text": {"type": "string"} } }), + None, ); let list_windows_tool = Tool::new( @@ -213,6 +218,13 @@ impl DeveloperRouter { "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, + }), ); let screen_capture_tool = Tool::new( @@ -241,6 +253,13 @@ impl DeveloperRouter { } } }), + Some(ToolAnnotations { + title: Some("Capture a full screen".to_string()), + read_only_hint: true, + destructive_hint: false, + idempotent_hint: false, + open_world_hint: false, + }), ); let image_processor_tool = Tool::new( @@ -263,6 +282,13 @@ impl DeveloperRouter { } } }), + Some(ToolAnnotations { + title: Some("Process Image".to_string()), + read_only_hint: true, + destructive_hint: false, + idempotent_hint: true, + open_world_hint: false, + }), ); // Get base instructions and working directory diff --git a/crates/goose-mcp/src/google_drive/mod.rs b/crates/goose-mcp/src/google_drive/mod.rs index 4c6d2664..1bbfb481 100644 --- a/crates/goose-mcp/src/google_drive/mod.rs +++ b/crates/goose-mcp/src/google_drive/mod.rs @@ -4,6 +4,7 @@ pub mod storage; use anyhow::{Context, Error}; use base64::Engine; use indoc::indoc; +use mcp_core::tool::ToolAnnotations; use oauth_pkce::PkceOAuth2Client; use regex::Regex; use serde_json::{json, Value}; @@ -209,6 +210,13 @@ impl GoogleDriveRouter { } }, }), + Some(ToolAnnotations { + title: Some("Search GDrive".to_string()), + read_only_hint: true, + destructive_hint: false, + idempotent_hint: false, + open_world_hint: false, + }), ); let read_tool = Tool::new( @@ -232,6 +240,13 @@ impl GoogleDriveRouter { }, "required": ["uri"], }), + Some(ToolAnnotations { + title: Some("Read GDrive".to_string()), + read_only_hint: true, + destructive_hint: false, + idempotent_hint: false, + open_world_hint: false, + }), ); let upload_tool = Tool::new( @@ -270,6 +285,13 @@ impl GoogleDriveRouter { }, "required": ["name", "mimeType"], }), + Some(ToolAnnotations { + title: Some("Upload file to GDrive".to_string()), + read_only_hint: false, + destructive_hint: false, + idempotent_hint: false, + open_world_hint: false, + }), ); let create_file_tool = Tool::new( @@ -313,6 +335,13 @@ impl GoogleDriveRouter { }, "required": ["name", "fileType"], }), + 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, + }), ); let move_file_tool = Tool::new( @@ -339,6 +368,13 @@ 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, + }), ); let update_tool = Tool::new( @@ -373,6 +409,13 @@ impl GoogleDriveRouter { }, "required": ["fileId", "mimeType"], }), + Some(ToolAnnotations { + title: Some("Update a file".to_string()), + read_only_hint: false, + destructive_hint: true, + idempotent_hint: false, + open_world_hint: false, + }), ); let update_file_tool = Tool::new( @@ -408,6 +451,13 @@ impl GoogleDriveRouter { }, "required": ["fileId", "fileType"], }), + Some(ToolAnnotations { + title: Some("Update a file".to_string()), + read_only_hint: false, + destructive_hint: true, + idempotent_hint: false, + open_world_hint: false, + }), ); let sheets_tool = Tool::new( @@ -468,6 +518,7 @@ impl GoogleDriveRouter { }, "required": ["spreadsheetId", "operation"], }), + None, ); let get_comments_tool = Tool::new( @@ -486,6 +537,13 @@ 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, + }), ); let create_comment_tool = Tool::new( @@ -508,6 +566,13 @@ impl GoogleDriveRouter { }, "required": ["fileId", "comment"], }), + Some(ToolAnnotations { + title: Some("Create file comment".to_string()), + read_only_hint: false, + destructive_hint: false, + idempotent_hint: false, + open_world_hint: false, + }), ); let reply_tool = Tool::new( @@ -538,6 +603,13 @@ impl GoogleDriveRouter { }, "required": ["fileId", "commentId", "content"], }), + Some(ToolAnnotations { + title: Some("Reply to a comment".to_string()), + read_only_hint: false, + destructive_hint: false, + idempotent_hint: false, + open_world_hint: false, + }), ); let list_drives_tool = Tool::new( @@ -555,6 +627,13 @@ 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, + }), ); let instructions = indoc::formatdoc! {r#" diff --git a/crates/goose-mcp/src/jetbrains/proxy.rs b/crates/goose-mcp/src/jetbrains/proxy.rs index 8c0ec34a..382f2714 100644 --- a/crates/goose-mcp/src/jetbrains/proxy.rs +++ b/crates/goose-mcp/src/jetbrains/proxy.rs @@ -237,6 +237,7 @@ impl JetBrainsProxy { name: name.to_string(), description: first_sentence, input_schema, + annotations: None, }) } else { debug!("Skipping invalid tool entry: {:?}", t); diff --git a/crates/goose-mcp/src/memory/mod.rs b/crates/goose-mcp/src/memory/mod.rs index 8f26f3c2..24dff4f1 100644 --- a/crates/goose-mcp/src/memory/mod.rs +++ b/crates/goose-mcp/src/memory/mod.rs @@ -16,7 +16,7 @@ use mcp_core::{ prompt::Prompt, protocol::ServerCapabilities, resource::Resource, - tool::{Tool, ToolCall}, + tool::{Tool, ToolAnnotations, ToolCall}, Content, }; use mcp_server::router::CapabilitiesBuilder; @@ -52,6 +52,13 @@ 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, + }), ); let retrieve_memories = Tool::new( @@ -65,6 +72,13 @@ 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, + }), ); let remove_memory_category = Tool::new( @@ -78,6 +92,13 @@ 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, + }), ); let remove_specific_memory = Tool::new( @@ -92,6 +113,13 @@ 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, + }), ); let instructions = formatdoc! {r#" diff --git a/crates/goose-mcp/src/tutorial/mod.rs b/crates/goose-mcp/src/tutorial/mod.rs index 2f32b03a..b2c26906 100644 --- a/crates/goose-mcp/src/tutorial/mod.rs +++ b/crates/goose-mcp/src/tutorial/mod.rs @@ -10,7 +10,7 @@ use mcp_core::{ protocol::ServerCapabilities, resource::Resource, role::Role, - tool::Tool, + tool::{Tool, ToolAnnotations}, }; use mcp_server::router::CapabilitiesBuilder; use mcp_server::Router; @@ -45,6 +45,13 @@ impl TutorialRouter { } } }), + Some(ToolAnnotations { + title: Some("Load Tutorial".to_string()), + read_only_hint: true, + destructive_hint: false, + idempotent_hint: false, + open_world_hint: false, + }), ); // Get base instructions and available tutorials diff --git a/crates/goose/examples/image_tool.rs b/crates/goose/examples/image_tool.rs index f3b5d94a..23ebe144 100644 --- a/crates/goose/examples/image_tool.rs +++ b/crates/goose/examples/image_tool.rs @@ -58,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)], + &[Tool::new("view_image", "View an image", input_schema, None)], ) .await?; diff --git a/crates/goose/src/agents/capabilities.rs b/crates/goose/src/agents/capabilities.rs index 68272428..40ac4c4f 100644 --- a/crates/goose/src/agents/capabilities.rs +++ b/crates/goose/src/agents/capabilities.rs @@ -245,6 +245,7 @@ impl Capabilities { format!("{}__{}", name, tool.name), &tool.description, tool.input_schema, + tool.annotations, )); } diff --git a/crates/goose/src/agents/permission_judge.rs b/crates/goose/src/agents/permission_judge.rs index a1fb8336..9be3660a 100644 --- a/crates/goose/src/agents/permission_judge.rs +++ b/crates/goose/src/agents/permission_judge.rs @@ -2,6 +2,7 @@ use crate::agents::capabilities::Capabilities; use crate::message::{Message, MessageContent, ToolRequest}; use chrono::Utc; use indoc::indoc; +use mcp_core::tool::ToolAnnotations; use mcp_core::{tool::Tool, TextContent}; use serde_json::{json, Value}; @@ -50,6 +51,13 @@ 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, + }), ) } diff --git a/crates/goose/src/agents/reference.rs b/crates/goose/src/agents/reference.rs index 812987b1..59ac01bc 100644 --- a/crates/goose/src/agents/reference.rs +++ b/crates/goose/src/agents/reference.rs @@ -21,7 +21,7 @@ use anyhow::{anyhow, Result}; use indoc::indoc; use mcp_core::prompt::Prompt; use mcp_core::protocol::GetPromptResult; -use mcp_core::tool::Tool; +use mcp_core::tool::{Tool, ToolAnnotations}; use serde_json::{json, Value}; /// Reference implementation of an Agent @@ -102,6 +102,13 @@ impl Agent for ReferenceAgent { "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, + }), ); let list_resources_tool = Tool::new( @@ -120,6 +127,13 @@ impl Agent for ReferenceAgent { "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, + }), ); if capabilities.supports_resources() { diff --git a/crates/goose/src/agents/summarize.rs b/crates/goose/src/agents/summarize.rs index 6c0da889..4f661b03 100644 --- a/crates/goose/src/agents/summarize.rs +++ b/crates/goose/src/agents/summarize.rs @@ -3,6 +3,7 @@ /// truncation method. Still cannot read resources. use async_trait::async_trait; use futures::stream::BoxStream; +use mcp_core::tool::ToolAnnotations; use std::collections::HashMap; use std::sync::Arc; use tokio::sync::mpsc; @@ -199,6 +200,13 @@ impl Agent for SummarizeAgent { "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, + }), ); let list_resources_tool = Tool::new( @@ -217,6 +225,13 @@ impl Agent for SummarizeAgent { "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, + }), ); if capabilities.supports_resources() { diff --git a/crates/goose/src/agents/truncate.rs b/crates/goose/src/agents/truncate.rs index bab25ed8..70fe57f0 100644 --- a/crates/goose/src/agents/truncate.rs +++ b/crates/goose/src/agents/truncate.rs @@ -2,6 +2,7 @@ /// It makes no attempt to handle context limits, and cannot read resources use async_trait::async_trait; use futures::stream::BoxStream; +use mcp_core::tool::ToolAnnotations; use std::collections::HashMap; use std::sync::Arc; use tokio::sync::mpsc; @@ -195,6 +196,13 @@ impl Agent for TruncateAgent { "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, + }), ); let list_resources_tool = Tool::new( @@ -213,6 +221,13 @@ impl Agent for TruncateAgent { "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, + }), ); if capabilities.supports_resources() { diff --git a/crates/goose/src/providers/formats/anthropic.rs b/crates/goose/src/providers/formats/anthropic.rs index df6c2dd7..b8219f09 100644 --- a/crates/goose/src/providers/formats/anthropic.rs +++ b/crates/goose/src/providers/formats/anthropic.rs @@ -530,6 +530,7 @@ mod tests { } } }), + None, ), Tool::new( "weather", @@ -543,6 +544,7 @@ mod tests { } } }), + None, ), ]; diff --git a/crates/goose/src/providers/formats/databricks.rs b/crates/goose/src/providers/formats/databricks.rs index acc974bb..fff5f1ad 100644 --- a/crates/goose/src/providers/formats/databricks.rs +++ b/crates/goose/src/providers/formats/databricks.rs @@ -675,6 +675,7 @@ mod tests { }, "required": ["input"] }), + None, ); let spec = format_tools(&[tool])?; @@ -766,6 +767,7 @@ mod tests { }, "required": ["input"] }), + None, ); let tool2 = Tool::new( @@ -781,6 +783,7 @@ mod tests { }, "required": ["input"] }), + None, ); let result = format_tools(&[tool1, tool2]); diff --git a/crates/goose/src/providers/formats/google.rs b/crates/goose/src/providers/formats/google.rs index 3f0038ed..6187b4ad 100644 --- a/crates/goose/src/providers/formats/google.rs +++ b/crates/goose/src/providers/formats/google.rs @@ -351,6 +351,7 @@ mod tests { input_schema: json!({ "properties": params }), + annotations: None, } } @@ -494,6 +495,7 @@ mod tests { input_schema: json!({ "properties": {} }), + annotations: None, }]; let result = format_tools(&tools); assert_eq!(result.len(), 1); diff --git a/crates/goose/src/providers/formats/openai.rs b/crates/goose/src/providers/formats/openai.rs index a4f17c70..c3368f2b 100644 --- a/crates/goose/src/providers/formats/openai.rs +++ b/crates/goose/src/providers/formats/openai.rs @@ -550,6 +550,7 @@ mod tests { }, "required": ["input"] }), + None, ); let spec = format_tools(&[tool])?; @@ -641,6 +642,7 @@ mod tests { }, "required": ["input"] }), + None, ); let tool2 = Tool::new( @@ -656,6 +658,7 @@ mod tests { }, "required": ["input"] }), + None, ); let result = format_tools(&[tool1, tool2]); diff --git a/crates/goose/src/token_counter.rs b/crates/goose/src/token_counter.rs index 1650df25..e9454a77 100644 --- a/crates/goose/src/token_counter.rs +++ b/crates/goose/src/token_counter.rs @@ -310,6 +310,7 @@ mod tests { }, "required": ["location"] }), + annotations: None, }]; let token_count_without_tools = counter.count_chat_tokens(system_prompt, &messages, &[]); diff --git a/crates/goose/tests/providers.rs b/crates/goose/tests/providers.rs index 332f3ee7..547ca54d 100644 --- a/crates/goose/tests/providers.rs +++ b/crates/goose/tests/providers.rs @@ -127,6 +127,7 @@ impl ProviderTester { } } }), + None, ); let message = Message::user().with_text("What's the weather like in San Francisco?"); diff --git a/crates/mcp-client/src/client.rs b/crates/mcp-client/src/client.rs index 6342ba1d..cb8247b9 100644 --- a/crates/mcp-client/src/client.rs +++ b/crates/mcp-client/src/client.rs @@ -245,7 +245,7 @@ where capabilities: ClientCapabilities, ) -> Result { let params = InitializeParams { - protocol_version: "2024-11-05".to_string(), + protocol_version: "2025-03-26".to_string(), client_info: info, capabilities, }; diff --git a/crates/mcp-core/src/tool.rs b/crates/mcp-core/src/tool.rs index 3f6f4246..65a954ff 100644 --- a/crates/mcp-core/src/tool.rs +++ b/crates/mcp-core/src/tool.rs @@ -3,6 +3,102 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; +/// Additional properties describing a tool to clients. +/// +/// NOTE: all properties in ToolAnnotations are **hints**. +/// They are not guaranteed to provide a faithful description of +/// tool behavior (including descriptive properties like `title`). +/// +/// Clients should never make tool use decisions based on ToolAnnotations +/// received from untrusted servers. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ToolAnnotations { + /// A human-readable title for the tool. + pub title: Option, + + /// If true, the tool does not modify its environment. + /// + /// Default: false + #[serde(default)] + pub read_only_hint: bool, + + /// If true, the tool may perform destructive updates to its environment. + /// If false, the tool performs only additive updates. + /// + /// (This property is meaningful only when `read_only_hint == false`) + /// + /// Default: true + #[serde(default = "default_true")] + pub destructive_hint: bool, + + /// If true, calling the tool repeatedly with the same arguments + /// will have no additional effect on its environment. + /// + /// (This property is meaningful only when `read_only_hint == false`) + /// + /// Default: false + #[serde(default)] + pub idempotent_hint: bool, + + /// If true, this tool may interact with an "open world" of external + /// entities. If false, the tool's domain of interaction is closed. + /// For example, the world of a web search tool is open, whereas that + /// of a memory tool is not. + /// + /// Default: true + #[serde(default = "default_true")] + pub open_world_hint: bool, +} + +impl Default for ToolAnnotations { + fn default() -> Self { + ToolAnnotations { + title: None, + read_only_hint: false, + destructive_hint: true, + idempotent_hint: false, + open_world_hint: true, + } + } +} + +fn default_true() -> bool { + true +} + +/// Implement builder methods for `ToolAnnotations` +impl ToolAnnotations { + pub fn new() -> Self { + Self::default() + } + + pub fn with_title(mut self, title: impl Into) -> Self { + self.title = Some(title.into()); + self + } + + pub fn with_read_only(mut self, read_only: bool) -> Self { + self.read_only_hint = read_only; + self + } + + pub fn with_destructive(mut self, destructive: bool) -> Self { + self.destructive_hint = destructive; + self + } + + pub fn with_idempotent(mut self, idempotent: bool) -> Self { + self.idempotent_hint = idempotent; + self + } + + pub fn with_open_world(mut self, open_world: bool) -> Self { + self.open_world_hint = open_world; + self + } +} + /// A tool that can be used by a model. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -13,11 +109,18 @@ pub struct Tool { pub description: String, /// A JSON Schema object defining the expected parameters for the tool pub input_schema: Value, + /// Optional additional tool information. + pub annotations: Option, } impl Tool { /// Create a new tool with the given name and description - pub fn new(name: N, description: D, input_schema: Value) -> Self + pub fn new( + name: N, + description: D, + input_schema: Value, + annotations: Option, + ) -> Self where N: Into, D: Into, @@ -26,6 +129,7 @@ impl Tool { name: name.into(), description: description.into(), input_schema, + annotations, } } } diff --git a/crates/mcp-server/src/main.rs b/crates/mcp-server/src/main.rs index 907cc1b1..ff2b7adf 100644 --- a/crates/mcp-server/src/main.rs +++ b/crates/mcp-server/src/main.rs @@ -2,6 +2,7 @@ use anyhow::Result; use mcp_core::content::Content; use mcp_core::handler::{PromptError, ResourceError}; use mcp_core::prompt::{Prompt, PromptArgument}; +use mcp_core::tool::ToolAnnotations; use mcp_core::{handler::ToolError, protocol::ServerCapabilities, resource::Resource, tool::Tool}; use mcp_server::router::{CapabilitiesBuilder, RouterService}; use mcp_server::{ByteTransport, Router, Server}; @@ -76,6 +77,13 @@ impl Router for CounterRouter { "properties": {}, "required": [] }), + Some(ToolAnnotations { + title: Some("Increment Tool".to_string()), + read_only_hint: false, + destructive_hint: false, + idempotent_hint: false, + open_world_hint: false, + }), ), Tool::new( "decrement".to_string(), @@ -85,6 +93,13 @@ impl Router for CounterRouter { "properties": {}, "required": [] }), + Some(ToolAnnotations { + title: Some("Decrement Tool".to_string()), + read_only_hint: false, + destructive_hint: false, + idempotent_hint: false, + open_world_hint: false, + }), ), Tool::new( "get_value".to_string(), @@ -94,6 +109,13 @@ impl Router for CounterRouter { "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, + }), ), ] } diff --git a/crates/mcp-server/src/router.rs b/crates/mcp-server/src/router.rs index 2c277d1c..0931966f 100644 --- a/crates/mcp-server/src/router.rs +++ b/crates/mcp-server/src/router.rs @@ -116,7 +116,7 @@ pub trait Router: Send + Sync + 'static { ) -> impl Future> + Send { async move { let result = InitializeResult { - protocol_version: "2024-11-05".to_string(), + protocol_version: "2025-03-26".to_string(), capabilities: self.capabilities().clone(), server_info: Implementation { name: self.name(),