Skip to content

Releases: kentcdodds/kody-celld

v0.2.0 — external MCP servers, package apps, one private-host allowlist

Choose a tag to compare

@kody-bot kody-bot released this 12 Oct 22:54
815e5b3

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: :latest follows main. Pin :0.2 / :0.2.0 for 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:

  1. Merge their entries into one comma-separated KODY_PRIVATE_HOSTS. It accepts hosts, *.suffix, IPs and CIDRs.
  2. Delete the old variables.
  3. Replace a bare loopback entry with explicit localhost, 127.0.0.1, ::1 (or 127.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 as kody.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.
  • 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_HOSTS at each hop. The DNS lookup used for that check goes through KODY_DNS_RESOLVER_URL.

Package apps (#42).

  • Packages that declare kody.app are served as server-rendered browser apps on a separate app origin set by the new KODY_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.assets is served under /_assets/* (#80). For now that means text files only, and kody.app.client is not built yet.
  • Off unless configured. See docs/package-apps.md; use a different registrable domain from KODY_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.vars host merging (#43).
  • packageStorageClear skips celld's litestream tables (#40).

Runs.

  • Run triage with runSummary, runUpdate and runUpdateBulk, plus auto-resolve and triage on the Activity page (#33).
  • Package apps record failures as app runs.

Search and docs.

  • guide:provision matches what this build ships (#76).
  • The docs cover KODY_PRIVATE_HOSTS and 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

  1. Back up the data volume (/data).
  2. Apply the two migrations above to your .env / compose.
  3. Set the image to ghcr.io/kentcdodds/kody-celld:0.2.0, then docker compose pull && docker compose up -d.
  4. Check that /health reports "version":"0.2.0".

Full changelog: v0.1.1...v0.2.0

v0.1.1 — keep dev email defaults out of single-node Docker

Choose a tag to compare

@kentcdodds kentcdodds released this 07 Oct 23:41
9ea94d3

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 trigger email.message.received subscriptions) even though the operator never configured email.
  • A loopback outbound bridge URL. KODY_EMAIL_OUTBOUND_PROVIDER=resend (or another API provider) without KODY_EMAIL_OUTBOUND_URL silently targeted http://127.0.0.1:9796 instead 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/* return 501 email_inbound_disabled when KODY_EMAIL_INBOUND_TOKEN is not set. There is no default token.
  • Resend and other API providers default to their API URL (e.g. https://api.resend.com) when KODY_EMAIL_OUTBOUND_URL is unset.
  • Added SECURITY.md (private vulnerability reporting).

Upgrade

docker compose pull && docker compose up -d

Image: 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

Choose a tag to compare

@devin-ai-integration devin-ai-integration released this 19 Sep 21:49
9a00b7f

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).

  1. Save the compose file from README → Self-host it and run docker compose up -d
  2. curl http://<host>:8080/health, then open http://<host>:8080/ and finish Set up Kody
  3. Point an MCP client at http://<host>:8080/mcp (OAuth-capable clients only need the URL; others use a kc_… 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).