diff --git a/Cargo.lock b/Cargo.lock index f99f3e9cac1f..cf3088070085 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5442,9 +5442,9 @@ dependencies = [ [[package]] name = "rmcp" -version = "0.6.2" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "817ef98583b16628962dd8214ee89e9a9df6a79de9b487eb29ceddb1f610a7f6" +checksum = "534fd1cd0601e798ac30545ff2b7f4a62c6f14edd4aaed1cc5eb1e85f69f09af" dependencies = [ "base64 0.22.1", "chrono", @@ -5470,9 +5470,9 @@ dependencies = [ [[package]] name = "rmcp-macros" -version = "0.6.2" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9712e19c12a2812bffa85ab71714e2a342a9d7e79656e6f74f650475b8094090" +checksum = "9ba777eb0e5f53a757e36f0e287441da0ab766564ba7201600eeb92a4753022e" dependencies = [ "darling 0.21.0", "proc-macro2", diff --git a/Cargo.toml b/Cargo.toml index e30ba9ee5a7f..c84d942ae194 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ description = "An AI agent" uninlined_format_args = "allow" [workspace.dependencies] -rmcp = { version = "0.6.2", features = ["schemars", "auth"] } +rmcp = { version = "0.7.0", features = ["schemars", "auth"] } # Patch for Windows cross-compilation issue with crunchy [patch.crates-io] diff --git a/crates/goose-cli/src/session/completion.rs b/crates/goose-cli/src/session/completion.rs index 4cad8ae2ee47..67021047458c 100644 --- a/crates/goose-cli/src/session/completion.rs +++ b/crates/goose-cli/src/session/completion.rs @@ -458,11 +458,13 @@ mod tests { name: "required_arg".to_string(), description: Some("A required argument".to_string()), required: Some(true), + title: None, }, PromptArgument { name: "optional_arg".to_string(), description: Some("An optional argument".to_string()), required: Some(false), + title: None, }, ]; diff --git a/crates/goose-mcp/Cargo.toml b/crates/goose-mcp/Cargo.toml index fecc26f44603..4db0e66b0063 100644 --- a/crates/goose-mcp/Cargo.toml +++ b/crates/goose-mcp/Cargo.toml @@ -12,7 +12,7 @@ workspace = true [dependencies] goose = { path = "../goose" } -rmcp = { version = "0.6.0", features = ["server", "client", "transport-io", "macros"] } +rmcp = { version = "0.7.0", features = ["server", "client", "transport-io", "macros"] } anyhow = "1.0.94" tokio = { version = "1", features = ["full"] } tokio-stream = { version = "0.1", features = ["io-util"] } diff --git a/crates/goose-mcp/src/autovisualiser/mod.rs b/crates/goose-mcp/src/autovisualiser/mod.rs index 9d23eb130a1b..7cef0197ae78 100644 --- a/crates/goose-mcp/src/autovisualiser/mod.rs +++ b/crates/goose-mcp/src/autovisualiser/mod.rs @@ -409,6 +409,9 @@ impl ServerHandler for AutoVisualiserRouter { server_info: Implementation { name: "goose-autovisualiser".to_string(), version: env!("CARGO_PKG_VERSION").to_owned(), + title: None, + icons: None, + website_url: None, }, capabilities: ServerCapabilities::builder().enable_tools().build(), instructions: Some(self.instructions.clone()), @@ -458,7 +461,7 @@ impl AutoVisualiserRouter { /// show a Sankey diagram from flow data #[tool( name = "render_sankey", - description = r#"show a Sankey diagram from flow data + description = r#"show a Sankey diagram from flow data The data must contain: - nodes: Array of objects with 'name' and optional 'category' properties - links: Array of objects with 'source', 'target', and 'value' properties @@ -537,7 +540,7 @@ Example: /// show a radar chart (spider chart) for multi-dimensional data comparison #[tool( name = "render_radar", - description = r#"show a radar chart (spider chart) for multi-dimensional data comparison + description = r#"show a radar chart (spider chart) for multi-dimensional data comparison The data must contain: - labels: Array of strings representing the dimensions/axes @@ -552,7 +555,7 @@ Example: "data": [85, 70, 90, 75, 80] }, { - "label": "Player 2", + "label": "Player 2", "data": [75, 85, 80, 90, 70] } ] diff --git a/crates/goose-mcp/src/computercontroller/mod.rs b/crates/goose-mcp/src/computercontroller/mod.rs index ba00f3850c0a..dadf508fa653 100644 --- a/crates/goose-mcp/src/computercontroller/mod.rs +++ b/crates/goose-mcp/src/computercontroller/mod.rs @@ -4,9 +4,9 @@ use reqwest::{Client, Url}; use rmcp::{ handler::server::{router::tool::ToolRouter, wrapper::Parameters}, model::{ - CallToolResult, Content, ErrorCode, ErrorData, Implementation, ListResourcesResult, - PaginatedRequestParam, RawResource, ReadResourceRequestParam, ReadResourceResult, Resource, - ResourceContents, ServerCapabilities, ServerInfo, + AnnotateAble, CallToolResult, Content, ErrorCode, ErrorData, Implementation, + ListResourcesResult, PaginatedRequestParam, RawResource, ReadResourceRequestParam, + ReadResourceResult, Resource, ResourceContents, ServerCapabilities, ServerInfo, }, schemars::JsonSchema, service::RequestContext, @@ -1293,6 +1293,9 @@ impl ServerHandler for ComputerControllerServer { server_info: Implementation { name: "goose-computercontroller".to_string(), version: env!("CARGO_PKG_VERSION").to_owned(), + title: None, + icons: None, + website_url: None, }, capabilities: ServerCapabilities::builder() .enable_tools() @@ -1311,15 +1314,12 @@ impl ServerHandler for ComputerControllerServer { let active_resources = self.active_resources.lock().unwrap(); let resources: Vec = active_resources .keys() - .map(|uri| Resource { - raw: RawResource { - name: uri.split('/').next_back().unwrap_or("").to_string(), - uri: uri.clone(), - description: None, - mime_type: None, - size: None, - }, - annotations: None, + .map(|uri| { + RawResource::new( + uri.clone(), + uri.split('/').next_back().unwrap_or("").to_string(), + ) + .no_annotation() }) .collect(); Ok(ListResourcesResult { diff --git a/crates/goose-mcp/src/developer/rmcp_developer.rs b/crates/goose-mcp/src/developer/rmcp_developer.rs index 7715a0f1dd45..bbd91d96ae25 100644 --- a/crates/goose-mcp/src/developer/rmcp_developer.rs +++ b/crates/goose-mcp/src/developer/rmcp_developer.rs @@ -150,6 +150,7 @@ fn load_prompt_files() -> HashMap { name: arg.name, description: arg.description, required: arg.required, + title: None, }) .collect::>(); @@ -383,6 +384,9 @@ impl ServerHandler for DeveloperServer { server_info: Implementation { name: "goose-developer".to_string(), version: env!("CARGO_PKG_VERSION").to_owned(), + title: None, + icons: None, + website_url: None, }, capabilities: ServerCapabilities::builder() .enable_tools() diff --git a/crates/goose-mcp/src/memory/mod.rs b/crates/goose-mcp/src/memory/mod.rs index 91109e797b54..71b1aa58e73a 100644 --- a/crates/goose-mcp/src/memory/mod.rs +++ b/crates/goose-mcp/src/memory/mod.rs @@ -521,6 +521,9 @@ impl ServerHandler for MemoryServer { server_info: Implementation { name: "goose-memory".to_string(), version: env!("CARGO_PKG_VERSION").to_owned(), + title: None, + icons: None, + website_url: None, }, capabilities: ServerCapabilities::builder().enable_tools().build(), instructions: Some(self.instructions.clone()), diff --git a/crates/goose-mcp/src/tutorial/mod.rs b/crates/goose-mcp/src/tutorial/mod.rs index a09124185d83..49e3d93c12c9 100644 --- a/crates/goose-mcp/src/tutorial/mod.rs +++ b/crates/goose-mcp/src/tutorial/mod.rs @@ -113,6 +113,9 @@ impl ServerHandler for TutorialServer { server_info: Implementation { name: "goose-tutorial".to_string(), version: env!("CARGO_PKG_VERSION").to_owned(), + title: None, + icons: None, + website_url: None, }, capabilities: ServerCapabilities::builder().enable_tools().build(), instructions: Some(self.instructions.clone()), diff --git a/crates/goose-server/src/openapi.rs b/crates/goose-server/src/openapi.rs index ff5d1d271e39..582caa2887ce 100644 --- a/crates/goose-server/src/openapi.rs +++ b/crates/goose-server/src/openapi.rs @@ -9,9 +9,9 @@ use goose::providers::base::{ConfigKey, ModelInfo, ProviderMetadata}; use goose::session::{Session, SessionInsights}; use rmcp::model::{ - Annotations, Content, EmbeddedResource, ImageContent, JsonObject, RawEmbeddedResource, - RawImageContent, RawResource, RawTextContent, ResourceContents, Role, TextContent, Tool, - ToolAnnotations, + Annotations, Content, EmbeddedResource, Icon, ImageContent, JsonObject, RawAudioContent, + RawEmbeddedResource, RawImageContent, RawResource, RawTextContent, ResourceContents, Role, + TextContent, Tool, ToolAnnotations, }; use utoipa::{OpenApi, ToSchema}; @@ -307,6 +307,7 @@ derive_utoipa!(ImageContent as ImageContentSchema); derive_utoipa!(TextContent as TextContentSchema); derive_utoipa!(RawTextContent as RawTextContentSchema); derive_utoipa!(RawImageContent as RawImageContentSchema); +derive_utoipa!(RawAudioContent as RawAudioContentSchema); derive_utoipa!(RawEmbeddedResource as RawEmbeddedResourceSchema); derive_utoipa!(RawResource as RawResourceSchema); derive_utoipa!(Tool as ToolSchema); @@ -314,30 +315,7 @@ derive_utoipa!(ToolAnnotations as ToolAnnotationsSchema); derive_utoipa!(Annotations as AnnotationsSchema); derive_utoipa!(ResourceContents as ResourceContentsSchema); derive_utoipa!(JsonObject as JsonObjectSchema); - -// Create a manual schema for the generic Annotated type -// We manually define this to avoid circular references from RawContent::Audio(AudioContent) -// where AudioContent = Annotated -struct AnnotatedSchema {} - -impl<'__s> ToSchema<'__s> for AnnotatedSchema { - fn schema() -> (&'__s str, utoipa::openapi::RefOr) { - let schema = Schema::OneOf( - OneOfBuilder::new() - .item(RefOr::Ref(Ref::new("#/components/schemas/RawTextContent"))) - .item(RefOr::Ref(Ref::new("#/components/schemas/RawImageContent"))) - .item(RefOr::Ref(Ref::new( - "#/components/schemas/RawEmbeddedResource", - ))) - .build(), - ); - ("Annotated", RefOr::T(schema)) - } - - fn aliases() -> Vec<(&'__s str, utoipa::openapi::schema::Schema)> { - Vec::new() - } -} +derive_utoipa!(Icon as IconSchema); #[derive(OpenApi)] #[openapi( @@ -419,9 +397,9 @@ impl<'__s> ToSchema<'__s> for AnnotatedSchema { TextContentSchema, RawTextContentSchema, RawImageContentSchema, + RawAudioContentSchema, RawEmbeddedResourceSchema, RawResourceSchema, - AnnotatedSchema, ToolResponse, ToolRequest, ToolConfirmationRequest, @@ -447,6 +425,7 @@ impl<'__s> ToSchema<'__s> for AnnotatedSchema { Session, SessionInsights, Conversation, + IconSchema, goose::session::extension_data::ExtensionData, super::routes::schedule::CreateScheduleRequest, super::routes::schedule::UpdateScheduleRequest, diff --git a/crates/goose/src/agents/extension_manager.rs b/crates/goose/src/agents/extension_manager.rs index 8deded6a7b93..c876231e3518 100644 --- a/crates/goose/src/agents/extension_manager.rs +++ b/crates/goose/src/agents/extension_manager.rs @@ -562,6 +562,8 @@ impl ExtensionManager { input_schema: tool.input_schema, annotations: tool.annotations, output_schema: tool.output_schema, + icons: None, + title: None, }); } } @@ -1134,27 +1136,21 @@ mod tests { use std::sync::Arc; Ok(ListToolsResult { tools: vec![ - Tool { - name: "tool".into(), - description: Some("A basic tool".into()), - input_schema: Arc::new(json!({}).as_object().unwrap().clone()), - annotations: None, - output_schema: None, - }, - Tool { - name: "available_tool".into(), - description: Some("An available tool".into()), - input_schema: Arc::new(json!({}).as_object().unwrap().clone()), - annotations: None, - output_schema: None, - }, - Tool { - name: "hidden_tool".into(), - description: Some("A hidden tool".into()), - input_schema: Arc::new(json!({}).as_object().unwrap().clone()), - annotations: None, - output_schema: None, - }, + Tool::new( + "tool".to_string(), + "A basic tool".to_string(), + Arc::new(json!({}).as_object().unwrap().clone()), + ), + Tool::new( + "available_tool".to_string(), + "An available tool".to_string(), + Arc::new(json!({}).as_object().unwrap().clone()), + ), + Tool::new( + "hidden_tool".to_string(), + "hidden tool".to_string(), + Arc::new(json!({}).as_object().unwrap().clone()), + ), ], next_cursor: None, }) diff --git a/crates/goose/src/agents/mcp_client.rs b/crates/goose/src/agents/mcp_client.rs index 735884c5d5ed..26718ec9c614 100644 --- a/crates/goose/src/agents/mcp_client.rs +++ b/crates/goose/src/agents/mcp_client.rs @@ -134,6 +134,9 @@ impl ClientHandler for GooseClient { client_info: Implementation { name: "goose".to_string(), version: env!("CARGO_PKG_VERSION").to_owned(), + icons: None, + title: None, + website_url: None, }, } } diff --git a/crates/goose/src/oauth/mod.rs b/crates/goose/src/oauth/mod.rs index 1dcb954fa5dc..96e287e35258 100644 --- a/crates/goose/src/oauth/mod.rs +++ b/crates/goose/src/oauth/mod.rs @@ -19,14 +19,13 @@ const CALLBACK_TEMPLATE: &str = include_str!("oauth_callback.html"); #[derive(Clone)] struct AppState { - code_receiver: Arc>>>, + code_receiver: Arc>>>, } #[derive(Debug, Deserialize)] struct CallbackParams { code: String, - #[allow(dead_code)] - state: Option, + state: String, } pub async fn oauth_flow( @@ -45,7 +44,7 @@ pub async fn oauth_flow( } } - let (code_sender, code_receiver) = oneshot::channel::(); + let (code_sender, code_receiver) = oneshot::channel::(); let app_state = AppState { code_receiver: Arc::new(Mutex::new(Some(code_sender))), }; @@ -55,7 +54,7 @@ pub async fn oauth_flow( let rendered = rendered.clone(); async move { if let Some(sender) = state.code_receiver.lock().await.take() { - let _ = sender.send(params.code); + let _ = sender.send(params); } Html(rendered) } @@ -86,8 +85,11 @@ pub async fn oauth_flow( eprintln!(" {}", authorization_url); } - let auth_code = code_receiver.await?; - oauth_state.handle_callback(&auth_code).await?; + let CallbackParams { + code: auth_code, + state: csrf_token, + } = code_receiver.await?; + oauth_state.handle_callback(&auth_code, &csrf_token).await?; if let Err(e) = save_credentials(name, &oauth_state).await { warn!("Failed to save credentials: {}", e); diff --git a/ui/desktop/openapi.json b/ui/desktop/openapi.json index 296ec589f14f..19c9091f057e 100644 --- a/ui/desktop/openapi.json +++ b/ui/desktop/openapi.json @@ -1715,19 +1715,6 @@ } } }, - "Annotated": { - "oneOf": [ - { - "$ref": "#/components/schemas/RawTextContent" - }, - { - "$ref": "#/components/schemas/RawImageContent" - }, - { - "$ref": "#/components/schemas/RawEmbeddedResource" - } - ] - }, "Annotations": { "type": "object", "properties": { @@ -1858,7 +1845,7 @@ { "allOf": [ { - "$ref": "#/components/schemas/Annotated" + "$ref": "#/components/schemas/RawAudioContent" } ] }, @@ -2587,6 +2574,23 @@ } } }, + "Icon": { + "type": "object", + "required": [ + "src" + ], + "properties": { + "mimeType": { + "type": "string" + }, + "sizes": { + "type": "string" + }, + "src": { + "type": "string" + } + } + }, "ImageContent": { "type": "object", "required": [ @@ -3100,6 +3104,21 @@ } } }, + "RawAudioContent": { + "type": "object", + "required": [ + "data", + "mimeType" + ], + "properties": { + "data": { + "type": "string" + }, + "mimeType": { + "type": "string" + } + } + }, "RawEmbeddedResource": { "type": "object", "required": [ @@ -3144,6 +3163,12 @@ "description": { "type": "string" }, + "icons": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Icon" + } + }, "mimeType": { "type": "string" }, @@ -3154,6 +3179,9 @@ "type": "integer", "minimum": 0 }, + "title": { + "type": "string" + }, "uri": { "type": "string" } @@ -3945,6 +3973,12 @@ "description": { "type": "string" }, + "icons": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Icon" + } + }, "inputSchema": { "type": "object", "additionalProperties": true @@ -3955,6 +3989,9 @@ "outputSchema": { "type": "object", "additionalProperties": true + }, + "title": { + "type": "string" } } }, diff --git a/ui/desktop/src/api/types.gen.ts b/ui/desktop/src/api/types.gen.ts index ec0fdcb247fa..962ba6f4a21c 100644 --- a/ui/desktop/src/api/types.gen.ts +++ b/ui/desktop/src/api/types.gen.ts @@ -9,8 +9,6 @@ export type AddSubRecipesResponse = { success: boolean; }; -export type Annotated = RawTextContent | RawImageContent | RawEmbeddedResource; - export type Annotations = { audience?: Array; lastModified?: string; @@ -65,7 +63,7 @@ export type ConfigResponse = { }; }; -export type Content = RawTextContent | RawImageContent | RawEmbeddedResource | Annotated | RawResource; +export type Content = RawTextContent | RawImageContent | RawEmbeddedResource | RawAudioContent | RawResource; export type ContextLengthExceeded = { msg: string; @@ -331,6 +329,12 @@ export type GetToolsQuery = { session_id: string; }; +export type Icon = { + mimeType?: string; + sizes?: string; + src: string; +}; + export type ImageContent = { _meta?: { [key: string]: unknown; @@ -503,6 +507,11 @@ export type ProvidersResponse = { providers: Array; }; +export type RawAudioContent = { + data: string; + mimeType: string; +}; + export type RawEmbeddedResource = { _meta?: { [key: string]: unknown; @@ -520,9 +529,11 @@ export type RawImageContent = { export type RawResource = { description?: string; + icons?: Array; mimeType?: string; name: string; size?: number; + title?: string; uri: string; }; @@ -826,6 +837,7 @@ export type Tool = { [key: string]: unknown; }; description?: string; + icons?: Array; inputSchema: { [key: string]: unknown; }; @@ -833,6 +845,7 @@ export type Tool = { outputSchema?: { [key: string]: unknown; }; + title?: string; }; export type ToolAnnotations = {