Skip to content

Commit

Permalink
feat: add list_resources too to use in conjunction with read_resource
Browse files Browse the repository at this point in the history
  • Loading branch information
kalvinnchau committed Jan 17, 2025
1 parent 3dfd6f2 commit 06388d2
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 4 deletions.
64 changes: 64 additions & 0 deletions crates/goose/src/agents/capabilities.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use chrono::{DateTime, TimeZone, Utc};
use futures::stream;
use futures::stream::StreamExt;
use mcp_client::McpService;
use rust_decimal_macros::dec;
use std::collections::HashMap;
Expand Down Expand Up @@ -368,12 +370,74 @@ impl Capabilities {
return Ok(result);
}

async fn list_resources(&self, params: Value) -> Result<Vec<Content>, ToolError> {
let system = params.get("system").and_then(|v| v.as_str());

if system.is_some() {
// grab the system
let client = self.clients.get(system.unwrap()).ok_or_else(|| {
ToolError::InvalidParameters(format!("System {} is not valid", system.unwrap()))
})?;

let client_guard = client.lock().await;
let result = client_guard
.list_resources(None)
.await
.map_err(|e| {
ToolError::ExecutionError(format!(
"Unable to list resources for {}, {:?}",
system.unwrap(),
e
))
})
.map(|lr| {
let resource_list = lr
.resources
.into_iter()
.map(|r| format!("{}, uri: ({})", r.name, r.uri))
.collect::<Vec<String>>()
.join("\n");

vec![Content::text(resource_list)]
});

return result;
}

// if no system name, loop through all systems
let results: Vec<_> = stream::iter(self.clients.clone().into_iter())
.then(|(_system_name, client)| async move {
let guard = client.lock().await;
guard.list_resources(None).await
})
.collect()
.await;

let (resources, errs): (Vec<_>, Vec<_>) = results.into_iter().partition(Result::is_ok);

let errs: Vec<_> = errs.into_iter().map(Result::unwrap_err).collect();
tracing::error!(errors = ?errs, "errors from listing rsources");

// take all resources and convert to Content as Tool response
let all_resources_str: String = resources
.into_iter()
.map(Result::unwrap)
.flat_map(|lr| lr.resources)
.map(|resource| format!("{}, uri: ({})", resource.name, resource.uri))
.collect::<Vec<_>>()
.join("\n");

Ok(vec![Content::text(all_resources_str)])
}

/// Dispatch a single tool call to the appropriate client
#[instrument(skip(self, tool_call), fields(input, output))]
pub async fn dispatch_tool_call(&self, tool_call: ToolCall) -> ToolResult<Vec<Content>> {
let result = if tool_call.name == "platform__read_resource" {
// Check if the tool is read_resource and handle it separately
self.read_resource(tool_call.arguments.clone()).await
} else if tool_call.name == "platform__list_resources" {
self.list_resources(tool_call.arguments.clone()).await
} else {
// Else, dispatch tool call based on the prefix naming convention
let client = self
Expand Down
32 changes: 28 additions & 4 deletions crates/goose/src/agents/reference.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,12 @@ impl Agent for ReferenceAgent {
"platform__read_resource".to_string(),
indoc! {r#"
Read a resource from a system.
Resources allow systems to share data that provide context to LLMs, such as
files, database schemas, or application-specific information. This tool searches for the
Resources allow systems to share data that provide context to LLMs, such as
files, database schemas, or application-specific information. This tool searches for the
resource URI in the provided system, and reads in the resource content. If no system
is provided, the tool will search all systems for the resource.
The read_resource tool is typically used with a search query (can be before or after). Here are two examples:
1. Search for files in Google Drive MCP Server, then call read_resource(gdrive:///<file_id>) to read the Google Drive file.
2. You need to gather schema information about a Postgres table before creating a query. So you call read_resource(postgres://<host>/<table>/schema)
Expand All @@ -96,7 +96,31 @@ impl Agent for ReferenceAgent {
}),
);

let list_resources_tool = Tool::new(
"platform__list_resources".to_string(),
indoc! {r#"
List resources from a system(s).
Resources allow systems to share data that provide context to LLMs, such as
files, database schemas, or application-specific information. This tool lists resources
in the provided system, and returns a list for the user to browse. If no system
is provided, the tool will search all systems for the resource.
The list_resources tool is typically used before a read_resource tool call. Here are two examples:
1. List files in Google Drive MCP Server, then call read_resource(gdrive:///<file_id>) to read the Google Drive file.
2. You want to see what tables exist in Postgre so you can find schema information about that table before creating a query. So you call list_resources to see whats available then you call read_resource(postgres://<host>/<table>/schema)
to get the schema information for a table and then use to construct your query.
"#}.to_string(),
json!({
"type": "object",
"properties": {
"system_name": {"type": "string", "description": "Optional system name"}
}
}),
);

tools.push(read_resource_tool);
tools.push(list_resources_tool);

let system_prompt = capabilities.get_system_prompt().await;
let _estimated_limit = capabilities
Expand Down

0 comments on commit 06388d2

Please sign in to comment.