Skip to main content
Glama
Elysia MCP Adapter

Elysia MCP Adapter

by mwillbanks

@mwillbanks/elysia-mcp-adapter turns eligible Elysia HTTP routes into MCP tools while preserving the request lifecycle you already rely on: parsing, validation, hooks, guards, authentication, error handling, and response mapping. It also provides thin APIs for standalone tools, resources, resource templates, and prompts.

Why this adapter?

  • Keep Elysia in charge. Route-backed calls run through app.handle(new Request(...)); handlers are never invoked out of band.

  • Reuse schemas. Existing TypeBox and route schemas are normalized to JSON Schema for MCP clients.

  • Secure by default. Origin checks are enabled, model-controlled headers are denied, credential-bearing response headers are redacted, and binary responses are disabled.

  • Stay protocol-focused. The package is ESM-only and does not introduce a second application framework or translate TypeBox to Zod.

Related MCP server: @absolutejs/mcp

Install

bun add @mwillbanks/elysia-mcp-adapter elysia

Node.js >=20.11, Bun >=1.1, Elysia >=1.4, and TypeScript >=5.8 are supported.

Agent guidance

The npm package includes an Agent Skill for integrating the adapter. Install TanStack Intent as a local development dependency in the consuming project. Intent 0.5.6 requires Node.js 20.12 or later.

bun add --dev --exact @tanstack/intent@0.5.6

Set an explicit allowlist in the consuming project's package.json:

{
  "intent": {
    "skills": ["@mwillbanks/elysia-mcp-adapter#elysia-mcp-adapter"],
    "exclude": []
  }
}

Use exact package#skill entries. Every dependency needs its own permission. Exclusions override permissions. To revoke a skill, remove its entry or add its exact reference to intent.exclude. Use "skills": [] to disable all sources.

Run the installed CLI from the consuming project:

./node_modules/.bin/intent list
./node_modules/.bin/intent load '@mwillbanks/elysia-mcp-adapter#elysia-mcp-adapter'

Load the matching skill only when it applies to the current task. Open the reference for that task. Use a project-local node_modules install for file-only discovery. Intent reads installed package metadata and guidance without running dependency code. When Yarn Plug'n'Play has no usable node_modules, Intent loads Yarn's .pnp.cjs resolver to locate the files. Editor hooks are optional convenience. Keep the trust policy in package.json and review guidance when dependencies change.

See the Intent trust model.

Quick start

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'
      }
    })
  )
  .get('/users/:id', ({ params }) => ({ id: params.id }), {
    params: t.Object({ id: t.String() }),
    detail: {
      operationId: 'users.get',
      summary: 'Get a user'
    }
  })
  .listen(3000)

The adapter exposes GET /users/:id as the users.get MCP tool at POST /mcp. The default protocol is 2026-07-28. Modern clients send the required protocol, method, target, and Accept headers. Tool calls are converted to internal HTTP requests and sent back through Elysia:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "users.get",
    "arguments": {
      "params": { "id": "user_123" }
    },
    "_meta": {
      "io.modelcontextprotocol/protocolVersion": "2026-07-28",
      "io.modelcontextprotocol/clientInfo": { "name": "example-client", "version": "1.0.0" },
      "io.modelcontextprotocol/clientCapabilities": {}
    }
  }
}

Explicit MCP primitives

Install the plugin, then register MCP-only behavior alongside your HTTP routes:

const app = new Elysia()
  .use(mcp({ allowedRoutes: [] }))
  .mcpTool('math.add', ({ a, b }: { a: number; b: number }) => ({ sum: a + b }), {
    inputSchema: {
      type: 'object',
      properties: {
        a: { type: 'number' },
        b: { type: 'number' }
      },
      required: ['a', 'b'],
      additionalProperties: false
    }
  })
  .mcpResource('config://runtime', () => ({ environment: 'production' }))
  .mcpPrompt('review-error', ({ message }: { message: string }) => `Review this error: ${message}`)

Routes become tools by default. Resources and prompts require explicit route metadata or the standalone methods above. Add a prompt option to an explicit or route-backed tool to register an associated prompt that guides the host to call that tool; use .mcpPrompt() for standalone prompts.

MCP extensions

Tasks, OAuth resource-server enforcement, enterprise authorization profiles, Apps, and Skills are opt-in:

app.use(
  mcp({
    transport: {
      protocolVersions: ['2026-07-28', '2025-11-25']
    },
    extensions: {
      tasks: {
        version: 'current',
        provider
      },
      auth: {
        version: 'current',
        resource: 'https://api.example.com/mcp',
        authorizationServers: ['https://auth.example.com'],
        verifyAccessToken
      },
      apps: {
        version: 'current'
      },
      skills: {
        version: 'current',
        directoryRead: true
      }
    }
  })
)

Omitting an extension version selects current; supported dated and draft selectors resolve through the exported MCP_EXTENSION_SUPPORT manifest. Tasks provides the approved current revision and the explicit draft draft-b3d28ac. Update retired draft selectors using the migration guidance in the configuration error. Modern MCP 2026-07-28 uses per-request protocol metadata and server/discover, while legacy 2025-11-25 initialization remains supported. Tasks require modern MCP and a durable provider.

Workers can create a McpTaskExecutionContext with createMcpTaskExecutionContext(options). Pass McpTaskExecutionContextOptions with the task ID, the original provider context, and the provider's persistence callbacks. Use execution.notify.status() for updates and execution.input.request() for client input. Rebuild the context from durable data after a restart. See the Tasks worker example for the full flow.

See the specification inventory for exact revisions and draft compatibility. Configure specification notices to receive deprecation and pre-release notices in application logs or through your OpenTelemetry Logs provider.

Modern core behavior

The core option configures published 2026-07-28 utilities:

mcp({
  core: {
    maxToolInputElements: 10_000,
    continuation: { signingKey: process.env.MCP_CONTINUATION_KEY! },
    pagination: { pageSize: 100, signingKey: process.env.MCP_CURSOR_KEY! },
    cache: { default: { cacheScope: 'private', ttlMs: 0 } },
    subscriptions: { provider, toolsListChanged: true, resources: true }
  }
})

Tools, resource reads, and prompts can return McpInputRequiredResult. Invocation contexts expose notify.progress(), opt-in notify.message(), and typed input helpers alongside the existing reportProgress(), inputResponses, and requestState APIs. Route handlers read the same request-scoped values with getMcpInvocationContext(request). Prompt and resource-template registrations accept completion callbacks. Pagination and subscriptions remain disabled until configured. Signing keys must contain at least 32 bytes. Single-use continuations require an application provider.

Use request language preferences to provide localized display text from your application.

See the messaging guide for live and durable updates, input, subscriptions, and tool-linked prompts. Set prompt on a tool to add a companion prompt without duplicating its registration.

core.maxToolInputElements optionally rejects oversized nested tool inputs before route handlers or task providers run. It counts object members and array elements and leaves input unlimited when omitted. transport.allowedOrigins accepts exact serialized origins and explicit non-HTTP scheme patterns such as chrome-extension://*. HTTP, HTTPS, and global wildcards remain forbidden.

The MCP client SDK 2.4.0 interoperates with modern discovery, progress SSE, prompts, and task reads and cancellation. It rejects valid task-creation responses after creating the task because its decoder does not accept resultType: 'task'. Use the minimal Tasks clients for creation and avoid blind retries.

Requests without a version header or version metadata use classic 2025-11-25 when enabled. Modern clients send the full 2026-07-28 envelope. Classic clients should send the negotiated MCP-Protocol-Version on requests after initialization. Hosts approve and fulfill elicitation, sampling, and roots requests before retrying the original operation.

The exported MCP_EXTENSION_LATEST_REVIEWED manifest records reviewed upstream sources for planning. It does not advertise adapter support.

The repository includes tested, package-root examples for every extension:

  • Tasks uses SQLite subprocess workers and BullMQ 6 with real Redis.

  • Authorization uses Better Auth, Bun SQLite, OAuth Provider, and SAML SSO.

  • Apps vanilla and Apps React use the MCP Apps v2 SDK and build one self-contained HTML document with Bun.

  • Modern core includes a minimal client for MRTR, completion, pagination, progress, cancellation, and subscriptions.

  • Skills registers exact skill bytes and verifies paginated manifests, direct lookup, and resource integrity.

  • Experimental extensions exercises server cards, interceptors, server variants, resource subscriptions, action metadata, and trust annotations.

  • Events demonstrates replay polling, event-only SSE, durable Bun SQLite subscriptions, and verified webhooks.

Server Cards

Server Cards are a finalized MCP extension. They publish advisory discovery metadata at GET /mcp/server-card. They use application/mcp-server-card+json, CORS, ETags, and conditional caching. Use HTTPS in production. Loopback HTTP is development-only. Cards must not contain credentials, private topology, or primitive catalogs. Clients decide whether and when to fetch them.

MCP_EXTENSION_SUPPORT reports Beta maturity for Tasks, Skills over MCP, and Server Cards. The approved Server Cards wire baseline still reports status: 'stable' under Final SEP-2127.

Experimental extension policy

Experimental extensions are off by default. Enable each feature explicitly and pin its exported revision constant. Revision selectors are immutable reviewed commits, not semver ranges. Server variants require legacy 2025-11-25; modern protocol configuration rejects them. Other experiments use modern transport methods where the draft defines them.

Interceptors expose canonical interceptors/list and singular interceptor/invoke. Pass sending to executeInterceptorChain() to mutate before validation. Pass receiving to validate before mutation. Validators run in parallel against isolated payload clones. Mutators run sequentially and use the phase-specific priorityHint, then name ordering for ties. Only severity: 'error' blocks. Audit mode never blocks and returns completed or failed outcomes for observability. Failures close by default; failOpen is explicit, and timeouts abort the local handler. Every wire result includes its interceptor, type, and phase. The adapter never contacts external interceptor services.

Legacy server variants bind discovery, selection, cursors, registries, subscriptions, and requests to the authenticated MCP-Session-Id. Metadata key io.modelcontextprotocol/server-variant takes precedence over MCP-Server-Variant. The first visible, ranked variant must be stable unless the client requests experimental results. Unsubscribe and session deletion close subscription iterators.

Action metadata describes destinations, sources, sensitivity, outcome, and review needs. Trust result metadata distinguishes an absent boolean from false. Evidence references are bounded descriptors; the adapter does not fetch them. Hosts own approval, union-strengthening, consent, evidence retrieval, and unavailable functionality. Behavioral annotations never grant authorization.

Events require MCP 2026-07-28 and default to accepted SEP-3415. The adapter supports authorized discovery, replay polling, event-only SSE with negotiated idle heartbeats, and durable webhook subscriptions. Clients own replay cursors. Webhook providers atomically preserve ownership, verification, secrets, expiry, and status, then reauthorize every delivery. The runnable example performs real signed TLS subscription, rotation, restart recovery, and unsubscribe operations through the adapter.

Client support changes independently of this package. Consult the canonical MCP Extension Support Matrix.

The experimental Filesystems repository currently defines no wire contract. The adapter exposes no filesystem methods and monitors its pinned README for future normative changes.

Documentation

The full guides and API reference live at mwillbanks.github.io/elysia-mcp-adapter:

Development

bun link
bun install
bun install --cwd website
bun run lint
bun run typecheck
bun --bun run --cwd website typecheck
bun test
bun test --coverage --coverage-reporter=lcov
bun run fallow
bun run build

Documentation development runs independently from website/ with bun install and bun run dev.

bun run fallow checks dependencies, duplication, and health for the adapter and website independently. Every finding fails the gate under the default thresholds. CI records measured Bun coverage separately from Fallow's estimated coverage. Installation, builds, tests, and packed-consumer checks use Bun; the declared Node.js compatibility floor is checked statically.

Extension revisions and toolchain

Use Bun 1.4.4. bun run lint checks formatting with bun fmt; bun run typecheck uses bun check. Fallow remains the static analysis gate. No standalone lint package is required.

Skills preserves optional JSON _meta on registrations and provider sources. Extension-owned keys use io.modelcontextprotocol.skills/. Resource digests still cover exact published bytes. Interceptors use the current 07c89a24 proposal, including timestamped invocation context, optional span/session identifiers, and per-call configuration validation. See the separate HTTP service in examples/experimental; local chain helpers remain local utilities. Events uses accepted revision f77a5fe and codes -32023 through -32027. Migrate older Events pins to current and discover Events on server/discover. Server Variants still uses legacy transport.

Apps, Client Credentials, and Enterprise-Managed Authorization have approved profiles. Server Cards follows Final SEP-2127. Tasks exposes approved and pinned draft selections. File Uploads has an eligible working-group draft and is planned. Filesystems remains inventory-only while the group develops its specification. See the website specification inventory for immutable sources, hashes, limitations, and all selectors.

License

MIT © Mike Willbanks

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      D
      maintenance
      A comprehensive ElysiaJS plugin for building Model Context Protocol (MCP) servers with HTTP transport, session management, and support for tools, resources, and prompts.
      14,076 npm
      49
      MIT
    • A
      license
      Not graded
      quality
      B
      maintenance
      Enables serving a remote Model Context Protocol endpoint by exposing tools, prompts, and resources with customizable authorization and hooks, allowing AI models to interact with your application through a standardized JSON-RPC interface.
      753 npm
      Business Source 1.1
    • A
      license
      Not graded
      quality
      C
      maintenance
      An MCP server plugin for the Elysia web framework, enabling AI clients to connect via Streamable HTTP transport with stateful or stateless sessions and authentication.
      19 npm
      MIT