Preserve component metadata in response cache (#4521)

This commit is contained in:
Jeremiah Lowin 2026-07-18 15:42:20 -04:00 committed by GitHub
commit 16383a64d6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 68 additions and 43 deletions

View file

@ -42,6 +42,7 @@ from fastmcp.server.middleware.caching import (
)
from fastmcp.server.middleware.middleware import CallNext, MiddlewareContext
from fastmcp.tools.base import Tool, ToolResult
from fastmcp.utilities.tasks import TaskConfig
TEST_URI = AnyUrl("https://test_uri")
@ -352,6 +353,47 @@ class TestResponseCachingMiddlewareIntegration:
assert pre_tool_list == post_tool_list
async def test_list_operations_preserve_component_metadata(self):
"""Base component fields should survive conversion through the cache."""
icon = mcp_types.Icon(src="https://example.com/component.png")
mcp = FastMCP("MetadataServer")
mcp.add_middleware(ResponseCachingMiddleware())
@mcp.tool(icons=[icon], task=TaskConfig(mode="optional"))
async def greet() -> str:
return "hello"
@mcp.resource("resource://metadata", icons=[icon])
def metadata_resource() -> str:
return "resource"
@mcp.prompt(icons=[icon])
def metadata_prompt() -> str:
return "prompt"
cached_tools = await mcp.list_tools()
cached_resources = await mcp.list_resources()
cached_prompts = await mcp.list_prompts()
assert type(cached_tools[0]) is Tool
assert type(cached_resources[0]) is Resource
assert type(cached_prompts[0]) is Prompt
assert not hasattr(cached_tools[0], "fn")
assert not hasattr(cached_resources[0], "fn")
assert not hasattr(cached_prompts[0], "fn")
async with Client(mcp) as client:
for _ in range(2):
tools = await client.list_tools()
resources = await client.list_resources()
prompts = await client.list_prompts()
assert tools[0].icons == [icon]
assert tools[0].execution == mcp_types.ToolExecution(
task_support="optional"
)
assert resources[0].icons == [icon]
assert prompts[0].icons == [icon]
async def test_call_tool(
self,
caching_server: FastMCP,
@ -595,13 +637,16 @@ class TestCachingWithImportedServerPrefixes:
):
"""Resource URIs should retain prefix after being served from cache."""
async with Client(parent_with_imported_child) as client:
# First call populates cache
resources_first = await client.list_resources()
resource_uris_first = [str(r.uri) for r in resources_first]
with warnings.catch_warnings():
warnings.simplefilter("error", UserWarning)
# Second call should come from cache
resources_cached = await client.list_resources()
resource_uris_cached = [str(r.uri) for r in resources_cached]
# First call populates cache
resources_first = await client.list_resources()
resource_uris_first = [str(r.uri) for r in resources_first]
# Second call should come from cache
resources_cached = await client.list_resources()
resource_uris_cached = [str(r.uri) for r in resources_cached]
# All resources should have prefix in URI path in both calls
# Resources get path-style prefix: resource://child/path