Skip to content

Commit 8d79f87

Browse files
ochafikclaude
andcommitted
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 <[email protected]>
1 parent 123d674 commit 8d79f87

File tree

2 files changed

+74
-0
lines changed

2 files changed

+74
-0
lines changed

src/app-bridge.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,34 @@ describe("App <-> AppBridge integration", () => {
203203
{ theme: "light" },
204204
]);
205205
});
206+
207+
it("sendResourceTeardown triggers app.onteardown", async () => {
208+
let teardownCalled = false;
209+
app.onteardown = async () => {
210+
teardownCalled = true;
211+
return {};
212+
};
213+
214+
await app.connect(appTransport);
215+
await bridge.sendResourceTeardown({});
216+
217+
expect(teardownCalled).toBe(true);
218+
});
219+
220+
it("sendResourceTeardown waits for async cleanup", async () => {
221+
const cleanupSteps: string[] = [];
222+
app.onteardown = async () => {
223+
cleanupSteps.push("start");
224+
await new Promise((resolve) => setTimeout(resolve, 10));
225+
cleanupSteps.push("done");
226+
return {};
227+
};
228+
229+
await app.connect(appTransport);
230+
await bridge.sendResourceTeardown({});
231+
232+
expect(cleanupSteps).toEqual(["start", "done"]);
233+
});
206234
});
207235

208236
describe("App -> Host notifications", () => {

src/app.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ import {
3131
McpUiMessageResultSchema,
3232
McpUiOpenLinkRequest,
3333
McpUiOpenLinkResultSchema,
34+
McpUiResourceTeardownRequest,
35+
McpUiResourceTeardownRequestSchema,
36+
McpUiResourceTeardownResult,
3437
McpUiSizeChangedNotification,
3538
McpUiToolCancelledNotification,
3639
McpUiToolCancelledNotificationSchema,
@@ -464,6 +467,48 @@ export class App extends Protocol<Request, Notification, Result> {
464467
);
465468
}
466469

470+
/**
471+
* Convenience handler for graceful shutdown requests from the host.
472+
*
473+
* Set this property to register a handler that will be called when the host
474+
* requests the app to prepare for teardown. This allows the app to perform
475+
* cleanup operations (save state, close connections, etc.) before being unmounted.
476+
*
477+
* The handler can be sync or async. The host will wait for the returned promise
478+
* to resolve before proceeding with teardown.
479+
*
480+
* This setter is a convenience wrapper around `setRequestHandler()` that
481+
* automatically handles the request schema.
482+
*
483+
* Register handlers before calling {@link connect} to avoid missing requests.
484+
*
485+
* @param callback - Function called when teardown is requested.
486+
* Can return void or a Promise that resolves when cleanup is complete.
487+
*
488+
* @example Perform cleanup before teardown
489+
* ```typescript
490+
* app.onteardown = async () => {
491+
* await saveState();
492+
* closeConnections();
493+
* console.log("App ready for teardown");
494+
* };
495+
* ```
496+
*
497+
* @see {@link setRequestHandler} for the underlying method
498+
* @see {@link McpUiResourceTeardownRequest} for the request structure
499+
*/
500+
set onteardown(
501+
callback: (
502+
params: McpUiResourceTeardownRequest["params"],
503+
extra: RequestHandlerExtra,
504+
) => McpUiResourceTeardownResult | Promise<McpUiResourceTeardownResult>,
505+
) {
506+
this.setRequestHandler(
507+
McpUiResourceTeardownRequestSchema,
508+
(request, extra) => callback(request.params, extra),
509+
);
510+
}
511+
467512
/**
468513
* Convenience handler for tool call requests from the host.
469514
*
@@ -570,6 +615,7 @@ export class App extends Protocol<Request, Notification, Result> {
570615
}
571616
return;
572617
case "ping":
618+
case "ui/resource-teardown":
573619
return;
574620
default:
575621
throw new Error(`No handler for method ${method} registered`);

0 commit comments

Comments
 (0)