{"id":46999929,"url":"https://github.com/actionhero/keryx","last_synced_at":"2026-04-10T20:02:53.641Z","repository":{"id":225733367,"uuid":"766706497","full_name":"actionhero/keryx","owner":"actionhero","description":"Keryx: The Fullstack TypeScript Framework for MCP and APIs","archived":false,"fork":false,"pushed_at":"2026-03-10T04:07:06.000Z","size":2127,"stargazers_count":4,"open_issues_count":5,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-03-10T09:16:38.139Z","etag":null,"topics":["ai","api","bun","framework","full-stack","mcp","typescript"],"latest_commit_sha":null,"homepage":"https://www.keryxjs.com","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/actionhero.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2024-03-04T00:51:15.000Z","updated_at":"2026-03-10T04:06:41.000Z","dependencies_parsed_at":"2026-03-05T09:01:05.720Z","dependency_job_id":null,"html_url":"https://github.com/actionhero/keryx","commit_stats":null,"previous_names":["evantahler/bun-api-template","evantahler/bun-actionhero","evantahler/keryx","actionhero/keryx"],"tags_count":43,"template":false,"template_full_name":null,"purl":"pkg:github/actionhero/keryx","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/actionhero%2Fkeryx","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/actionhero%2Fkeryx/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/actionhero%2Fkeryx/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/actionhero%2Fkeryx/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/actionhero","download_url":"https://codeload.github.com/actionhero/keryx/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/actionhero%2Fkeryx/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30338573,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-10T14:50:13.961Z","status":"ssl_error","status_checked_at":"2026-03-10T14:49:32.490Z","response_time":106,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai","api","bun","framework","full-stack","mcp","typescript"],"created_at":"2026-03-11T17:00:42.062Z","updated_at":"2026-04-01T22:56:53.510Z","avatar_url":"https://github.com/actionhero.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Keryx\n\n\u003cp align=\"center\"\u003e\u003cstrong\u003eThe fullstack TypeScript framework for MCP and APIs.\u003c/strong\u003e\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://raw.githubusercontent.com/actionhero/keryx/main/docs/public/images/horn.svg\" alt=\"Keryx\" width=\"200\" /\u003e\n\u003c/p\u003e\n\n[![Test](https://github.com/actionhero/keryx/actions/workflows/test.yaml/badge.svg)](https://github.com/actionhero/keryx/actions/workflows/test.yaml)\n\n## What is this Project?\n\nThis is a ground-up rewrite of [ActionHero](https://www.actionherojs.com), built on [Bun](https://bun.sh). I still believe in the core ideas behind ActionHero — it was an attempt to take the best ideas from Rails and Node.js and shove them together — but the original framework needed a fresh start with Bun, Zod, Drizzle, and first-class MCP support.\n\nThe big idea: **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 validation, same middleware, same response. No duplication.\n\nThat includes AI agents. Every action is automatically an MCP tool — agents authenticate via built-in OAuth 2.1, get typed errors, and call the same validated endpoints your HTTP clients use. No separate MCP server, no duplicated schemas.\n\n### One Action, Every Transport\n\nHere's what that looks like in practice. This is one action:\n\n```ts\nexport class UserCreate implements Action {\n  name = \"user:create\";\n  description = \"Create a new user\";\n  inputs = z.object({\n    name: z.string().min(3),\n    email: z.string().email(),\n    password: secret(z.string().min(8)),\n  });\n  web = { route: \"/user\", method: HTTP_METHOD.PUT };\n  task = { queue: \"default\" };\n\n  async run(params: ActionParams\u003cUserCreate\u003e) {\n    const user = await createUser(params);\n    return { user: serializeUser(user) };\n  }\n}\n```\n\nThat one class gives you:\n\n**HTTP** — `PUT /api/user` with JSON body, query params, or form data:\n```bash\ncurl -X PUT http://localhost:8080/api/user \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\":\"Evan\",\"email\":\"evan@example.com\",\"password\":\"secret123\"}'\n```\n\n**WebSocket** — send a JSON message over an open connection:\n```json\n{ \"messageType\": \"action\", \"action\": \"user:create\",\n  \"params\": { \"name\": \"Evan\", \"email\": \"evan@example.com\", \"password\": \"secret123\" } }\n```\n\n**CLI** — flags are generated from the Zod schema automatically:\n```bash\n./keryx.ts \"user:create\" --name Evan --email evan@example.com --password secret123 -q | jq\n```\n\n**Background Task** — enqueued to a Resque worker via Redis:\n```ts\nawait api.actions.enqueue(\"user:create\", { name: \"Evan\", email: \"evan@example.com\", password: \"secret123\" });\n```\n\n**MCP** — exposed as a tool for AI agents automatically:\n```json\n{\n  \"mcpServers\": {\n    \"my-app\": {\n      \"url\": \"http://localhost:8080/mcp\"\n    }\n  }\n}\n```\n\nSame 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.\n\nThat's it. The agent can now discover all your actions as tools, authenticate via OAuth, and call them with full type validation.\n\n### Key Components\n\n- **MCP-native** — every action is an MCP tool with OAuth 2.1 auth, typed errors, and per-session isolation\n- **Transport-agnostic Actions** — HTTP, WebSocket, CLI, background tasks, and MCP from one class\n- **Zod input validation** — type-safe params with automatic error responses and OpenAPI generation\n- **Built-in background tasks** via [node-resque](https://github.com/actionhero/node-resque), with a [fan-out pattern](#fan-out-tasks) for parallel job processing\n- **Strongly-typed frontend integration** — `ActionResponse\u003cMyAction\u003e` gives the frontend type-safe API responses, no code generation needed\n- **Drizzle ORM** with auto-migrations (replacing the old `ah-sequelize-plugin`)\n- **Companion Vite + React frontend** as a separate application (replacing `ah-next-plugin`)\n- **Streaming responses** — SSE and chunked binary streaming via `StreamingResponse`, with per-transport behavior (HTTP, WebSocket, MCP)\n- **Pagination helpers** — `paginationInputs()` Zod mixin + `paginate()` utility for standardized paginated responses\n- **Database transactions** — `withTransaction()` and `TransactionMiddleware` for automatic commit/rollback across action execution\n- **Redis caching patterns** — cache-aside and response-level cache middleware using the built-in ioredis connection\n\n### Why Bun?\n\nTypeScript 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:\n\n- Native TypeScript — no compilation step\n- Built-in test runner\n- Module resolution that just works\n- Fast startup and an excellent packager\n- `fetch` included natively — great for testing\n\n## Project Structure\n\n- **root** — a slim `package.json` wrapping the workspaces. `bun install` and `bun dev` work here, but you need to `cd` into each workspace for tests.\n- **packages/keryx** — the framework package (publishable)\n- **example/backend** — the example backend application\n- **example/frontend** — the example Vite + React frontend\n- **docs** — the [documentation site](https://keryxjs.com)\n\n## Quick Start\n\nCreate a new project:\n\n```bash\nbunx keryx new my-app\ncd my-app\ncp .env.example .env\nbun install\nbun dev\n```\n\nRequires Bun, PostgreSQL, and Redis. See the [Getting Started guide](https://keryxjs.com/guide/) for full setup instructions.\n\n### Developing the framework itself\n\nIf you're contributing to Keryx, clone the monorepo instead:\n\n```bash\ngit clone https://github.com/actionhero/keryx.git\ncd keryx\nbun install\ncp example/backend/.env.example example/backend/.env\ncp example/frontend/.env.example example/frontend/.env\nbun dev\n```\n\n## Production Builds\n\n```bash\nbun compile\n# set NODE_ENV=production in .env\nbun start\n```\n\n## Databases and Migrations\n\nWe use [Drizzle](https://orm.drizzle.team) as the ORM. Migrations are derived from schemas — edit your schema files in `schema/*.ts`, then generate and apply:\n\n```bash\ncd example/backend \u0026\u0026 bun run migrations\n# restart the server — pending migrations auto-apply\n```\n\n## Actions, CLI Commands, and Tasks\n\nUnlike 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.\n\n### Web Actions\n\nAdd 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:\n\n```ts\nweb = { route: \"/user/:id\", method: HTTP_METHOD.GET };\n```\n\n### WebSocket Actions\n\nEnabled 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.\n\n### CLI Actions\n\nEnabled by default. Every action is registered as a CLI command via [Commander](https://github.com/tj/commander.js). The Zod schema generates `--flags` and `--help` text automatically:\n\n```bash\n./keryx.ts \"user:create\" --name evan --email \"evantahler@gmail.com\" --password password -q | jq\n```\n\nThe `-q` flag suppresses logs so you get clean JSON. Use `--help` on any action to see its params.\n\n### Task Actions\n\nAdd a `task` property to schedule an action as a background job. A `queue` is required, and `frequency` is optional for recurring execution:\n\n```ts\ntask = { queue: \"default\", frequency: 1000 * 60 * 60 }; // every hour\n```\n\n### MCP Actions\n\nWhen the MCP server is enabled (`MCP_SERVER_ENABLED=true`), every action is automatically registered as an [MCP](https://modelcontextprotocol.io) 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`.\n\nAction 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.\n\nTo exclude an action from MCP tools:\n\n```ts\nmcp = { tool: false };\n```\n\nTo expose an action as an MCP resource or prompt:\n\n```ts\n// Resource — clients fetch this by URI\nmcp = { tool: false, resource: { uri: \"myapp://status\", mimeType: \"application/json\" } };\n\n// Prompt — clients invoke this as a named template\nmcp = { tool: false, prompt: { title: \"Greeting\" } };\n```\n\nOAuth 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.\n\n### Fan-Out Tasks\n\nA 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](https://keryxjs.com/guide/tasks) for full API and examples.\n\n### Streaming Responses\n\nActions can stream data by returning a `StreamingResponse`. The framework handles SSE, chunked binary, and cross-transport behavior automatically:\n\n```ts\nasync run(params: { prompt: string }) {\n  const sse = StreamingResponse.sse();\n\n  (async () =\u003e {\n    try {\n      for await (const token of callLLM(params.prompt)) {\n        sse.send(token, { event: \"token\" });\n      }\n      sse.close();\n    } catch (e) {\n      sse.sendError(String(e));\n    }\n  })();\n\n  return sse;\n}\n```\n\nOver HTTP this is native SSE; over WebSocket each chunk becomes an incremental message; over MCP chunks are forwarded as logging messages. See the [Streaming guide](https://keryxjs.com/guide/streaming) for full details.\n\n## Coming from ActionHero?\n\nKeryx keeps the core ideas but rewrites everything on Bun with first-class MCP support. The biggest changes: unified controllers (actions = tasks = CLI commands = MCP tools), separate frontend/backend applications, Drizzle ORM, and MCP as a first-class transport.\n\nSee the full [migration guide](https://keryxjs.com/guide/from-actionhero) for details.\n\n## Production Deployment\n\nEach application has its own `Dockerfile`, and a `docker-compose.yml` runs them together. You probably won't use this exact setup in production, but it shows how the pieces fit together.\n\n## Documentation\n\nFull docs at [keryxjs.com](https://keryxjs.com), including:\n- [Getting Started](https://keryxjs.com/guide/)\n- [Actions Guide](https://keryxjs.com/guide/actions)\n- [Streaming](https://keryxjs.com/guide/streaming)\n- [Caching](https://keryxjs.com/guide/caching)\n- [API Reference](https://keryxjs.com/reference/actions)\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://raw.githubusercontent.com/actionhero/keryx/main/docs/public/images/lion-standing.svg\" alt=\"Keryx lion\" width=\"120\" /\u003e\n\u003c/p\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Factionhero%2Fkeryx","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Factionhero%2Fkeryx","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Factionhero%2Fkeryx/lists"}