Skip to content

MCP is stateless: rip out the DO-backed client path#1464

Merged
jonastemplestein merged 1 commit into
mainfrom
itx-stateless-mcp
Jun 10, 2026
Merged

MCP is stateless: rip out the DO-backed client path#1464
jonastemplestein merged 1 commit into
mainfrom
itx-stateless-mcp

Conversation

@jonastemplestein

@jonastemplestein jonastemplestein commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Final follow-up to the codemode rip (#1445/#1446/#1447). MCP's streamable HTTP transport is a stateless protocol — fetch with metadata — so nothing about an MCP client belongs in a Durable Object.

What

  • OutboundMcpFromOurClientCapability DO deleted — class, worker export, alchemy namespace + OUTBOUND_MCP_FROM_OUR_CLIENT_CAPABILITY binding, vitest harness wiring. Nothing has dialed it since the codemode rip (its only caller was the deleted provider registration).
  • Core helpers promoted to src/itx/caps/mcp-client-core.ts with clean names: connectMcp (custom-fetch aware, closes on failed connect), listMcpTools, executeMcpToolCall. McpClient dedupes onto them; the mock-server unit test moved and adapted.
  • The DO allusion is gone from the docs: McpClient's header now states statelessness as the design ("connect → call → close, per invocation; deliberately no Durable Object anywhere in this path") instead of presenting connection caching as a future optimization.

Deploy note

This is the repo's first Durable Object class deletion. The DO had no SQLite (in-memory cache only) and no inbound subscribers, so deletion is data-safe; alchemy is expected to emit the deleted_classes migration on deploy — this PR's preview deploy is the proof. If it refuses, the fallback is a tombstone class like CodemodeSession's.

Testing

Repo typecheck / lint / knip / unit suite green locally (incl. the moved mock-server core test). The McpClient e2e remains gated on a reachable MCP server.

🤖 Generated with Claude Code

Environment Config Lease

No active environment config lease.

OS

Status: released
Commit: 5c575ed
Preview: https://os.iterate-preview-4.com
Summary: Preview app released.
Workflow run
Updated: 2026-06-10T15:50:34.371Z

MCP's streamable HTTP transport is fetch with metadata — there is no
session state worth a Durable Object:

- OutboundMcpFromOurClientCapability DO deleted (class, worker export,
  alchemy namespace + OUTBOUND_MCP_FROM_OUR_CLIENT_CAPABILITY binding,
  vitest harness wiring). Nothing dialed it after the codemode rip.
- Its core helpers move to src/itx/caps/mcp-client-core.ts with clean
  names (connectMcp / listMcpTools / executeMcpToolCall, custom-fetch
  aware, close-on-failed-connect inside connect); McpClient dedupes onto
  them; the mock-server test moves and adapts.
- The "becomes a durable-object ref when handshake latency matters"
  allusion in McpClient's docs is gone — the doc now states the
  statelessness as the design, not a tradeoff.

Note for deploy: this is the repo's first DO class deletion; alchemy is
expected to emit the deleted_classes migration. The preview deploy on
this PR is the proof — if it refuses, fallback is a tombstone class.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jonastemplestein jonastemplestein merged commit 3c738a3 into main Jun 10, 2026
7 of 8 checks passed
@jonastemplestein jonastemplestein deleted the itx-stateless-mcp branch June 10, 2026 15:47
jonastemplestein added a commit that referenced this pull request Jun 10, 2026
… legacy define compat, nested workspace.git (#1476)

The parked debts from itx-next.md, bandaid pulled to the max per review
— **no backcompat anywhere** (prd/preview/dev itx data is erasable).

## CodemodeSession tombstone — deleted

The tombstone DO class, its `codemode-session-local` namespace, the
`CODEMODE_SESSION` binding, and the vitest wrangler entries are gone.
Alchemy emits the `deleted_classes` migration (mechanism proven in
#1464). Streams that still carry durable subscriber events dialing the
namespace will error on delivery — accepted, the data is being erased.

## executeCodemodeFunctionCall — protocol fully deleted (~2.5k lines)

Every capability entrypoint loses its legacy dispatch method (agents,
gmail, repos, secrets, slack, streams, workspace, openapi-bridge,
AiCapability, OrpcCapability, test entries), along with
`legacy-codemode-call.ts` and each wrapper's dead arg-validation
helpers. Two callers were still alive and got clean replacements:
- the agent chat/debug tool path now dials
`AgentDurableObject.callAgentTool({ tool, path, args, callId })`
- the ingress test entry upserts secrets via `OrpcCapability.call({
path: ["secrets", "upsert"], … })`

`packages/shared/src/codemode/` → `packages/shared/src/type-tree/`
(context-proxy deleted — only importer was a deleted legacy test
provider); generated typing identifiers de-codemoded (`ItxConsole`,
`generateContextTypesFromJsonSchema`, …).

## Registry legacy compat — deleted

`caps.define` takes a `target` (SerializableCapTarget) **only**, end to
end (registry, ContextDO, Project DO, handle, REPL typings): the legacy
`source`/`kind: "worker" | "facet"` inputs, the `codeId` spelling of
`cacheKey`, stored `worker`/`facet` kinds, the `source_json` rollback
column + sync writes, and all read-side normalization are gone. All
callers (browser REPL examples, every e2e suite) spell rpc/source
targets directly.

## Nested `itx.workspace.git.*` — deleted

Nested RpcTargets returned from entrypoint getters don't survive RPC
boundaries; the flat
`gitClone`/`gitAdd`/`gitCommit`/`gitPush`/`gitStatus` methods are the
surface. The agent preset prompt was actively teaching the broken nested
spelling — fixed.

## allowedHosts debt — closed as misdiagnosis

The config is `allowedHosts: true`; the 403/502s came from a wedged vite
process behind the still-connected cloudflared tunnel. Documented in
itx-next.md; e2e through `os.iterate-dev-jonas.com` verified passing.

## Verification

`pnpm typecheck` / `lint` / `knip` / `format` green; apps/os unit tests
green; workerd suites (`test:project-ingress` 6/6,
`test:itx-server-handle` 5/5 pre-#1472, `test:type-tree` 113/113) green;
itx e2e (itx, fork, http, subscribe — 20 tests) green against a local
dev server on the merged tree, including the post-#1474 capnweb fork.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **High Risk**
> Large breaking change to capability registration, MCP/ingress
execution paths, and production streams that may still dial removed
CodemodeSession subscribers; preview/dev data is intentionally erasable.
> 
> **Overview**
> This PR **removes the last codemode-era wiring** and tightens the itx
capability model with **no read-side backcompat**.
> 
> **CodemodeSession** is fully removed: the tombstone Durable Object,
`CODEMODE_SESSION` binding/namespace in Alchemy and vitest wrangler
configs, worker exports, and the dedicated test script. MCP and tests
now assume execution goes through the shared itx runner.
> 
> **Legacy `executeCodemodeFunctionCall` dispatch is deleted** across
capability entrypoints (~2.5k lines), along with
`legacy-codemode-call.ts` and shared `codemode/context-proxy`. Call
sites move to itx-native paths (`callAgentTool`, `OrpcCapability.call`,
OpenAPI `call({ path, args })`). **`packages/shared` codemode** is
renamed to **`type-tree`** with de-codemoded type-generation names.
> 
> **`caps.define` is target-only**: required `SerializableCapTarget`, no
`source`/`kind`/`codeId`, no `source_json` column or
`normalizeCapTarget` on read—stored rows use `target_json` verbatim.
Docs, REPL typings, e2e, and browser REPL examples all use `{ type:
"rpc", worker: { type: "source", source: { cacheKey, … } } }`.
> 
> **Workspace git** drops the nested `git` RpcTarget getter; agents,
presets, e2e, and preview scripts use flat **`gitClone` / `gitAdd` /
`gitCommit` / `gitPush` / `gitStatus`**. **itx-next.md** records
resolved debts (legacy define, nested git, allowedHosts misdiagnosis).
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
9cf0edd. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

<!-- CLOUDFLARE_PREVIEW -->
## Environment Config Lease
<!-- CLOUDFLARE_PREVIEW_STATE -->
<!--
{
  "apps": {
    "os": {
      "appDisplayName": "OS",
      "appSlug": "os",
      "status": "deployed",
      "updatedAt": "2026-06-10T20:40:22.067Z",
      "headSha": "9cf0eddaebfbcf1516be75f3e6acffdc31004f08",
      "message": null,
      "publicUrl": "https://os.iterate-preview-6.com",
"runUrl": "https://github.com/iterate/iterate/actions/runs/27304663825",
      "shortSha": "9cf0edd"
    },
    "semaphore": {
      "appDisplayName": "Semaphore",
      "appSlug": "semaphore",
      "status": "deployed",
      "updatedAt": "2026-06-10T20:31:34.680Z",
      "headSha": "0f50df98f943df63d8133e6be05dcdb33e77305c",
      "message": null,
      "publicUrl": "https://semaphore.iterate-preview-6.com",
"runUrl": "https://github.com/iterate/iterate/actions/runs/27304258909",
      "shortSha": "0f50df9"
    }
  },
  "environmentConfigLease": {
    "dopplerConfig": "preview_6",
    "leasedUntil": 1781127403360,
    "leaseId": "6fe10f72-e7bb-431a-9402-1abae9841129",
    "slug": "preview-6",
    "type": "environment-config-lease"
  }
}
-->
<!-- /CLOUDFLARE_PREVIEW_STATE -->
Lease: `preview-6`
Doppler config: `preview_6`
Type: `environment-config-lease`
Leased until: 2026-06-10T21:36:43.360Z

### OS
Status: deployed
Commit: `9cf0edd`
Preview: https://os.iterate-preview-6.com
[Workflow
run](https://github.com/iterate/iterate/actions/runs/27304663825)
Updated: 2026-06-10T20:40:22.067Z

### Semaphore
Status: deployed
Commit: `0f50df9`
Preview: https://semaphore.iterate-preview-6.com
[Workflow
run](https://github.com/iterate/iterate/actions/runs/27304258909)
Updated: 2026-06-10T20:31:34.680Z
<!-- /CLOUDFLARE_PREVIEW -->

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant