feat: add permission field to the list tools response (#2080)
This commit is contained in:
@@ -1,11 +1,12 @@
|
|||||||
use utoipa::OpenApi;
|
|
||||||
|
|
||||||
use goose::agents::extension::Envs;
|
use goose::agents::extension::Envs;
|
||||||
|
use goose::agents::extension::ToolInfo;
|
||||||
use goose::agents::ExtensionConfig;
|
use goose::agents::ExtensionConfig;
|
||||||
|
use goose::config::permission::PermissionLevel;
|
||||||
use goose::config::ExtensionEntry;
|
use goose::config::ExtensionEntry;
|
||||||
use goose::providers::base::ConfigKey;
|
use goose::providers::base::ConfigKey;
|
||||||
use goose::providers::base::ProviderMetadata;
|
use goose::providers::base::ProviderMetadata;
|
||||||
use mcp_core::tool::{Tool, ToolAnnotations};
|
use mcp_core::tool::{Tool, ToolAnnotations};
|
||||||
|
use utoipa::OpenApi;
|
||||||
|
|
||||||
#[allow(dead_code)] // Used by utoipa for OpenAPI generation
|
#[allow(dead_code)] // Used by utoipa for OpenAPI generation
|
||||||
#[derive(OpenApi)]
|
#[derive(OpenApi)]
|
||||||
@@ -36,6 +37,8 @@ use mcp_core::tool::{Tool, ToolAnnotations};
|
|||||||
Envs,
|
Envs,
|
||||||
Tool,
|
Tool,
|
||||||
ToolAnnotations,
|
ToolAnnotations,
|
||||||
|
ToolInfo,
|
||||||
|
PermissionLevel,
|
||||||
))
|
))
|
||||||
)]
|
)]
|
||||||
pub struct ApiDoc;
|
pub struct ApiDoc;
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
use crate::state::AppState;
|
use crate::state::AppState;
|
||||||
use axum::{
|
use axum::{
|
||||||
extract::State,
|
extract::{Query, State},
|
||||||
http::{HeaderMap, StatusCode},
|
http::{HeaderMap, StatusCode},
|
||||||
routing::{get, post},
|
routing::{get, post},
|
||||||
Json, Router,
|
Json, Router,
|
||||||
};
|
};
|
||||||
use goose::config::Config;
|
use goose::{agents::AgentFactory, config::PermissionManager, model::ModelConfig, providers};
|
||||||
use goose::{agents::AgentFactory, model::ModelConfig, providers};
|
use goose::{
|
||||||
use mcp_core::Tool;
|
agents::{capabilities::get_parameter_names, extension::ToolInfo},
|
||||||
|
config::Config,
|
||||||
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::env;
|
use std::env;
|
||||||
@@ -62,6 +64,11 @@ struct ProviderList {
|
|||||||
details: ProviderDetails,
|
details: ProviderDetails,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct GetToolsQuery {
|
||||||
|
extension_name: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
async fn get_versions() -> Json<VersionsResponse> {
|
async fn get_versions() -> Json<VersionsResponse> {
|
||||||
let versions = AgentFactory::available_versions();
|
let versions = AgentFactory::available_versions();
|
||||||
let default_version = AgentFactory::default_version().to_string();
|
let default_version = AgentFactory::default_version().to_string();
|
||||||
@@ -167,6 +174,9 @@ async fn list_providers() -> Json<Vec<ProviderList>> {
|
|||||||
#[utoipa::path(
|
#[utoipa::path(
|
||||||
get,
|
get,
|
||||||
path = "/agent/tools",
|
path = "/agent/tools",
|
||||||
|
params(
|
||||||
|
("extension_name" = Option<String>, Query, description = "Optional extension name to filter tools")
|
||||||
|
),
|
||||||
responses(
|
responses(
|
||||||
(status = 200, description = "Tools retrieved successfully", body = Vec<Tool>),
|
(status = 200, description = "Tools retrieved successfully", body = Vec<Tool>),
|
||||||
(status = 401, description = "Unauthorized - invalid secret key"),
|
(status = 401, description = "Unauthorized - invalid secret key"),
|
||||||
@@ -177,7 +187,8 @@ async fn list_providers() -> Json<Vec<ProviderList>> {
|
|||||||
async fn get_tools(
|
async fn get_tools(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
) -> Result<Json<Vec<Tool>>, StatusCode> {
|
Query(query): Query<GetToolsQuery>,
|
||||||
|
) -> Result<Json<Vec<ToolInfo>>, StatusCode> {
|
||||||
let secret_key = headers
|
let secret_key = headers
|
||||||
.get("X-Secret-Key")
|
.get("X-Secret-Key")
|
||||||
.and_then(|value| value.to_str().ok())
|
.and_then(|value| value.to_str().ok())
|
||||||
@@ -189,11 +200,30 @@ async fn get_tools(
|
|||||||
|
|
||||||
let mut agent = state.agent.write().await;
|
let mut agent = state.agent.write().await;
|
||||||
let agent = agent.as_mut().ok_or(StatusCode::PRECONDITION_REQUIRED)?;
|
let agent = agent.as_mut().ok_or(StatusCode::PRECONDITION_REQUIRED)?;
|
||||||
|
let permission_manager = PermissionManager::default();
|
||||||
|
|
||||||
// Since list_tools() now returns Vec<Tool> directly, not a Result
|
let tools = agent
|
||||||
let tools = agent.list_tools().await;
|
.list_tools()
|
||||||
|
.await
|
||||||
|
.into_iter()
|
||||||
|
.filter(|tool| {
|
||||||
|
// Apply the filter only if the extension name is present in the query
|
||||||
|
if let Some(extension_name) = &query.extension_name {
|
||||||
|
tool.name.starts_with(extension_name)
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.map(|tool| {
|
||||||
|
ToolInfo::new(
|
||||||
|
&tool.name,
|
||||||
|
&tool.description,
|
||||||
|
get_parameter_names(&tool),
|
||||||
|
permission_manager.get_user_permission(&tool.name),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
// Return the tools directly
|
|
||||||
Ok(Json(tools))
|
Ok(Json(tools))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use utoipa::ToSchema;
|
|||||||
|
|
||||||
use crate::config;
|
use crate::config;
|
||||||
use crate::config::extensions::name_to_key;
|
use crate::config::extensions::name_to_key;
|
||||||
|
use crate::config::permission::PermissionLevel;
|
||||||
|
|
||||||
/// Errors from Extension operation
|
/// Errors from Extension operation
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
@@ -277,19 +278,26 @@ impl ExtensionInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Information about the tool used for building prompts
|
/// Information about the tool used for building prompts
|
||||||
#[derive(Clone, Debug, Serialize)]
|
#[derive(Clone, Debug, Serialize, ToSchema)]
|
||||||
pub struct ToolInfo {
|
pub struct ToolInfo {
|
||||||
name: String,
|
name: String,
|
||||||
description: String,
|
description: String,
|
||||||
parameters: Vec<String>,
|
parameters: Vec<String>,
|
||||||
|
permission: Option<PermissionLevel>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToolInfo {
|
impl ToolInfo {
|
||||||
pub fn new(name: &str, description: &str, parameters: Vec<String>) -> Self {
|
pub fn new(
|
||||||
|
name: &str,
|
||||||
|
description: &str,
|
||||||
|
parameters: Vec<String>,
|
||||||
|
permission: Option<PermissionLevel>,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
description: description.to_string(),
|
description: description.to_string(),
|
||||||
parameters,
|
parameters,
|
||||||
|
permission,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -289,7 +289,14 @@ impl Agent for ReferenceAgent {
|
|||||||
let tools = capabilities.get_prefixed_tools().await?;
|
let tools = capabilities.get_prefixed_tools().await?;
|
||||||
let tools_info = tools
|
let tools_info = tools
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|tool| ToolInfo::new(&tool.name, &tool.description, get_parameter_names(&tool)))
|
.map(|tool| {
|
||||||
|
ToolInfo::new(
|
||||||
|
&tool.name,
|
||||||
|
&tool.description,
|
||||||
|
get_parameter_names(&tool),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let plan_prompt = capabilities.get_planning_prompt(tools_info).await;
|
let plan_prompt = capabilities.get_planning_prompt(tools_info).await;
|
||||||
|
|||||||
@@ -489,7 +489,14 @@ impl Agent for SummarizeAgent {
|
|||||||
let tools = capabilities.get_prefixed_tools().await?;
|
let tools = capabilities.get_prefixed_tools().await?;
|
||||||
let tools_info = tools
|
let tools_info = tools
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|tool| ToolInfo::new(&tool.name, &tool.description, get_parameter_names(&tool)))
|
.map(|tool| {
|
||||||
|
ToolInfo::new(
|
||||||
|
&tool.name,
|
||||||
|
&tool.description,
|
||||||
|
get_parameter_names(&tool),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let plan_prompt = capabilities.get_planning_prompt(tools_info).await;
|
let plan_prompt = capabilities.get_planning_prompt(tools_info).await;
|
||||||
|
|||||||
@@ -774,7 +774,14 @@ impl Agent for TruncateAgent {
|
|||||||
let tools = capabilities.get_prefixed_tools().await?;
|
let tools = capabilities.get_prefixed_tools().await?;
|
||||||
let tools_info = tools
|
let tools_info = tools
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|tool| ToolInfo::new(&tool.name, &tool.description, get_parameter_names(&tool)))
|
.map(|tool| {
|
||||||
|
ToolInfo::new(
|
||||||
|
&tool.name,
|
||||||
|
&tool.description,
|
||||||
|
get_parameter_names(&tool),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let plan_prompt = capabilities.get_planning_prompt(tools_info).await;
|
let plan_prompt = capabilities.get_planning_prompt(tools_info).await;
|
||||||
|
|||||||
@@ -5,9 +5,10 @@ use serde::{Deserialize, Serialize};
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
use utoipa::ToSchema;
|
||||||
|
|
||||||
/// Enum representing the possible permission levels for a tool.
|
/// Enum representing the possible permission levels for a tool.
|
||||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
|
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, ToSchema)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum PermissionLevel {
|
pub enum PermissionLevel {
|
||||||
AlwaysAllow, // Tool can always be used without prompt
|
AlwaysAllow, // Tool can always be used without prompt
|
||||||
|
|||||||
@@ -19,6 +19,18 @@
|
|||||||
"super::routes::agent"
|
"super::routes::agent"
|
||||||
],
|
],
|
||||||
"operationId": "get_tools",
|
"operationId": "get_tools",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "extension_name",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Optional extension name to filter tools",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"nullable": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "Tools retrieved successfully",
|
"description": "Tools retrieved successfully",
|
||||||
@@ -547,6 +559,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"PermissionLevel": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Enum representing the possible permission levels for a tool.",
|
||||||
|
"enum": [
|
||||||
|
"always_allow",
|
||||||
|
"ask_before",
|
||||||
|
"never_allow"
|
||||||
|
]
|
||||||
|
},
|
||||||
"ProviderDetails": {
|
"ProviderDetails": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
@@ -688,6 +709,37 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"ToolInfo": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Information about the tool used for building prompts",
|
||||||
|
"required": [
|
||||||
|
"name",
|
||||||
|
"description",
|
||||||
|
"parameters"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"description": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"parameters": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"permission": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/PermissionLevel"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nullable": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"UpsertConfigQuery": {
|
"UpsertConfigQuery": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
|
|||||||
@@ -84,6 +84,11 @@ export type ExtensionResponse = {
|
|||||||
extensions: Array<ExtensionEntry>;
|
extensions: Array<ExtensionEntry>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum representing the possible permission levels for a tool.
|
||||||
|
*/
|
||||||
|
export type PermissionLevel = 'always_allow' | 'ask_before' | 'never_allow';
|
||||||
|
|
||||||
export type ProviderDetails = {
|
export type ProviderDetails = {
|
||||||
/**
|
/**
|
||||||
* Indicates whether the provider is fully configured
|
* Indicates whether the provider is fully configured
|
||||||
@@ -204,6 +209,16 @@ export type ToolAnnotations = {
|
|||||||
title?: string | null;
|
title?: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Information about the tool used for building prompts
|
||||||
|
*/
|
||||||
|
export type ToolInfo = {
|
||||||
|
description: string;
|
||||||
|
name: string;
|
||||||
|
parameters: Array<string>;
|
||||||
|
permission?: PermissionLevel | null;
|
||||||
|
};
|
||||||
|
|
||||||
export type UpsertConfigQuery = {
|
export type UpsertConfigQuery = {
|
||||||
is_secret: boolean;
|
is_secret: boolean;
|
||||||
key: string;
|
key: string;
|
||||||
@@ -213,7 +228,12 @@ export type UpsertConfigQuery = {
|
|||||||
export type GetToolsData = {
|
export type GetToolsData = {
|
||||||
body?: never;
|
body?: never;
|
||||||
path?: never;
|
path?: never;
|
||||||
query?: never;
|
query?: {
|
||||||
|
/**
|
||||||
|
* Optional extension name to filter tools
|
||||||
|
*/
|
||||||
|
extension_name?: string | null;
|
||||||
|
};
|
||||||
url: '/agent/tools';
|
url: '/agent/tools';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user