Announcing Keryx: A Full-Stack TypeScript Framework for APIs and MCP Servers

ai engineering mcp node.js open-source typescript 
2026-03-13
↞ See all posts


Announcing Keryx: A Full-Stack TypeScript Framework for APIs and MCP Servers

I've been building Actionhero for over 14 years. It started as a side project in 2011 — a Node.js server that could speak HTTP and WebSocket from the same codebase, with background jobs built in. The core idea was simple: write your business logic once, expose it everywhere. Over the years, Actionhero picked up a few thousand GitHub stars, got used in production by companies I never expected, and even got approved by the VA for healthcare systems. I'm proud of it.

But the world has changed. TypeScript won. Bun happened. Zod became the standard for validation. And then MCP showed up.

Keryx is what I'd build if I started Actionhero today — which is exactly what I did.

What Is Keryx?

Keryx is a full-stack TypeScript framework for building APIs and MCP servers. The core philosophy is the same one that drove Actionhero: you write your controller once, and it works across every transport your application needs. But "every transport" means something different in 2026 than it did in 2012.

A single Keryx Action automatically becomes:

  • An HTTP endpoint with JSON/form data support
  • A WebSocket handler for real-time communication
  • A CLI command with auto-generated flags
  • A background task via Resque workers
  • An MCP tool that AI agents can discover and use

Same validation. Same middleware. Same error handling. Five transports, one class.

One Action, Every Transport

Here's what that looks like in practice:

1export class UserView implements Action { 2 name = "user:view"; 3 description = "View a user's profile by ID or email"; 4 inputs = z.object({ 5 id: z.number().optional(), 6 email: z.string().email().optional(), 7 }); 8 web = { route: "/user", method: HTTP_METHOD.GET }; 9 mcp = { tool: true }; 10 11 async run(params: ActionParams<UserView>) { 12 const user = await findUser(params); 13 return { user: serializeUser(user) }; 14 } 15}

That's it. This class is simultaneously a GET /user endpoint, a WebSocket action, a user:view CLI command, and an MCP tool. The Zod schema drives input validation for every transport, and it auto-generates your OpenAPI documentation. An AI agent running user:view gets the same validation and error handling as a curl request hitting /user — because it's the same code path.

If you've used Actionhero, this should feel familiar. If you haven't, the idea is straightforward: your business logic shouldn't care how a request arrived.

Why MCP Changes Things

The first four transports — HTTP, WebSocket, CLI, tasks — those are table stakes for a full-stack framework. Actionhero had all of them (well, CLI was a stretch). The reason I built Keryx instead of continuing to evolve Actionhero is the fifth one: MCP.

MCP (Model Context Protocol) is how AI agents discover and use tools. It's becoming the standard interface between agents and the services they interact with. And here's the thing — if you're already defining your actions with typed inputs, descriptions, and structured outputs… you're 90% of the way to an MCP tool. The shape of a good Action and the shape of a good MCP tool are nearly identical.

Keryx makes the last 10% automatic. Every Action is registered as an MCP tool with zero additional configuration. Your Zod schema becomes the tool's input schema. Your Action's name becomes the tool name. An AI agent can discover your API the same way a human developer reads your OpenAPI docs — except the agent gets a protocol it natively understands.

That also means Keryx gives you per-session agent isolation and OAuth 2.1 with PKCE out of the box. Because when agents are calling your API, authentication and scoping aren't optional — they're the whole game.

Modern Defaults

Actionhero was built in a world of callbacks and npm install. Keryx is built for how we write TypeScript today:

  • Bun as the runtime — native TypeScript execution, no compilation step, fast
  • Zod for validation — type-safe schemas that generate OpenAPI docs
  • Drizzle ORM with auto-migrations — your database schema lives in TypeScript
  • Redis for PubSub, caching, and job queues (via node-resque, which also got a refresh)
  • OpenTelemetry for metrics and structured logging — observability from day one

These aren't plugins you install later. They're built in, configured, and working when you scaffold a new project. I've started enough projects to know that the things you defer to "later" are the things that never get done.

What About Actionhero?

Actionhero isn't going anywhere. It's stable, it works, and people depend on it. But I'm not going to pretend it's where my energy is going. Keryx is the framework I want to build with, and I think it's the better choice for new projects.

If you're running Actionhero in production today, there's no urgency to migrate. If you're starting something new… take a look at Keryx.

Getting Started

1bunx keryx new my-app 2cd my-app 3cp .env.example .env 4bun install 5bun dev

You'll need Bun, PostgreSQL, and Redis installed locally. The scaffolded project comes with example Actions, database migrations, and a working MCP server — so you can point an AI agent at it immediately.

The docs are at keryxjs.com, and the project is MIT licensed.

Source: keryxjs.com

Evan Tahler is Head of Engineering at Arcade. He's the creator of Actionhero, node-resque, and now Keryx.

Hi, I'm Evan

I write about Technology, Software, and Startups. I use my Product Management, Software Engineering, and Leadership skills to build teams that create world-class digital products.

Get in touch