diff --git a/Cargo.lock b/Cargo.lock index dbf7c77a..652e6405 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3572,6 +3572,7 @@ dependencies = [ "tempfile", "thiserror 1.0.69", "url", + "utoipa", ] [[package]] diff --git a/crates/goose-server/src/openapi.rs b/crates/goose-server/src/openapi.rs index bcbed312..27282645 100644 --- a/crates/goose-server/src/openapi.rs +++ b/crates/goose-server/src/openapi.rs @@ -5,6 +5,7 @@ use goose::agents::ExtensionConfig; use goose::config::ExtensionEntry; use goose::providers::base::ConfigKey; use goose::providers::base::ProviderMetadata; +use mcp_core::tool::{Tool, ToolAnnotations}; #[allow(dead_code)] // Used by utoipa for OpenAPI generation #[derive(OpenApi)] @@ -32,6 +33,8 @@ use goose::providers::base::ProviderMetadata; ExtensionConfig, ConfigKey, Envs, + Tool, + ToolAnnotations, )) )] pub struct ApiDoc; diff --git a/crates/goose/src/agents/extension.rs b/crates/goose/src/agents/extension.rs index 056786e2..838f8bbe 100644 --- a/crates/goose/src/agents/extension.rs +++ b/crates/goose/src/agents/extension.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; use mcp_client::client::Error as ClientError; +use mcp_core::tool::Tool; use serde::{Deserialize, Serialize}; use thiserror::Error; use tracing::warn; @@ -155,7 +156,7 @@ pub enum ExtensionConfig { /// The name used to identify this extension name: String, /// The tools provided by the frontend - tools: Vec, + tools: Vec, /// Instructions for how to use these tools instructions: Option, }, diff --git a/crates/mcp-core/Cargo.toml b/crates/mcp-core/Cargo.toml index d2d613dc..2e7d4cc6 100644 --- a/crates/mcp-core/Cargo.toml +++ b/crates/mcp-core/Cargo.toml @@ -13,6 +13,7 @@ anyhow = "1.0" chrono = { version = "0.4.38", features = ["serde"] } url = "2.5" base64 = "0.21" +utoipa = "4.1" [dev-dependencies] tempfile = "3.8" diff --git a/crates/mcp-core/src/tool.rs b/crates/mcp-core/src/tool.rs index 65a954ff..e6d0c2c6 100644 --- a/crates/mcp-core/src/tool.rs +++ b/crates/mcp-core/src/tool.rs @@ -2,6 +2,7 @@ /// Tool calls represent requests from the client to execute one use serde::{Deserialize, Serialize}; use serde_json::Value; +use utoipa::ToSchema; /// Additional properties describing a tool to clients. /// @@ -11,7 +12,7 @@ use serde_json::Value; /// /// Clients should never make tool use decisions based on ToolAnnotations /// received from untrusted servers. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct ToolAnnotations { /// A human-readable title for the tool. @@ -100,7 +101,7 @@ impl ToolAnnotations { } /// A tool that can be used by a model. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct Tool { /// The name of the tool diff --git a/ui/desktop/openapi.json b/ui/desktop/openapi.json index c3b85a5a..6744e2cb 100644 --- a/ui/desktop/openapi.json +++ b/ui/desktop/openapi.json @@ -424,6 +424,39 @@ ] } } + }, + { + "type": "object", + "description": "Frontend-provided tools that will be called through the frontend", + "required": [ + "name", + "tools", + "type" + ], + "properties": { + "instructions": { + "type": "string", + "description": "Instructions for how to use these tools", + "nullable": true + }, + "name": { + "type": "string", + "description": "The name used to identify this extension" + }, + "tools": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Tool" + }, + "description": "The tools provided by the frontend" + }, + "type": { + "type": "string", + "enum": [ + "frontend" + ] + } + } } ], "description": "Represents the different types of MCP extensions that can be added to the manager", @@ -566,6 +599,63 @@ } } }, + "Tool": { + "type": "object", + "description": "A tool that can be used by a model.", + "required": [ + "name", + "description", + "inputSchema" + ], + "properties": { + "annotations": { + "allOf": [ + { + "$ref": "#/components/schemas/ToolAnnotations" + } + ], + "nullable": true + }, + "description": { + "type": "string", + "description": "A description of what the tool does" + }, + "inputSchema": { + "description": "A JSON Schema object defining the expected parameters for the tool" + }, + "name": { + "type": "string", + "description": "The name of the tool" + } + } + }, + "ToolAnnotations": { + "type": "object", + "description": "Additional properties describing a tool to clients.\n\nNOTE: all properties in ToolAnnotations are **hints**.\nThey are not guaranteed to provide a faithful description of\ntool behavior (including descriptive properties like `title`).\n\nClients should never make tool use decisions based on ToolAnnotations\nreceived from untrusted servers.", + "properties": { + "destructiveHint": { + "type": "boolean", + "description": "If true, the tool may perform destructive updates to its environment.\nIf false, the tool performs only additive updates.\n\n(This property is meaningful only when `read_only_hint == false`)\n\nDefault: true" + }, + "idempotentHint": { + "type": "boolean", + "description": "If true, calling the tool repeatedly with the same arguments\nwill have no additional effect on its environment.\n\n(This property is meaningful only when `read_only_hint == false`)\n\nDefault: false" + }, + "openWorldHint": { + "type": "boolean", + "description": "If true, this tool may interact with an \"open world\" of external\nentities. If false, the tool's domain of interaction is closed.\nFor example, the world of a web search tool is open, whereas that\nof a memory tool is not.\n\nDefault: true" + }, + "readOnlyHint": { + "type": "boolean", + "description": "If true, the tool does not modify its environment.\n\nDefault: false" + }, + "title": { + "type": "string", + "description": "A human-readable title for the tool.", + "nullable": true + } + } + }, "UpsertConfigQuery": { "type": "object", "required": [ diff --git a/ui/desktop/src/api/types.gen.ts b/ui/desktop/src/api/types.gen.ts index 84716738..4fc3647d 100644 --- a/ui/desktop/src/api/types.gen.ts +++ b/ui/desktop/src/api/types.gen.ts @@ -52,6 +52,20 @@ export type ExtensionConfig = { name: string; timeout?: number | null; type: 'builtin'; +} | { + /** + * Instructions for how to use these tools + */ + instructions?: string | null; + /** + * The name used to identify this extension + */ + name: string; + /** + * The tools provided by the frontend + */ + tools: Array; + type: 'frontend'; }; export type ExtensionEntry = ExtensionConfig & { @@ -121,6 +135,75 @@ export type ProvidersResponse = { providers: Array; }; +/** + * A tool that can be used by a model. + */ +export type Tool = { + annotations?: ToolAnnotations | null; + /** + * A description of what the tool does + */ + description: string; + /** + * A JSON Schema object defining the expected parameters for the tool + */ + inputSchema: unknown; + /** + * The name of the tool + */ + name: string; +}; + +/** + * 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. + */ +export type ToolAnnotations = { + /** + * 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 + */ + destructiveHint?: boolean; + /** + * 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 + */ + idempotentHint?: boolean; + /** + * 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 + */ + openWorldHint?: boolean; + /** + * If true, the tool does not modify its environment. + * + * Default: false + */ + readOnlyHint?: boolean; + /** + * A human-readable title for the tool. + */ + title?: string | null; +}; + export type UpsertConfigQuery = { is_secret: boolean; key: string;