Elysia Mcp — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited Elysia Mcp (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Turn your existing Elysia routes into MCP tools. No manual registration, no schema duplication, no handler rewrites.
The Model Context Protocol lets AI agents discover and call tools over a standard JSON-RPC interface. If you already have an Elysia API with typed schemas and handlers, you shouldn't have to rewrite all of that as MCP tool definitions.
@8monkey/elysia-mcp bridges the gap: add .use(mcp()) and every endpoint becomes a callable MCP tool, with its name, description, and input schema derived from what you already wrote.
detail.operationId and detail.summary also drive MCP tool discovery. Write once, serve both humans and AI agents.app.handle(), so derive, resolve, beforeHandle, afterHandle, error hooks, and all plugins run exactly as they do for normal HTTP requests.GET /users becomes list_users, GET /users/:id becomes get_user, POST /users becomes create_user, and nested paths like GET /users/:uid/posts become list_user_postsCompared to kerlos/elysia-mcp and keithagroves/Elysia-mcp, which require manual tool registration with separate Zod schemas and standalone handlers:
app.handle(), not standalone functions, so all middleware appliesbun add @8monkey/elysia-mcpPeer dependency: elysia >= 1.4.0
Add .use(mcp()) and all routes become MCP tools:
import { Elysia } from "elysia";
import { mcp } from "@8monkey/elysia-mcp";
const app = new Elysia()
.use(mcp())
.get("/users", () => db.users.findAll())
.get("/users/:id", ({ params }) => db.users.find(params.id))
.post("/users", ({ body }) => db.users.create(body))
.listen(3000);This exposes a POST /mcp endpoint. An MCP client calling tools/list will see list_users, get_user, and create_user.
Good descriptions are critical for AI agents to understand when and how to call your tools. The plugin uses Elysia's standard detail.summary as the MCP tool description, and TypeBox description on each property as the parameter description. These are the same fields that Elysia uses for OpenAPI/Swagger documentation, meaning there's zero duplication. Write them once and they serve both your API docs and your MCP tools.
The plugin warns at startup if any property is missing a description, since agents rely on these to choose the right tool and pass the correct arguments.
detail.summaryUse detail.summary to describe what the tool does. This becomes the MCP tool description and the OpenAPI operation summary:
.get("/users", () => db.users.findAll(), {
detail: {
operationId: "list_users",
summary: "List all users in the system",
mcp: true,
},
})If operationId is omitted, the plugin falls back to generated names like list_users or get_user.
descriptionAdd description to each schema property. These become the MCP parameter descriptions and the OpenAPI property descriptions — the same metadata, no duplication:
import { t } from "elysia";
.get("/users/:id", ({ params }) => db.users.find(params.id), {
params: t.Object({
id: t.String({ description: "The user's unique ID" }),
}),
detail: { summary: "Get user by ID" },
})MCP tools accept a single flat input object. The plugin merges params, query, and body into one schema:
Route: PATCH /users/:id (params: { id }, query: { fields }, body: { name, email })
↓
MCP Tool Input: { id: string, fields?: string, name: string, email: string }Property descriptions are preserved. The plugin warns at startup if properties collide across buckets or lack descriptions.
By default, all routes are exposed. Opt out individual routes with mcp: false:
.get("/health", () => ({ status: "ok" }), {
detail: { mcp: false },
})Or flip the default — set allRoutes: false to require explicit opt-in:
.use(mcp({ allRoutes: false }))
.get("/users", () => db.users.findAll(), {
detail: { mcp: true }, // only this route becomes a tool
})
.get("/health", () => "ok") // not exposedAuto-generated names follow a {verb}_{resource} convention. If you want an explicit tool name, set detail.operationId:
.get("/items", handler, {
detail: {
operationId: "search_items",
summary: "Full-text search across all items",
mcp: true,
},
})| Method + Path | Generated Name |
|---|---|
GET /users | list_users |
GET /users/:id | get_user |
POST /users | create_user |
PATCH /users/:id | update_user |
DELETE /users/:id | delete_user |
GET /users/:uid/posts | list_user_posts |
mcp({
name: "my-api", // MCP server name (default: "elysia-mcp")
version: "1.0.0", // MCP server version (default: "1.0.0")
path: "/mcp", // Endpoint path (default: "/mcp")
allRoutes: true, // Expose all routes by default (default: true)
});When an MCP client calls a tool:
params, query, and bodyapp.handle(request) runs the full Elysia lifecycle — derive, resolve, beforeHandle, the handler, afterHandle, and error hooksThis means your auth middleware, rate limiting, validation, and every other plugin work exactly the same for MCP calls as they do for REST calls.
Point any MCP-compatible client at your /mcp endpoint. For example, with the MCP Inspector:
npx @modelcontextprotocol/inspector --transport http http://localhost:3000/mcpOr configure it in Claude Desktop, Cursor, or any other MCP-enabled tool as an HTTP MCP server at http://localhost:3000/mcp.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.