by actionhero
Keryx: The Fullstack TypeScript Framework for MCP and APIs
# Add to your Claude Code skills
git clone https://github.com/actionhero/keryxGuides for using mcp servers skills like keryx.
See how keryx compares with popular alternatives.
keryx is an open-source mcp servers skill for AI coding assistants such as Claude Code, Codex CLI, and ChatGPT, built by actionhero. Keryx: The Fullstack TypeScript Framework for MCP and APIs. It has 165 GitHub stars.
keryx's catalog security scan is still queued. You can run an instant dependency and prompt-injection check now with the "Scan for vulnerabilities" button above.
Clone the repository with "git clone https://github.com/actionhero/keryx" and add it to your Claude Code skills directory (see the Installation section above).
keryx is primarily written in TypeScript. It is open-source under actionhero on GitHub, so you can review or fork the full source.
Yes. SkillsLLM lists many other MCP Servers skills you can browse and compare side by side. Open the MCP Servers category from the badge at the top of this page, or use the Related Skills and comparison links further down to weigh keryx against similar tools.
No comments yet. Be the first to share your thoughts!
Top skills in this category by stars
Available after the catalog security scan passes.
⚠️ Third-Party Software Notice
This skill is third-party open-source software developed and hosted independently on GitHub. SkillsLLM is an informational directory and does not control or maintain the underlying repository.
Any security checks, ratings, or warnings displayed by SkillsLLM are automated and limited in scope. They do not constitute a security certification or guarantee that the software is safe, error-free, or free from malicious code, vulnerabilities, compromised dependencies, or prompt-injection risks.
Review the source code, permissions, dependencies, and configuration before installing or running any third-party skill. Use is at your own risk. To the maximum extent permitted by applicable law, SkillsLLM is not liable for losses arising from third-party software.
The deep catalog scan for this skill is still queued. Run an instant dependency check now instead.
The big idea behind Keryx: write your controller once, and it works everywhere. A single action class handles HTTP requests, WebSocket messages, CLI commands, background tasks, and MCP tool calls — same inputs, same Zod validation, same middleware, same response. You write the logic once; the only thing that changes is how the request arrived and how the response gets delivered.
That last transport — MCP — is the reason I built this now. Every action is automatically an MCP tool, so AI agents authenticate with built-in OAuth 2.1, get typed errors, and call the exact same validated endpoints your HTTP clients do. There's no second MCP server to maintain and no schemas to keep in sync… you expose your app to agents by writing the app.
And it's a real backend, not a toy. You get Drizzle ORM with auto-migrations, background tasks with fan-out on Resque, streaming responses (SSE, chunked binary, and the same stream over WebSocket and MCP), OpenAPI generation, pagination and caching helpers, database transactions, and a companion Vite + React frontend that reads your actions with end-to-end type safety — no code generation. It's all on Bun, so TypeScript runs natively and startup is fast.
Here's what that looks like in practice. This is one action:
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:
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:
{ "messageType": "action", "action": "user:create",
"params": { "name": "Evan", "email": "evan@example.com", "password": "secret123" } }
CLI — flags are generated from the Zod schema automatically:
./keryx.ts "user:create" --name Evan --email evan@example.com --password secret123 -q | jq
Background Task — enqueued to a Resque worker via Redis:
await api.actions.enqueue("user:create", { name: "Evan", email: "evan@example.com", password: "secret123" });
MCP — exposed as a tool for AI agents automatically:
{
"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.
That's it. The agent can now discover all your actions as tools, authenticate via OAuth, and call them with full type validation.
ActionResponse<MyAction> gives the frontend type-safe API responses, no code generation neededah-sequelize-plugin)ah-next-plugin)StreamingResponse, with per-transport behavior (HTTP, WebSocket, MCP)paginationInputs() Zod mixin + paginate() utility for standardized paginated responseswithTransaction() and TransactionMiddleware for automatic commit/rollback across action executionTypeScript is still the best language for web APIs. But Node.js has stalled — Bun is moving faster and includes everything we need out of the box:
fetch included natively — great for testingpackage.json wrapping the workspaces. bun install and bun dev work here, but you need to cd into each workspace for tests.Create a new project:
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 (including running without Postgres or Redis for quick prototypes).
If you're contributing to Keryx, clone the monorepo instead:
git clone https://github.com/actionhero/keryx.git
cd keryx
bun install
cp example/backend/.env.example example/backend/.env
cp example/frontend/.env.example example/frontend/.env
bun dev
bun compile
# set NODE_ENV=production in .env
bun start
We use Drizzle as the ORM. Migrations are derived from schemas — edit your schema files in schema/*.ts, then generate and apply:
cd example/backend && bun run migrations
# restart the server — pending migrations auto-apply
Unlike the original ActionHero, we've removed the distinction between actions, CLI commands, and tasks. They're all the same thing now. You can run any action from the CLI, schedule any action as a background task, call any action via HTTP or WebSocket, and expose any action as an MCP tool for AI agents. Same input validation, same responses, same middleware.
Add a web property to expose an action as an HTTP endpoint. Routes support :param path parameters and RegExp patterns — the route lives on the action itself, no separate routes.ts file:
web = { route: "/user/:id", method: HTTP_METHOD.GET };
Enabled by default. Clients send JSON messages with { messageType: "action", action: "user:create", params: { ... } }. The server validates params through the same Zod schema and sends the response back over the socket. WebSocket connections also support channel subscriptions for real-time PubSub.
Enabled by default. Every action is registered as a CLI command via Commander. The Zod schema generates --flags and --help text automatically:
./keryx.ts "user:create" --name evan --email "evantahler@gmail.com" --password password -q | jq
The -q flag suppresses logs so you get clean JSON. Use --help on any action to see its params.
Add a task property to schedule an action as a background job. A queue is required, and frequency is optional for recurring execution:
task = { queue: "default", frequency: 1000 * 60 * 60 }; // every hour
When the MCP server is enabled (MCP_SERVER_ENABLED=true), every action is automatically registered as an MCP tool. AI agents and LLM clients (Claude Desktop, VS Code, etc.) can discover and call your actions through the standard Model Context Protocol. Actions can also be exposed as MCP resources (URI-addressed data) and prompts (named templates) via mcp.resource and mcp.prompt.
Action names are converted to valid MCP tool names by replacing : with - (e.g., user:create becomes user-create). The action's Zod schema is converted to JSON Schema for tool and prompt parameter definitions.
To exclude an action from MCP tools:
mcp = { tool: false };
To expose an action as an MCP resource or prompt:
// Resource — clients fetch this by URI
mcp = { tool: false, resource: { uri: "myapp://status", mimeType: "application/json" } };
// Prompt — clients invoke this as a named template
mcp = { tool: false, prompt: { title: "Greeting" } };
OAuth 2.1 with PKCE is used for authentication — MCP clients go through a browser-based login flow, and subsequent tool calls carry a Bearer token tied to the authenticated user's session.
A parent task can distribute work across many child jobs using api.actions.fanOut() for parallel processing. Results are collected automatically in Redis. See the Tasks guide for full API and examples.
Actions can stream data by returning a StreamingResponse. The framework handles SSE, chunked binary, and cross-transport behavior automatically:
async run(params: { prompt: string }) {
const sse = StreamingResponse.sse();
(async ()