Problem
The ext-apps spec types the notification's params as a partial update:
export interface McpUiHostContextChangedNotification {
method: "ui/notifications/host-context-changed";
/** @description Partial context update containing only changed fields. */
params: McpUiHostContext;
}
createSynapse (src/core.ts) replaces currentHostContext wholesale on every notification, so any field the host omits is re-derived as absent. A host that toggles dark mode with a bare { theme: "dark" } therefore loses its entire palette mid-session.
Reproduction
Handshake with styles.variables: { "--color-text-primary": "#ff00ff" }, then dispatch { theme: "dark" }:
AssertionError: expected '#fafafa' to be '#ff00ff'
#fafafa is the SDK's own neutral dark default — the host's value is gone, and getTheme().tokens is {}.
Why this is filed rather than fixed in #45
Pre-existing; the handler shape is unchanged from main. #45 pinned the fonts half of this by test (an absent synapse/fontFaces means "unchanged"), because unloading a typeface on an unrelated toggle is a visible break. The same argument applies with more force to colour, but fixing it changes what every onHostContextChanged subscriber observes — a wider blast radius than that PR's charter.
Note connect() (src/connect.ts) and the mcpapps adapter both already merge tokens. createSynapse is the lone outlier of the three paths, which is also the one most apps use.
Suggested fix
Merge the incoming partial into currentHostContext rather than replacing it, matching the other two paths and the spec. Decide deliberately what subscribers should receive — the merged snapshot or the notification as sent — since that is the actual behaviour change.
Acceptance
Problem
The ext-apps spec types the notification's params as a partial update:
createSynapse(src/core.ts) replacescurrentHostContextwholesale on every notification, so any field the host omits is re-derived as absent. A host that toggles dark mode with a bare{ theme: "dark" }therefore loses its entire palette mid-session.Reproduction
Handshake with
styles.variables: { "--color-text-primary": "#ff00ff" }, then dispatch{ theme: "dark" }:#fafafais the SDK's own neutral dark default — the host's value is gone, andgetTheme().tokensis{}.Why this is filed rather than fixed in #45
Pre-existing; the handler shape is unchanged from
main. #45 pinned the fonts half of this by test (an absentsynapse/fontFacesmeans "unchanged"), because unloading a typeface on an unrelated toggle is a visible break. The same argument applies with more force to colour, but fixing it changes what everyonHostContextChangedsubscriber observes — a wider blast radius than that PR's charter.Note
connect()(src/connect.ts) and themcpappsadapter both already merge tokens.createSynapseis the lone outlier of the three paths, which is also the one most apps use.Suggested fix
Merge the incoming partial into
currentHostContextrather than replacing it, matching the other two paths and the spec. Decide deliberately what subscribers should receive — the merged snapshot or the notification as sent — since that is the actual behaviour change.Acceptance
host-context-changedpreserves unmentioned spec fields (styles.variables,toolInfo,containerDimensions)onHostContextChangeddelivers