Skip to content

bug: app.onhostcontextchanged handlers silently overwrite each other #225

Description

@antonpk1

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 updates

Example 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]);

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions