createMcpExpressRouter
Builds the mountable Express router that serves the MCP JSON-RPC endpoint.
createMcpExpressRouter(server: McpServer, options?: CreateMcpExpressRouterOptions): McpExpressRouter
Mount the router on the path that the host connects to.
import express from "express";
import { createMcpExpressRouter } from "ng-mcp-ui/server";
const app = express();
app.use(express.json());
app.use("/mcp", createMcpExpressRouter(createMcpServer()));
app.use(express.json()) must run before the router. The handler reads req.body.
Mount the router before the SSR catch-all route of Angular. A catch-all that runs first answers
/mcp with your application shell.
The router builds one SDK handler from the blueprint. That handler then builds a fresh SDK server
for each request. The router forwards every verb to it, restores the full request path that Express
strips at the mount point, and defaults a missing params.arguments on tools/call to {}.
The wire
The endpoint speaks MCP 2026-07-28 only. The router pins the SDK to legacy: "reject", and you
cannot override that from handlerOptions. A 2025-era client gets the
unsupported-protocol-version error, which names the revision that this endpoint supports.
Options
| Option | Type | Default | Purpose |
|---|---|---|---|
cors |
boolean | true |
Applies a permissive CORS layer. A host fetches this endpoint from another origin, therefore the default is true. Set false if your app already controls CORS for this path. |
errorMiddleware |
ErrorRequestHandler[] |
[] |
Express error handlers for this router. They run before the built-in JSON-RPC error handler. |
onerror |
(error: Error) => void |
None | Reports an error inside the SDK handler. It only observes, and it never changes the response. |
handlerOptions |
CreateMcpHandlerOptions without legacy and onerror |
{} |
Passed to the SDK handler: responseMode, bus, maxSubscriptions, keepAliveMs. |
The router object
type McpExpressRouter = express.Router & { mcp: McpHttpHandler };
router.mcp is the SDK handler behind the router. Your application code reaches it without holding
a second handler instance.
| Member | Purpose |
|---|---|
fetch |
Serves one web Request and resolves with the Response. |
close |
Aborts the exchanges in flight and closes their per-request servers. |
notify |
Publishes a change event to each open subscriptions/listen stream. |
bus |
The change-event bus of those streams. Share it across processes with your own implementation. |
notify has one method for each change event. A call with no open subscription does nothing.
| Method | Publishes |
|---|---|
notify.toolsChanged() |
notifications/tools/list_changed |
notify.promptsChanged() |
notifications/prompts/list_changed |
notify.resourcesChanged() |
notifications/resources/list_changed |
notify.resourceUpdated(uri) |
notifications/resources/updated for one URI |
const mcpRouter = createMcpExpressRouter(server);
app.use("/mcp", mcpRouter);
// Later, when your data changes:
mcpRouter.mcp.notify.resourcesChanged();
A host only receives these events on a subscriptions/listen stream, and only for the events it
subscribed to. Declare the matching capability on the server, for example
capabilities: { resources: { subscribe: true, listChanged: true } }.
Errors
The two error seams cover different failures. Pick the one that matches.
| Seam | Sees |
|---|---|
errorMiddleware |
Failures at the router level: CORS, body handling, and any middleware that you mount inside the router. |
onerror |
Failures inside the SDK handler, and rejected requests. |
An error that the SDK handler raises does not reach errorMiddleware. The Node adapter catches
it, reports it to onerror, and writes its own JSON-RPC 500.
app.use(
"/mcp",
createMcpExpressRouter(server, {
onerror: (err) => logger.error(err),
errorMiddleware: [
(err, req, res, next) => {
logger.error(err);
next(err);
},
],
}),
);
- A handler that sends a response stops the built-in handler.
- A handler that calls
next(err)passes the error to the built-in handler, which answers with a JSON-RPC 500.
createMcpFetchHandler
createMcpFetchHandler(server: McpServer, options?: CreateMcpFetchHandlerOptions): McpHttpHandler
The same endpoint as a web-standard handler, for a runtime that has no Express: Vercel, workerd, or your own hosting.
import { createMcpFetchHandler } from "ng-mcp-ui/server";
export default createMcpFetchHandler(createMcpServer());
CreateMcpFetchHandlerOptions is the SDK CreateMcpHandlerOptions without legacy, which the 1.x
wire policy pins. Therefore onerror, responseMode, bus, maxSubscriptions and keepAliveMs
are available here directly.
The Express router is a thin wrapper around this handler, and it exposes the same object as
router.mcp.
No app-level middleware
The server has no use() method and no useOnError() method. It gives you a router, and it does
not own an Express app. 1.x also has no protocol middleware layer: the SDK builds a fresh server
for each request and offers no hook to wrap.
Put HTTP middleware on your own app, around the router:
app.use("/mcp", rateLimit, createMcpExpressRouter(server));
For per-request work that needs the MCP context, read ctx in the handler itself. See
Migrate from 0.2.x for the full replacement table.