{"record":{"id":"f4952e784f208108","repo":"PrefectHQ/fastmcp","slug":"a-custom-cache-store-requires-cacheconfig-target-i","errorCode":null,"errorMessage":"a custom cache store requires CacheConfig.target_id for FastMCP transports: the server URL the SDK derives an identity from is not available here, so entries in a shared store could never be served to another client","messagePattern":"a custom cache store requires CacheConfig\\.target_id for FastMCP transports: the server URL the SDK derives an identity from is not available here, so entries in a shared store could never be served to another client","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"fastmcp_slim/fastmcp/client/client.py","lineNumber":611,"sourceCode":"        Response caching is opt-in: `None` (the default) and `False` both leave it\n        disabled, so a legacy connection is byte-identical to pre-v4 behavior (no\n        message-handler wrapping, no caching). `True` enables it with the default\n        `CacheConfig` (honoring server `ttlMs`/`cacheScope` hints via a per-client\n        in-memory store); a `CacheConfig` customizes it.\n\n        Our transports abstract away the server URL the SDK Client uses to derive a\n        cache identity, so `target_id` comes from the explicit `CacheConfig.target_id`\n        or a random per-client id — meaning a custom shared store cannot serve one\n        client's entries to another (documented on the parameter).\n        \"\"\"\n        if cache is None or cache is False:\n            return None\n        config = cache if isinstance(cache, CacheConfig) else CacheConfig()\n\n        target_id = config.target_id\n        if target_id is None:\n            if config.store is not None:\n                raise ValueError(\n                    \"a custom cache store requires CacheConfig.target_id for FastMCP \"\n                    \"transports: the server URL the SDK derives an identity from is not \"\n                    \"available here, so entries in a shared store could never be served \"\n                    \"to another client\"\n                )\n            target_id = uuid.uuid4().hex\n\n        return ClientResponseCache(\n            store=config.store\n            if config.store is not None\n            else InMemoryResponseCacheStore(),\n            partition=config.partition,\n            arm_id=hashlib.sha256(target_id.encode()).hexdigest(),\n            default_ttl_ms=config.default_ttl_ms,\n            clock=config.clock,\n            share_public=config.share_public,\n            # Lazy: the negotiated version is unknown until the handshake completes.\n            negotiated_version=lambda: (","sourceCodeStart":593,"sourceCodeEnd":629,"githubUrl":"https://github.com/PrefectHQ/fastmcp/blob/1f021142978e0861cd910c8df4e8074bc7cf3978/fastmcp_slim/fastmcp/client/client.py#L593-L629","documentation":"The SEP-2549 response cache partitions a shared store by a target_id derived from the server URL — but FastMCP transports abstract that URL away, so the SDK cannot derive it. If you supply CacheConfig(store=...) without an explicit target_id, entries could never be correctly attributed across clients, so Client.__init__/_build_response_cache raises ValueError rather than silently mis-partitioning the shared store.","triggerScenarios":"Client(url, cache=CacheConfig(store=my_shared_store)) — a CacheConfig with a custom store but target_id=None, on any FastMCP transport. (CacheConfig() without a store is fine: it gets a random per-client id.)","commonSituations":"Teams sharing a Redis/disk cache store across many clients for rate-limit or latency reasons; copying a CacheConfig example and adding a store but not target_id.","solutions":["Set CacheConfig.target_id to a stable identifier for the server (e.g. the MCP server URL or a slug) so the shared store partitions correctly.","Use one target_id per distinct MCP server; reuse it across all clients sharing the store.","If you don't need cross-client sharing, drop the custom store and use the default in-memory store (CacheConfig() or cache=True).","Use cache=False to disable caching if a store isn't required."],"exampleFix":"// before: shared store without identity\nclient = Client(url, cache=CacheConfig(store=redis_store))\n// after: explicit partition id for the shared store\nclient = Client(url, cache=CacheConfig(store=redis_store, target_id='https://mcp.example.com/mcp'))","handlingStrategy":"validation","validationCode":"from fastmcp.client.client import CacheConfig\ncfg = CacheConfig(store=redis_store)\nif cfg.store is not None and cfg.target_id is None:\n    raise ValueError('Custom cache store requires an explicit target_id')","typeGuard":"def cache_config_is_valid(cfg: CacheConfig) -> bool:\n    return not (cfg.store is not None and cfg.target_id is None)","tryCatchPattern":"try:\n    client = Client(url, cache=CacheConfig(store=redis_store))\nexcept ValueError as e:\n    if 'custom cache store requires' in str(e):\n        client = Client(url, cache=CacheConfig(store=redis_store, target_id=server_identity))\n    else:\n        raise","preventionTips":["Always pair a custom cache store with a stable CacheConfig.target_id (e.g. the server URL).","Use one target_id per MCP server so shared-store partitions stay correct.","If you don't need cross-client sharing, omit the store and use the default in-memory cache.","Validate CacheConfig at config-load time, not at Client construction time."],"tags":["cache","configuration","validation","arguments"],"backgroundTag":"missing-required-config","analyzedSha":"1f021142978e0861cd910c8df4e8074bc7cf3978","analyzedAt":"2026-08-29T14:31:16.082Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}