Install
$ agentstack add mcp-tsukumohq-dokan Open-source listing, not yet scanned by AgentStack. Follow the source repository for install instructions.
Security review
⚠ Flagged1 finding(s); flagged for manual review. · v0.1.0 How review works →
- • Prompt-injection patterns
- • Secret / credential exfiltration
- • Dangerous shell & filesystem operations
- • Untrusted network calls
- • Known-malicious package signatures
- high Pipes remote content directly into a shell (remote code execution).
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ● Environment & secrets Used
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
dokan (導管)
Your AI coding agent builds and runs the workflow. You don't click.
Agent-operated automation runtime · deterministic scripts in Docker · zero LLM inside · Apache-2.0
dokan is an automation runtime built for the agent era. Instead of a human clicking through a UI, your coding agent stands up, runs, and schedules workflows itself by talking to dokan over MCP. The platform runs deterministic code in clean containers and burns zero tokens: the expensive intelligence stays in your agent, outside the runtime.
Think Sidekiq/cron for AI agents: the agent scripts the mechanical 80%, dokan executes it cheaply and reliably, you don't touch a dashboard.
Why dokan
- Agent-operated. your agent uploads, wires, triggers, reads logs over MCP. No UI.
- Zero LLM inside = zero token burn. deterministic code, not LLM-in-the-loop. The platform never spends tokens to run your workflows.
- Hermetic by default. one job = one clean container, per-job CPU/mem caps, timeouts, retries. Jobs run with network disabled by default (v0.4.0) — output is a pure function of its inputs, so identical inputs hit a content-addressed cache instead of recomputing, and the run is provable by re-execution. Set
network=truefor a job that must reach an API (most monitors do). Every run carries a tamper-evident receipt (advisory for networked runs, since their output can depend on the outside world). - Real triggers. cron + inbound webhooks (POST /hook/, Stripe/Calendly/GitHub-ready).
- Token-frugal. every MCP response is shaped for an agent's context budget — IDs over payloads, paginated logs, counts over dumps.
How it works
flowchart LR
agent["your coding agent"] -- MCP --> dokan["dokan daemon(Rust · axum + rmcp)"]
dokan -- state --> pg[("Postgres")]
dokan -- one job, one container --> c1["job container(isolated; hermetic by default, network=true to opt in)"]
c1 -- "stdout · ::dokan:result::" --> dokan
dokan -- "result POST" --> relay["your agent / relay"]
human["operator (optional)"] -. watch .-> cockpit["cockpit /"]
cockpit --- dokan
A single Rust daemon (axum + an rmcp MCP server, stdio or Streamable HTTP). State lives in Postgres. Execution is one job → one fresh container (python:3.12-slim / node:22-slim / alpine), discarded after the run, with per-job caps and a hard timeout. Logs stream into Postgres, served cursor-paginated. A thin operator cockpit runs at /, Prometheus at /metrics. No LLM runs inside dokan — the agent is the only place tokens are spent.
Quickstart
Prereq: Docker running (Docker Desktop, or colima / podman). The daemon talks to the default Docker socket — set DOCKER_HOST for colima/podman. No Rust toolchain needed.
One command stands up the runtime and installs the Claude Code operator skill:
curl -fsSL https://raw.githubusercontent.com/TsukumoHQ/dokan/main/install.sh | sh
It downloads and SHA-256-verifies the binary into ~/.local/bin, lands the operator skill in ~/.claude/skills/dokan/ (where Claude Code loads it), generates per-install crypto keys (secure-by-default — secrets sealed at rest, receipts signed; saved 0600 in ~/.dokan/dokan.env), starts Postgres on :5499, and brings up the daemon on 127.0.0.1:8088. Re-running is safe (idempotent). Then wire your agent:
claude mcp add --transport http dokan http://127.0.0.1:8088/mcp
Verify it's up: open (the operator cockpit) — once it loads, an agent can wire MCP and run scripts. Schema migrations apply automatically on boot.
Build from source instead (contributors / unsupported platforms)
Needs a Rust toolchain. The default DATABASE_URL already points at the compose database, so there's nothing to configure.
docker compose up -d # Postgres state store (pgvector) on :5499
cargo build --release
./target/release/dokan # HTTP daemon on 127.0.0.1:8088 — UI at /, MCP at /mcp
You'll need crypto keys set (DOKAN_SECRET_KEY, DOKAN_RECEIPT_KEY, DOKAN_RECEIPT_ED25519_SECRET) or DOKAN_DEV_INSECURE=1 for local dev — the daemon fails closed otherwise.
Run your first job
With the daemon up, run the flagship demo — it drives a real DAG over MCP exactly the way an agent does (uploads 4 deterministic steps, wires a flow with map fan-out + a when branch, runs it, prints the result). No secrets, no job network, fully reproducible. Needs curl + jq:
./examples/flagship/run.sh # override the daemon with DOKAN_ADDR=host:port
That's the whole loop — upload → compose → run → read — in one command. See [examples/flagship/](examples/flagship/) for the steps and the expected output.
Wire into your agent (MCP)
Point your agent's MCP config at the daemon:
"dokan": { "type": "http", "url": "http://127.0.0.1:8088/mcp" }
Your agent now has the full dokan toolset over MCP.
Core concepts
Scripts
A script is code in python | node | bash. It reads its input from the DOKAN_INPUT env var (a JSON string — not stdin, not argv). Secrets arrive as their own env vars. upload_script(..., upsert=true) re-provisions by name idempotently, so a respawning agent never leaves orphan duplicates. A script's nonzero exit is treated as the script's own deterministic verdict (e.g. a monitor finding) and is not retried — only a genuine infra failure (container vanished / timeout) retries.
Flows (DAG)
compose_flow wires scripts into a validated, acyclic graph; run_flow runs it and the engine drives the DAG. Each step is one container run that sees {flow_input, deps, step} as its DOKAN_INPUT.
flowchart LR
intake["intake"] --> score["score(map: one run per order)"]
score --> summarize["summarize(structured result + branch token)"]
summarize -- "when deps.summarize == FLAGGED" --> alert["alert"]
summarize -- else --> done(["done"])
The engine gives you:
mapfan-out — one child run per item; the parent collapses the children into a{n, ok, failed}count instead of listing each.whenbranches — a step runs only if a predicate over an upstream result holds.compensate(saga rollback) — a step can declare a compensating action that runs if a later step fails, so a partially-applied flow unwinds cleanly.- retries with backoff on transient failures.
- step-boundary durability — progress is committed at each step, so a crashed engine resumes; succeeded steps are skipped. Steps must be idempotent (a dying step re-runs).
Schedules & triggers
schedule(script_id, cron)— 6-field cron with leading seconds (0 */5 * * * *= every 5 min). A 5-field expression is rejected loudly rather than silently never firing. Each tick enqueues a run.create_webhook— an externalPOST /hook/enqueues a script or flow with the request body as input. The unguessable URL token is the auth (the endpoint sits outside the bearer gate).
Secrets
set_secret(name, value) once → available to a job as a tmpfs file at /run/secrets/ (mode 0400, never persisted) and, for back-compat, as an env var (e.g. $OPENAI_API_KEY). Write-only: values are never returned or logged; list_secrets shows names only. By default a script gets all globals; scope it with a per-script allowlist — upload_script(..., secrets=["openai_key"]) injects only those.
Resource limits
Per-job caps default to 1024 MiB / 2.0 CPU, set globally on the daemon. A single heavier script can override them — upload_script(..., mem_limit_mb, cpu_limit) — and only that script runs on a dedicated container with the raised cap; everything else is untouched.
Structured results & stateful monitors
A job prints a line ::dokan:result:: {json} on stdout; dokan captures the last one as the run's structured result. It is returned by wait_for/read_logs and POSTed to the relay on completion — so a monitor emits a finding and the agent reacts event-driven, no polling.
For a monitor that should fire only when something changes, set upload_script(..., feed_prev_result=true): dokan feeds the previous run's structured result into the next run as DOKAN_INPUT.prev_result. The monitor reads prev_result.state, diffs against the current state, emits the new state, and exits nonzero on a change — a cross-run diff with no host files and no external store, on a stateless runtime.
Run artifacts (input files)
To hand a job a real document — a PDF, a dataset, a big .md — without stuffing it into DOKAN_INPUT (an env var, ~100 KB): upload_blob(bytes) stores it in a content-addressed blob store (re-uploading identical bytes deduplicates) and returns a handle; run_script(..., files={"doc.md": ""}) materializes each file read-only at /input/ in the container. The blob hashes fold into the run's cache key and receipt, so a job that reads /input stays a pure function of its declared inputs.
Determinism & receipts
A network=false job is a pure function of its inputs — source + DOKAN_INPUT + input-file blobs + the pinned image digest. Two consequences:
- Content-addressed cache.
run_script(..., cache=true)recalls a prior identical success instead of recomputing — no container spawned. - Tamper-evident, publicly-verifiable receipt. every run carries a receipt binding (image digest, source hash, input hash, output hash, input-blob hashes). It's HMAC-keyed for holders of
DOKAN_RECEIPT_KEYand Ed25519-signed inside an in-toto / DSSE envelope — so a third party canverifyit offline with only the public key (no shared secret), andreproducere-executes the run and byte-compares the output against the receipt (REPRODUCED/DIVERGED/TAMPERED). A networked job's receipt is advisory, since its output can depend on the outside world. - Honesty note: the structured
::dokan:result::output is compared by JSON value (object key order doesn't matter). Output FILES are compared by raw bytes — dokan does not yet canonicalize file-packaging metadata, so an artifact that differs only in non-content metadata (gzip mtime, tar entry order, embedded build timestamps) willDIVERGE. Emit reproducible archives (e.g.gzip -n, sortedtar,SOURCE_DATE_EPOCH) for a cleanREPRODUCED. (Canonicalizing this safely is a tracked follow-up.)
MCP surface (token-frugal)
| Tool | Returns | |---|---| | searchscript · listscripts · getscript | ranked / listed IDs + 1-line desc; bodies only on request | | uploadscript | scriptid + version (flags: network, mem/cpu limit, feedprevresult) | | runscript | runid immediately, never blocks (input + files for /input artifacts) | | readlogs · waitfor | cursor logs / long-poll to terminal + tail + structured result | | schedule · listschedules · unschedule | cron a script (6-field) | | composeflow · runflow · getflowrun | declarative DAG; per-step status (map children collapsed to a count) | | createwebhook · listwebhooks · deletewebhook | inbound HTTP trigger to a script/flow | | uploadblob · downloadblob · listblobs | content-addressed input/output files | | getreceipt · verify · reproduce | fetch / offline-Ed25519-verify / re-execute + byte-compare a run's receipt | | setsecret · listsecrets | write-only secrets → /run/secrets tmpfs (+ env); per-script allowlist | | onresult · listtriggers · deletetrigger | reactive triggers: enqueue a script when a result matches | | cancel · listruns · listexecutors | … |
Server instructions ship in-band so the agent self-limits.
Status
v0.4.x — beta / preview. Active development, built and run in production by the team that makes it (we run our own agent fleet's automation on dokan). Ready for: demos, design partners, technical early adopters. Not yet turnkey multi-tenant enterprise (no SSO/RBAC/HA; the MCP control plane is unauthenticated + single-tenant — see [SECURITY.md](SECURITY.md)). Honest about where it is.
License
Apache-2.0. Use it, embed it, build on it.
dokan is one of four open tools from the Tsukumo studio for running AI agents well at scale: trovex (canonical context) · yoru (session record + replay) · dokan (job runtime) · wrai.th (agent relay).
Source & license
This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: TsukumoHQ
- Source: TsukumoHQ/dokan
- License: Apache-2.0
- Homepage: https://tsukumo.ch/open-source
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.