express-to-mcp
Exposes an existing Express app or router as MCP tools, dispatching tool calls through the Express middleware stack in memory without opening a network port.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@express-to-mcpexpose my Express user routes as MCP tools"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
express-to-mcp
Expose an existing Express router as Model Context Protocol tools — without writing a second server, and without a network hop.
Tool calls are dispatched through your Express middleware stack in memory. No port is bound, no HTTP request leaves the process, and your existing auth, validation, and error-handling middleware all run exactly as they do in production.
LLM ──JSON-RPC──▶ MCP Server ──▶ mock req/res ──▶ your Express stack ──▶ handler
(in-process, no socket)Install
npm install express-to-mcp @modelcontextprotocol/sdk zodRequires Express 5 and Node 20+. express, zod and @modelcontextprotocol/sdk are peer dependencies, so your app's own copies are used.
Related MCP server: FastAPI-MCP
Usage
import express from 'express';
import { z } from 'zod';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { ExpressMcpBridge } from 'express-to-mcp';
const app = express(); // ← your existing app
app.use(express.json());
app.get('/api/users/:id', (req, res) => res.json({ id: req.params.id }));
const bridge = new ExpressMcpBridge({
app,
serverInfo: { name: 'my-api', version: '1.0.0' },
headers: { authorization: `Bearer ${process.env.API_TOKEN}` },
routes: [
{
name: 'get_user',
description: 'Fetch a single user by id.',
method: 'GET',
path: '/api/users/:id',
schema: z.object({ id: z.string().min(1).describe('The user id') }),
annotations: { readOnlyHint: true },
},
],
});
await bridge.connect(new StdioServerTransport());That's a complete MCP server. See examples/stdio.ts for a fuller one.
No transport is bundled — bridge.server is the configured MCP Server, so attach whichever you need (StdioServerTransport, StreamableHTTPServerTransport, …).
How arguments map onto a request
One flat argument object from the LLM becomes a real HTTP request:
Argument | Goes to | Why |
matches a | the URL path | encoded per segment, so a value containing |
anything else, on | the query string | |
anything else, on | a JSON body |
Override per route with argsIn: 'body' | 'query', or per argument with queryParams / bodyParams:
{
name: 'search',
method: 'POST',
path: '/api/search',
queryParams: ['page'], // -> POST /api/search?page=2
schema: z.object({ term: z.string(), page: z.number().optional() }),
}req.params and req.query are never assigned directly — they are derived by Express from the URL we build, which is the only way that works reliably (Express recomputes req.params on every matched layer, and req.query is a get-only accessor).
Auth and per-call context
headers is applied to every request, so existing auth middleware runs unchanged. For per-call identity, use buildRequest — it receives the validated arguments and runs just before dispatch:
new ExpressMcpBridge({
app,
routes,
headers: { 'x-service': 'mcp-bridge' },
buildRequest: (toolName, args) => ({
headers: { authorization: `Bearer ${tokenFor(args.tenantId)}` },
extend: { user: { id: 'svc', scopes: ['read'] } }, // assigned onto `req`
}),
});extend sets properties directly on the mocked req, which is useful when your handlers expect auth middleware to have already populated something like req.user.
Options
Option | Default | |
| — | An Express application, or an |
| — | The tools to expose |
|
| Name and version reported over MCP |
|
| Headers added to every mocked request |
| — | Per-call headers and |
|
| Per-call budget; a hung handler becomes an error result |
| read from |
|
|
| Drop |
Route paths use Express 5 syntax
Paths are parsed with path-to-regexp v8, so Express 4 patterns are rejected at construction with a message telling you the replacement:
Express 4 | Express 5 |
|
|
|
|
| two routes, or validate in the Zod schema |
()[]?+! are reserved; escape them with \.
Error handling
callTool never throws — every failure comes back as an MCP isError result the LLM can act on:
Situation | Result |
arguments fail the Zod schema |
|
handler responds 4xx/5xx |
|
no route matched |
|
error escaped all middleware |
|
handler never responds |
|
Developer mistakes — a malformed path, a duplicate tool name, a non-object argument schema, an argument that can't survive the app's query parser — throw from the constructor instead, so they surface at startup rather than as a confusing tool failure.
Zod 3 and Zod 4
Both work. Zod 4 schemas are converted with its native z.toJSONSchema(); Zod 3 schemas go through zod-to-json-schema. Schemas are compiled once at construction, using the input JSON Schema so that .default() and .transform() fields are advertised as optional.
Limitations
JSON bodies only.
express.urlencoded()andmultipart(multer) are not supported; onlyapplication/jsonis generated.Express 5 only. Express 4's
app._routerand path syntax are not supported.compressionis bypassed, deliberately: noaccept-encodingrequest header is sent, so the middleware negotiatesidentityand we capture readable JSON rather than gzipped bytes.Timeouts cannot cancel a running handler — Node has no such primitive. The dispatch is abandoned and the streams destroyed so
on-finishedcleanup runs, but the handler itself keeps going.
Lower-level API
The mock pipeline is exported on its own, which is handy for testing an app without a server:
import { createExchange, dispatch } from 'express-to-mcp';
const exchange = createExchange({ method: 'POST', url: '/api/items?dry=1', body: { name: 'x' } });
const res = await dispatch(app, exchange);
res.status; // 201
res.headers; // lowercase keys, set-cookie preserved as an array
res.json(); // parsed bodyContributing
npm install
npm run verify # typecheck + tests + build + packaged smoke testThe suite includes regression tests that pin the Node and Express internals this library depends on — response prototype replacement, socket-dependent finish and body parsing, drain forwarding, byte-length content headers. They exist because every one of those fails silently if it regresses; see CONTRIBUTING.md before removing one.
Bug reports and PRs welcome. Development needs Node 22.12+ (a vitest 5 requirement); the published library supports Node 20+, which CI verifies by installing the packed tarball.
License
MIT © Ashish Lohia
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.
No tool schema history has been recorded yet.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
The OpenRouter for tools. One MCP connection gives any AI agent 254 hosted tools, pay per call.
Give any MCP-compatible AI assistant a builder for live, hosted web tools and workflows.
Toolbox dynamically routes to all MCPs in the Smithery registry based on your agent's need. When a…
Hosted MCP runtime where the agent is the operator: sign up by tool call, publish your own tools.
Related MCP Servers
- AlicenseNot gradedqualityCmaintenanceExposes Express.js API endpoints as MCP tools, preserving existing schemas and authentication behavior. Supports streaming responses and can be mounted directly to existing Express apps or run as a standalone gateway.101MIT
- AlicenseNot gradedqualityDmaintenanceExposes FastAPI endpoints as Model Context Protocol (MCP) tools while preserving existing authentication, schemas, and documentation. It enables seamless integration of FastAPI services into MCP ecosystems using a native ASGI transport layer.MIT
- AlicenseNot gradedqualityCmaintenanceA lightweight MCP router for FastAPI that enables adding MCP tools to FastAPI applications with ease.2MIT
- AlicenseNot gradedqualityDmaintenanceExposes OpenAPI endpoints as MCP tools, enabling LLMs to discover and interact with REST APIs through the MCP protocol.10MIT
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/ashishlohia70/express-to-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server