@@ -129,6 +129,10 @@ def __init__(
129129 burst = rate_limit_rps * 2 # Default burst = 2x rate
130130 self ._rate_limiter = TokenBucketRateLimiter (rate = rate_limit_rps , burst = burst )
131131
132+ # SSR data cache: resource_uri → structuredContent from last tool call.
133+ # Consumed (popped) on next resources/read so the view can be server-rendered.
134+ self ._view_data_cache : dict [str , Any ] = {}
135+
132136 # Don't log during init to keep stdio mode clean
133137 logger .debug ("MCP protocol handler initialized with chuk_mcp" )
134138
@@ -266,6 +270,25 @@ def _maybe_register_view_resource(self, meta: dict[str, Any]) -> None:
266270 import httpx # noqa: F811 — lazy import, transitive dep
267271
268272 async def _fetch_view_html () -> str :
273+ # Try SSR first if we have cached data from a recent tool call
274+ cached_data = self ._view_data_cache .pop (resource_uri , None )
275+ if cached_data :
276+ try :
277+ ssr_url = view_url .rstrip ("/" ) + "/ssr"
278+ async with httpx .AsyncClient () as client :
279+ resp = await client .post (
280+ ssr_url ,
281+ json = {"data" : cached_data },
282+ timeout = 5.0 ,
283+ )
284+ if resp .status_code == 200 :
285+ logger .debug (f"SSR render succeeded for { resource_uri } " )
286+ return resp .text
287+ logger .debug (f"SSR returned { resp .status_code } , falling back to static" )
288+ except Exception as ssr_err :
289+ logger .debug (f"SSR failed for { resource_uri } : { ssr_err } , falling back" )
290+
291+ # Fallback: fetch static SPA HTML from CDN
269292 async with httpx .AsyncClient () as client :
270293 resp = await client .get (view_url , follow_redirects = True )
271294 resp .raise_for_status ()
@@ -770,6 +793,25 @@ async def _progress_notify(
770793 tool_result ["_meta" ]["links" ] = links
771794 set_resource_links (None )
772795
796+ # Forward _meta.ui from tool definition to tool result so
797+ # clients (Claude.ai) know this result is linked to a view resource.
798+ if "structuredContent" in tool_result :
799+ ui_meta = (tool_handler .meta or {}).get (MCP_APPS_UI_KEY , {})
800+ resource_uri = ui_meta .get (MCP_APPS_UI_RESOURCE_URI , "" )
801+ if ui_meta :
802+ tool_result ["_meta" ] = tool_result .get ("_meta" , {})
803+ tool_result ["_meta" ][MCP_APPS_UI_KEY ] = ui_meta
804+
805+ # Cache structuredContent for SSR: when resources/read is called
806+ # for this tool's view, we can server-render with the actual data.
807+ if resource_uri :
808+ self ._view_data_cache [resource_uri ] = tool_result ["structuredContent" ]
809+ # Invalidate resource cache so next read triggers SSR fetch
810+ resource = self .resources .get (resource_uri )
811+ if resource :
812+ resource ._cached_content = None
813+ resource ._cache_timestamp = None
814+
773815 response = {JSONRPC_KEY : JSONRPC_VERSION , KEY_ID : msg_id , KEY_RESULT : tool_result }
774816
775817 # Mark task completed
0 commit comments