Summary
Setting app.onhostcontextchanged multiple times silently overwrites previous handlers. This breaks real-time theme updates and any custom host context handling.
Root Cause
The setter calls setNotificationHandler() which replaces the previous handler:
// app.ts line 510-521setonhostcontextchanged(callback){this.setNotificationHandler(McpUiHostContextChangedNotificationSchema,(n)=>{this._hostContext={ ...this._hostContext, ...n.params};callback(n.params);// Only ONE callback ever runs},);}Breaking Examples
Example 1: useHostStyles internal conflict
// User writes this simple code:functionMyWidget(){const{ app }=useApp({ ... });useHostStyles(app,hostContext);// Expects theme updates to workreturn<div>...</div>;}// BREAKS because inside useHostStyles:useHostStyleVariables(app, ...);// Sets handler A (theme + variables)useHostFonts(app, ...);// Sets handler B (fonts only) - OVERWRITES A!// Result: Theme changes → only fonts handler runs → theme never updatesExample 2: User handler + SDK hook
functionMyWidget(){const{ app }=useApp({ ... });const[ctx,setCtx]=useState(null);useEffect(()=>{if(!app)return;// User sets their own handlerapp.onhostcontextchanged=(params)=>{setCtx(prev=>({ ...prev, ...params}));// Update React statetrackAnalytics("theme_changed",params);// Custom logic};},[app]);// Later, user adds SDK hook for convenienceuseHostStyles(app,ctx);// OVERWRITES user handler!// Result: setCtx() never called, trackAnalytics() never called}Example 3: Two SDK hooks
functionMyWidget(){const{ app }=useApp({ ... });// User wants variables but not fontsuseHostStyleVariables(app,ctx);// User also wants to track theme changesuseEffect(()=>{app.onhostcontextchanged=(params)=>{console.log("Theme changed to:",params.theme);// OVERWRITES SDK hook!};},[app]);// Result: Variables never applied, only console.log runs}Example 4: Multiple custom handlers (impossible today)
functionMyWidget(){const{ app }=useApp({ ... });// Component A wants to know about themeuseEffect(()=>{app.onhostcontextchanged=(params)=>{updateComponentA(params);};},[app]);// Component B also wants to know about theme useEffect(()=>{app.onhostcontextchanged=(params)=>{updateComponentB(params);// OVERWRITES Component A!};},[app]);// Result: Only Component B gets updates}Symptoms
- Dark/light mode toggle does not update widget in real-time
- Custom
onhostcontextchanged handlers stop working after adding SDK hooks - No errors in console (silent failure)
- Requires page refresh to see changes
Proposed Fix
Change the setter to chain handlers instead of replacing:
private _hostContextChangedHandlers: Array<(params)=>void>=[];setonhostcontextchanged(callback){this._hostContextChangedHandlers.push(callback);// Only register the notification handler onceif(this._hostContextChangedHandlers.length===1){this.setNotificationHandler(McpUiHostContextChangedNotificationSchema,(n)=>{this._hostContext={ ...this._hostContext, ...n.params};for(consthandlerofthis._hostContextChangedHandlers){handler(n.params);}},);}}Workaround
Until fixed, use a single combined handler instead of SDK hooks:
useEffect(()=>{if(!app)return;constctx=app.getHostContext();if(ctx?.theme)applyDocumentTheme(ctx.theme);if(ctx?.styles?.variables)applyHostStyleVariables(ctx.styles.variables);if(ctx?.styles?.css?.fonts)applyHostFonts(ctx.styles.css.fonts);app.onhostcontextchanged=(params)=>{if(params.theme)applyDocumentTheme(params.theme);if(params.styles?.variables)applyHostStyleVariables(params.styles.variables);if(params.styles?.css?.fonts)applyHostFonts(params.styles.css.fonts);// Add any custom logic here too};},[app]);
Summary
Setting
app.onhostcontextchangedmultiple times silently overwrites previous handlers. This breaks real-time theme updates and any custom host context handling.Root Cause
The setter calls
setNotificationHandler()which replaces the previous handler:Breaking Examples
Example 1: useHostStyles internal conflict
Example 2: User handler + SDK hook
Example 3: Two SDK hooks
Example 4: Multiple custom handlers (impossible today)
Symptoms
onhostcontextchangedhandlers stop working after adding SDK hooksProposed Fix
Change the setter to chain handlers instead of replacing:
Workaround
Until fixed, use a single combined handler instead of SDK hooks: