MCP supports bidirectional notifications between clients and servers. Notifications are one-way JSON-RPC messages that don’t require a response.
Server → Client: Servers send notifications to signal events like tools/list_changed, resources/list_changed, or custom events.
Client → Server: Clients send notifications like roots/list_changed when their roots change.
Receiving Notifications (Server → Client)
Register a notification handler on your session using the on method:
import { MCPClient } from "mcp-use";
const client = new MCPClient({
mcpServers: {
myServer: { url: "http://localhost:3000/mcp" }
}
});
const session = await client.createSession("myServer");
// Register notification handler
session.on("notification", async (notification) => {
console.log(`Received: ${notification.method}`, notification.params);
});
Notification Structure
Notifications follow the JSON-RPC 2.0 format without an id field:
interface Notification {
method: string; // The notification method name
params?: Record<string, any>; // Optional parameters
}
Standard MCP Notifications
MCP defines several standard notification types that servers may send:
Sent when the server’s available tools have changed:
session.on("notification", async (notification) => {
if (notification.method === "notifications/tools/list_changed") {
console.log("Tools list updated - refreshing...");
// Refresh tools cache
const tools = await session.connector.listTools();
console.log(`Now have ${tools.length} tools`);
}
});
Resources List Changed
Sent when resources have been added, removed, or modified:
session.on("notification", async (notification) => {
if (notification.method === "notifications/resources/list_changed") {
console.log("Resources updated");
const resources = await session.connector.listAllResources();
// Update your UI or cache
}
});
Prompts List Changed
Sent when prompts have changed:
session.on("notification", async (notification) => {
if (notification.method === "notifications/prompts/list_changed") {
console.log("Prompts updated");
const prompts = await session.connector.listPrompts();
// Update your UI or cache
}
});
Custom Notifications
Servers can send custom notifications with any method name:
session.on("notification", async (notification) => {
switch (notification.method) {
case "custom/heartbeat":
console.log(`Heartbeat #${notification.params?.count}`);
break;
case "custom/user-joined":
console.log(`User joined: ${notification.params?.username}`);
break;
case "custom/data-updated":
console.log("Data updated:", notification.params);
break;
default:
console.log(`Unknown notification: ${notification.method}`);
}
});
Sending Notifications (Client → Server)
Roots Changed
Roots represent directories or files that the client has access to. When you update roots, the client automatically sends a notifications/roots/list_changed notification to the server:
import { MCPClient, Root } from "mcp-use";
const client = new MCPClient({
mcpServers: {
myServer: { url: "http://localhost:3000/mcp" }
}
});
const session = await client.createSession("myServer");
// Set roots - this sends notifications/roots/list_changed to the server
await session.setRoots([
{ uri: "file:///home/user/project", name: "My Project" },
{ uri: "file:///home/user/data", name: "Data Directory" }
]);
// Get current roots
const roots = session.getRoots();
console.log(`Current roots: ${roots.length}`);
Initial Roots at Connection
You can provide initial roots when creating the connector:
import { HttpConnector } from "mcp-use";
const connector = new HttpConnector("http://localhost:3000/mcp", {
roots: [
{ uri: "file:///workspace", name: "Workspace" }
]
});
const session = new MCPSession(connector);
await session.connect();
await session.initialize();
// Later, update roots
await session.setRoots([
{ uri: "file:///workspace", name: "Workspace" },
{ uri: "file:///tmp/scratch", name: "Scratch" }
]);
Root Type
interface Root {
uri: string; // Must start with "file://"
name?: string; // Optional human-readable name
}
Practical Examples
Building a Real-Time Dashboard
import { MCPClient, Notification } from "mcp-use";
const client = new MCPClient({
mcpServers: {
dashboard: { url: "http://localhost:3000/mcp" }
}
});
const session = await client.createSession("dashboard");
// State
let connectedUsers = 0;
let lastHeartbeat: Date | null = null;
session.on("notification", async (notification: Notification) => {
switch (notification.method) {
case "custom/heartbeat":
lastHeartbeat = new Date();
connectedUsers = notification.params?.connectedClients ?? 0;
updateStatusUI();
break;
case "custom/alert":
showAlert(notification.params?.message);
break;
case "notifications/tools/list_changed":
await refreshToolsList();
break;
}
});
function updateStatusUI() {
console.log(`Status: ${connectedUsers} users, last heartbeat: ${lastHeartbeat}`);
}
Handling Connection Events
session.on("notification", async (notification) => {
if (notification.method === "custom/welcome") {
console.log("Connected to server!");
console.log("Server time:", notification.params?.serverTime);
console.log("Available tools:", notification.params?.availableTools);
}
});
Progress Updates
session.on("notification", async (notification) => {
if (notification.method === "custom/progress") {
const { taskId, progress, status } = notification.params ?? {};
console.log(`Task ${taskId}: ${progress}% - ${status}`);
// Update progress bar UI
updateProgressBar(taskId, progress);
}
});
Multiple Handlers
You can register multiple notification handlers:
// Handler for logging
session.on("notification", async (notification) => {
console.log("[LOG]", notification.method, notification.params);
});
// Handler for specific notifications
session.on("notification", async (notification) => {
if (notification.method === "custom/important") {
await handleImportantNotification(notification);
}
});
// Handler for UI updates
session.on("notification", async (notification) => {
if (notification.method.startsWith("ui/")) {
updateUI(notification);
}
});
Error Handling
Errors in notification handlers are caught and logged, but don’t stop other handlers:
session.on("notification", async (notification) => {
try {
// Process notification
await processNotification(notification);
} catch (error) {
console.error("Error processing notification:", error);
// Handle error - maybe retry or log for debugging
}
});
Browser Usage
Notifications work the same way in browser environments:
import { MCPClient } from "mcp-use/browser";
const client = new MCPClient({
mcpServers: {
api: { url: "https://api.example.com/mcp" }
}
});
const session = await client.createSession("api");
session.on("notification", (notification) => {
// Update React state, Vue refs, etc.
setNotifications(prev => [...prev, notification]);
});
TypeScript Types
Import the Notification type for type-safe handlers:
import { MCPClient, Notification, NotificationHandler } from "mcp-use";
// Type-safe handler
const handler: NotificationHandler = async (notification: Notification) => {
// notification.method is string
// notification.params is Record<string, any> | undefined
};
session.on("notification", handler);
Requirements
Notifications require:
- Stateful Connection: The server must maintain sessions (stateful mode)
- Active Session: The client must have an active, initialized session
- Streaming Transport: Either SSE or Streamable HTTP transport
Notifications don’t work in stateless edge environments where sessions aren’t maintained between requests.
Best Practices
- Handle Unknown Notifications: Always have a fallback for notifications you don’t recognize
- Keep Handlers Fast: Notification handlers should be quick - offload heavy work
- Use TypeScript: Leverage the
Notification type for better code quality
- Log Notifications: During development, log all notifications to understand server behavior
- Clean Up: If your session disconnects, handlers won’t receive new notifications
Next Steps