Atlas / MCP servers / actionhero / Keryx

KeryxCAUTION

mcp/actionhero/keryx

Keryx: The Fullstack TypeScript Framework for MCP and APIs

Verdict
CAUTION
Grade
D
Trust score
61 /100
Exposed tools
4 4r · 0w · 0d
Transport
streamable-http
License
MIT
Stars
35
01

Overview

From the repository's own README, as read at the audited commit. Badges and raw HTML are left out.

The fullstack TypeScript framework for MCP and APIs.

[](https://github.com/actionhero/keryx/actions/workflows/test.yaml)

What is this Project?

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.

One Action, Every Transport

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 = 
Read from source at commit 0820c51d8103OBSERVED · 2026-10-08
02

Connect

Built from this server's own package name, version and transport as found in its source — not copied from anyone's documentation, so it cannot drift against a page we do not control. Replace the environment placeholders with a token scoped to the least it needs.

claude-code
claude mcp add tracing --env RESQUE_ADMIN_PASSWORD=${RESQUE_ADMIN_PASSWORD} -- npx -y @keryxjs/[email protected]
claude-desktop
{
  "mcpServers": {
    "tracing": {
      "command": "npx",
      "args": [
        "-y",
        "@keryxjs/[email protected]"
      ],
      "env": {
        "RESQUE_ADMIN_PASSWORD": "${RESQUE_ADMIN_PASSWORD}"
      }
    }
  }
}
03

Exposed tools (4)

4 read · 0 write · 0 destructive.

ToolRiskDescription
messagesreadAuthenticated channel for real-time messages
plugin-channelreadA channel from a plugin
pluginActionreadAn action from a plugin
xreadDoes the X thing
04

Trust audit

CAUTIONgrade D · trust 61/100 Install with care. The audit found things worth knowing before you trust its output.

LayerWhat it checksResult
L0Provenance & inventoryPASS
L1Static analysis of the codeWARN
L2Instruction surface (what it tells the agent)PASS
L3Class-specific surfacePASS
L4Behavioural (sandbox)SKIPPED

What the source does

Filesystem
none-observed
Network
declared (1 observation(s))
Shell
none-observed
Dependencies
not all pinned
Secrets in source
found

Findings (25)

MEDIUMHard-coded secrets · secret.db_uri · CWE-798, CWE-321
.github/workflows/test.yaml:73
DATABASE_URL_TEST: postgres://postgres:postgres@localhost:5432/keryx-test
MEDIUMHard-coded secrets · secret.db_uri · CWE-798, CWE-321
.github/workflows/test.yaml:118
DATABASE_URL_TEST: postgres://postgres:postgres@localhost:5432/keryx-test
MEDIUMHard-coded secrets · secret.db_uri · CWE-798, CWE-321
.github/workflows/test.yaml:158
DATABASE_URL_TEST: postgres://postgres:postgres@localhost:5432/keryx-test
MEDIUMHard-coded secrets · secret.db_uri · CWE-798, CWE-321
.github/workflows/test.yaml:190
DATABASE_URL_TEST: postgres://postgres:postgres@localhost:5432/keryx-test
MEDIUMHard-coded secrets · secret.db_uri · CWE-798, CWE-321
.github/workflows/test.yaml:222
DATABASE_URL_TEST: postgres://postgres:postgres@localhost:5432/keryx-test
MEDIUMHard-coded secrets · secret.generic · CWE-798, CWE-321
packages/plugins/admin/__tests__/admin.test.ts:1091
const secret = "s3cret-parameter-value";
LOWInventory / provenance · inv.hidden_file · CWE-1104
docs/.prettierignore
.prettierignore
Why it matters. hidden member outside the usual dotfiles
Fix. review its purpose
LOWInventory / provenance · inv.hidden_file · CWE-1104
example/backend/actions/.index.ts
.index.ts
Why it matters. hidden member outside the usual dotfiles
Fix. review its purpose
LOWCode injection · code.eval_exec · CWE-78, CWE-94, CWE-95
docs/scripts/generate-docs-data.ts:219
taskFrequency = eval(freqMatch[1].trim());
Why it matters. evaluates text as code
Fix. remove; use a parser or a dispatch table
LOWCode injection · code.eval_exec · CWE-78, CWE-94, CWE-95
packages/plugins/admin/__tests__/admin.test.ts:1186
expect(() => new Function(script)).not.toThrow();
Why it matters. evaluates text as code
Fix. remove; use a parser or a dispatch table
LOWFilesystem / path · fs.traversal · CWE-22, CWE-59
example/backend/__tests__/actions/channel.test.ts:3
import type { ChannelMembers } from "../../actions/channel";
LOWFilesystem / path · fs.traversal · CWE-22, CWE-59
example/backend/__tests__/actions/channel.test.ts:4
import type { SessionCreate } from "../../actions/session";
LOWFilesystem / path · fs.traversal · CWE-22, CWE-59
example/backend/__tests__/actions/files.test.ts:4
import type { FileUpload } from "../../actions/files";
LOWFilesystem / path · fs.traversal · CWE-22, CWE-59
example/backend/__tests__/actions/mcp.test.ts:6
import type { GreetingPrompt, StatusResource } from "../../actions/mcp";
LOWFilesystem / path · fs.traversal · CWE-22, CWE-59
example/backend/__tests__/actions/message-tasks.test.ts:11
import { MessagesCleanup, MessagesHello } from "../../actions/message";
LOWNetwork egress · net.metadata_ip · CWE-200, CWE-319
docs/guide/mcp.md:413
- The URL resolves to a publicly routable address. Loopback, private, link-local (including cloud instance-metadata endpoints such as `169.254.169.254`), CGNAT, and multicast targets are refused, and
Why it matters. cloud metadata endpoint: the classic SSRF credential grab
LOWNetwork egress · net.metadata_ip · CWE-200, CWE-319
docs/guide/security.md:147
- **SSRF guard** — the host must resolve to a publicly routable address. Loopback, private (RFC 1918), link-local (including `169.254.169.254`, the cloud instance-metadata endpoint), CGNAT, benchmarki
Why it matters. cloud metadata endpoint: the classic SSRF credential grab
LOWNetwork egress · net.metadata_ip · CWE-200, CWE-319
packages/keryx/__tests__/util/cimd.test.ts:407
"https://169.254.169.254/latest/meta-data.json",
Why it matters. cloud metadata endpoint: the classic SSRF credential grab
LOWNetwork egress · net.raw_ip · CWE-200, CWE-319
example/backend/__tests__/initializers/mcp.test.ts:807
redirect_uris: ["http://127.0.0.1:3000/callback"],
LOWNetwork egress · net.raw_ip · CWE-200, CWE-319
packages/keryx/__tests__/util/cimd.test.ts:401
"https://127.0.0.1/client.json",
LOWNetwork egress · net.raw_ip · CWE-200, CWE-319
packages/keryx/__tests__/util/cimd.test.ts:403
"https://10.1.2.3/client.json",
LOWNetwork egress · net.raw_ip · CWE-200, CWE-319
packages/keryx/__tests__/util/cimd.test.ts:404
"https://192.168.1.1/client.json",
LOWNetwork egress · net.raw_ip · CWE-200, CWE-319
packages/keryx/__tests__/util/cimd.test.ts:405
"https://172.16.0.1/client.json",
LOWObfuscation / stealth · obf.decode_call · CWE-506, CWE-94
packages/keryx/util/webBasicAuth.ts:28
decoded = atob(header.slice(6).trim());
LOWSupply chain · supply.unpinned · CWE-829, CWE-1357
docs/package.json
vitepress, vue, vitepress-plugin-llms, ts-morph
Why it matters. 4 dependency range(s) float
Fix. pin exact versions or ship a lockfile

Gates applied: no_behavioural_pass.

Audited 2026-10-08 · audit v0.4.1 · source sha 0820c51d8103full audit observations/trust-audit/mcp-server/actionhero__keryx.json · Report an issue / request a re-scan
05

Audit history

Every audit this server has had. A grade with a past is a grade somebody is still checking.

DateSourceVerdictGradeScoreChange
2026-10-080820c51d8103CAUTIOND61first audit
06

Questions

What is the Keryx MCP server?

Keryx: The Fullstack TypeScript Framework for MCP and APIs

What tools does Keryx expose?

4 in total: 4 read-only, 0 that write, and 0 that can delete or overwrite. Every one is listed on this page with its risk.

Is Keryx safe to connect to an agent?

With care. The audit graded it D (61/100) and found 25 things worth knowing before you trust this server, listed below with the exact line each was found on.

What credentials does Keryx need?

It reads RESQUE_ADMIN_PASSWORD from the environment. Give it a token scoped to the least it needs — an agent that can be talked into calling a tool can be talked into calling it with your credentials.

How does Keryx run?

It speaks streamable-http, so it runs as a service you connect to over the network. It is published on npm as @keryxjs/tracing at 0.4.1.

How current is this page?

The grade is for one exact copy of the source (0820c51d8103), read on 2026-10-08. The repository is watched and re-audited when it changes.

Advertisement