//DOCS Runtime Engine

Punk as an MCP server in Claude Code: tools, prompt ingest, MCP registry, trust.
Open App

Punk Runtime Engine

Punk speaks MCP in both directions:

  • Punk IS an MCP server. The Punk Runtime Engine side-loads Punk's runtime into your existing Claude Code (or Claude app) workflow: Claude stays the interface, Punk becomes the runtime underneath it. Your workflows, the semantic web, savings, approvals, and run explanations become native tools, and, optionally, every prompt you type lands in Punk as an observed, governed, auditable run.
  • Punk CONSUMES MCP servers. A per-tenant registry of external MCP servers (stdio or http) backs tool_call workflow nodes, with governance and side-effect classification applied before any connection is made.

This page is the usage guide for both directions. Using Punk From Claude covers the first; Using MCP Servers From Punk covers the second.

Using Punk From Claude

The engine (apps/mcp, bun run mcp) is a stdio MCP server and a pure HTTP client of the gateway: it never touches storage directly, so it can point at any Punk deployment, local or remote.

Setup

Prerequisites: a running Punk gateway (bun run dev, default http://localhost:4100) and Bun on the machine running Claude Code.

One-liner (from anywhere — --cwd points at your Punk checkout):

claude mcp add punk -- bun run --cwd /path/to/punk mcp

Or per-project via .mcp.json at your project root:

{
  "mcpServers": {
    "punk": {
      "command": "bun",
      "args": ["run", "--cwd", "/path/to/punk", "mcp"],
      "env": {
        "PUNK_GATEWAY_URL": "http://localhost:4100",
        "PUNK_MCP_API_KEY": "pk_…(only when the gateway sets PUNK_API_KEY)"
      }
    }
  }
}
VarDefaultMeaning
PUNK_GATEWAY_URLhttp://localhost:4100The Punk gateway to talk to.
PUNK_MCP_API_KEYunsetBearer token; required when the gateway runs with PUNK_API_KEY.

The engine inherits whatever its API key is allowed to do. Use a non-admin key unless you want approval decisions available in-conversation.

Verify the install without Claude:

PUNK_GATEWAY_URL=http://localhost:4100 bun run mcp --selfcheck

The selfcheck exercises the full tool surface against the live gateway through an in-process MCP client and exits non-zero on any failure.

The Nine Tools

ToolWhat it doesSay something like
punk_list_workflowsList the workflows available to run (id, name, schedule, enabled)."What Punk workflows do I have?"
punk_run_workflowRun a workflow by id or name, synchronously, with optional input variables. Returns the output plus what the run cost and what the optimizer saved."Run the support-triage workflow on this ticket."
punk_web_fetchFetch a page through the semantic web runtime: compact SOM context instead of raw HTML — the same information in far fewer tokens."Use punk_web_fetch to read example.com/pricing."
punk_web_actClick, type, select, or submit through Punk's governed web sessions. Pass url to open a session, then reuse the returned sessionId. Writes are policy-checked server-side and may be refused."Open a session on that page and click the docs link."
punk_savingsSummarize tenant savings: spend, saved, optimized-run share, cache/artifact hit rates, ghost savings."What has Punk saved us so far?"
punk_opportunitiesRank not-yet-optimized patterns by estimated monthly savings, with the next step that unblocks each."Where are the next Punk savings?"
punk_list_approvalsList pending human-in-the-loop approvals (artifact promotions, policy exceptions)."Anything waiting for approval in Punk?"
punk_decide_approvalApprove or reject a pending approval by id (requires an admin API key)."Approve approval app_3f2a with reason 'verified output'."
punk_explain_runNarrate any run's route decision (live, cache, artifact, substitution, blocked), what it cost, what was saved, and why."Explain run run_8c41 — why was it served from cache?"

Workflow runs triggered through the engine are real workflow runs (trigger mcp): node-level traces, cost/savings rollup, and the learning loop all apply, so repeated runs get cheaper (see Workflows).

Prompt Ingest Hook

An optional UserPromptSubmit hook (apps/mcp/hooks/ingest-prompt.sh) posts each Claude Code prompt to POST /api/v1/ingest/prompt. Install it in .claude/settings.json (project) or ~/.claude/settings.json (user):

{
  "hooks": {
    "UserPromptSubmit": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "PUNK_GATEWAY_URL=http://localhost:4100 sh /path/to/punk/apps/mcp/hooks/ingest-prompt.sh"
          }
        ]
      }
    ]
  }
}

Set PUNK_MCP_API_KEY in the same command string when the gateway requires auth.

What appears in the dashboard: each prompt becomes a completed observed run — provider claude-code, model external, route live, $0 cost, integrity-chained trace events — visible in the runs list, the audit trail, and savings accounting. The hook is fire-and-forget with a 1-second timeout and always exits 0. It can never block or slow down a prompt.

Privacy: the hook sends your prompt text to the Punk gateway, where it is stored in the trace ledger. Enable the gateway's redaction tenant setting first so emails, phone numbers, card numbers, and secret-shaped tokens are masked before they reach the ledger:

curl -X PUT http://localhost:4100/api/v1/settings \
  -H 'content-type: application/json' \
  -d '{"key": "redaction", "value": true}'

See Governance for what redaction masks.

Troubleshooting: Claude → Punk

SymptomCauseFix
Tool errors with cannot reach the Punk gateway at http://… — is it running?The gateway is down, or PUNK_GATEWAY_URL points at the wrong host/port.Start the gateway (bun run dev), confirm curl http://localhost:4100/health answers, and check the env in .mcp.json.
Tool errors mention HTTP 401 / unauthorizedThe gateway runs with PUNK_API_KEY (or login mode) but the engine has no PUNK_MCP_API_KEY, or the token is revoked.Create an API key (POST /api/v1/keys or dashboard Governance → API keys) and set PUNK_MCP_API_KEY in the engine env.
punk_decide_approval fails with forbiddenThe engine's key is not admin.Approval decisions are admin-only by design; use an admin key only if you want them in-conversation.
Tools never appear in ClaudeThe MCP registration is wrong or Bun is missing.Run claude mcp list, then bun run mcp --selfcheck from the Punk checkout to isolate engine vs. registration.
Prompts don't show up as runsThe hook isn't installed, or it points at the wrong gateway.Re-check the UserPromptSubmit block and the PUNK_GATEWAY_URL inside its command; the hook is silent on failure by design.

Using MCP Servers From Punk

Punk consumes external MCP servers through a per-tenant registry. Registered servers back tool_call workflow nodes; every invocation is governance-checked with a classified side-effect level before any connection is made.

Registering Servers

Registrations are stdio (command, args?, env?) or http (url, headers?). Mutations require admin; create, delete, and test are audited. The dashboard manages all of this under Governance → MCP servers, or via the API:

A stdio server (the gateway spawns the command and speaks MCP over its stdio):

curl -X POST http://localhost:4100/api/v1/mcp/servers \
  -H 'content-type: application/json' \
  -d '{
    "name": "github",
    "transport": "stdio",
    "command": "bunx",
    "args": ["@modelcontextprotocol/server-github"],
    "env": { "GITHUB_TOKEN": "cred:cred_1a2b3c" }
  }'

An http server (streamable HTTP, with a one-shot SSE fallback for older servers):

curl -X POST http://localhost:4100/api/v1/mcp/servers \
  -H 'content-type: application/json' \
  -d '{
    "name": "internal-tools",
    "transport": "http",
    "url": "https://mcp.example.com/mcp",
    "headers": { "Authorization": "cred:cred_9f8e7d" }
  }'

Validation is strict: stdio requires a non-empty command, http requires url, and http URLs pass the same SSRF guard as web fetch (scheme and shape always; private/loopback/link-local/metadata destinations blocked when auth is enabled, with the open-dev carve-out for local work) at registration and again at connect time.

Credentials: cred:<id> References

Env and header values of the form cred:<id> resolve a stored credential at connect time — tenant-checked, inside the store boundary, never echoed by the API. Store the secret once, reference it forever:

curl -X POST http://localhost:4100/api/v1/credentials \
  -H 'content-type: application/json' \
  -d '{ "name": "github-bot", "provider": "github", "secret": { "value": "ghp_…" } }'
# → { "credential": { "id": "cred_1a2b3c", … } }   (the secret is never returned)

A credential's value key (or its sole key) replaces the reference. Credentials are AES-256-GCM encrypted at rest (PUNK_ENCRYPTION_KEY); see Workflows § Credentials.

OAuth Credentials And Auto-Refresh

Google-, GitHub-, and similar OAuth2 MCP servers hand out short-lived access tokens. Punk keeps them working by storing the full OAuth2 secret and refreshing it automatically — you never have to re-paste a token.

Store the OAuth shape (all string values — the vault holds string → string):

curl -X POST http://localhost:4100/api/v1/credentials \
  -H 'content-type: application/json' \
  -d '{ "name": "google-mcp", "provider": "google", "secret": {
        "access_token":  "ya29.…",
        "refresh_token": "1//…",
        "expires_at":    "1750000000000",
        "token_url":     "https://oauth2.googleapis.com/token",
        "client_id":     "…apps.googleusercontent.com",
        "client_secret": "…",
        "scope":         "https://www.googleapis.com/auth/drive.readonly"
      } }'

expires_at is the absolute expiry in epoch milliseconds. Reference the credential from the http server's Authorization header:

{
  "name": "google-drive",
  "transport": "http",
  "url": "https://mcp.example.com/sse",
  "headers": { "Authorization": "cred:cred_google" }
}

When the resolved credential has the OAuth shape, Punk sends Authorization: Bearer <access_token> and:

  • Refreshes ahead of expiry. If expires_at is within 60 seconds at connect time, Punk exchanges the refresh token (grant_type=refresh_token POST to token_url) for a fresh access token before connecting.
  • Refreshes on a 401. If the server rejects the token with an auth error, Punk refreshes once, reconnects, and retries the call — exactly once, so a genuinely revoked grant surfaces rather than looping.
  • Re-encrypts the rotated secret. After any refresh, the new access_token / expires_at (and the rotated refresh_token, if the provider issued one) are written back into the credential vault, so the next call starts from the latest tokens.

The dashboard's Governance → Credentials form has an + OAUTH2 FIELDS button that pre-fills these keys. Non-OAuth cred:<id> references keep the existing value-key behavior unchanged.

Connection Testing

POST /api/v1/mcp/servers/:id/test connects (resolving any cred: references), lists tools, and persists status (ok/error), toolCount, and the last error onto the registration:

curl -X POST http://localhost:4100/api/v1/mcp/servers/mcp_…/test
# → { "ok": true, "toolCount": 12, "tools": [{ "name": "search_issues", … }], "latencyMs": 240 }

GET /api/v1/mcp/servers/:id/tools serves the tool list cached from the last test and re-lists live when it is older than 10 minutes. The dashboard's TEST button shows latency and tool chips inline.

Connections are pooled per server, closed after 5 idle minutes, and evicted whenever the registration changes — an edited server never keeps serving through a stale connection.

Binding Tools Into Workflows

A tool_call node binds a registered server's tool into a workflow graph. Config: serverId (required), tool (required), args (map of argument name → IRExpr), saveAs? (vars key, default tool). A concrete node:

{
  "id": "lookup",
  "kind": "tool_call",
  "next": "summarize",
  "config": {
    "serverId": "mcp_4d5e6f",
    "tool": "search_issues",
    "args": {
      "query": { "kind": "template", "template": "{{input.topic}} label:bug" },
      "limit": { "kind": "literal", "value": 5 }
    },
    "saveAs": "issues"
  }
}

The tool result lands in the vars environment under saveAs (structured content preferred, JSON-parsed text otherwise) and downstream nodes reference it like any other variable ({ "kind": "ref", "path": "issues" }). See Workflows for the full IR.

Governance And Side Effects

Before any connection is made, the gateway classifies the tool's side-effect level (declared level wins; otherwise the tool name is matched against verb heuristics — delete/pay-shaped names land at 4, send/create at 3, reads at 1, unknown names default to 3, conservative) and runs the governance check as agent workflow-engine with the action actionForTool derives (read:<resource> for levels ≤ 1, write:<resource> for 2–3, destroy:<resource> for 4).

  • deny AND approval_required fail the node CLOSED — an unattended workflow cannot wait on a human.
  • tool.called / tool.completed events (with the classified side-effect level) land on the workflow run's integrity-chained ledger.
  • Policy checks and decisions are audited like every other governed action.

Endpoints

MethodPathPurpose
GET/api/v1/mcp/serversList registered servers.
POST/api/v1/mcp/serversRegister (stdio requires command, http requires url).
GET/api/v1/mcp/servers/:idRead one.
PATCH/api/v1/mcp/servers/:idUpdate; re-checks transport coherence and evicts the pooled connection.
DELETE/api/v1/mcp/servers/:idDelete.
POST/api/v1/mcp/servers/:id/testConnect, list tools, persist status/tool count/latency.
GET/api/v1/mcp/servers/:id/toolsTool list, cached from the last test; re-listed live when older than 10 minutes.

Troubleshooting: Punk → MCP Servers

SymptomCauseFix
Test returns ok: false with a spawn errorstdio command not found on the gateway host.The command runs where the GATEWAY runs, not where your browser is — install it there, or use an absolute path.
Test fails with a URL/SSRF errorhttp url points at a private/loopback address with auth enabled.Use a public endpoint, or for trusted local work set PUNK_ALLOW_PRIVATE_WEB_FETCH=true (or run open dev mode).
Tool fails with an auth error on the remote serverA cred:<id> reference is wrong, deleted, or belongs to another tenant.List /api/v1/credentials, re-store the secret, and update the server's env/header value.
tool_call node fails with blocked by policyGovernance denied the derived action, or the rule requires approval.Check Governance → Audit for the verdict; adjust the policy or the tool's declared side-effect level. Approval-required rules always fail workflow nodes closed.
Tools list is stale after editing the serverYou are inside the 10-minute cache window.POST /:id/test refreshes immediately (PATCH also evicts the pooled connection).

Trust Boundaries

  • A stdio registration executes its command on the gateway host. That is operator-level configuration, the same trust level that can set webhook URLs and promote artifacts, which is why every registry mutation is admin-gated. Do not register commands you would not run on the gateway host yourself.
  • http URLs go through the same SSRF guard as web fetch at registration and again at connect time.
  • Tool invocations from workflows are governance-checked first with classified side-effect levels; deny and approval_required fail the node closed.
  • The Runtime Engine itself holds no secrets beyond its gateway API key and inherits whatever that key is allowed to do.