Repository navigation
Releases: kentcdodds/kody-celld
Release list
v0.2.0 — external MCP servers, package apps, one private-host allowlist
v0.2.0 — external MCP servers, package apps, one private-host allowlist
33 commits since v0.1.1. Two breaking configuration changes. Read "Before you upgrade" first. The node refuses to start while a removed setting or provider name is still set, and the error points at the notice to follow.
If you run
ghcr.io/kentcdodds/kody-celld:latest, you already have these changes::latestfollows main. Pin:0.2/:0.2.0for versioned upgrades.
Before you upgrade (breaking)
1. One private-host allowlist: KODY_PRIVATE_HOSTS (#66, notice #62). Four settings are removed:
KODY_MCP_ALLOW_PRIVATE_HOSTS;KODY_BROWSER_ALLOW_PRIVATE_HOSTS;KODY_PACKAGE_SOURCE_HOSTS;KODY_ALLOW_INSECURE_SECRET_HOSTS.
To migrate:
- Merge their entries into one comma-separated
KODY_PRIVATE_HOSTS. It accepts hosts,*.suffix, IPs and CIDRs. - Delete the old variables.
- Replace a bare
loopbackentry with explicitlocalhost,127.0.0.1,::1(or127.0.0.0/8).
Plain http: secret fetches are now allowed only to hosts on this list. Private package sources (Gitea, host.docker.internal, …) must be listed too.
2. Generic adapters only (#67, notice #63).
| Removed | Use instead |
|---|---|
Outbound email resend, postmark, mailgun, sendgrid |
KODY_EMAIL_OUTBOUND_PROVIDER=bridge with the mail-bridge sidecar relaying over SMTP (compose.mail.yaml). Resend, for example, becomes MAIL_BRIDGE_SMTP_URL=smtps://resend:<key>@smtp.resend.com:465 |
Inbound email postmark, mailgun, sendgrid, cloudflare |
/email/inbound/bridge (mail-bridge, or the Cloudflare Email Worker in examples/cloudflare-email-forwarder) or /email/inbound/generic |
KODY_AI_PROVIDER=anthropic |
openai with any OpenAI-compatible KODY_AI_BASE_URL |
KODY_BROWSER_PROVIDER=cloudflare |
browserless (compose.browser.yaml) |
| Separate R2 blob lane | KODY_BLOB_PROVIDER=s3 (S3-compatible). A bound BLOBS bucket still works |
The provider-only knobs go with them, e.g. KODY_EMAIL_MAILGUN_* and KODY_BROWSER_CF_ACCOUNT_ID.
Highlights
External MCP servers (#44).
mcpServerAdd({ name, url })adds remote MCP servers. Call their tools from execute askody.mcp[name].tool(input)(#46).- OAuth for MCP servers (#48):
- three ways for Kody to identify itself: a pre-registered client, a Client ID Metadata Document (served at
/oauth/client-metadata.json), or Dynamic Client Registration; - a consent page, then a callback;
- host-side refresh;
mcpServerReconnect.
- three ways for Kody to identify itself: a pre-registered client, a Client ID Metadata Document (served at
- Hardening: OAuth attempts are bound to their server and client (#57, #65); cross-origin authorization endpoints are refused, and the 401 challenge is used (#60); the CSP allows an
http:LAN authorization server (#56); DNS answers are reused per host within one call (#74). - Hosts are checked against
KODY_PRIVATE_HOSTSat each hop. The DNS lookup used for that check goes throughKODY_DNS_RESOLVER_URL.
Package apps (#42).
- Packages that declare
kody.appare served as server-rendered browser apps on a separate app origin set by the newKODY_APP_BASE_URL. It supports path mode (https://apps.example) and subdomain mode (https://*.apps.example) (#79). - The Open app link and
packageAppUrl()go through a one-minute, single-use handoff bound to the browser that started it. - Owner-only. Kody credentials never reach app code.
kody.app.assetsis served under/_assets/*(#80). For now that means text files only, andkody.app.clientis not built yet.- Off unless configured. See
docs/package-apps.md; use a different registrable domain fromKODY_PUBLIC_URL.
Packages.
- TypeScript, TSX and JSX packages and ad hoc TypeScript run (#38).
- Git packs are inflated with a pinned pako (#35).
- Each package file is stored in its own row (#35).
- Only real imports are rewritten (and unresolvable ones are named).
- The package files explorer arrives, with its follow-ups (#30, #31).
- The preview page shows the package description (#32).
- Fixes for URL-cache freshness, SHA pinning before tarball download, and
.dev.varshost merging (#43). packageStorageClearskips celld's litestream tables (#40).
Runs.
- Run triage with
runSummary,runUpdateandrunUpdateBulk, plus auto-resolve and triage on the Activity page (#33). - Package apps record failures as
appruns.
Search and docs.
guide:provisionmatches what this build ships (#76).- The docs cover
KODY_PRIVATE_HOSTSand the generic adapters throughout (#75, #78). - Imported principles (#61), a faithful self-host architecture decision (#70), and the contributor PR bar and steward checklist (#71).
- Files copied from kody are pinned and checked in CI (#68, #72).
New settings
KODY_PRIVATE_HOSTS: the one private-host allowlist (replaces four, see above).KODY_APP_BASE_URL: the package-app origin (optional; unset means apps are off).KODY_DNS_RESOLVER_URL: the DNS-over-HTTPS resolver for MCP host checks.KODY_MCP_CALL_TIMEOUT_MS.KODY_QUOTA_MCP_SERVERS.
The Docker entrypoint now forwards KODY_DNS_*, KODY_PRIVATE_* and KODY_APP_* to the Worker.
Upgrade
- Back up the data volume (
/data). - Apply the two migrations above to your
.env/ compose. - Set the image to
ghcr.io/kentcdodds/kody-celld:0.2.0, thendocker compose pull && docker compose up -d. - Check that
/healthreports"version":"0.2.0".
Full changelog: v0.1.1...v0.2.0
v0.1.1 — keep dev email defaults out of single-node Docker
Security fix release for single-node Docker installs.
What was wrong
Before 0.1.1, the single-node Docker image (docker/entrypoint.sh → celld dev) inherited development/smoke values from wrangler.jsonc for any email setting the operator left unset:
- A public inbound email token. Anyone who could reach the node could authenticate to
/email/inbound/*and/email/events/*(and triggeremail.message.receivedsubscriptions) even though the operator never configured email. - A loopback outbound bridge URL.
KODY_EMAIL_OUTBOUND_PROVIDER=resend(or another API provider) withoutKODY_EMAIL_OUTBOUND_URLsilently targetedhttp://127.0.0.1:9796instead of the provider's API.
Fleet mode (compose.fleet.yaml) was not affected.
What changed
- Email and private-host/insecure-host smoke defaults are gone from
wrangler.jsonc; local dev seeds them from.dev.vars.example→.dev.vars(npm run dev). /email/inbound/*and/email/events/*return501 email_inbound_disabledwhenKODY_EMAIL_INBOUND_TOKENis not set. There is no default token.- Resend and other API providers default to their API URL (e.g.
https://api.resend.com) whenKODY_EMAIL_OUTBOUND_URLis unset. - Added
SECURITY.md(private vulnerability reporting).
Upgrade
docker compose pull && docker compose up -dImage: ghcr.io/kentcdodds/kody-celld:0.1.1 (linux/amd64, linux/arm64).
If you use inbound email, set KODY_EMAIL_INBOUND_TOKEN to your own secret value. If you use the SMTP bridge, set KODY_EMAIL_OUTBOUND_URL explicitly.
Credit
Thanks to Alan Żur for responsibly reporting this issue.
Full changelog: v0.1.0...v0.1.1
v0.1.0 — first public self-hosted release
First public release of kody-celld — a self-hosted Kody that runs on Deno celld (Workers + Durable Objects on your own machines), from a single Docker container on a NAS to a multi-node fleet on an S3-compatible bucket.
Try it (Docker, five minutes)
Prebuilt multi-arch image: ghcr.io/kentcdodds/kody-celld:0.1.0 (linux/amd64, linux/arm64).
- Save the compose file from README → Self-host it and run
docker compose up -d curl http://<host>:8080/health, then openhttp://<host>:8080/and finish Set up Kody- Point an MCP client at
http://<host>:8080/mcp(OAuth-capable clients only need the URL; others use akc_…token from/account/tokens)
Full walkthrough, NAS specifics, overlays (local AI, headless browser, SMTP bridge, self-hosted npm CDN) and the fleet path: docs/getting-started.md.
What's in it
- MCP
search+execute(streamable HTTP), built-in OAuth 2.1 authorization server (DCR + PKCE) so clients connect with just the URL - Packages: save/run,
kody:imports,packageStorage(), install from GitHub/URL, per-install community catalog, durable npm cache (+ self-hostable ESM CDN) - Secrets: encrypted at rest,
{{secret:…}}placeholders injected only at the network boundary for admin-approved hosts; provider-backed secrets ({{secret/<provider>:…}}) and OAuth integrations ({{integration-token:…}}) - Jobs on celld's real cron; run history, quotas, audit log, master-key rotation
- Memories + hybrid search (SQLite FTS5 + vectors, Qdrant adapter), AI adapters (OpenAI-compatible, Anthropic), blob storage (R2 binding / S3), browser rendering adapters, inbound/outbound email adapters + SMTP mail-bridge, package webhooks
- Web UI (setup, sign-in, account, operator console, community) on the same Remix 3 / design-system stack as kody
- Docker single-node +
compose.fleet.yaml(MinIO + 2 nodes + Caddy); every PR runs the full smoke suite against the Docker image
How every formerly-Cloudflare piece is provided (built-in vs. adapter): docs/known-gaps.md.
Verified for this release
From-zero run against this prebuilt image (not a local build): docker compose pull && up -d → /health → first user → search/execute via the stock MCP Inspector → secretSave → {{secret}} fetch denied (secret_host_not_approved) → host approved → delivered → down/up with everything intact.
Feedback
Found a bug or a gap in the docs? Open an issue with your platform (NAS model / Docker version / arch) and the docker compose logs kody tail.
License: FSL-1.1-ALv2 (same as kody).