feat: implement ui/notifications/tool-cancelled + add missing App.onteardown (#120)

* 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>
This commit is contained in:
Olivier Chafik 2025-12-11 20:31:17 +01:00 • committed by GitHub
commit 70b393a7e6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 379 additions and 15 deletions

View file

@ -1732,6 +1732,28 @@
}
]
},
"McpUiToolCancelledNotification": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"method": {
"type": "string",
"const": "ui/notifications/tool-cancelled"
},
"params": {
"type": "object",
"properties": {
"reason": {
"description": "Optional reason for the cancellation (e.g., \"user action\", \"timeout\").",
"type": "string"
}
},
"additionalProperties": false
}
},
"required": ["method", "params"],
"additionalProperties": false
},
"McpUiToolInputNotification": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",

View file

@ -51,6 +51,10 @@ export type McpUiToolInputPartialNotificationSchemaInferredType = z.infer<
typeof generated.McpUiToolInputPartialNotificationSchema
>;
export type McpUiToolCancelledNotificationSchemaInferredType = z.infer<
typeof generated.McpUiToolCancelledNotificationSchema
>;
export type McpUiResourceTeardownRequestSchemaInferredType = z.infer<
typeof generated.McpUiResourceTeardownRequestSchema
>;
@ -151,6 +155,12 @@ expectType<spec.McpUiToolInputPartialNotification>(
expectType<McpUiToolInputPartialNotificationSchemaInferredType>(
{} as spec.McpUiToolInputPartialNotification,
);
expectType<spec.McpUiToolCancelledNotification>(
{} as McpUiToolCancelledNotificationSchemaInferredType,
);
expectType<McpUiToolCancelledNotificationSchemaInferredType>(
{} as spec.McpUiToolCancelledNotification,
);
expectType<spec.McpUiResourceTeardownRequest>(
{} as McpUiResourceTeardownRequestSchemaInferredType,
);

View file

@ -160,6 +160,24 @@ export const McpUiToolInputPartialNotificationSchema = z.object({
}),
});
/**
* @description Notification that tool execution was cancelled (Host -> Guest UI).
* Host MUST send this if tool execution was cancelled for any reason (user action,
* sampling error, classifier intervention, etc.).
*/
export const McpUiToolCancelledNotificationSchema = z.object({
method: z.literal("ui/notifications/tool-cancelled"),
params: z.object({
/** @description Optional reason for the cancellation (e.g., "user action", "timeout"). */
reason: z
.string()
.optional()
.describe(
'Optional reason for the cancellation (e.g., "user action", "timeout").',
),
}),
});
/**
* @description Request for graceful shutdown of the Guest UI (Host -> Guest UI).
* @see {@link app-bridge.AppBridge.sendResourceTeardown} for the host method that sends this