Files
lda-wf/docs/fastmcp_resource_link_issue_guide.md
T
2026-07-30 01:18:53 +07:00

177 lines
4.2 KiB
Markdown

# FastMCP Proxy ResourceLink Issue Guide
Use this as a copy/rewrite starting point for an upstream FastMCP issue. Keep
the code blocks and concrete examples; rewrite the prose however you want.
## Suggested Title
```text
Namespace transform rewrites listed resource URIs but not ResourceLink URIs returned from proxied tools
```
## Short Summary
```text
When a FastMCP proxy has a Namespace transform, listed resources and resource
templates are correctly namespaced, and namespaced resource reads work.
However, ResourceLink content returned inside a proxied tools/call result keeps
the raw upstream URI instead of the downstream namespaced URI.
```
## Why This Looks Like A Proxy Bug
```text
The proxy is already doing the hard part correctly for normal resource surfaces:
- resources/list returns namespaced URIs
- resource_templates/list returns namespaced URI templates
- resources/read accepts the namespaced URI and reverses it upstream
Only ResourceLink content inside tools/call results escapes with the upstream
URI unchanged. That makes the returned link unusable from the downstream client
even though the same resource is available through the proxy under its
namespaced URI.
```
## Minimal Example
```python
from fastmcp.server import create_proxy
from fastmcp.server.transforms import Namespace
proxy = create_proxy(upstream_client, name="Proxy-everything.default")
proxy.add_transform(Namespace("everything.default"))
```
Given an upstream resource:
```text
demo://resource/dynamic/text/2
```
The proxy correctly exposes it as:
```text
demo://everything.default/resource/dynamic/text/2
```
But if an upstream tool returns:
```python
mcp.types.ResourceLink(
type="resource_link",
name="dynamic-text",
uri="demo://resource/dynamic/text/2",
)
```
the proxied tool result still contains:
```text
demo://resource/dynamic/text/2
```
instead of:
```text
demo://everything.default/resource/dynamic/text/2
```
## Observed Behavior
```text
resources/list:
upstream: demo://resource/dynamic/text/2
proxied: demo://everything.default/resource/dynamic/text/2
resources/read:
demo://everything.default/resource/dynamic/text/2
-> works
tools/call result content:
ResourceLink.uri == demo://resource/dynamic/text/2
-> raw upstream URI leaks through unchanged
```
## Expected Behavior
```text
If a proxy transform rewrites a resource URI for resources/list and
resources/read, ResourceLink content emitted by proxied tools should expose the
same downstream-facing URI.
```
## Relevant FastMCP Code Paths
`Namespace` already owns the URI mapping:
```python
class Namespace(Transform):
async def list_resources(...):
...
async def get_resource(...):
...
```
`ProxyTool.run(...)` currently returns upstream tool content unchanged:
```python
result = await client.call_tool_mcp(
name=backend_name,
arguments=arguments,
meta=meta,
)
return ToolResult(
content=result.content,
structured_content=result.structuredContent,
meta=result.meta,
)
```
FastMCP's `Transform` base class has hooks for listing/getting tools,
resources, templates, and prompts, but there does not appear to be a hook for
transforming tool results.
## Suggested Direction
```text
Prefer a general result-transform hook over special-casing Namespace logic in
ProxyTool.
```
For example, something in this shape:
```python
class Transform:
async def tool_result(self, result: ToolResult) -> ToolResult:
return result
```
Then `Namespace` could rewrite only typed MCP resource-link content:
```python
if isinstance(content, mcp.types.ResourceLink):
content = content.model_copy(update={"uri": self._transform_uri(str(content.uri))})
```
That would keep URI rewriting logic in `Namespace`, where the forward and
reverse resource URI mapping already lives.
## Important Boundary
```text
This is specifically about mcp.types.ResourceLink content inside tools/call
results. Normal resources/list, resource_templates/list, and resources/read
already behave correctly through the proxy.
```
## Extra Note
```text
Session-scoped resources returned by some tools may have additional lifecycle
constraints beyond URI rewriting. This issue is about ordinary ResourceLink URI
projection only.
```