This is the documentation for the v2 beta — looking for the v1 documentation?
Skip to content

Notifications

A notification is a one-way message your server pushes to a connected client; change notifications tell clients that a list or a resource they cached is stale.

Send a list-changed notification

Start from a server with one tool.

ts
import { McpServer } from '@modelcontextprotocol/server';

const jobs = ['nightly-backup'];

const server = new McpServer({ name: 'jobs', version: '1.0.0' });

server.registerTool('list-jobs', { description: 'List the configured jobs' }, async () => ({
    content: [{ type: 'text', text: jobs.join('\n') }]
}));

Every notification on this page is observed by an in-memory Client connected to the server above — Test a server shows that wiring — which logs each notification method it receives. Push a tool-list change yourself when the tool set changes for a reason the registration API cannot see.

ts
server.sendToolListChanged();

The client receives one notifications/tools/list_changed and re-fetches tools/list:

notifications/tools/list_changed

sendPromptListChanged() and sendResourceListChanged() are the prompt and resource siblings.

Let registration changes notify for you

Hold on to the handle registerTool returns — every mutation through it sends the matching list-changed on its own.

ts
const report = server.registerTool('run-report', { description: 'Run the weekly report' }, async () => ({
    content: [{ type: 'text', text: 'report queued' }]
}));

report.update({ description: 'Run the weekly report and email it' });
report.disable();

Registering, updating, and disabling each sent a notification of its own — three more, none explicit:

notifications/tools/list_changed
notifications/tools/list_changed
notifications/tools/list_changed

enable() and remove() notify the same way, and the handles returned by registerResource and registerPrompt send notifications/resources/list_changed and notifications/prompts/list_changed. Most servers never call a send*ListChanged() method directly.

McpServer advertised tools: { listChanged: true } the moment you registered a tool, and does the same for prompts and resources. Only the low-level Server needs the capability declared up front.

ts
const lowLevel = new Server({ name: 'jobs', version: '1.0.0' }, { capabilities: { tools: { listChanged: true } } });

The low-level Server refuses to send a notification its capabilities do not cover — sendToolListChanged() throws without a tools capability — and clients use the listChanged flag to decide which notification types to ask for.

Publish a resource update through the handler

On a 2026-07-28 connection, change notifications reach a client only on a subscriptions/listen stream the client opens — see Protocol versions. Behind createMcpHandler the McpServer instance is per-request, so publish through the handler, not the instance: notify is a typed facade over the handler's open subscription streams.

ts
const handler = createMcpHandler(() => buildJobsServer());

// After config://app changes:
handler.notify.resourceUpdated('config://app');

Every client whose stream listed config://app receives notifications/resources/updated carrying that URI; notify.toolsChanged(), notify.promptsChanged(), and notify.resourcesChanged() publish the three list-changed types the same way. Per-resource updates have one extra gate: the server your factory builds must advertise resources: { subscribe: true }.

TIP

On stdio, serveStdio routes the instance's own send*ListChanged() and sendResourceUpdated() calls onto its open subscription stream — no notify facade needed.

Coming from 2025-era subscriptions

A 2025-era connection delivers per-resource updates without a listen stream — Subscriptions covers that path, and Protocol versions the era split.

Pick an event bus for multi-process deployments

The bus option accepts any ServerEventBus; InMemoryServerEventBus is what createMcpHandler builds when you omit it.

ts
const bus = new InMemoryServerEventBus();

const shared = createMcpHandler(() => buildJobsServer(), { bus });

The in-memory bus never leaves the process, so one process needs nothing more. Run more than one and you implement the two-method ServerEventBus interface — publish and subscribe — over your own pub/sub backend, then pass the same instance to every handler; see Sessions, state, and scaling.

Recap

  • sendToolListChanged(), sendPromptListChanged(), and sendResourceListChanged() push a list-changed notification to connected clients.
  • Registering, updating, enabling, disabling, or removing through a registration handle sends the matching list-changed for you.
  • McpServer advertises listChanged as you register; only the low-level Server declares it up front.
  • Behind createMcpHandler, publish through handler.notify; delivery reaches every open subscription stream that opted in.
  • One process needs no bus; more than one shares a ServerEventBus.