Add explicit .js extensions to the relative imports in app.ts,
app-bridge.ts and their tests so the emitted declarations resolve under
NodeNext (#704). Replace the zod 3 JSON Schema fallback test, whose
code path was removed, with the remaining non-Standard-JSON-Schema
rejection case as its own test.
The base SDK Protocol silently replaces an existing request or
notification handler. v1's ProtocolWithEvents threw instead, so a stray
setRequestHandler / setNotificationHandler could not disconnect on*
handlers or addEventListener listeners. Restore that guard on both App
and AppBridge with a small MethodRegistry, keep replace semantics for the
on* setters via a protected replaceRequestHandler, and release methods
on removeRequestHandler / removeNotificationHandler.
Also:
- give oncreatesamplingmessage a getter, a replace warning and the same
clearable shape as the other request setters
- let App register handlers for custom (non-spec) methods; the default
branch of assertRequestHandlerCapability threw a misleading
"No handler for method X registered" error
- move the RESOURCE_URI_META_KEY / RESOURCE_MIME_TYPE docs to
src/constants.ts where the constants now live
- fix stale "inherited from Server" wording and point @see links at
@modelcontextprotocol/client, which is what hosts install
The emitted .d.ts copy relative specifiers verbatim, and the extensionless form
(e.g. `from "./events"`) doesn't resolve under Node16/NodeNext, hiding inherited
members such as `addEventListener` (TS2339). Appending `.js` matches the existing
SDK-import style and works with both bundler-mode type-checking and Bun.build.
Types-only; runtime is unaffected.
Fixes#704
App and AppBridge extend the public Protocol from @modelcontextprotocol/client; the iframe wire protocol (ui/initialize only) is unchanged. Peers move to the split SDK 2.0 packages with client and server as optional role peers. Fixups (lockfile, peer ranges, restored handler guards, docs, wire-compat tests) follow in a separate PR.
Co-authored-by: Andrew Khadder <andrew@manufact.com>
Co-authored-by: Felix Weinberger <fweinberger@anthropic.com>
Closes#702
Replaces the Client/Server subclassing from the v2 migration with direct
Protocol extension, now that the SDK exports Protocol from the client
package root. This restores the v1 wire contract on the iframe channel:
ui/initialize is the first and only handshake, so deployed hosts keep
working unchanged.
Deleted with the double handshake: the inner MCP initialize round-trip
and its pinned protocol version, the contentless inner capabilities and
their retro-registration plumbing, the oninitialized defineProperty
shadow, and the getCapabilities/getHostCapabilities split (v1
getCapabilities restored). Tests pin the wire order and assert no MCP
initialize is sent in either direction.
Note: package.json overrides point the SDK packages at locally built
tarballs carrying the Protocol export; replace with published versions
before any upstream PR.
* fix(react): close App on useApp unmount; relax late-handler guard for re-registration
useApp's effect cleanup now closes the App (and its transport) instead
of only flipping a `mounted` flag. Under React StrictMode dev
double-invoke, the abandoned first instance's PostMessageTransport
otherwise keeps a window `message` listener and receives every host
postMessage alongside the second instance.
The late-handler guard from #629 now tracks `_everHadListener` per
event and only flags the *first* registration of a one-shot event
post-handshake. Re-registration (React useEffect cleanup + re-add on
dep change) of an event that had a pre-connect handler is silent,
including under `strict: true`.
* feat(app-bridge): warn on second ui/initialize
If a View double-mounts (e.g. React StrictMode in dev) without closing
the previous App, AppBridge now logs a console.warn when it receives a
second ui/initialize. Behavior is unchanged — it still responds and the
latest appInfo/appCapabilities replace the previous values — this is
purely a diagnostic for host implementers.
_assertInitialized used console.error while _assertHandlerTiming and the
AppBridge override used console.warn. Align all three on console.warn for
the non-strict path; strict still throws.
Tests: hoist the console.warn spy to beforeEach/afterEach in the
pre-handshake describe to avoid bun:test spy stacking now that all three
guards target the same console method.
Mirrors the outbound-side guard from #623: registering a toolinput /
toolinputpartial / toolresult / toolcancelled handler after the
ui/initialize handshake has completed now logs a console.warn (or
throws under { strict: true }), since the host may have already fired
the notification by then. hostcontextchanged is exempt — late
listeners for repeating events are legitimate.
Covers both the on* setters and addEventListener.
* feat: Add tool registration and bidirectional tool support
This PR adds comprehensive tool support for MCP Apps, enabling apps
to register their own tools and handle tool calls from the host.
- Add `registerTool()` method for registering tools with input/output schemas
- Add `oncalltool` setter for handling tool call requests from host
- Add `onlisttools` setter for handling tool list requests from host
- Add `sendToolListChanged()` for notifying host of tool updates
- Registered tools support enable/disable/update/remove operations
- Add `sendCallTool()` method for calling tools on the app
- Add `sendListTools()` method for listing available app tools
- Fix: Use correct ListToolsResultSchema (was ListToolsRequestSchema)
- Add comprehensive tests for tool registration lifecycle
- Add tests for input/output schema validation
- Add tests for bidirectional tool call communication
- Add tests for tool list change notifications
- All 27 tests passing
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* nits
* Update apps.mdx
* feat: Add automatic request handlers for app tool registration
Implement automatic `oncalltool` and `onlisttools` handlers that are
initialized when apps register tools. This removes the need for manual
handler setup and ensures tools work seamlessly out of the box.
- Add automatic `oncalltool` handler that routes calls to registered tools
- Add automatic `onlisttools` handler that returns full Tool objects with JSON schemas
- Convert Zod schemas to MCP-compliant JSON Schema using `zod-to-json-schema`
- Add 27 comprehensive tests covering automatic handlers and tool lifecycle
- Test coverage includes error handling, schema validation, and multi-app isolation
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* type updates
* fix: Ensure tools/list returns valid JSON Schema for all tools
- Always return inputSchema as object (never undefined)
- Keep filter for enabled tools only in list
- Update test to match behavior (only enabled tools in list)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* type updates
* rm zod-to-json-schema
* fix: Update RegisteredTool to use 'handler' instead of 'callback' (SDK API change)
* refactor: Rename sendCallTool/sendListTools to callTool/listTools
Avoid double-verb naming pattern for consistency with existing API.
* feat: Add screenshot and click support to SDK
- Add McpUiScreenshotRequest/Result and McpUiClickRequest/Result types
- Add onscreenshot and onclick handlers to App class
- Add screenshot() and click() methods to AppBridge class
- Generate updated Zod schemas
* feat(map-server): Enable registerTool with navigate-to and get-current-view
- Uncomment and fix the navigate-to tool for animated navigation
- Add get-current-view tool to query camera position and bounding box
- Add flyToBoundingBox function for smooth camera animation
- Add setLabel function for displaying location labels
* feat(pdf-server): Add widget interaction tools
- get-document-info: Get title, current page, total pages, zoom level
- go-to-page: Navigate to a specific page
- get-page-text: Extract text from a page
- search-text: Search for text across the document
- set-zoom: Adjust zoom level
* feat(shadertoy-server): Add widget interaction tools
* feat(examples): Add widget interaction tools to budget-allocator, shadertoy, wiki-explorer, and threejs
budget-allocator:
- get-allocations: Get current budget allocations
- set-allocation: Set allocation for a category
- set-total-budget: Adjust total budget
- set-company-stage: Change stage for benchmarks
- get-benchmark-comparison: Compare against benchmarks
shadertoy:
- set-shader-source: Update shader source code
- get-shader-info: Get shader source and compilation status
- Sends errors via updateModelContext
wiki-explorer:
- search-article: Search for Wikipedia articles
- get-current-article: Get current article info
- highlight-node: Highlight a graph node
- get-visible-nodes: List visible nodes
threejs:
- set-scene-source: Update the Three.js scene source code
- get-scene-info: Get current scene state and any errors
- Sends syntax errors to model via updateModelContext
* feat: Add expand-node tool to wiki-explorer and update tool descriptions
Wiki Explorer:
- Add expand-node tool - the critical missing tool for graph exploration
- Claude can now programmatically expand nodes to discover linked articles
Server descriptions updated to mention widget tools:
- map-server: navigate-to, get-current-view
- pdf-server: go-to-page, get-page-text, search-text, set-zoom, get-document-info
- budget-allocator: get-allocations, set-allocation, set-total-budget, etc.
- shadertoy: set-shader-source, get-shader-info
- wiki-explorer: expand-node, search-article, highlight-node, etc.
All descriptions now mention 'Use list_widget_tools to discover available actions.'
* refactor: Simplify tool descriptions - remove client implementation details
The server tool descriptions now just mention that widgets are interactive
and can be controlled, without teaching the model about list_widget_tools
(which is the client's responsibility to teach).
Before: 'The widget exposes tools: X, Y, Z. Use list_widget_tools to discover...'
After: 'The widget is interactive and exposes tools for X and Y.'
* Remove ui/screenshot and ui/click protocol methods
These will be proposed separately from the app tool registration changes.
* fix(app): auto-register tools capability in registerTool
When registerTool is called before connect() on an App created without
explicit tools capability, setRequestHandler's capability assertion
would throw, breaking app initialization at module load.
Auto-register { tools: { listChanged: true } } on first registerTool
call (pre-connect only), mirroring McpServer.registerTool behavior.
Fixes pdf-annotations e2e failures where the PDF canvas never rendered
because registerTool threw at module scope.
* pdf-server: replace app-tools with 1:1 interact command mapping
Converts the 5 placeholder app-registered tools into 12 tools that map
directly to the server's interact commands (navigate, search, find,
search_navigate, zoom, add_annotations, update_annotations,
remove_annotations, highlight_text, fill_form, get_text, get_screenshot)
plus the existing get-document-info.
Implementation stays DRY by dispatching through the existing
processCommands() handler — each tool callback constructs a PdfCommand
and runs it via a small runCommand() wrapper. For get_text and
get_screenshot, the page-data collection is extracted from
handleGetPages into a shared collectPageData() helper so results can
be returned directly instead of round-tripping through the server.
Tool names and zod schemas mirror the interact command parameter shapes
from server.ts so the model sees the same surface whether it goes
through interact or app-tools. The server-side interact tool is
unchanged and remains available for hosts without app-tool support.
* chore: drop unused test vars and stale Request/Result imports
* docs(spec): address review feedback on app tool registration
- Add outputSchema to Tool struct (matches SDK + core MCP)
- Reword "black box" bullets: drop stale screenshots reference, clarify no DOM access
- Clarify push vs pull contrast with explicit setWidgetState + ui/update-model-context refs
- Add Task-reference future-work bullet for tools outliving render lifecycle
* docs(spec): clarify app DOM is not host-introspectable; frame tools as pull complementing update-model-context push
* fix(app): emit title and omit absent outputSchema in tools/list
* feat(app): accept Standard Schema in registerTool/registerAppTool
App.registerTool and registerAppTool now accept any schema implementing
the Standard Schema spec (Zod ≥3.25, ArkType, Valibot, …) instead of
zod-only. Uses ~standard.validate / ~standard.jsonSchema directly, so
app.ts no longer imports zod at runtime — zod is now an optional peer.
Mirrors the StandardSchemaWithJSON type from
modelcontextprotocol/typescript-sdk#1689 so bumping to SDK v2 is a
drop-in import swap. Adds @standard-schema/spec (types-only) and a
hand-rolled non-zod test.
* docs(spec): reference core MCP Tasks for long-running app tools
App tools/call MAY be task-augmented per the core MCP Tasks utility
(spec 2025-11-25). Adds a Long-Running App Tools subsection: hosts proxy
tasks/* while the app is mounted; apps SHOULD delegate long-lived work
to the server so the task survives iframe teardown. Removes the stale
'future extension' bullet — Tasks is shipped.
* docs(spec): note Schema Validation accepts any Standard Schema library
* chore: clean unused-var and no-op-await diagnostics in app.ts and tests
* refactor(app): fold error prefix into validateStandardSchema; clarify registerAppTool runtime caveat
* chore(typedoc): mark RequestHandlerExtra as intentionally not exported
AppToolCallback (introduced with the Standard Schema migration) references
the local RequestHandlerExtra alias in its parameter type. The alias is a
private Parameters<> derivation and isn't part of the public API surface,
so add it to intentionallyNotExported alongside AppOptions/MethodSchema.
* fix(app): restore zod v3.25 compat via lazy z.toJSONSchema fallback
zod v3.25.x implements ~standard.validate but not ~standard.jsonSchema,
so constraining to StandardSchemaWithJSON broke the ^3.25.0 peer range.
Widen registerTool to StandardSchemaV1 and fall back to a lazy zod/v4
import for serialization when ~standard.jsonSchema is absent and
vendor==='zod'. Non-zod schemas without jsonSchema get a clear error.
* docs(spec): slim Long-Running App Tools to a pointer into core MCP Tasks
* chore: add TODO(sdk-v2) marker in standard-schema.ts
* fix(app): align registerTool runtime with declared types
- Handler reads registeredTool.{input,output}Schema so update() takes effect
- Call cb(extra) when no inputSchema, matching AppToolCallback<undefined>
- Throw on duplicate name; guard list_changed pre-connect; notify on register
- Drop misleading zod optional peer (still required by generated/schema.ts)
- examples: fix pdf search/find stale match count; threejs set-scene-source re-render
- spec: Zod → Standard Schema; clarify tools availability via tools/list after init
* feat(app): type structuredContent from outputSchema in registerTool
AppToolCallback gains an Out param: when outputSchema is provided,
structuredContent is required and typed via StandardSchemaV1.InferOutput,
with an `isError: true` escape hatch. Without outputSchema, return type
is unchanged (CallToolResult). Uses intersection rather than Omit since
CallToolResult's index signature swallows Omit's known keys.
Also drops the `as any` casts on z.object(...) in tests — zod 4 satisfies
StandardSchemaV1 directly.
* fix(app): registerTool edge cases + example tool correctness
App.registerTool:
- Skip outputSchema validation when result.isError (matches AppToolResult type)
- Pin callback arity to original config.inputSchema; only the validation schema is mutable via update()
- Default missing tools/call arguments to {}
- remove() is a no-op if the handle is stale (re-registered or already removed)
- Gate list_changed notifications on the listChanged capability
Examples:
- threejs: propagate executeThreeCode failures to onSceneError/onSceneRendering; catch updateModelContext rejection
- wiki-explorer: search-article preserves graph on no-match
- budget-allocator: fix category/stage values in tool descriptions
* feat(app,app-bridge): guard requests sent before handshake completes (#620)
* feat(app,app-bridge): guard requests sent before handshake completes
Calling host-bound methods before the ui/initialize →
ui/notifications/initialized handshake completes can race the handshake
on strict hosts and leave the iframe permanently hidden.
App (View side):
- All 8 host-bound methods (callServerTool, readServerResource,
listServerResources, sendMessage, updateModelContext, openLink,
downloadFile, requestDisplayMode) now check that connect() has sent
the initialized notification.
- New AppOptions.strict (default false): console.error when false,
throw when true. AppOptions is now exported.
- useApp() forwards strict to the App constructor.
- Flag resets on reconnect.
AppBridge (Host side):
- replaceRequestHandler is overridden to wrap every host-bound handler
with a console.warn if the request arrives before
ui/notifications/initialized. Never throws. ui/initialize and ping
use setRequestHandler directly and are exempt; notifications are
unaffected.
- Catches Views that hand-roll postMessage without the SDK.
- Flag resets on reconnect.
Refs anthropics/claude-ai-mcp#61, anthropics/claude-ai-mcp#149.
* fix(docs): drop AppOptions from intentionallyNotExported now that it is exported
---------
Co-authored-by: Claude <noreply@anthropic.com>
* feat: MCP app sampling support via stock SDK types
Alternative to #477. Instead of forking a stripped-down sampling schema,
this reuses the stock `CreateMessageRequest` / `CreateMessageResult` /
`CreateMessageResultWithTools` types from `@modelcontextprotocol/sdk`
directly — same pattern already used for `tools/call`.
- Spec: `sampling/createMessage` listed under `### Standard MCP Messages`,
`sampling?: { tools?: {} }` added to `HostCapabilities` (mirrors MCP
`ClientCapabilities.sampling` shape for easy pass-through).
- SDK: `App.createSamplingMessage()` with overloads that narrow the return
type based on whether `params.tools` is set; `AppBridge.oncreatesamplingmessage`
setter; `CreateMessageRequest`/`CreateMessageResult*` added to the
`AppRequest`/`AppResult` unions.
- Picks up SEP-1577 tool-calling support (`tools`, `toolChoice`, `tool_use`
content blocks, `stopReason: "toolUse"`, array content) for free — unblocks
the "nested agents" use case motivated by the original PR.
https://claude.ai/code/session_01ENGWTtsfcyP4S6fTWtUMAh
* feat(app): guard createSamplingMessage before handshake completes
---------
Co-authored-by: Claude <noreply@anthropic.com>
* feat(app,app-bridge): guard requests sent before handshake completes
Calling host-bound methods before the ui/initialize →
ui/notifications/initialized handshake completes can race the handshake
on strict hosts and leave the iframe permanently hidden.
App (View side):
- All 8 host-bound methods (callServerTool, readServerResource,
listServerResources, sendMessage, updateModelContext, openLink,
downloadFile, requestDisplayMode) now check that connect() has sent
the initialized notification.
- New AppOptions.strict (default false): console.error when false,
throw when true. AppOptions is now exported.
- useApp() forwards strict to the App constructor.
- Flag resets on reconnect.
AppBridge (Host side):
- replaceRequestHandler is overridden to wrap every host-bound handler
with a console.warn if the request arrives before
ui/notifications/initialized. Never throws. ui/initialize and ping
use setRequestHandler directly and are exempt; notifications are
unaffected.
- Catches Views that hand-roll postMessage without the SDK.
- Flag resets on reconnect.
Refs anthropics/claude-ai-mcp#61, anthropics/claude-ai-mcp#149.
* fix(docs): drop AppOptions from intentionallyNotExported now that it is exported
* feat(events): DOM-model on* semantics with addEventListener/removeEventListener
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>
* Fix typedoc validation warnings
- 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)
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Follow-up to #470. Matches MCP SDK Client.listResources(params?) signature
so callers can omit the empty {} for the common case.
Also reflows a line in app-bridge.test.ts that prettier flagged.
* fix: guard against double-connect in App and AppBridge (#429)
Calling connect() twice on the same transport instance caused the MCP
SDK's Protocol class to chain onmessage handlers, so each incoming
message was processed twice. This made incoming responses get consumed
by the first processing (resolving the handler) and then trigger a
spurious "Received a response for an unknown message ID" error on the
second processing pass, since the handler was already removed from the
response map.
Add an explicit guard in both App.connect() and AppBridge.connect() that
throws if the instance is already connected. This converts the silent
message-corruption into a clear, actionable error and prevents
downstream confusion.
Adds three regression tests:
- AppBridge.connect() throws when already connected
- App.connect() throws when already connected
- Demonstrates the double-processing root cause and verifies it is fixed
Fixes#429https://claude.ai/code/session_01XU3FW1TpjBkyRbkBUH5gqR
* test: replace redundant regression test with cleaner same-transport guard check
The previous third test in "double-connect guard" never actually attempted
a double-connect — it created fresh instances and verified normal operation,
which every other test already covers. The stray bridge.onupdatemodelcontext
and bridgeTransport.close() at the top were leftover noise.
Replace it with a focused test that verifies the guard fires even when
the caller passes the *same* transport object (not just a different one),
which is the exact scenario described in #429.
https://claude.ai/code/session_01XU3FW1TpjBkyRbkBUH5gqR
---------
Co-authored-by: Claude <noreply@anthropic.com>
Users frequently call callServerTool("tool_name", args) instead of the
correct callServerTool({ name: "tool_name", arguments: args }), resulting
in a silent/confusing failure. Detect this and throw immediately with
a message that shows the correct call shape.
Also fixes the incorrect example in examples/pdf-server/README.md that
demonstrated this same wrong usage pattern.
Fixes#386https://claude.ai/code/session_01GqAyN4Ux7svWoU2HqsSLZF
Co-authored-by: Claude <noreply@anthropic.com>
* refactor: rename request methods to remove send prefix
Following the MCP TS SDK convention where request methods don't have
the "send" prefix (reserved for notifications):
- App.sendMessage() → App.message()
- App.sendOpenLink() → App.openLink()
- AppBridge.sendResourceTeardown() → AppBridge.resourceTeardown()
The old method names are kept as deprecated aliases for backwards
compatibility.
Updated all examples to use the new method names.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* test: add tests for deprecated method aliases
- Update existing tests to use new method names (message, openLink)
- Add tests verifying deprecated aliases are same reference as new methods
- Add tests verifying deprecated aliases still work functionally
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* refactor: rename message→sendMessage, resourceTeardown→teardownResource
- App.message() → App.sendMessage() (with deprecated message alias)
- AppBridge.resourceTeardown() → AppBridge.teardownResource() (with deprecated aliases)
This better follows the pattern where methods that send data have 'send' or action verb prefix.
* fix: update examples to use sendMessage instead of message
* fix: update basic-host to use teardownResource
* fix: update threejs-server to use sendMessage
* refactor: remove deprecated app.message alias
The app.message alias was removed since sendMessage is the correct name.
sendResourceTeardown already exists in AppBridge.
* refactor: remove resourceTeardown alias, keep only sendResourceTeardown
---------
Co-authored-by: Claude <noreply@anthropic.com>
* docs: add tool visibility and restructure _meta.ui
- Restructure tool metadata: `_meta["ui/resourceUri"]` → `_meta.ui.resourceUri`
- Add `visibility` array field: ["model"], ["apps"], or ["model", "apps"]
- Default ["model"] preserves standard MCP behavior
- ["apps"] enables widget-only tools hidden from agent
- Add McpUiToolMeta interface for type safety
- Add Design Decision #4 explaining approach vs OpenAI's two-field model
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* address PR review comments
- Change default visibility to ["model", "apps"]
- Add deprecation notice for flat ui/resourceUri format
- Add McpUiToolMeta and McpUiToolVisibility to spec.types.ts
- Improve tools/list and tools/call behavior wording
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* chore: regenerate schemas for McpUiToolMeta types
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* feat: tool visibility and nested _meta.ui format
- Rename visibility "apps" → "app" in McpUiToolVisibility type
- Add _meta.ui.resourceUri nested format (deprecate flat format)
- Add getToolUiResourceUri() utility with backward compatibility
- Add visibility demo to system-monitor-server:
- get-system-stats: visibility ["model"] with resourceUri
- refresh-stats: visibility ["app"] (app-only polling)
- Update all example servers to use new _meta.ui format
- Add 11 unit tests for getToolUiResourceUri()
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* chore: regenerate schemas with updated dependencies
* feat: export McpUiToolMeta type and add type annotations
- Export McpUiToolMeta and McpUiToolVisibility types from types.ts
- Export corresponding Zod schemas (McpUiToolMetaSchema, McpUiToolVisibilitySchema)
- Add `as McpUiToolMeta` type annotations to all example servers
- Update docs/quickstart.md with proper typing
Ensures type safety for `_meta.ui` tool metadata across the codebase.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* feat: add server helpers and optional connect() transport
Add `src/server/` with convenience functions for registering MCP App tools
and resources:
- `registerAppTool(server, name, config, handler)`
- `registerAppResource(server, name, uri, config, callback)`
The `transport` parameter in `App.connect()` is now optional, defaulting to
`PostMessageTransport(window.parent)`.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Update src/server/index.ts
Co-authored-by: Jonathan Hefner <jonathan@hefner.pro>
* feat: update McpUiAppToolConfig to support both _meta.ui and flat formats
* refactor: simplify server helper imports
* docs: add deprecation notice and improve resourceUri documentation
* feat: add backward compat normalization in registerAppTool
- If _meta.ui.resourceUri is set, also set legacy flat key
- If legacy flat key is set, also set _meta.ui.resourceUri
- Preserves existing visibility when merging
- Does not overwrite if both formats already set
* refactor: avoid mutating config arg in registerAppTool
* style: format with prettier
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Olivier Chafik <ochafik@anthropic.com>
Co-authored-by: Jonathan Hefner <jonathan@hefner.pro>
* Add setter-based handlers for MCP request/notification forwarding
- Add oncalltool, onlistresources, onlistresourcetemplates, onreadresource, onlistprompts setters for Guest UI → Host requests
- Add sendToolListChanged, sendResourceListChanged, sendPromptListChanged methods for Host → Guest UI notifications
- Refactor connect() to use new setters with inlined callbacks
- Update documentation to clarify optional client parameter behavior
- Remove unused forwardRequest/forwardNotification helper methods
- Add comprehensive tests for AppBridge without MCP client (manual handlers)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Potential fix for pull request finding 'Unused variable, import, function or class'
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
* fix: move prettier to dependencies for git-based installs
* fix: add explicit prettierrc to prevent inheriting parent configs during git-based installs
* Apply suggestions from code review
Co-authored-by: Jonathan Hefner <jonathan@hefner.pro>
* refactor(tests): factor out test values and add missing assertions
Address PR review feedback:
- Factor out test value objects (toolCall, resultContent, etc.) for clearer tests
- Add missing assertions on receivedRequests[0] for list handlers
- Improve test readability by defining expected values at the top of each test
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* docs: improve TypeDoc formatting and use `as const` for notification methods
- Use proper TypeDoc syntax with `@param callback.params` and `@param callback.extra`
- Add {@link ResultType} references for return types
- Replace `as Notification` with `as const` on method literals where possible
(sendResourceListChanged, sendPromptListChanged)
- Simplify sendHostContextChange cast syntax
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* refactor(types): use explicit type unions for AppBridge protocol types
- Add AppBridgeRequest, AppBridgeNotification, AppBridgeResult type unions
that explicitly list all valid types for the protocol
- Replace generic SDK Request/Notification/Result with our specific types
- Remove all type casts by using `as const` on method literals
- Add index signature to McpUiHostContext for forward compatibility
This improves type safety and documents exactly what the AppBridge supports.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* refactor: move AppRequest/AppNotification/AppResult to types.ts
Move protocol type unions from app-bridge.ts to types.ts where they
belong with other type definitions.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* chore: regenerate schemas with clean npm install
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: Jonathan Hefner <jonathan@hefner.pro>
* fix: prettify generated files on the fly
- Add prettier --write to generate:schemas script to format generated
files immediately after generation
- Remove redundant prettier:fix from CI schema verification step
(now that generation includes formatting)
This fixes the release CI failure where generated files weren't
formatted. Previously, PR CI masked this by running prettier:fix
before the final prettier check, while release CI ran the check
directly on freshly-generated (unformatted) files.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* format
---------
Co-authored-by: Claude <noreply@anthropic.com>
* feat: store and expose hostContext in App class
Fixes#129 - The App class now stores the initial hostContext received
during initialization and exposes it via getHostContext().
Changes:
- Add _hostContext private property to App class
- Store result.hostContext in connect() method
- Add getHostContext() getter method (parallel to getHostCapabilities())
- Update onhostcontextchanged setter to merge partial updates into
stored context
- Add default notification handler to update context even when
user hasn't set onhostcontextchanged
This enables apps to:
- Access toolInfo immediately after connection (only in initial context)
- Render with correct theme/locale without waiting for first notification
- Synchronously access viewport, timezone, and other initial state
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* fix: use sendHostContextChange for partial update accumulation test
Address PR review feedback: The test was using setHostContext with full
context spreads which didn't clearly demonstrate partial update
accumulation. Now uses sendHostContextChange directly to send truly
partial updates (only theme, then only viewport), making it clear
the App correctly merges multiple partial notifications.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* feat: implement ui/notifications/tool-cancelled throughout SDK
Wire the tool-cancelled notification that hosts MUST send when tool
execution is cancelled (user action, timeout, classifier intervention, etc.):
- Add McpUiToolCancelledNotification type in spec.types.ts
- Generate Zod schema via ts-to-zod
- Add AppBridge.sendToolCancelled() method for hosts
- Add App.ontoolcancelled setter for guest UIs
- Export type and schema from types.ts
- Add tests for notification with/without reason
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* feat: add App.onteardown handler for graceful shutdown
Wire the guest UI side of ui/resource-teardown request:
- Add onteardown setter in App class for guest UIs to handle teardown
- Allow ui/resource-teardown in assertRequestHandlerCapability
- Import McpUiResourceTeardownRequest/Result types
- Add tests for sync and async cleanup handlers
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* feat(example): wire tool-cancelled and teardown in basic-host
Update basic-host example to properly use the new notifications:
- Send sendToolCancelled when tool call promise rejects
- Send sendResourceTeardown when React component unmounts
- Store appBridge ref for cleanup access
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(example): implement graceful teardown per spec recommendation
Address review feedback: the host now waits for guest's teardown
response before unmounting, following the spec: "Host SHOULD wait
for a response before tearing down the resource (to prevent data loss)."
Changes:
- Add close button to tool call panels
- Two-phase unmount: isDestroying state dims panel and disables events
- sendResourceTeardown() completes before component removal
- Non-app tool results close immediately
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* feat(examples): add onteardown handlers to demo apps
Demonstrate graceful shutdown with simulated 500ms cleanup work.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* Add App <-> AppBridge integration tests
- Add minimalistic tests validating communication between App (guest) and AppBridge (host)
- Test initialization handshake flow
- Test Host -> App notifications (toolInput, toolInputPartial, toolResult, hostContext)
- Test App -> Host notifications (sizeChange, log)
- Test App -> Host requests (sendMessage, sendOpenLink)
- Test ping functionality
- Add setHostContext and sendToolInputPartial to AppBridge
- Add test script to package.json
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* docs: add JSDoc for setHostContext and sendToolInputPartial
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* fix: add @types/bun for test type checking
- Add @types/bun devDependency for bun:test types
- Fix mock client request return type
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>