Skip to content

Commit 123d674

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

File tree

8 files changed

+170
-0
lines changed

8 files changed

+170
-0
lines changed

src/app-bridge.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,36 @@ describe("App <-> AppBridge integration", () => {
140140
});
141141
});
142142

143+
it("sendToolCancelled triggers app.ontoolcancelled", async () => {
144+
const receivedCancellations: unknown[] = [];
145+
app.ontoolcancelled = (params) => {
146+
receivedCancellations.push(params);
147+
};
148+
149+
await app.connect(appTransport);
150+
await bridge.sendToolCancelled({
151+
reason: "User cancelled the operation",
152+
});
153+
154+
expect(receivedCancellations).toHaveLength(1);
155+
expect(receivedCancellations[0]).toEqual({
156+
reason: "User cancelled the operation",
157+
});
158+
});
159+
160+
it("sendToolCancelled works without reason", async () => {
161+
const receivedCancellations: unknown[] = [];
162+
app.ontoolcancelled = (params) => {
163+
receivedCancellations.push(params);
164+
};
165+
166+
await app.connect(appTransport);
167+
await bridge.sendToolCancelled({});
168+
169+
expect(receivedCancellations).toHaveLength(1);
170+
expect(receivedCancellations[0]).toEqual({});
171+
});
172+
143173
it("setHostContext triggers app.onhostcontextchanged", async () => {
144174
const receivedContexts: unknown[] = [];
145175
app.onhostcontextchanged = (params) => {

src/app-bridge.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import {
3434
import {
3535
type McpUiSandboxResourceReadyNotification,
3636
type McpUiSizeChangedNotification,
37+
type McpUiToolCancelledNotification,
3738
type McpUiToolInputNotification,
3839
type McpUiToolInputPartialNotification,
3940
type McpUiToolResultNotification,
@@ -737,6 +738,43 @@ export class AppBridge extends Protocol<Request, Notification, Result> {
737738
});
738739
}
739740

741+
/**
742+
* Notify the Guest UI that tool execution was cancelled.
743+
*
744+
* The host MUST send this notification if tool execution was cancelled for any
745+
* reason, including user action, sampling error, classifier intervention, or
746+
* any other interruption. This allows the Guest UI to update its state and
747+
* display appropriate feedback to the user.
748+
*
749+
* @param params - Optional cancellation details:
750+
* - `reason`: Human-readable explanation for why the tool was cancelled
751+
*
752+
* @example User-initiated cancellation
753+
* ```typescript
754+
* // User clicked "Cancel" button
755+
* bridge.sendToolCancelled({ reason: "User cancelled the operation" });
756+
* ```
757+
*
758+
* @example System-level cancellation
759+
* ```typescript
760+
* // Sampling error or timeout
761+
* bridge.sendToolCancelled({ reason: "Request timeout after 30 seconds" });
762+
*
763+
* // Classifier intervention
764+
* bridge.sendToolCancelled({ reason: "Content policy violation detected" });
765+
* ```
766+
*
767+
* @see {@link McpUiToolCancelledNotification} for the notification type
768+
* @see {@link sendToolResult} for sending successful results
769+
* @see {@link sendToolInput} for sending tool arguments
770+
*/
771+
sendToolCancelled(params: McpUiToolCancelledNotification["params"]) {
772+
return this.notification(<McpUiToolCancelledNotification>{
773+
method: "ui/notifications/tool-cancelled",
774+
params,
775+
});
776+
}
777+
740778
/**
741779
* Send HTML resource to the sandbox proxy for secure loading.
742780
*

src/app.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ import {
3232
McpUiOpenLinkRequest,
3333
McpUiOpenLinkResultSchema,
3434
McpUiSizeChangedNotification,
35+
McpUiToolCancelledNotification,
36+
McpUiToolCancelledNotificationSchema,
3537
McpUiToolInputNotification,
3638
McpUiToolInputNotificationSchema,
3739
McpUiToolInputPartialNotification,
@@ -388,6 +390,41 @@ export class App extends Protocol<Request, Notification, Result> {
388390
);
389391
}
390392

393+
/**
394+
* Convenience handler for receiving tool cancellation notifications from the host.
395+
*
396+
* Set this property to register a handler that will be called when the host
397+
* notifies that tool execution was cancelled. This can occur for various reasons
398+
* including user action, sampling error, classifier intervention, or other
399+
* interruptions. Apps should update their state and display appropriate feedback.
400+
*
401+
* This setter is a convenience wrapper around `setNotificationHandler()` that
402+
* automatically handles the notification schema and extracts the params for you.
403+
*
404+
* Register handlers before calling {@link connect} to avoid missing notifications.
405+
*
406+
* @param callback - Function called when tool execution is cancelled
407+
*
408+
* @example Handle tool cancellation
409+
* ```typescript
410+
* app.ontoolcancelled = (params) => {
411+
* console.log("Tool cancelled:", params.reason);
412+
* showCancelledMessage(params.reason ?? "Operation was cancelled");
413+
* };
414+
* ```
415+
*
416+
* @see {@link setNotificationHandler} for the underlying method
417+
* @see {@link McpUiToolCancelledNotification} for the notification structure
418+
* @see {@link ontoolresult} for successful tool completion
419+
*/
420+
set ontoolcancelled(
421+
callback: (params: McpUiToolCancelledNotification["params"]) => void,
422+
) {
423+
this.setNotificationHandler(McpUiToolCancelledNotificationSchema, (n) =>
424+
callback(n.params),
425+
);
426+
}
427+
391428
/**
392429
* Convenience handler for host context changes (theme, viewport, locale, etc.).
393430
*

src/generated/schema.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1732,6 +1732,28 @@
17321732
}
17331733
]
17341734
},
1735+
"McpUiToolCancelledNotification": {
1736+
"$schema": "https://json-schema.org/draft/2020-12/schema",
1737+
"type": "object",
1738+
"properties": {
1739+
"method": {
1740+
"type": "string",
1741+
"const": "ui/notifications/tool-cancelled"
1742+
},
1743+
"params": {
1744+
"type": "object",
1745+
"properties": {
1746+
"reason": {
1747+
"description": "Optional reason for the cancellation (e.g., \"user action\", \"timeout\").",
1748+
"type": "string"
1749+
}
1750+
},
1751+
"additionalProperties": false
1752+
}
1753+
},
1754+
"required": ["method", "params"],
1755+
"additionalProperties": false
1756+
},
17351757
"McpUiToolInputNotification": {
17361758
"$schema": "https://json-schema.org/draft/2020-12/schema",
17371759
"type": "object",

src/generated/schema.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ export type McpUiToolInputPartialNotificationSchemaInferredType = z.infer<
5151
typeof generated.McpUiToolInputPartialNotificationSchema
5252
>;
5353

54+
export type McpUiToolCancelledNotificationSchemaInferredType = z.infer<
55+
typeof generated.McpUiToolCancelledNotificationSchema
56+
>;
57+
5458
export type McpUiResourceTeardownRequestSchemaInferredType = z.infer<
5559
typeof generated.McpUiResourceTeardownRequestSchema
5660
>;
@@ -151,6 +155,12 @@ expectType<spec.McpUiToolInputPartialNotification>(
151155
expectType<McpUiToolInputPartialNotificationSchemaInferredType>(
152156
{} as spec.McpUiToolInputPartialNotification,
153157
);
158+
expectType<spec.McpUiToolCancelledNotification>(
159+
{} as McpUiToolCancelledNotificationSchemaInferredType,
160+
);
161+
expectType<McpUiToolCancelledNotificationSchemaInferredType>(
162+
{} as spec.McpUiToolCancelledNotification,
163+
);
154164
expectType<spec.McpUiResourceTeardownRequest>(
155165
{} as McpUiResourceTeardownRequestSchemaInferredType,
156166
);

src/generated/schema.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,24 @@ export const McpUiToolInputPartialNotificationSchema = z.object({
160160
}),
161161
});
162162

163+
/**
164+
* @description Notification that tool execution was cancelled (Host -> Guest UI).
165+
* Host MUST send this if tool execution was cancelled for any reason (user action,
166+
* sampling error, classifier intervention, etc.).
167+
*/
168+
export const McpUiToolCancelledNotificationSchema = z.object({
169+
method: z.literal("ui/notifications/tool-cancelled"),
170+
params: z.object({
171+
/** @description Optional reason for the cancellation (e.g., "user action", "timeout"). */
172+
reason: z
173+
.string()
174+
.optional()
175+
.describe(
176+
'Optional reason for the cancellation (e.g., "user action", "timeout").',
177+
),
178+
}),
179+
});
180+
163181
/**
164182
* @description Request for graceful shutdown of the Guest UI (Host -> Guest UI).
165183
* @see {@link app-bridge.AppBridge.sendResourceTeardown} for the host method that sends this

src/spec.types.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,19 @@ export interface McpUiToolResultNotification {
167167
params: CallToolResult;
168168
}
169169

170+
/**
171+
* @description Notification that tool execution was cancelled (Host -> Guest UI).
172+
* Host MUST send this if tool execution was cancelled for any reason (user action,
173+
* sampling error, classifier intervention, etc.).
174+
*/
175+
export interface McpUiToolCancelledNotification {
176+
method: "ui/notifications/tool-cancelled";
177+
params: {
178+
/** @description Optional reason for the cancellation (e.g., "user action", "timeout"). */
179+
reason?: string;
180+
};
181+
}
182+
170183
/**
171184
* @description Rich context about the host environment provided to Guest UIs.
172185
*/

src/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export {
2424
type McpUiToolInputNotification,
2525
type McpUiToolInputPartialNotification,
2626
type McpUiToolResultNotification,
27+
type McpUiToolCancelledNotification,
2728
type McpUiHostContext,
2829
type McpUiHostContextChangedNotification,
2930
type McpUiResourceTeardownRequest,
@@ -51,6 +52,7 @@ export {
5152
McpUiToolInputNotificationSchema,
5253
McpUiToolInputPartialNotificationSchema,
5354
McpUiToolResultNotificationSchema,
55+
McpUiToolCancelledNotificationSchema,
5456
McpUiHostContextSchema,
5557
McpUiHostContextChangedNotificationSchema,
5658
McpUiResourceTeardownRequestSchema,

0 commit comments

Comments
 (0)