fix(extension-manager): require extension_name on read_resource (#8989)

Signed-off-by: olaservo <olahungerford@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ola Hungerford
2026-05-07 12:51:51 -07:00
committed by GitHub
parent c80044d9d0
commit 23b4754643
2 changed files with 26 additions and 79 deletions
+10 -67
View File
@@ -1304,76 +1304,19 @@ impl ExtensionManager {
cancellation_token: CancellationToken,
) -> Result<Vec<Content>, ErrorData> {
let uri = require_str_parameter(&params, "uri")?;
let extension_name = require_str_parameter(&params, "extension_name")?;
let extension_name = params.get("extension_name").and_then(|v| v.as_str());
let read_result = self
.read_resource(session_id, uri, extension_name, cancellation_token)
.await?;
// If extension name is provided, we can just look it up
if let Some(ext_name) = extension_name {
let read_result = self
.read_resource(session_id, uri, ext_name, cancellation_token.clone())
.await?;
let mut result = Vec::new();
for content in read_result.contents {
if let ResourceContents::TextResourceContents { text, .. } = content {
let content_str = format!("{}\n\n{}", uri, text);
result.push(Content::text(content_str));
}
}
return Ok(result);
}
// If extension name is not provided, we need to search for the resource across all extensions
// Loop through each extension and try to read the resource, don't raise an error if the resource is not found
// TODO: do we want to find if a provided uri is in multiple extensions?
// currently it will return the first match and skip any others
let extension_names: Vec<String> = self
.extensions
.lock()
.await
.iter()
.filter(|(_name, ext)| ext.supports_resources())
.map(|(name, _)| name.clone())
.collect();
for extension_name in extension_names {
let read_result = self
.read_resource(session_id, uri, &extension_name, cancellation_token.clone())
.await;
match read_result {
Ok(read_result) => {
let mut result = Vec::new();
for content in read_result.contents {
if let ResourceContents::TextResourceContents { text, .. } = content {
let content_str = format!("{}\n\n{}", uri, text);
result.push(Content::text(content_str));
}
}
return Ok(result);
}
Err(_) => continue,
let mut result = Vec::new();
for content in read_result.contents {
if let ResourceContents::TextResourceContents { text, .. } = content {
result.push(Content::text(format!("{}\n\n{}", uri, text)));
}
}
// None of the extensions had the resource so we raise an error
let available_extensions = self
.extensions
.lock()
.await
.keys()
.map(|s| s.as_str())
.collect::<Vec<&str>>()
.join(", ");
let error_msg = format!(
"Resource with uri '{}' not found. Here are the available extensions: {}",
uri, available_extensions
);
Err(ErrorData::new(
ErrorCode::RESOURCE_NOT_FOUND,
error_msg,
None,
))
Ok(result)
}
pub async fn read_resource(
@@ -1493,7 +1436,7 @@ impl ExtensionManager {
params: Value,
cancellation_token: CancellationToken,
) -> Result<Vec<Content>, ErrorData> {
let extension = params.get("extension").and_then(|v| v.as_str());
let extension = params.get("extension_name").and_then(|v| v.as_str());
match extension {
Some(extension_name) => {
@@ -53,8 +53,7 @@ pub struct ManageExtensionsParams {
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct ReadResourceParams {
pub uri: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub extension_name: Option<String>,
pub extension_name: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
@@ -325,15 +324,17 @@ impl ExtensionManagerClient {
files, database schemas, or application-specific information. This tool lists resources
in the provided extension, and returns a list for the user to browse. If no extension
is provided, the tool will search all extensions for the resource.
"#}.to_string(),
"#}
.to_string(),
Arc::new(
serde_json::to_value(schema_for!(ListResourcesParams))
.expect("Failed to serialize schema")
.as_object()
.expect("Schema must be an object")
.clone()
.clone(),
),
).annotate(ToolAnnotations::from_raw(
)
.annotate(ToolAnnotations::from_raw(
Some("List resources".to_string()),
Some(true),
Some(false),
@@ -343,21 +344,24 @@ impl ExtensionManagerClient {
Tool::new(
READ_RESOURCE_TOOL_NAME.to_string(),
indoc! {r#"
Read a resource from an extension.
Read a resource from a specific extension.
Resources allow extensions 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 extension, and reads in the resource content. If no extension
is provided, the tool will search all extensions for the resource.
"#}.to_string(),
files, database schemas, or application-specific information. You must pass the
owning extension as `extension_name`; if you don't know which extension owns a
URI, call `list_resources` first — its output labels each resource with its
extension.
"#}
.to_string(),
Arc::new(
serde_json::to_value(schema_for!(ReadResourceParams))
.expect("Failed to serialize schema")
.as_object()
.expect("Schema must be an object")
.clone()
.clone(),
),
).annotate(ToolAnnotations::from_raw(
)
.annotate(ToolAnnotations::from_raw(
Some("Read a resource".to_string()),
Some(true),
Some(false),