Describe the bug
The read_resource tool the Extension Manager exposes to the model treats extension_name as optional, and when the model omits it, the handler iterates every resource-capable extension, swallows every error, and returns the first match. This produces silent failures on URI collisions and silent loss of distinguishable errors (auth, transport, not-found, server-side). The tool description actively encourages the model to take this path.
A related but separate bug: the list_resources JSON schema declares extension_name, but the handler reads params.get("extension") — model calls passing extension_name are silently ignored and fall into the same fan-out.
To Reproduce
- Connect goose to two MCP servers that both expose a resource at the same URI (e.g. two filesystem-style servers both exposing
file:///some/path, or two skill-style servers both exposing skill://example/SKILL.md).
- Start a session and ask the model to read that URI.
- Observe that the model calls
read_resource with uri only (the tool description encourages this), and goose silently returns whichever extension happens to be hit first by the iteration order, with no indication that there was ambiguity.
To reproduce the related list_resources mismatch:
- Connect goose to multiple resource-capable extensions.
- Have the model call
list_resources with {"extension_name": "<known>"}.
- Observe that the result is the multi-extension fan-out, not the filtered per-extension list. The
extension_name argument was silently ignored because the handler reads extension.
Expected behavior
read_resource should require extension_name. The model already has the (extension, uri) pair available from list_resources, whose output at extension_manager.rs:1482 is formatted extension - name, uri: (uri).
- When the extension is unknown, surface the existing
"Extension '{}' not found. Here are the available extensions: …" error (already at extension_manager.rs:1394-1402).
- The fan-out loop and
Err(_) => continue should be removed entirely; the in-code TODO about URI collisions becomes moot.
list_resources's handler should read extension_name to match its declared schema. The field stays optional (list-all is the natural default for discovery), and only the parameter name needs to be reconciled.
Screenshots
N/A
- OS & Arch: Not platform-specific (model-facing tool contract)
- Interface: Both UI and CLI affected (Extension Manager is shared)
- Version:
goose crate 1.33.0 (current main, branched at 45d8bf81d)
- Extensions enabled: Reproducible with any combination of two or more resource-capable extensions
- Provider & Model: Provider-agnostic; reproduces with any model that uses the Extension Manager's
read_resource / list_resources tools
Additional context
Proposed fix
- Make
ReadResourceParams.extension_name required.
- Rewrite the
read_resource tool description to direct the model to call list_resources first when ownership is unknown.
- Remove the fan-out fallback in
read_resource_tool; rely on the existing read_resource() helper's error path.
- Fix the
list_resources param-name mismatch (extension → extension_name).
Tradeoff considered
If the model has a bare URI from outside (chat paste, memory recall, etc.) it can't call read_resource directly — it has to call list_resources first to find the owning extension. That's one extra round-trip in a rare (?) case, in exchange for retiring the silent probe path in the common case.
Describe the bug
The
read_resourcetool the Extension Manager exposes to the model treatsextension_nameas optional, and when the model omits it, the handler iterates every resource-capable extension, swallows every error, and returns the first match. This produces silent failures on URI collisions and silent loss of distinguishable errors (auth, transport, not-found, server-side). The tool description actively encourages the model to take this path.A related but separate bug: the
list_resourcesJSON schema declaresextension_name, but the handler readsparams.get("extension")— model calls passingextension_nameare silently ignored and fall into the same fan-out.To Reproduce
file:///some/path, or two skill-style servers both exposingskill://example/SKILL.md).read_resourcewithurionly (the tool description encourages this), and goose silently returns whichever extension happens to be hit first by the iteration order, with no indication that there was ambiguity.To reproduce the related
list_resourcesmismatch:list_resourceswith{"extension_name": "<known>"}.extension_nameargument was silently ignored because the handler readsextension.Expected behavior
read_resourceshould requireextension_name. The model already has the (extension, uri) pair available fromlist_resources, whose output atextension_manager.rs:1482is formattedextension - name, uri: (uri)."Extension '{}' not found. Here are the available extensions: …"error (already atextension_manager.rs:1394-1402).Err(_) => continueshould be removed entirely; the in-code TODO about URI collisions becomes moot.list_resources's handler should readextension_nameto match its declared schema. The field stays optional (list-all is the natural default for discovery), and only the parameter name needs to be reconciled.Screenshots
N/A
goosecrate1.33.0(currentmain, branched at45d8bf81d)read_resource/list_resourcestoolsAdditional context
Proposed fix
ReadResourceParams.extension_namerequired.read_resourcetool description to direct the model to calllist_resourcesfirst when ownership is unknown.read_resource_tool; rely on the existingread_resource()helper's error path.list_resourcesparam-name mismatch (extension→extension_name).Tradeoff considered
If the model has a bare URI from outside (chat paste, memory recall, etc.) it can't call
read_resourcedirectly — it has to calllist_resourcesfirst to find the owning extension. That's one extra round-trip in a rare (?) case, in exchange for retiring the silent probe path in the common case.