Skip to content

KeryxThe Fullstack TypeScript Framework for MCP and APIs

One action class. Five transports. Your API is automatically an MCP server for AI agents, a WebSocket handler, a CLI tool, and a background task runner. Built on Bun, powered by Zod.

$bunx keryx new my-appCopy
Keryx herald

Write Once, Run Everywhere

One action class. It's your HTTP endpoint, your CLI command, your WebSocket handler, and your background task โ€” all at the same time.

export class Status implements Action {
  name = "status";
  description = "Return the status of the server";
  inputs = z.object({});
  web = { route: "/status", method: HTTP_METHOD.GET };
  task = { queue: "default", frequency: 60000 };

  async run() {
    return {
      name: api.process.name,
      uptime: Date.now() - api.bootTime,
    };
  }
}

That's it. Same validation, same error handling, same response shape โ€” whether the request comes from a browser, a CLI, or a cron job.

One Action, Every Transport โ€‹

This is one action:

ts
export class UserCreate implements Action {
  name = "user:create";
  description = "Create a new user";
  inputs = z.object({
    name: z.string().min(3),
    email: z.string().email(),
    password: secret(z.string().min(8)),
  });
  web = { route: "/user", method: HTTP_METHOD.PUT };
  task = { queue: "default" };

  async run(params: ActionParams<UserCreate>) {
    const user = await createUser(params);
    return { user: serializeUser(user) };
  }
}

That one class gives you:

HTTP โ€” PUT /api/user with JSON body, query params, or form data:

bash
curl -X PUT http://localhost:8080/api/user \
  -H "Content-Type: application/json" \
  -d '{"name":"Evan","email":"evan@example.com","password":"secret123"}'

WebSocket โ€” send a JSON message over an open connection:

json
{
  "messageType": "action",
  "action": "user:create",
  "params": {
    "name": "Evan",
    "email": "evan@example.com",
    "password": "secret123"
  }
}

CLI โ€” flags are generated from the Zod schema automatically:

bash
./keryx.ts "user:create" --name Evan --email evan@example.com --password secret123 -q | jq

Background Task โ€” enqueued to a Resque worker via Redis:

ts
await api.actions.enqueue("user:create", {
  name: "Evan",
  email: "evan@example.com",
  password: "secret123",
});

MCP โ€” exposed as a tool for AI agents automatically:

json
{
  "mcpServers": {
    "my-app": {
      "url": "http://localhost:8080/mcp"
    }
  }
}

Same validation, same middleware chain, same run() method, same response shape. The only thing that changes is how the request arrives and how the response is delivered.

Why Keryx? โ€‹

Most backends start simple โ€” an HTTP framework โ€” then bolt on a WebSocket server, a CLI tool, a job queue, and now an MCP layer. Each one has its own handler, its own validation, its own auth. You end up maintaining five implementations of the same logic.

Keryx flips that: write your controller once, and the framework delivers it across every transport.

FeatureKeryxHonoElysiaNestJSFastAPIDjango
HTTPyesyesyesyesyesyes
WebSocketyesadapteryesyesyeschannels
CLI commandsyesโ€”โ€”limitedโ€”yes
Background tasksyesโ€”โ€”BullCeleryCelery
MCP toolsyesโ€”โ€”โ€”โ€”โ€”
Unified controlleryesโ€”โ€”โ€”โ€”โ€”
Type-safe responsesyesyesyespartialyesโ€”
OAuth 2.1 built-inyesโ€”โ€”Passportโ€”allauth
Per-session agentsyesโ€”โ€”โ€”โ€”โ€”

See detailed comparisons โ†’

The MCP SDK gives you the protocol. Keryx gives you the framework โ€” actions, validation, middleware, auth, database, and background tasks alongside your MCP tools.

Built for AI Agents โ€‹

Keryx is the only TypeScript framework where your API is automatically an MCP server. No separate tool definitions, no duplicated auth, no schema mapping.

  • Zero-config tool registration โ€” write an action, it's an MCP tool. The Zod schema becomes the tool's input schema automatically.
  • OAuth 2.1 + PKCE built-in โ€” agents authenticate the same way browser clients do. One auth layer, not two.
  • Dynamic OAuth forms โ€” login and signup pages are generated from your Zod schemas. Change a field, the form updates.
  • Per-session MCP servers โ€” each agent connection gets isolated state. No cross-session leaks.
  • Typed errors โ€” agents get structured ErrorType values, not generic failure messages. They can distinguish validation errors from auth failures.
  • Real-time notifications โ€” PubSub events are forwarded to connected agents as MCP logging messages.

Claude Desktop, VS Code Copilot, Cursor, Windsurf, and any other MCP client can discover and call your actions out of the box.

Building for AI Agents โ†’

Why Bun? โ€‹

  • Native TypeScript โ€” no compilation step, no tsconfig gymnastics
  • Built-in test runner โ€” bun test with watch mode, no extra dependencies
  • Fast startup โ€” sub-second cold starts for dev and production
  • Module resolution that works โ€” ESM, CommonJS, and .ts imports without configuration
  • fetch included natively โ€” great for testing your own API

Quick Start โ€‹

bash
bunx keryx new my-app
cd my-app
cp .env.example .env
bun install
bun dev

Requires Bun, PostgreSQL, and Redis. See the Getting Started guide for full setup instructions.

Built With โ€‹

Bun ยท Zod ยท Drizzle ยท Redis ยท PostgreSQL ยท OpenTelemetry

Released under the MIT License.