elysiajs-mcp
by dvaJi
README.md
# elysiajs-mcp
[](https://www.npmjs.com/package/elysiajs-mcp)
[](https://github.com/dvaJi/elysiajs-mcp/actions/workflows/ci.yml)
[](https://github.com/dvaJi/elysiajs-mcp#license)
[Model Context Protocol](https://modelcontextprotocol.io) (MCP) server transport and plugin for the [Elysia](https://elysiajs.com) web framework (Bun).
Connect AI clients to your Elysia app over the MCP **Streamable HTTP** transport. Built on the official [`@modelcontextprotocol/sdk`](https://github.com/modelcontextprotocol/typescript-sdk) web-standard transport, with an idiomatic Elysia plugin that handles session lifecycle for you.
## Features
- **`mcp()` plugin** — mount a full MCP server in one line, with automatic stateful (per-session) **and** stateless modes.
- **`StreamableHTTPTransport`** — a low-level transport you can wire up manually (mirrors the `@hono/mcp` API).
- **Permissive `Accept` header handling by default** — works out of the box with Gemini CLI, the Java MCP SDK, Open WebUI, and `curl`. Toggle strict mode if you prefer.
- **`MemoryEventStore`** — in-memory event store enabling SSE **resumability**.
- **Auth helpers** — `bearerAuth()` for token validation and `mcpAuthMetadata()` for the `/.well-known` OAuth discovery endpoints.
- Runs on any web-standard runtime (Bun, Node, Deno, Workers).
## Install
```bash
bun add elysiajs-mcp @modelcontextprotocol/sdk elysia
```
## Quick start
```ts
import { Elysia } from "elysia";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import { mcp } from "elysiajs-mcp";
new Elysia()
.use(
mcp({
server: () => {
const server = new McpServer({ name: "my-server", version: "1.0.0" });
server.registerTool(
"greet",
{
description: "Greet someone by name",
inputSchema: { name: z.string().default("world") },
},
async ({ name }) => ({
content: [{ type: "text", text: `Hello, ${name}!` }],
}),
);
return server;
},
}),
)
.listen(3000);
```
Connect any MCP client (e.g. the [MCP Inspector](https://github.com/modelcontextprotocol/inspector)) to `http://localhost:3000/mcp`.
## How it works
`mcp()` mounts a single `.all('/mcp')` route. A `server()` factory is invoked for every new session (stateful mode) or every request (stateless mode), so each session gets isolated tools, prompts, and resources.
| Option | Default | Description |
| ---------------------- | --------------------------- | ------------------------------------------------------------ |
| `server` | _(required)_ | Factory returning an MCP `Server` / `McpServer`. |
| `path` | `'/mcp'` | Endpoint path. |
| `sessionIdGenerator` | `() => crypto.randomUUID()` | Pass `undefined` for **stateless** mode. |
| `strictAcceptHeader` | `false` | Strictly enforce the MCP `Accept` header spec. |
| `enableJsonResponse` | `false` | Return JSON instead of SSE for POST requests. |
| `eventStore` | _none_ | Provide a `MemoryEventStore` (or your own) for resumability. |
| `auth` | _none_ | `(request) => AuthInfo \| Response \| undefined` hook. |
| `onsessioninitialized` | _none_ | Called when a session is created. |
| `onsessionclosed` | _none_ | Called when a session is closed via `DELETE`. |
### Stateful vs. stateless
```ts
// Stateful (default): one server + transport kept alive per session.
mcp({ server: () => new McpServer({ name: "s", version: "1" }) });
// Stateless: a fresh server + transport per request, torn down after.
mcp({ server: () => new McpServer({ name: "s", version: "1" }), sessionIdGenerator: undefined });
```
## Resumability (event store)
Pass an `eventStore` so clients that disconnect can resume missed messages via `Last-Event-ID`.
```ts
import { mcp, MemoryEventStore } from 'elysiajs-mcp'
mcp({ server: () => …, eventStore: new MemoryEventStore() })
```
`MemoryEventStore` keeps the last 100 streams × 100 events by default (both configurable).
## Auth
### Bearer tokens
```ts
import { mcp, bearerAuth } from 'elysiajs-mcp'
new Elysia().use(
mcp({
server: () => …,
auth: bearerAuth({
require: true,
verify: async (token) => {
const user = await verifyToken(token)
return user ? { token, clientId: user.id, scopes: user.scopes } : undefined
},
}),
})
)
```
### OAuth discovery endpoints
Advertise where clients should authenticate (RFC 8414 / RFC 9728):
```ts
import { mcpAuthMetadata } from "elysiajs-mcp";
new Elysia().use(
mcpAuthMetadata({
issuerUrl: "https://auth.example.com",
resourceServerUrl: new URL("http://localhost:3000/mcp"),
}),
);
```
This serves `/.well-known/oauth-authorization-server` and `/.well-known/oauth-protected-resource`.
## Low-level transport
If you need full control (mirrors `@hono/mcp`'s API):
```ts
import { Elysia } from "elysia";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPTransport } from "elysiajs-mcp";
const server = new McpServer({ name: "low-level", version: "1.0.0" });
const transport = new StreamableHTTPTransport({ sessionIdGenerator: () => crypto.randomUUID() });
new Elysia().all("/mcp", async ({ request }) => {
if (!server.isConnected()) await server.connect(transport);
return transport.handleRequest(request);
});
```
`StreamableHTTPTransport` accepts the same options as the SDK's transport, plus `strictAcceptHeader`.
## API
### `mcp(options): Elysia`
Mount an MCP Streamable HTTP server.
### `StreamableHTTPTransport`
Extends `WebStandardStreamableHTTPServerTransport`. Adds `strictAcceptHeader` (default `false`).
### `MemoryEventStore`
In-memory `EventStore` implementation with bounded ring buffers.
### `bearerAuth(options)` / `unauthorizedResponse(request, url?)`
Bearer-token auth extractor (`401` challenge helper).
### `mcpAuthMetadata(options)` / `createOAuthMetadata(options)`
Elysia plugin + helper serving OAuth discovery metadata.
## Scripts
```bash
bun run lint # oxlint
bun run lint:fix # oxlint --fix
bun run format # oxfmt (write)
bun run format:check # oxfmt --check
bun run typecheck # tsc --noEmit
bun run check # lint + format:check + typecheck
bun test # run the test suite
bun run dev # run the example server (example/server.ts)
bun run build # bundle to dist/
```
## Credits & license
Inspired by [`@hono/mcp`](https://github.com/honojs/middleware/tree/main/packages/mcp) by [Aditya Mathur](https://github.com/mathuraditya724).
## License
[MIT](./LICENSE) © [Francisco Pizarro](https://github.com/dvaJi)
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues