-
Notifications
You must be signed in to change notification settings - Fork 1.4k
[Draft] Add Response Caching Middleware #1845
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
6850047
to
09369b5
Compare
03da107
to
d5f01da
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR implements a comprehensive response caching middleware system for FastMCP. It adds caching capabilities for various MCP operations including tool calls, resource reads, and prompt requests to improve performance and reduce server load.
- Implements both in-memory and disk-based caching backends
- Adds configurable TTL settings and filtering options for different operation types
- Includes cache invalidation through MCP notifications and comprehensive test coverage
Reviewed Changes
Copilot reviewed 5 out of 6 changed files in this pull request and generated 4 comments.
Show a summary per file
File | Description |
---|---|
tests/server/middleware/test_caching.py |
Comprehensive test suite covering cache implementations, middleware functionality, and integration tests |
src/fastmcp/server/middleware/middleware.py |
Updated middleware base class to use proper return type for resource reading operations |
src/fastmcp/server/middleware/caching.py |
Core caching middleware implementation with cache protocols, backends, and operation handlers |
pyproject.toml |
Added caching dependencies and test dependencies |
docs/servers/middleware.mdx |
Documentation for the new caching middleware functionality |
TypedDicts are a lot less fun than I was expecting and I'm not sure the cache entry model is going to stick around The implementation for the cache entries is mostly for the benefit of distributed cache implementations that can't just pickle |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
Copilot reviewed 8 out of 11 changed files in this pull request and generated 3 comments.
class TestResponseCachingMiddlewareIntegration: | ||
"""Integration tests with real FastMCP server.""" | ||
|
||
@pytest.fixture(params=["memory", "disk", "elasticsearch"]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test fixture includes 'elasticsearch' as a parameter but the actual implementation in the fixture method only handles 'memory' and 'disk' cases, which will cause the tests to fail when 'elasticsearch' is selected.
Copilot uses AI. Check for mistakes.
|
||
def get_size_of_value(value: CachableValueTypes) -> int: | ||
"""Get the size of a cache entry.""" | ||
if isinstance(value, BaseModel | ToolResult | ReadResourceContents): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The isinstance check uses union syntax with |
but this will not work correctly. The condition should use a tuple: isinstance(value, (BaseModel, ToolResult, ReadResourceContents))
if isinstance(value, BaseModel | ToolResult | ReadResourceContents): | |
if isinstance(value, (BaseModel, ToolResult, ReadResourceContents)): |
Copilot uses AI. Check for mistakes.
body={ | ||
"query": { | ||
"range": { | ||
"expires_at": {"lt": datetime.now(tz=timezone.utc).timestamp()}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comparison uses timestamp()
but the mapping defines expires_at
as a date
type, not a numeric timestamp. This should use an ISO format string instead: datetime.now(tz=timezone.utc).isoformat()
"expires_at": {"lt": datetime.now(tz=timezone.utc).timestamp()}, | |
"expires_at": {"lt": datetime.now(tz=timezone.utc).isoformat()}, |
Copilot uses AI. Check for mistakes.
Love this idea! The |
Prompt cannot be instantiated directly as its an ABC and so what comes through the middleware is a FunctionPrompt which can't be serialized/deserialized due to the function reference -- so the CachedPrompt offers a serializable/deserializable model Would love other ideas if you have them |
Oh! what do you think about removing the ABC from all components and just raising NotImplementedError() instead? Less self-documenting, more compatible? |
Description
Add a response caching middleware + in memory cache implementation + on disk cache implementation
Todo:
Contributors Checklist
Review Checklist