From 67e530fed17475ab70da07480b056b25a1caaac6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Barthelet?= Date: Thu, 8 Jan 2026 11:55:55 +0100 Subject: [PATCH 1/6] Add ui/close-resource request for UI to initiate termination --- specification/draft/apps.mdx | 32 ++++++++++++++++++++++++- src/app-bridge.test.ts | 29 +++++++++++++++++++++++ src/app-bridge.ts | 37 +++++++++++++++++++++++++++++ src/app.ts | 45 ++++++++++++++++++++++++++++++++++++ src/generated/schema.json | 17 ++++++++++++++ src/generated/schema.test.ts | 10 ++++++++ src/generated/schema.ts | 13 +++++++++++ src/spec.types.ts | 13 +++++++++++ src/types.ts | 6 ++++- 9 files changed, 200 insertions(+), 2 deletions(-) diff --git a/specification/draft/apps.mdx b/specification/draft/apps.mdx index e7c343b8..76ca0317 100644 --- a/specification/draft/apps.mdx +++ b/specification/draft/apps.mdx @@ -1303,6 +1303,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 @@ -1457,17 +1482,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: diff --git a/src/app-bridge.test.ts b/src/app-bridge.test.ts index adbe62cd..85e91a09 100644 --- a/src/app-bridge.test.ts +++ b/src/app-bridge.test.ts @@ -520,6 +520,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", () => { diff --git a/src/app-bridge.ts b/src/app-bridge.ts index 014420e8..18ef01ae 100644 --- a/src/app-bridge.ts +++ b/src/app-bridge.ts @@ -74,6 +74,8 @@ import { McpUiDownloadFileResult, McpUiResourceTeardownRequest, McpUiResourceTeardownResultSchema, + McpUiRequestCloseNotification, + McpUiRequestCloseNotificationSchema, McpUiSandboxProxyReadyNotification, McpUiSandboxProxyReadyNotificationSchema, McpUiSizeChangedNotificationSchema, @@ -673,6 +675,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. * diff --git a/src/app.ts b/src/app.ts index 92db4810..7108b1f1 100644 --- a/src/app.ts +++ b/src/app.ts @@ -44,6 +44,7 @@ import { McpUiResourceTeardownRequest, McpUiResourceTeardownRequestSchema, McpUiResourceTeardownResult, + McpUiRequestCloseNotification, McpUiSizeChangedNotification, McpUiToolCancelledNotification, McpUiToolCancelledNotificationSchema, @@ -1134,6 +1135,50 @@ export class App extends Protocol { ); } + /** + * 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({ + method: "ui/notifications/request-close", + params, + }); + } + /** * Request a change to the display mode. * diff --git a/src/generated/schema.json b/src/generated/schema.json index 522f1592..36f5268b 100644 --- a/src/generated/schema.json +++ b/src/generated/schema.json @@ -4013,6 +4013,23 @@ }, "additionalProperties": {} }, + "McpUiRequestCloseNotification": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "method": { + "type": "string", + "const": "ui/notifications/request-close" + }, + "params": { + "type": "object", + "properties": {}, + "additionalProperties": false + } + }, + "required": ["method"], + "additionalProperties": false + }, "McpUiRequestDisplayModeRequest": { "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", diff --git a/src/generated/schema.test.ts b/src/generated/schema.test.ts index cebad70b..cd656338 100644 --- a/src/generated/schema.test.ts +++ b/src/generated/schema.test.ts @@ -91,6 +91,10 @@ export type McpUiSupportedContentBlockModalitiesSchemaInferredType = z.infer< typeof generated.McpUiSupportedContentBlockModalitiesSchema >; +export type McpUiRequestCloseNotificationSchemaInferredType = z.infer< + typeof generated.McpUiRequestCloseNotificationSchema +>; + export type McpUiHostCapabilitiesSchemaInferredType = z.infer< typeof generated.McpUiHostCapabilitiesSchema >; @@ -255,6 +259,12 @@ expectType( expectType( {} as spec.McpUiSupportedContentBlockModalities, ); +expectType( + {} as McpUiRequestCloseNotificationSchemaInferredType, +); +expectType( + {} as spec.McpUiRequestCloseNotification, +); expectType( {} as McpUiHostCapabilitiesSchemaInferredType, ); diff --git a/src/generated/schema.ts b/src/generated/schema.ts index 8eb12b5a..a5b0f244 100644 --- a/src/generated/schema.ts +++ b/src/generated/schema.ts @@ -478,6 +478,19 @@ export const McpUiSupportedContentBlockModalitiesSchema = z.object({ .describe("Host supports structured content."), }); +/** + * @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 const McpUiRequestCloseNotificationSchema = z.object({ + method: z.literal("ui/notifications/request-close"), + params: z.object({}).optional(), +}); + /** * @description Capabilities supported by the host application. * @see {@link McpUiInitializeResult `McpUiInitializeResult`} for the initialization result that includes these capabilities diff --git a/src/spec.types.ts b/src/spec.types.ts index 8e0e2eb0..90efe09f 100644 --- a/src/spec.types.ts +++ b/src/spec.types.ts @@ -474,6 +474,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 diff --git a/src/types.ts b/src/types.ts index 739da6fa..ec2d1eaf 100644 --- a/src/types.ts +++ b/src/types.ts @@ -52,6 +52,7 @@ export { type McpUiHostContextChangedNotification, type McpUiResourceTeardownRequest, type McpUiResourceTeardownResult, + type McpUiRequestCloseNotification, type McpUiHostCapabilities, type McpUiAppCapabilities, type McpUiInitializeRequest, @@ -85,6 +86,7 @@ import type { McpUiInitializedNotification, McpUiSizeChangedNotification, McpUiSandboxProxyReadyNotification, + McpUiRequestCloseNotification, McpUiInitializeResult, McpUiOpenLinkResult, McpUiDownloadFileResult, @@ -118,6 +120,7 @@ export { McpUiHostContextChangedNotificationSchema, McpUiResourceTeardownRequestSchema, McpUiResourceTeardownResultSchema, + McpUiRequestCloseNotificationSchema, McpUiHostCapabilitiesSchema, McpUiAppCapabilitiesSchema, McpUiInitializeRequestSchema, @@ -189,7 +192,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 = @@ -207,6 +210,7 @@ export type AppNotification = | McpUiInitializedNotification | McpUiSizeChangedNotification | McpUiSandboxProxyReadyNotification + | McpUiRequestCloseNotification | LoggingMessageNotification; /** From 9e46d052c968d9d4baa4d404710e33841e51df52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Barthelet?= Date: Fri, 20 Feb 2026 15:22:22 +0100 Subject: [PATCH 2/6] Add missing METHOD export --- src/spec.types.ts | 2 ++ src/types.ts | 1 + 2 files changed, 3 insertions(+) diff --git a/src/spec.types.ts b/src/spec.types.ts index 90efe09f..3ff5a42e 100644 --- a/src/spec.types.ts +++ b/src/spec.types.ts @@ -813,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_CLOSE_METHOD: McpUiRequestCloseNotification["method"] = + "ui/notifications/request-close"; export const RESOURCE_TEARDOWN_METHOD: McpUiResourceTeardownRequest["method"] = "ui/resource-teardown"; export const INITIALIZE_METHOD: McpUiInitializeRequest["method"] = diff --git a/src/types.ts b/src/types.ts index ec2d1eaf..74df0078 100644 --- a/src/types.ts +++ b/src/types.ts @@ -23,6 +23,7 @@ export { TOOL_RESULT_METHOD, TOOL_CANCELLED_METHOD, HOST_CONTEXT_CHANGED_METHOD, + REQUEST_CLOSE_METHOD, RESOURCE_TEARDOWN_METHOD, INITIALIZE_METHOD, INITIALIZED_METHOD, From 57393a2f6ebef02736bc59bfc9b41638174d16ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Barthelet?= Date: Fri, 20 Feb 2026 15:25:05 +0100 Subject: [PATCH 3/6] Fix typo in remaining ui/request-close --- src/app-bridge.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app-bridge.ts b/src/app-bridge.ts index 18ef01ae..a2680b59 100644 --- a/src/app-bridge.ts +++ b/src/app-bridge.ts @@ -678,7 +678,7 @@ 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. + * The view sends `ui/notifications/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. From 5102b293ea935aff288f5455bb92c692d42c0459 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Barthelet?= Date: Tue, 17 Mar 2026 18:41:53 +0100 Subject: [PATCH 4/6] Align spec terminology to teardown/termination instead of close/clearnup --- specification/draft/apps.mdx | 33 ++++++++++++++----------------- src/app-bridge.test.ts | 18 +++++++---------- src/app-bridge.ts | 28 +++++++++++++------------- src/app.ts | 38 ++++++++++++++++++------------------ src/generated/schema.json | 34 ++++++++++++++++---------------- src/generated/schema.test.ts | 12 ++++++------ src/generated/schema.ts | 12 ++++++------ src/spec.types.ts | 16 +++++++-------- src/types.ts | 12 ++++++------ 9 files changed, 98 insertions(+), 105 deletions(-) diff --git a/specification/draft/apps.mdx b/specification/draft/apps.mdx index 76ca0317..fc17f271 100644 --- a/specification/draft/apps.mdx +++ b/specification/draft/apps.mdx @@ -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 @@ -1303,30 +1305,25 @@ 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 +`ui/notifications/request-teardown` - View requests host to tear it down ```typescript { jsonrpc: "2.0", - method: "ui/notifications/request-close", + method: "ui/notifications/request-teardown", 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). +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 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) +- Host decides whether to proceed with the teardown +- 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). **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 +- View MAY persist unsaved state before sending this notification. processes MUST be completed before responding to `ui/resource-teardown` Host request. +- View SHOULD NOT teardown its UI and assume it's going to be removed when sending this notification. Final teardown decision and timing remains Host responsibility. `ui/notifications/host-context-changed` - Host context has changed @@ -1480,16 +1477,16 @@ sequenceDiagram end ``` -#### 4. Cleanup +#### 4. Teardown -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. +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 close - UI ->> H: ui/notifications/request-close + opt View-initiated teardown + UI ->> H: ui/notifications/request-teardown end H ->> UI: ui/resource-teardown UI --> UI: Graceful termination @@ -1497,7 +1494,7 @@ sequenceDiagram H -x H: Tear down iframe and listeners ``` -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. +Note: Teardown may be triggered at any point in the lifecycle following View initialization. #### Key Differences from Pre-SEP MCP-UI: diff --git a/src/app-bridge.test.ts b/src/app-bridge.test.ts index 85e91a09..c1f4612c 100644 --- a/src/app-bridge.test.ts +++ b/src/app-bridge.test.ts @@ -521,31 +521,27 @@ describe("App <-> AppBridge integration", () => { ).rejects.toThrow("Context update failed"); }); - it("app.requestClose allows host to initiate teardown flow", async () => { + it("app.requestTeardown 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 + bridge.onrequestteardown = async () => { + events.push("teardown-requested"); await bridge.teardownResource({}); events.push("teardown-complete"); }; - // App handles teardown (cleanup before unmount) app.onteardown = async () => { - events.push("app-cleanup"); + events.push("persist-unsaved-state"); return {}; }; await app.connect(appTransport); - await app.requestClose(); + await app.requestTeardown(); await flush(); - // Verify the full flow: request → teardown → cleanup expect(events).toEqual([ - "close-requested", - "app-cleanup", + "teardown-requested", + "persist-unsaved-state", "teardown-complete", ]); }); diff --git a/src/app-bridge.ts b/src/app-bridge.ts index a2680b59..d409fc06 100644 --- a/src/app-bridge.ts +++ b/src/app-bridge.ts @@ -74,8 +74,8 @@ import { McpUiDownloadFileResult, McpUiResourceTeardownRequest, McpUiResourceTeardownResultSchema, - McpUiRequestCloseNotification, - McpUiRequestCloseNotificationSchema, + McpUiRequestTeardownNotification, + McpUiRequestTeardownNotificationSchema, McpUiSandboxProxyReadyNotification, McpUiSandboxProxyReadyNotificationSchema, McpUiSizeChangedNotificationSchema, @@ -676,21 +676,21 @@ export class AppBridge extends Protocol< } /** - * Register a handler for app-initiated close request notifications from the view. + * Register a handler for app-initiated teardown request notifications from the view. * - * The view sends `ui/notifications/request-close` when it wants the host to close it. - * If the host decides to proceed with the close, it should send + * 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 cleanup, then unmount the iframe after the view responds. + * the view to perform gracefull termination, then unmount the iframe after the view responds. * - * @param callback - Handler that receives close request params + * @param callback - Handler that receives teardown 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 + * 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 @@ -698,14 +698,14 @@ export class AppBridge extends Protocol< * }; * ``` * - * @see {@link McpUiRequestCloseNotification `McpUiRequestCloseNotification`} for the notification type + * @see {@link McpUiRequestTeardownNotification `McpUiRequestTeardownNotification`} for the notification type * @see {@link teardownResource `teardownResource`} for initiating teardown */ - set onrequestclose( - callback: (params: McpUiRequestCloseNotification["params"]) => void, + set onrequestteardown( + callback: (params: McpUiRequestTeardownNotification["params"]) => void, ) { this.setNotificationHandler( - McpUiRequestCloseNotificationSchema, + McpUiRequestTeardownNotificationSchema, (request) => callback(request.params), ); } diff --git a/src/app.ts b/src/app.ts index 7108b1f1..3f2c9e43 100644 --- a/src/app.ts +++ b/src/app.ts @@ -44,7 +44,7 @@ import { McpUiResourceTeardownRequest, McpUiResourceTeardownRequestSchema, McpUiResourceTeardownResult, - McpUiRequestCloseNotification, + McpUiRequestTeardownNotification, McpUiSizeChangedNotification, McpUiToolCancelledNotification, McpUiToolCancelledNotificationSchema, @@ -178,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 * @@ -1136,32 +1136,32 @@ export class App extends Protocol { } /** - * Request the host to close this app. + * Request the host to tear down 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 + * 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 close was initiated by the app or the host. + * 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 close, the app will receive a `ui/resource-teardown` - * request via the {@link onteardown `onteardown`} handler to perform cleanup. + * 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 close after user action + * @example App-initiated teardown 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 + * // 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 close) + * // Set up teardown handler (called for both app-initiated and host-initiated teardown) * app.onteardown = async () => { * await saveState(); * closeConnections(); @@ -1169,12 +1169,12 @@ export class App extends Protocol { * }; * ``` * - * @see {@link McpUiRequestCloseNotification `McpUiRequestCloseNotification`} for notification structure - * @see {@link onteardown `onteardown`} for the cleanup handler + * @see {@link McpUiRequestTeardownNotification `McpUiRequestTeardownNotification`} for notification structure + * @see {@link onteardown `onteardown`} for the graceful termination handler */ - requestClose(params: McpUiRequestCloseNotification["params"] = {}) { - return this.notification({ - method: "ui/notifications/request-close", + requestTeardown(params: McpUiRequestTeardownNotification["params"] = {}) { + return this.notification({ + method: "ui/notifications/request-teardown", params, }); } diff --git a/src/generated/schema.json b/src/generated/schema.json index 36f5268b..eb2a71f2 100644 --- a/src/generated/schema.json +++ b/src/generated/schema.json @@ -4013,23 +4013,6 @@ }, "additionalProperties": {} }, - "McpUiRequestCloseNotification": { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "type": "object", - "properties": { - "method": { - "type": "string", - "const": "ui/notifications/request-close" - }, - "params": { - "type": "object", - "properties": {}, - "additionalProperties": false - } - }, - "required": ["method"], - "additionalProperties": false - }, "McpUiRequestDisplayModeRequest": { "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", @@ -4091,6 +4074,23 @@ "required": ["mode"], "additionalProperties": {} }, + "McpUiRequestTeardownNotification": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "method": { + "type": "string", + "const": "ui/notifications/request-teardown" + }, + "params": { + "type": "object", + "properties": {}, + "additionalProperties": false + } + }, + "required": ["method"], + "additionalProperties": false + }, "McpUiResourceCsp": { "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", diff --git a/src/generated/schema.test.ts b/src/generated/schema.test.ts index cd656338..57d989fd 100644 --- a/src/generated/schema.test.ts +++ b/src/generated/schema.test.ts @@ -91,8 +91,8 @@ export type McpUiSupportedContentBlockModalitiesSchemaInferredType = z.infer< typeof generated.McpUiSupportedContentBlockModalitiesSchema >; -export type McpUiRequestCloseNotificationSchemaInferredType = z.infer< - typeof generated.McpUiRequestCloseNotificationSchema +export type McpUiRequestTeardownNotificationSchemaInferredType = z.infer< + typeof generated.McpUiRequestTeardownNotificationSchema >; export type McpUiHostCapabilitiesSchemaInferredType = z.infer< @@ -259,11 +259,11 @@ expectType( expectType( {} as spec.McpUiSupportedContentBlockModalities, ); -expectType( - {} as McpUiRequestCloseNotificationSchemaInferredType, +expectType( + {} as McpUiRequestTeardownNotificationSchemaInferredType, ); -expectType( - {} as spec.McpUiRequestCloseNotification, +expectType( + {} as spec.McpUiRequestTeardownNotification, ); expectType( {} as McpUiHostCapabilitiesSchemaInferredType, diff --git a/src/generated/schema.ts b/src/generated/schema.ts index a5b0f244..aed1f965 100644 --- a/src/generated/schema.ts +++ b/src/generated/schema.ts @@ -479,15 +479,15 @@ export const McpUiSupportedContentBlockModalitiesSchema = z.object({ }); /** - * @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 + * @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.requestClose} for the app method that sends this + * @see {@link app.App.requestTeardown} for the app method that sends this */ -export const McpUiRequestCloseNotificationSchema = z.object({ - method: z.literal("ui/notifications/request-close"), +export const McpUiRequestTeardownNotificationSchema = z.object({ + method: z.literal("ui/notifications/request-teardown"), params: z.object({}).optional(), }); diff --git a/src/spec.types.ts b/src/spec.types.ts index 3ff5a42e..9f83fa97 100644 --- a/src/spec.types.ts +++ b/src/spec.types.ts @@ -475,15 +475,15 @@ export interface McpUiSupportedContentBlockModalities { } /** - * @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 + * @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.requestClose} for the app method that sends this + * @see {@link app.App.requestTeardown} for the app method that sends this */ -export interface McpUiRequestCloseNotification { - method: "ui/notifications/request-close"; +export interface McpUiRequestTeardownNotification { + method: "ui/notifications/request-teardown"; params?: {}; } @@ -813,8 +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_CLOSE_METHOD: McpUiRequestCloseNotification["method"] = - "ui/notifications/request-close"; +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"] = diff --git a/src/types.ts b/src/types.ts index 74df0078..24bffece 100644 --- a/src/types.ts +++ b/src/types.ts @@ -23,7 +23,7 @@ export { TOOL_RESULT_METHOD, TOOL_CANCELLED_METHOD, HOST_CONTEXT_CHANGED_METHOD, - REQUEST_CLOSE_METHOD, + REQUEST_TEARDOWN_METHOD, RESOURCE_TEARDOWN_METHOD, INITIALIZE_METHOD, INITIALIZED_METHOD, @@ -53,7 +53,7 @@ export { type McpUiHostContextChangedNotification, type McpUiResourceTeardownRequest, type McpUiResourceTeardownResult, - type McpUiRequestCloseNotification, + type McpUiRequestTeardownNotification, type McpUiHostCapabilities, type McpUiAppCapabilities, type McpUiInitializeRequest, @@ -87,7 +87,7 @@ import type { McpUiInitializedNotification, McpUiSizeChangedNotification, McpUiSandboxProxyReadyNotification, - McpUiRequestCloseNotification, + McpUiRequestTeardownNotification, McpUiInitializeResult, McpUiOpenLinkResult, McpUiDownloadFileResult, @@ -121,7 +121,7 @@ export { McpUiHostContextChangedNotificationSchema, McpUiResourceTeardownRequestSchema, McpUiResourceTeardownResultSchema, - McpUiRequestCloseNotificationSchema, + McpUiRequestTeardownNotificationSchema, McpUiHostCapabilitiesSchema, McpUiAppCapabilitiesSchema, McpUiInitializeRequestSchema, @@ -193,7 +193,7 @@ export type AppRequest = * - Sandbox resource ready * * App to host: - * - Initialized, size-changed, sandbox-proxy-ready, request-close + * - Initialized, size-changed, sandbox-proxy-ready, request-teardown * - Logging messages */ export type AppNotification = @@ -211,7 +211,7 @@ export type AppNotification = | McpUiInitializedNotification | McpUiSizeChangedNotification | McpUiSandboxProxyReadyNotification - | McpUiRequestCloseNotification + | McpUiRequestTeardownNotification | LoggingMessageNotification; /** From 7c179391d49b32e052c7d31f6c1961fcb33e3d2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Barthelet?= Date: Thu, 19 Mar 2026 12:21:20 +0100 Subject: [PATCH 5/6] Implement final comments --- specification/draft/apps.mdx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/specification/draft/apps.mdx b/specification/draft/apps.mdx index fc17f271..4cf4ad33 100644 --- a/specification/draft/apps.mdx +++ b/specification/draft/apps.mdx @@ -1318,12 +1318,11 @@ The View SHOULD send this notification when rendered content body size changes ( 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 decides whether to proceed with the teardown +- 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). **View behavior:** -- View MAY persist unsaved state before sending this notification. processes MUST be completed before responding to `ui/resource-teardown` Host request. -- View SHOULD NOT teardown its UI and assume it's going to be removed when sending this notification. Final teardown decision and timing remains Host responsibility. +- View MAY persist unsaved state before sending this notification. These processes MUST be completed before responding to `ui/resource-teardown` Host request. `ui/notifications/host-context-changed` - Host context has changed From ea00acfee667e9bc5c59e7dd1dbf0e5449cec051 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Barthelet?= Date: Thu, 19 Mar 2026 18:06:28 +0100 Subject: [PATCH 6/6] Removing view behavior on initiation of request termination --- specification/draft/apps.mdx | 3 --- 1 file changed, 3 deletions(-) diff --git a/specification/draft/apps.mdx b/specification/draft/apps.mdx index 4cf4ad33..6c810d63 100644 --- a/specification/draft/apps.mdx +++ b/specification/draft/apps.mdx @@ -1321,9 +1321,6 @@ The View MAY send this notification to request that the host tear it down. This - 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). -**View behavior:** -- View MAY persist unsaved state before sending this notification. These processes MUST be completed before responding to `ui/resource-teardown` Host request. - `ui/notifications/host-context-changed` - Host context has changed ```typescript