Chapter 18: Extensions - Notifications (Push Messages)
While standard MCP is based on a request-response model, Notifications allow one side (server or client) to send information to the other without waiting for a response. This is the "push" principle of MCP.
1. What Are Notifications?
In MCP, notifications are asynchronous messages. They have no request_id because no response is expected or possible. They serve to synchronize state or report events.
Standard Notification Types (Server -> Client)
notifications/message: Sends log data to the client.notifications/progress: Reports progress on a running operation.notifications/resources/list_changed: Signals that the list of available resources has changed.notifications/prompts/list_changed: Signals changes to the prompts.
2. Implementation in the Server (Go)
A server sends notifications via the Session object, which is included in the tool request.
Example: Reporting Progress
func handleLongRun(ctx context.Context, req *mcp.CallToolRequest, args any) (*mcp.CallToolResult, any, error) {
token := req.Params.GetProgressToken()
if token != nil {
req.Session.NotifyProgress(ctx, &mcp.ProgressNotificationParams{
Progress: 25.0,
Total: 100.0,
Message: "A quarter done...",
ProgressToken: token,
})
}
return mcp.NewToolResultText("Done!"), nil, nil
}3. Implementation in the Client (our mcp-tester)
For a client to be able to process notifications, it must register corresponding handlers when starting the session. Our mcp-tester uses ClientOptions for that.
Here is an excerpt from transport.go:
func getClient(verbose bool) *mcp.Client {
opts := &mcp.ClientOptions{
// Handler for log messages
LoggingMessageHandler: func(ctx context.Context, req *mcp.LoggingMessageRequest) {
fmt.Printf("[SERVER LOG] [%s] %s: %v\n",
req.Params.Level, req.Params.Logger, req.Params.Data)
},
// Handler for progress updates
ProgressNotificationHandler: func(ctx context.Context, req *mcp.ProgressNotificationClientRequest) {
fmt.Printf("[PROGRESS] Token: %v, %.2f%%: %s\n",
req.Params.ProgressToken, (req.Params.Progress/req.Params.Total)*100, req.Params.Message)
},
}
// ... create the client ...
}Why Is This Important?
Without these handlers, the client would simply ignore incoming JSON-RPC notifications. With the handlers, we can give the user real-time feedback while they wait for the result of a long-running tool call.
4. Detecting Support
Whether a server or client supports notifications is negotiated during the initialize step via capabilities:
- Server capability:
logging: {}means the server will send logs. - Client capability:
roots: { listChanged: true }means the client wants to be informed about changes to the workspace roots.
In mcp-tester you can observe this negotiation with the inspect command and the verbose flag (-v).
5. Request Cancellation: Aborting Tasks
A particularly powerful aspect of notifications is request cancellation. It allows the client to abort a request that has already been sent but not yet completed (e.g. a long tool call).
The Mechanism: `$/cancelRequest`
When a client wants to cancel a request, it sends a notification of type $/cancelRequest with the original requestId.
Implementation in Go (Server Side)
Our Go library makes handling cancellations extremely simple, since it is based directly on the standard Go pattern context.Context. When a client sends a cancellation, the ctx passed to the tool handler is automatically "cancelled".
Example of clean cancellation:
func handleCalculations(ctx context.Context, req *mcp.CallToolRequest, args any) (*mcp.CallToolResult, any, error) {
for i := 0; i < 100; i++ {
// IMPORTANT: check whether the chat/agent has been cancelled
select {
case <-ctx.Done():
// Cleanup and exit quickly
return nil, nil, ctx.Err()
default:
// Continue working...
doHeavyWork()
}
}
return mcp.NewToolResultText("Done"), nil, nil
}Why Is This Important?
Without cancellation, long-running tools would consume valuable server resources (CPU, memory), even when the agent no longer needs the response (e.g. because the user closed the chat or asked a different question). In a scaled environment this is essential for performance and cost efficiency.
Conclusion
Notifications make MCP come alive. From simple logging to progress bars to hard task cancellation (cancelRequest) - they turn a static API into an interactive interface that shows the user (and the model) what is happening "behind the scenes".
← Chapter 17: Security & Authentication | Table of Contents | Next Chapter: Extensions - Tasks →
Copyright Michael Lechner - 2026-03-01