Elysia MCP Adapterelysia-mcp-adapter
Getting Started

Quick Start

Install the adapter and expose a typed Elysia route as an MCP tool.

Install

npm install @mwillbanks/elysia-mcp-adapter elysia

The npm code-block language automatically presents equivalent npm, pnpm, Yarn, and Bun commands.

Create a route-backed tool

import { mcp } from "@mwillbanks/elysia-mcp-adapter";
import { Elysia, t } from "elysia";

const app = new Elysia()
  .use(
    mcp({
      server: {
        name: "users-api",
        version: "1.0.0",
        title: "Users API",
      },
    }),
  )
  .get("/users/:id", ({ params }) => ({ id: params.id, displayName: "Ada" }), {
    params: t.Object({ id: t.String() }),
    response: t.Object({
      id: t.String(),
      displayName: t.String(),
    }),
    detail: {
      operationId: "users.get",
      summary: "Get a user",
    },
  })
  .listen(3000);

The route is discoverable as the users.get tool because detail.operationId supplies a stable name. Its input uses the default envelope shape:

{
  "params": { "id": "user_123" }
}

Call it over MCP

Send JSON-RPC to the default endpoint:

curl http://localhost:3000/mcp \
  --header 'content-type: application/json' \
  --data '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
      "name": "users.get",
      "arguments": { "params": { "id": "user_123" } }
    }
  }'

The adapter constructs GET /users/user_123, calls app.handle() with the internal request, and marshals the Elysia response into MCP content.

Make exposure explicit

Use route-level mcp metadata when you want to override discovery:

.get('/orders/:id', getOrder, {
  params: t.Object({ id: t.String() }),
  mcp: {
    expose: true,
    name: 'orders.get',
    title: 'Get an order',
    description: 'Fetch an order by identifier.',
    annotations: {
      readOnlyHint: true,
      idempotentHint: true
    }
  }
})

Continue with Installation for runtime requirements or Route-backed Tools for discovery, naming, schemas, and inputs.

On this page