Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 26 additions & 3 deletions specification/draft/apps.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -1285,9 +1285,11 @@ Host MUST send this notification if the tool execution was cancelled, for any re
}
```

Host MUST send this notification before tearing down the UI resource, for any reason, including user action, resource re-allocation, etc. The Host MAY specify the reason.
Host MUST send this notification before tearing down the UI resource, for any reason, including user action, view-initiated teardown, resource re-allocation, etc. The Host MAY specify the reason.
Host SHOULD wait for a response before tearing down the resource (to prevent data loss).

#### Notifications (View → Host)

`ui/notifications/size-changed` - View's size changed

```typescript
Expand All @@ -1303,6 +1305,22 @@ Host SHOULD wait for a response before tearing down the resource (to prevent dat

The View SHOULD send this notification when rendered content body size changes (e.g. using ResizeObserver API to report up to date size).

`ui/notifications/request-teardown` - View requests host to tear it down

```typescript
{
jsonrpc: "2.0",
method: "ui/notifications/request-teardown",
params: {}
}
```

The View MAY send this notification to request that the host tear it down. This enables View-initiated teardown flows (e.g., user clicks a "Done" button in the View).

**Host behavior:**
- Host MAY defer or ignore the teardown request.
- If the Host accepts the request, it MUST follow the graceful termination process by sending `ui/resource-teardown` to the View. The Host SHOULD wait for a response before tearing down the resource (to prevent data loss).

`ui/notifications/host-context-changed` - Host context has changed

```typescript
Expand Down Expand Up @@ -1455,19 +1473,24 @@ sequenceDiagram
end
```

#### 4. Cleanup
#### 4. Teardown

The Host can tear down Views. Views may request teardown by sending `ui/notifications/request-teardown` to the Host. In any case, the Host MUST send `ui/resource-teardown` to allow the View to terminate gracefully.

```mermaid
sequenceDiagram
participant H as Host
participant UI as View (iframe)
opt View-initiated teardown
UI ->> H: ui/notifications/request-teardown
end
H ->> UI: ui/resource-teardown
UI --> UI: Graceful termination
UI -->> H: ui/resource-teardown response
H -x H: Tear down iframe and listeners
```

Note: Cleanup may be triggered at any point in the lifecycle following View initialization.
Note: Teardown may be triggered at any point in the lifecycle following View initialization.

#### Key Differences from Pre-SEP MCP-UI:

Expand Down
25 changes: 25 additions & 0 deletions src/app-bridge.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,31 @@ describe("App <-> AppBridge integration", () => {
}),
).rejects.toThrow("Context update failed");
});

it("app.requestTeardown allows host to initiate teardown flow", async () => {
const events: string[] = [];

bridge.onrequestteardown = async () => {
events.push("teardown-requested");
await bridge.teardownResource({});
events.push("teardown-complete");
};

app.onteardown = async () => {
events.push("persist-unsaved-state");
return {};
};

await app.connect(appTransport);
await app.requestTeardown();
await flush();

expect(events).toEqual([
"teardown-requested",
"persist-unsaved-state",
"teardown-complete",
]);
});
});

describe("App -> Host requests", () => {
Expand Down
37 changes: 37 additions & 0 deletions src/app-bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ import {
McpUiDownloadFileResult,
McpUiResourceTeardownRequest,
McpUiResourceTeardownResultSchema,
McpUiRequestTeardownNotification,
McpUiRequestTeardownNotificationSchema,
McpUiSandboxProxyReadyNotification,
McpUiSandboxProxyReadyNotificationSchema,
McpUiSizeChangedNotificationSchema,
Expand Down Expand Up @@ -673,6 +675,41 @@ export class AppBridge extends Protocol<
);
}

/**
* Register a handler for app-initiated teardown request notifications from the view.
*
* The view sends `ui/notifications/request-teardown` when it wants the host to tear it down.
* If the host decides to proceed, it should send
* `ui/resource-teardown` (via {@link teardownResource `teardownResource`}) to allow
* the view to perform gracefull termination, then unmount the iframe after the view responds.
*
* @param callback - Handler that receives teardown request params
* - params - Empty object (reserved for future use)
*
* @example
* ```typescript
* bridge.onrequestteardown = async (params) => {
* console.log("App requested teardown");
* // Initiate teardown to allow the app to persist unsaved state
* // Alternatively, the callback can early return to prevent teardown
* await bridge.teardownResource({});
* // Now safe to unmount the iframe
* iframe.remove();
* };
* ```
*
* @see {@link McpUiRequestTeardownNotification `McpUiRequestTeardownNotification`} for the notification type
* @see {@link teardownResource `teardownResource`} for initiating teardown
*/
set onrequestteardown(
callback: (params: McpUiRequestTeardownNotification["params"]) => void,
) {
this.setNotificationHandler(
McpUiRequestTeardownNotificationSchema,
(request) => callback(request.params),
);
}

/**
* Register a handler for display mode change requests from the view.
*
Expand Down
47 changes: 46 additions & 1 deletion src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import {
McpUiResourceTeardownRequest,
McpUiResourceTeardownRequestSchema,
McpUiResourceTeardownResult,
McpUiRequestTeardownNotification,
McpUiSizeChangedNotification,
McpUiToolCancelledNotification,
McpUiToolCancelledNotificationSchema,
Expand Down Expand Up @@ -177,7 +178,7 @@ type RequestHandlerExtra = Parameters<
* 1. **Create**: Instantiate App with info and capabilities
* 2. **Connect**: Call `connect()` to establish transport and perform handshake
* 3. **Interactive**: Send requests, receive notifications, call tools
* 4. **Cleanup**: Host sends teardown request before unmounting
* 4. **Teardown**: Host sends teardown request before unmounting
*
* ## Inherited Methods
*
Expand Down Expand Up @@ -1134,6 +1135,50 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
);
}

/**
* Request the host to tear down this app.
*
* Apps call this method to request that the host tear them down. The host
* decides whether to proceed - if approved, the host will send
* `ui/resource-teardown` to allow the app to perform gracefull termination before being
* unmounted. This piggybacks on the existing teardown mechanism, ensuring
* the app only needs a single shutdown procedure (via {@link onteardown `onteardown`})
* regardless of whether the teardown was initiated by the app or the host.
*
* This is a fire-and-forget notification - no response is expected.
* If the host approves, the app will receive a `ui/resource-teardown`
* request via the {@link onteardown `onteardown`} handler to persist unsaved state.
*
* @param params - Empty params object (reserved for future use)
* @returns Promise that resolves when the notification is sent
*
* @example App-initiated teardown after user action
* ```typescript
* // User clicks "Done" button in the app
* async function handleDoneClick() {
* // Request the host to tear down the app
* await app.requestTeardown();
* // If host approves, onteardown handler will be called for termination
* }
*
* // Set up teardown handler (called for both app-initiated and host-initiated teardown)
* app.onteardown = async () => {
* await saveState();
* closeConnections();
* return {};
* };
* ```
*
* @see {@link McpUiRequestTeardownNotification `McpUiRequestTeardownNotification`} for notification structure
* @see {@link onteardown `onteardown`} for the graceful termination handler
*/
requestTeardown(params: McpUiRequestTeardownNotification["params"] = {}) {
return this.notification(<McpUiRequestTeardownNotification>{
method: "ui/notifications/request-teardown",
params,
});
}

/**
* Request a change to the display mode.
*
Expand Down
17 changes: 17 additions & 0 deletions src/generated/schema.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions src/generated/schema.test.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions src/generated/schema.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions src/spec.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,19 @@ export interface McpUiSupportedContentBlockModalities {
structuredContent?: {};
}

/**
* @description Notification for app-initiated teardown request (View -> Host).
* Views send this to request that the host tear them down. The host decides
* whether to proceed - if approved, the host will send
* `ui/resource-teardown` to allow the view to perform cleanup before being
* unmounted.
* @see {@link app.App.requestTeardown} for the app method that sends this
*/
export interface McpUiRequestTeardownNotification {
method: "ui/notifications/request-teardown";
params?: {};
}

/**
* @description Capabilities supported by the host application.
* @see {@link McpUiInitializeResult `McpUiInitializeResult`} for the initialization result that includes these capabilities
Expand Down Expand Up @@ -800,6 +813,8 @@ export const TOOL_CANCELLED_METHOD: McpUiToolCancelledNotification["method"] =
"ui/notifications/tool-cancelled";
export const HOST_CONTEXT_CHANGED_METHOD: McpUiHostContextChangedNotification["method"] =
"ui/notifications/host-context-changed";
export const REQUEST_TEARDOWN_METHOD: McpUiRequestTeardownNotification["method"] =
"ui/notifications/request-teardown";
export const RESOURCE_TEARDOWN_METHOD: McpUiResourceTeardownRequest["method"] =
"ui/resource-teardown";
export const INITIALIZE_METHOD: McpUiInitializeRequest["method"] =
Expand Down
7 changes: 6 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export {
TOOL_RESULT_METHOD,
TOOL_CANCELLED_METHOD,
HOST_CONTEXT_CHANGED_METHOD,
REQUEST_TEARDOWN_METHOD,
RESOURCE_TEARDOWN_METHOD,
INITIALIZE_METHOD,
INITIALIZED_METHOD,
Expand Down Expand Up @@ -52,6 +53,7 @@ export {
type McpUiHostContextChangedNotification,
type McpUiResourceTeardownRequest,
type McpUiResourceTeardownResult,
type McpUiRequestTeardownNotification,
type McpUiHostCapabilities,
type McpUiAppCapabilities,
type McpUiInitializeRequest,
Expand Down Expand Up @@ -85,6 +87,7 @@ import type {
McpUiInitializedNotification,
McpUiSizeChangedNotification,
McpUiSandboxProxyReadyNotification,
McpUiRequestTeardownNotification,
McpUiInitializeResult,
McpUiOpenLinkResult,
McpUiDownloadFileResult,
Expand Down Expand Up @@ -118,6 +121,7 @@ export {
McpUiHostContextChangedNotificationSchema,
McpUiResourceTeardownRequestSchema,
McpUiResourceTeardownResultSchema,
McpUiRequestTeardownNotificationSchema,
McpUiHostCapabilitiesSchema,
McpUiAppCapabilitiesSchema,
McpUiInitializeRequestSchema,
Expand Down Expand Up @@ -189,7 +193,7 @@ export type AppRequest =
* - Sandbox resource ready
*
* App to host:
* - Initialized, size-changed, sandbox-proxy-ready
* - Initialized, size-changed, sandbox-proxy-ready, request-teardown
* - Logging messages
*/
export type AppNotification =
Expand All @@ -207,6 +211,7 @@ export type AppNotification =
| McpUiInitializedNotification
| McpUiSizeChangedNotification
| McpUiSandboxProxyReadyNotification
| McpUiRequestTeardownNotification
| LoggingMessageNotification;

/**
Expand Down
Loading