Documentation

Docs & MCP reference.

Quickstart

DenkOps is driven from Claude Code through the MCP interface, with no CLI to learn. Install the plugin, log in with one browser click, then just ask Claude to deploy. DenkOps packs your directory, picks a build lane, and returns a live, SSL-secured URL.

/plugin marketplace add DenkOpsAI/denkops-mcp
/plugin install denkops
log in to DenkOps # browser opens → Approve
deploy on DenkOps
→ live at https://my-agent.denkops.app

Build lanes

Two optimized paved-road templates. The bun-hono fast lane for lightweight APIs and MCP servers; the python-fastapi heavy lane for AI and long-running workloads. No Dockerfile needed, or bring your own container.

MCP interface

The MCP server is the primary interface. After a one-time browser login (say “log in to DenkOps” → Approve), Claude talks straight to the control plane and operates your infrastructure via tools.

ToolDoes
deployPack + build + ship the current project
logsFetch logs + ai_hint for a deploy
statusHealth, version, resource usage
rollbackActivate a previous version
set_env / list_env / unset_envManage env vars (write-only)
redeployRe-run the current version (apply env changes)

Install in Claude Code

Three steps: install the plugin, log in with a browser click, then just ask. After that Claude drives DenkOps directly from your editor: deploy, read logs and roll back, with no CLI and no token to copy.

1 · Install the plugin

Add the DenkOps marketplace and install the plugin from inside Claude Code:

/plugin marketplace add DenkOpsAI/denkops-mcp
/plugin install denkops

2 · Log in

Say “log in to DenkOps”. A browser tab opens on the dashboard. Click Approve. No token to copy or paste.

log in to DenkOps
→ browser opens on the dashboard, click Approve

3 · Deploy & manage

Say “deploy on DenkOps”: Claude packs, builds and ships the current project, and returns a live URL plus a DENKOPS_API_KEY. Manage it straight from the chat, or call the API directly with Authorization: Bearer <DENKOPS_API_KEY> (/health is public, no key required).

deploy on DenkOps
→ live at https://my-agent.denkops.app
read the logs
rollback
status

Connect other agents

Claude Code isn't required. DenkOps ships a standalone MCP server, @denkopsai/mcp, that any MCP-capable agent can run over stdio. In the dashboard open Connect, pick your agent (Codex, OpenClaw, Hermes, or “Other”), and mint a scoped token. The page hands you a copy-paste command with the token already filled in.

# Codex
codex mcp add denkops -- npx -y @denkopsai/mcp
# env: DENKOPS_TOKEN=dko_… DENKOPS_CONTROL_PLANE_URL=https://api.denkops.com

Any other MCP client works the same way: run npx -y @denkopsai/mcp with DENKOPS_TOKEN and DENKOPS_CONTROL_PLANE_URL set. Claude Code users skip this entirely; the plugin bundles the same server.

Connections & tokens

Every agent connects with a scoped, least-privilege token, minted per agent from the dashboard Connect page and owner-managed:

  • Scoped: pin a token to specific projects (out-of-scope calls return 403), choose whether it may create new projects, and grant or withhold environment-variable access (default: none).
  • Expiring: 30, 90, 365 days, or no expiry, chosen at mint time.
  • Revocable: revoke a connection in one click; that token immediately returns 401.
  • Auditable: each connection shows its agent type, when it connected, last-active time, and a per-connection activity log.

Claude Code's browser login (“log in to DenkOps” → Approve) issues one of these scoped tokens automatically, so you never copy or paste it.

Deploy from GitHub Actions

Auto-deploy on git push: a GitHub Actions job runs a one-shot deploy against DenkOps. Three steps.

1 · Mint a CI token

On the dashboard Connect page, mint a connection token pinned to this project with create disabled (least privilege, redeploy-only). The token is shown once and is your CI secret.

2 · Store it as a GitHub secret

In your repo: Settings → Secrets and variables → Actions → New repository secret, named DENKOPS_TOKEN, with that token as the value.

3 · Add the workflow

Commit .github/workflows/deploy.yml. On every push to main it packs the repo and deploys, failing the job on a bad deploy.

name: Deploy to DenkOps
on:
  push:
    branches: [main]
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: "20"
      - run: npx -y @denkopsai/mcp deploy
        env:
          DENKOPS_TOKEN: ${{ secrets.DENKOPS_TOKEN }}
          DENKOPS_CONTROL_PLANE_URL: https://api.denkops.com

Auth is env-only (DENKOPS_TOKEN + DENKOPS_CONTROL_PLANE_URL), no browser login. The same command works in any CI that can run npx.

Scheduled jobs (cron)

Scheduled jobs run on the same always-on slot as your app, on standard 5-field cron syntax, UTC by default. There are two paved paths, pick by runtime and how much visibility you want.

1 · denkops.cron (SDK)

An in-process helper from the @denkopsai/sdk, for Bun and JS apps. Schedule work directly in your code, no separate config file:

import { denkops } from "@denkopsai/sdk";

denkops.cron("0 2 * * *", async () => {
  await job();
}, { name: "nightly", timezone: "UTC", catchUp: true, overlap: false, onError: (err) => log(err) });

Shorthand schedules like @daily, @hourly, or @every 5m work too. State lives on /persist, so a restart does not skip or double-run a job. Check on it with denkops.cron.status(name).

2 · Managed declarative cron (denkops.json)

For Python or any non-Bun runtime, declare schedules in denkops.json instead. DenkOps calls your app's path on schedule, over HTTP:

{
  "cron": [
    { "schedule": "0 2 * * *", "path": "/jobs/nightly", "method": "POST", "timezone": "UTC", "name": "nightly" }
  ]
}

schedule and path are required. method defaults to POST, timezone defaults to UTC, and name is optional. Managed crons show up on the dashboard's Scheduled jobs panel and can be triggered by hand from there, or via the MCP tools list_crons and run_cron.

Which do I pick?

WantUse
Bun app, job is in-process codedenkops.cron (SDK)
Python or any non-Bun appmanaged cron (denkops.json)
Schedule visible and manually triggerable from dashboard or agentmanaged cron (denkops.json)
No HTTP hop, tightest in-process controldenkops.cron (SDK)

Self-healing

When a deploy crashes, DenkOps attaches a machine-readable ai_hint with the likely cause, file and line. Claude reads it, edits forward, and redeploys. No human in the loop for the common failures.

Build a connector

Turn a deployed app into a custom connector Claude can call. A connector speaks the MCP protocol over POST /mcp, so your app has to serve that endpoint. The paved road is defineMcp from @denkopsai/sdk/mcp, which serves the whole protocol for you. Ask Claude to “build an MCP on DenkOps” and it scaffolds exactly this:

// index.ts
import { defineMcp } from "@denkopsai/sdk/mcp";
import { z } from "zod";

export default defineMcp({
  name: "explainer-video",
  version: "1.0.0",
  tools: {
    render: {
      description: "Render an explainer video from a script",
      input: z.object({ script: z.string() }),
      handler: async ({ script }) => ({ url: await render(script) }),
    },
  },
});

export default the app and DenkOps serves it. Do not bind a port or hand-roll JSON-RPC: the SDK handles tools/list, tools/call, resources and prompts, plus a tolerant handshake that matches whichever protocol version the client asks for. input takes a Zod schema (validation, types, and the shape shown to the agent) or a plain JSON Schema.

OAuth & access

Set "connector": true in denkops.json and redeploy. That wraps your /mcp endpoint in the DenkOps OAuth layer at https://<slug>.denkops.app/mcp. You write no auth code: DenkOps runs the whole sign-in flow (dynamic client registration and PKCE) and publishes the discovery document Claude needs. In Claude, add a custom connector with that /mcp URL and authorize.

The flag adds OAuth in front of /mcp, it does not make a plain app speak MCP. If your app has no /mcp route, Claude authorizes and then hits nothing. Serve /mcp first with defineMcp, then flip the flag.

Access is owner-controlled on the dashboard Connect page:

  • Public: anyone who reaches the URL and signs in.
  • Email allowlist: only the exact addresses you list.
  • Members only (default): anyone in your workspace.

People authorizing sign in with GitHub, Google or Microsoft. Every connection is listed on the Connect page and revocable in one click, effective immediately.

Troubleshooting

If Claude authorizes your account but then fails to connect, its message tells you which half is wrong:

Claude saysWhat to fix
“no MCP server was found at the provided URL”Your app is not serving POST /mcp, the URL does not end in /mcp, or "connector": true was not deployed. Add defineMcp and redeploy.
“…or your account doesn’t have access to it”The connection was revoked or does not match the project. Re-authorize the connector in Claude.

Quickest check: call your own endpoint with your DENKOPS_API_KEY. A 200 with a protocolVersion means /mcp works and the issue is the connection or the URL; a 404 means the /mcp route is missing.

curl -i -X POST https://<slug>.denkops.app/mcp \
  -H "authorization: Bearer $DENKOPS_API_KEY" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}'

Note: /health is not a connector reachability check. On a connector project the OAuth layer gates every path, so /health answers 401 without a key. That is expected; use the check above instead.

Zero-trust egress

All outbound traffic is denied by default. Whitelist domains explicitly; everything else is blocked and logged. Manage it with denkops egress add <domain>.

Secrets

Environment variables are managed by owners from the dashboard's project Environment variables panel, or via the MCP tools set_env, list_env, and unset_env. Agent tokens can read or write them only when granted the env scope at mint time (default: no access). Values are write-only, so they never appear in logs or the dashboard. A Redeploy (dashboard button or the redeploy MCP tool) applies staged changes.

Persistent state

Each project has a durable disk mounted at /persist, and it survives redeploys, restarts and crashes. The @denkopsai/sdk (bun) / denkops (python) SDK gives you a one-line key-value store there: denkops.store.set(key, value) / .get(key), persistence without wiring up an external database.