Skip to content
Open
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
32 changes: 31 additions & 1 deletion specification/draft/apps.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -1214,6 +1214,31 @@ 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).

#### Notifications (View → Host)

`ui/notifications/request-close` - View requests host to close it

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

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

**Host behavior:**
- Host decides whether to proceed with the close
- If approved, Host MUST send `ui/resource-teardown` to allow the View to perform cleanup
- Host MUST wait for the View's teardown response before unmounting the iframe
- Host MAY deny or defer the close request (e.g., if there are unsaved changes elsewhere)

**View behavior:**
- View SHOULD NOT perform cleanup before sending this notification
- View SHOULD handle cleanup in its `ui/resource-teardown` handler
- This ensures the View has a single cleanup procedure regardless of whether the close was initiated by the View or the Host

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

```typescript
Expand Down Expand Up @@ -1368,17 +1393,22 @@ sequenceDiagram

#### 4. Cleanup

Cleanup can be initiated by either the Host or the View. In both cases, the Host sends `ui/resource-teardown` to allow the View to perform cleanup before unmounting.

```mermaid
sequenceDiagram
participant H as Host
participant UI as View (iframe)
opt View-initiated close
UI ->> H: ui/notifications/request-close
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: Cleanup may be triggered at any point in the lifecycle following View initialization. If the View sends `ui/notifications/request-close`, the Host MAY deny or defer the request.

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

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

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

// Host handles close request by initiating teardown
bridge.onrequestclose = async () => {
events.push("close-requested");
// Host decides to proceed with close - initiate teardown
await bridge.teardownResource({});
events.push("teardown-complete");
};

// App handles teardown (cleanup before unmount)
app.onteardown = async () => {
events.push("app-cleanup");
return {};
};

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

// Verify the full flow: request → teardown → cleanup
expect(events).toEqual([
"close-requested",
"app-cleanup",
"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 @@ -71,6 +71,8 @@ import {
McpUiOpenLinkResult,
McpUiResourceTeardownRequest,
McpUiResourceTeardownResultSchema,
McpUiRequestCloseNotification,
McpUiRequestCloseNotificationSchema,
McpUiSandboxProxyReadyNotification,
McpUiSandboxProxyReadyNotificationSchema,
McpUiSizeChangedNotificationSchema,
Expand Down Expand Up @@ -585,6 +587,41 @@ export class AppBridge extends Protocol<
);
}

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

/**
* Register a handler for display mode change requests from the view.
*
Expand Down
45 changes: 45 additions & 0 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {
McpUiResourceTeardownRequest,
McpUiResourceTeardownRequestSchema,
McpUiResourceTeardownResult,
McpUiRequestCloseNotification,
McpUiSizeChangedNotification,
McpUiToolCancelledNotification,
McpUiToolCancelledNotificationSchema,
Expand Down Expand Up @@ -924,6 +925,50 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
/** @deprecated Use {@link openLink `openLink`} instead */
sendOpenLink: App["openLink"] = this.openLink;

/**
* Request the host to close this app.
*
* Apps call this method to request that the host close them. The host decides
* whether to proceed with the close - if approved, the host will send
* `ui/resource-teardown` to allow the app to perform cleanup 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 close was initiated by the app or the host.
*
* This is a fire-and-forget notification - no response is expected.
* If the host approves the close, the app will receive a `ui/resource-teardown`
* request via the {@link onteardown `onteardown`} handler to perform cleanup.
*
* @param params - Empty params object (reserved for future use)
* @returns Promise that resolves when the notification is sent
*
* @example App-initiated close after user action
* ```typescript
* // User clicks "Done" button in the app
* async function handleDoneClick() {
* // Request the host to close the app
* await app.requestClose();
* // If host approves, onteardown handler will be called for cleanup
* }
*
* // Set up teardown handler (called for both app-initiated and host-initiated close)
* app.onteardown = async () => {
* await saveState();
* closeConnections();
* return {};
* };
* ```
*
* @see {@link McpUiRequestCloseNotification `McpUiRequestCloseNotification`} for notification structure
* @see {@link onteardown `onteardown`} for the cleanup handler
*/
requestClose(params: McpUiRequestCloseNotification["params"] = {}) {
return this.notification(<McpUiRequestCloseNotification>{
method: "ui/notifications/request-close",
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.

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

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

/**
* @description Capabilities supported by the host application.
* @see {@link McpUiInitializeResult `McpUiInitializeResult`} for the initialization result that includes these capabilities
Expand Down
6 changes: 5 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export {
type McpUiHostContextChangedNotification,
type McpUiResourceTeardownRequest,
type McpUiResourceTeardownResult,
type McpUiRequestCloseNotification,
type McpUiHostCapabilities,
type McpUiAppCapabilities,
type McpUiInitializeRequest,
Expand Down Expand Up @@ -81,6 +82,7 @@ import type {
McpUiInitializedNotification,
McpUiSizeChangedNotification,
McpUiSandboxProxyReadyNotification,
McpUiRequestCloseNotification,
McpUiInitializeResult,
McpUiOpenLinkResult,
McpUiMessageResult,
Expand Down Expand Up @@ -111,6 +113,7 @@ export {
McpUiHostContextChangedNotificationSchema,
McpUiResourceTeardownRequestSchema,
McpUiResourceTeardownResultSchema,
McpUiRequestCloseNotificationSchema,
McpUiHostCapabilitiesSchema,
McpUiAppCapabilitiesSchema,
McpUiInitializeRequestSchema,
Expand Down Expand Up @@ -181,7 +184,7 @@ export type AppRequest =
* - Sandbox resource ready
*
* App to host:
* - Initialized, size-changed, sandbox-proxy-ready
* - Initialized, size-changed, sandbox-proxy-ready, request-close
* - Logging messages
*/
export type AppNotification =
Expand All @@ -199,6 +202,7 @@ export type AppNotification =
| McpUiInitializedNotification
| McpUiSizeChangedNotification
| McpUiSandboxProxyReadyNotification
| McpUiRequestCloseNotification
| LoggingMessageNotification;

/**
Expand Down
Loading