Motivation
xmcp has a great file-system-based convention for tools (tools/ directory, each file exports a tool(...) definition). There is currently no equivalent for MCP client notifications — there is no way to subscribe to events like roots/list_changed, $/cancelRequest, or any other notification the client may send.
The only workaround today is to reach for the underlying McpServer instance, which xmcp does not expose. This forces implementors to resort to per-call polling instead of event-driven logic.
Proposed API
Introduce a notifications/ directory (parallel to tools/) where each file exports a subscribe call:
// notifications/roots-changed.ts
import { subscribe } from 'xmcp';
export default subscribe('roots/list_changed', async () => {
// update workspace context here
});
subscribe(notificationName, handler) mirrors the ergonomics of tool(name, schema, handler) — the framework handles all the plumbing (registering setNotificationHandler on the underlying McpServer) and the engineer just writes the handler.
Why this fits xmcp's design philosophy
| Concern |
Tools |
Notifications (proposed) |
| Declaration |
tools/my-tool.ts exports tool(...) |
notifications/roots-changed.ts exports subscribe(...) |
| Discovery |
Framework scans tools/ at startup |
Framework scans notifications/ at startup |
| Plumbing |
Framework registers tool on McpServer |
Framework calls setNotificationHandler on McpServer |
| Engineer writes |
Input schema + handler function |
Notification name + handler function |
Alternatives considered
- Expose
McpServer directly (e.g. via extra.server or a lifecycle hook) — lower-level, still requires engineers to know about the SDK internals (setNotificationHandler, schema objects, etc.). The subscribe() abstraction is more in the spirit of xmcp.
Environment
Motivation
xmcp has a great file-system-based convention for tools (
tools/directory, each file exports atool(...)definition). There is currently no equivalent for MCP client notifications — there is no way to subscribe to events likeroots/list_changed,$/cancelRequest, or any other notification the client may send.The only workaround today is to reach for the underlying
McpServerinstance, which xmcp does not expose. This forces implementors to resort to per-call polling instead of event-driven logic.Proposed API
Introduce a
notifications/directory (parallel totools/) where each file exports asubscribecall:subscribe(notificationName, handler)mirrors the ergonomics oftool(name, schema, handler)— the framework handles all the plumbing (registeringsetNotificationHandleron the underlyingMcpServer) and the engineer just writes the handler.Why this fits xmcp's design philosophy
tools/my-tool.tsexportstool(...)notifications/roots-changed.tsexportssubscribe(...)tools/at startupnotifications/at startupMcpServersetNotificationHandleronMcpServerAlternatives considered
McpServerdirectly (e.g. viaextra.serveror a lifecycle hook) — lower-level, still requires engineers to know about the SDK internals (setNotificationHandler, schema objects, etc.). Thesubscribe()abstraction is more in the spirit of xmcp.Environment
xmcp:0.6.5