feat: add permission field to the list tools response (#2080)

This commit is contained in:
Yingjie He
2025-04-08 09:47:29 -07:00
committed by GitHub
parent 76e2686c74
commit 318f419861
9 changed files with 152 additions and 17 deletions
+5 -2
View File
@@ -1,11 +1,12 @@
use utoipa::OpenApi;
use goose::agents::extension::Envs;
use goose::agents::extension::ToolInfo;
use goose::agents::ExtensionConfig;
use goose::config::permission::PermissionLevel;
use goose::config::ExtensionEntry;
use goose::providers::base::ConfigKey;
use goose::providers::base::ProviderMetadata;
use mcp_core::tool::{Tool, ToolAnnotations};
use utoipa::OpenApi;
#[allow(dead_code)] // Used by utoipa for OpenAPI generation
#[derive(OpenApi)]
@@ -36,6 +37,8 @@ use mcp_core::tool::{Tool, ToolAnnotations};
Envs,
Tool,
ToolAnnotations,
ToolInfo,
PermissionLevel,
))
)]
pub struct ApiDoc;
+38 -8
View File
@@ -1,13 +1,15 @@
use crate::state::AppState;
use axum::{
extract::State,
extract::{Query, State},
http::{HeaderMap, StatusCode},
routing::{get, post},
Json, Router,
};
use goose::config::Config;
use goose::{agents::AgentFactory, model::ModelConfig, providers};
use mcp_core::Tool;
use goose::{agents::AgentFactory, config::PermissionManager, model::ModelConfig, providers};
use goose::{
agents::{capabilities::get_parameter_names, extension::ToolInfo},
config::Config,
};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::env;
@@ -62,6 +64,11 @@ struct ProviderList {
details: ProviderDetails,
}
#[derive(Deserialize)]
pub struct GetToolsQuery {
extension_name: Option<String>,
}
async fn get_versions() -> Json<VersionsResponse> {
let versions = AgentFactory::available_versions();
let default_version = AgentFactory::default_version().to_string();
@@ -167,6 +174,9 @@ async fn list_providers() -> Json<Vec<ProviderList>> {
#[utoipa::path(
get,
path = "/agent/tools",
params(
("extension_name" = Option<String>, Query, description = "Optional extension name to filter tools")
),
responses(
(status = 200, description = "Tools retrieved successfully", body = Vec<Tool>),
(status = 401, description = "Unauthorized - invalid secret key"),
@@ -177,7 +187,8 @@ async fn list_providers() -> Json<Vec<ProviderList>> {
async fn get_tools(
State(state): State<AppState>,
headers: HeaderMap,
) -> Result<Json<Vec<Tool>>, StatusCode> {
Query(query): Query<GetToolsQuery>,
) -> Result<Json<Vec<ToolInfo>>, StatusCode> {
let secret_key = headers
.get("X-Secret-Key")
.and_then(|value| value.to_str().ok())
@@ -189,11 +200,30 @@ async fn get_tools(
let mut agent = state.agent.write().await;
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.list_tools().await;
let tools = agent
.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))
}
+10 -2
View File
@@ -9,6 +9,7 @@ use utoipa::ToSchema;
use crate::config;
use crate::config::extensions::name_to_key;
use crate::config::permission::PermissionLevel;
/// Errors from Extension operation
#[derive(Error, Debug)]
@@ -277,19 +278,26 @@ impl ExtensionInfo {
}
/// Information about the tool used for building prompts
#[derive(Clone, Debug, Serialize)]
#[derive(Clone, Debug, Serialize, ToSchema)]
pub struct ToolInfo {
name: String,
description: String,
parameters: Vec<String>,
permission: Option<PermissionLevel>,
}
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 {
name: name.to_string(),
description: description.to_string(),
parameters,
permission,
}
}
}
+8 -1
View File
@@ -289,7 +289,14 @@ impl Agent for ReferenceAgent {
let tools = capabilities.get_prefixed_tools().await?;
let tools_info = tools
.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();
let plan_prompt = capabilities.get_planning_prompt(tools_info).await;
+8 -1
View File
@@ -489,7 +489,14 @@ impl Agent for SummarizeAgent {
let tools = capabilities.get_prefixed_tools().await?;
let tools_info = tools
.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();
let plan_prompt = capabilities.get_planning_prompt(tools_info).await;
+8 -1
View File
@@ -774,7 +774,14 @@ impl Agent for TruncateAgent {
let tools = capabilities.get_prefixed_tools().await?;
let tools_info = tools
.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();
let plan_prompt = capabilities.get_planning_prompt(tools_info).await;
+2 -1
View File
@@ -5,9 +5,10 @@ use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs;
use std::path::{Path, PathBuf};
use utoipa::ToSchema;
/// 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")]
pub enum PermissionLevel {
AlwaysAllow, // Tool can always be used without prompt