Chapter 11: Real-Time Feedback & Cancellation - Interactive Tools
So far we have treated MCP as a simple request-response system. But for production applications that run for longer, MCP offers powerful utilities: real-time feedback (logging, progress) and the ability to abort running tasks (cancellation).
1. Progress & Feedback (Progress & Logging)
When a tool performs a longer computation (e.g. a data export or a video conversion), the server should send status updates to the client.
Logging (Structured Logging)
The server can send log messages to the client at any time. MCP uses a standardised system based on RFC 5424 for this.
- Technically: A
notifications/messagewith a level, an optional logger name, and data. - Log levels: MCP defines eight levels:
debug,info,notice,warning,error,critical,alert,emergency. - Client-side control: A client can tell the server via
logging/setLevelfrom which level it wants to receive logs. This saves bandwidth on SSE connections.
Progress (Progress Indicator)
For long-running tools, the server can feed a progress bar.
- Prerequisite: The client sends a
progressTokenalong with the call. - Update: The server regularly sends
progress(current value) andtotal(target value).
2. Cancellation: Pulling the Plug (New 2025-11)
What happens when the user aborts a running tool request? In the MCP specification (as of 2025-11-25) there is a mechanism for this: cancellation.
The Principle
Both the client and the server can cancel a running request. This is done via the notification notifications/cancelled.
- ID-based: The notification contains the
requestIdof the original request. - Reason: Optionally, a reason (e.g. "User clicked cancel") can be supplied.
- Fire-and-forget: The cancellation expects no response. The receiver should stop processing as quickly as possible.
Why does this matter?
Without cancellation, long-running processes on the server would consume valuable resources (CPU, DB connections) even though the client no longer expects the result.
Implementation in Go
The Go SDK makes cancellation very easy for us: it uses standard Go's context.Context. When a client aborts a request, the ctx of the tool handler is closed automatically (ctx.Done()).
func handleLongTask(ctx context.Context, request *mcp.CallToolRequest, args any) (*mcp.CallToolResult, any, error) {
// 1. Report progress (if a token is present)
if token := request.Params.GetProgressToken(); token != nil {
request.Session.NotifyProgress(ctx, &mcp.ProgressNotificationParams{
Progress: 10, Total: 100, ProgressToken: token,
})
}
// 2. Long-running loop with abort check
for i := 0; i < 100; i++ {
select {
case <-ctx.Done():
// IMPORTANT: Clean up resources here!
log.Println("Request was cancelled by the client.")
return nil, nil, ctx.Err()
default:
// Simulate work
time.Sleep(100 * time.Millisecond)
}
}
return &mcp.CallToolResult{
Content: []mcp.Content{&mcp.TextContent{Text: "Done!"}},
}, nil, nil
}Validation with `mcp-tester`
The mcp-tester can display progress and logs in verbose mode (-v). In test scripts you can additionally force a client-side cancellation using the timeout command:
# Aborts the tool call after 500ms
timeout 500 call_tool progressTest 10This is ideal for validating that your server behaves robustly when the user interrupts it.
Conclusion
With progress, logging and cancellation, MCP becomes a robust protocol for professional applications. It makes sure that server resources are used efficiently and that the user always stays informed about the state of their requests.
← Chapter 10: Binary Data | Table of Contents | Next Chapter: Limits & Boundaries →
Copyright Michael Lechner - 2026-03-09