Add addEventListener/removeEventListener with DOM-model on* semantics#573
Add addEventListener/removeEventListener with DOM-model on* semantics#573
Conversation
PreviewPreview deployments for this PR have been cleaned up. |
@modelcontextprotocol/ext-apps
@modelcontextprotocol/server-basic-preact
@modelcontextprotocol/server-basic-react
@modelcontextprotocol/server-basic-solid
@modelcontextprotocol/server-basic-svelte
@modelcontextprotocol/server-basic-vanillajs
@modelcontextprotocol/server-basic-vue
@modelcontextprotocol/server-budget-allocator
@modelcontextprotocol/server-cohort-heatmap
@modelcontextprotocol/server-customer-segmentation
@modelcontextprotocol/server-debug
@modelcontextprotocol/server-map
@modelcontextprotocol/server-pdf
@modelcontextprotocol/server-scenario-modeler
@modelcontextprotocol/server-shadertoy
@modelcontextprotocol/server-sheet-music
@modelcontextprotocol/server-system-monitor
@modelcontextprotocol/server-threejs
@modelcontextprotocol/server-transcript
@modelcontextprotocol/server-video-resource
@modelcontextprotocol/server-wiki-explorer
commit: |
f121180 to
a1ebc1b
Compare
|
love it! I think we should keep |
If we keep the old handler, would it be possible to add a console warning when it's being overwritten and that it's deprecated? |
| params: McpUiResourceTeardownRequest["params"], | ||
| extra: RequestHandlerExtra, | ||
| ) => McpUiResourceTeardownResult | Promise<McpUiResourceTeardownResult>) | ||
| | undefined, |
There was a problem hiding this comment.
Any issues with this being undefined? Wouldn't if (callback) skip on undefined?
There was a problem hiding this comment.
Good catch — fixed. The closure now reads from the backing field (this._onteardown) at call time instead of capturing callback. When cleared with undefined, the backing field is undefined and the next request throws "No onteardown handler set". The old callback is never called again.
set onteardown(callback: ... | undefined) {
this._onteardown = callback;
this.replaceRequestHandler(Schema, (request, extra) => {
if (!this._onteardown) throw new Error("No onteardown handler set");
return this._onteardown(request.params, extra);
});
}Same pattern applied to all 13 request handler setters.
| | (( | ||
| params: ListToolsRequest["params"], | ||
| extra: RequestHandlerExtra, | ||
| ) => Promise<ListToolsResult>) |
There was a problem hiding this comment.
Previous implementations like this would break, right?
app.onlisttools = () => ({ tools: ["foo"]})Do we need to document this?
There was a problem hiding this comment.
Yes, app.onlisttools = () => ({ tools: ["foo"]}) would be a TypeScript error with the new type. The old type Promise<{ tools: string[] }> was incorrect though — tools/list returns Tool objects (with name, inputSchema, etc.), not strings. This fixes the type to match the MCP protocol. Will mention as a type fix in the changelog.
a1ebc1b to
9aee887
Compare
…ntListener Fixes #551 (useHostStyles broken: fonts clobber theme/CSS variables) Fixes #225 (user onhostcontextchanged and SDK hooks mutually exclusive) The on* setters now follow the DOM event model: - Replace semantics (like el.onclick) with getters - Coexist with addEventListener/removeEventListener listeners - Both channels fire on dispatch: on* handler then listeners Introduces ProtocolWithEvents base class between Protocol and App/AppBridge. Each notification event gets a slot with two independent channels (singular on* handler + addEventListener listener array). useHostStyles hooks now use addEventListener with proper cleanup, so they compose correctly with user on* handlers. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
9aee887 to
618d252
Compare
|
Done in latest push:
|
- Export AppEventMap, AppBridgeEventMap, ProtocolWithEvents so they appear in generated docs (they're part of public addEventListener signatures) - Move AppBridgeEventMap above the AppBridge class JSDoc — it had been inserted between the doc block and the class, orphaning the AppBridge docs onto the wrong type - Drop @param callback tags from on* getter/setter pairs — typedoc attaches the doc block to the getter signature which has no params - Add MethodSchema to intentionallyNotExported (internal zod helper referenced by the now-documented eventSchemas field)
Adds DOM-model event semantics to
on*setters and introducesaddEventListener/removeEventListenerfor multi-listener support. Theon*setters now have replace semantics (likeel.onclick) with getters, and coexist withaddEventListenerlisteners — both fire on dispatch.useHostStylesbroken: fonts handler clobbers theme/CSS variables)onhostcontextchangedand SDK hooks are mutually exclusive)getHostContext()robust (context merge now happens unconditionally inonEventDispatch, not in a replaceable setter wrapper)How it works
New
ProtocolWithEventsbase class betweenProtocolandApp/AppBridge. Each notification event gets a slot with two independent channels:on*setteraddEventListenerapp.ontoolinput = undefinedremoveEventListener(event, fn)app.ontoolinputreturns handlerDispatch order:
onEventDispatch()(side-effects) →on*handler →addEventListenerlisteners.Other changes
on*properties on bothAppandAppBridgereplaceRequestHandlerfor request-handleron*setters (replace semantics, no double-set throw)useHostStyleVariablesanduseHostFontsnow useaddEventListenerwith proper cleanupTest plan
on*replace semantics, coexistence withaddEventListener, getters, clearingsetRequestHandler/setNotificationHandler🤖 Generated with Claude Code