itx: McpClient — first-party MCP client capability (loopback ref)#1441
Merged
Conversation
The first-party half of the litmus test (itx-next.md §1): MCP is a client
implementation, not a transport. McpClient is a loopback-dialable
path-call entrypoint, parameterized per server by props
{ serverUrl, headers }. All transport HTTP rides the project egress pipe
via the cap's own itx handle, so headers may carry getSecret()
placeholders (Law 5) — the credential never exists in the isolate.
Stateless by design: connect → call → close per invocation. When
handshake latency matters, the same class becomes a durable-object ref
without changing callers (the old OutboundMcpFromOurClientCapability DO
pattern).
listTools() is an ordinary call, not a discovery protocol. Added to
DIALABLE_LOOPBACKS; e2e test gated on OS_E2E_MCP_SERVER_URL /
MOCK_PROVIDER_BASE_URL.
Replaces (in the upcoming codemode drop): connectToMcpServer +
createOutboundMcpFromOurClientToolProviderRegistration.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
31496c0 to
6ac16d9
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
There are 2 total unresolved issues (including 1 from previous review).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 6ac16d9. Configure here.
Bugbot findings on #1441: the SDK's custom fetch can receive a URL (which itx.fetch would pass through un-stringified, skipping egress) or a Request plus separate init (whose headers must merge before secret substitution sees them) — build a real Request first. And connect() now runs inside the try so a partial handshake still hits close(). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Contributor
Author
|
Bugbot findings addressed in 24c632b: the transport's custom fetch now builds a real |
this.ctx.props is properly typed by WorkerEntrypoint's second generic (GmailCapability already used it directly); the cast dance was vestigial. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
bd6c7d7 to
236953d
Compare
jonastemplestein
added a commit
that referenced
this pull request
Jun 10, 2026
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
jonastemplestein
added a commit
that referenced
this pull request
Jun 10, 2026
Stacked on #1441 (McpClient). Part of the codemode rip-out sequence (itx-next.md §4). ## What The two-event execution record that replaces codemode's six-event protocol: - `events.iterate.com/itx/execution-requested` `{ executionId, code, vars, context }` - `events.iterate.com/itx/execution-completed` `{ executionId, ok, result | error+stack, durationMs, context }` Both land on the owning project's `/itx` stream (D9-consistent: context id in the payload). **Record-only mode**: the events are the durable history, not the transport — callers get the outcome from the return value/HTTP response, and everything between the two events is invisible to the stream. Appends are best-effort, matching the registry's audit posture. Recorded results are bounded (64k chars, truncated with a preview beyond that); the full value still returns to the caller. Mechanically: the loader harness moves out of `fetch.ts` into a shared `src/itx/run.ts` (`runItxScript()`), `/api/itx/run` becomes a thin resolve-and-delegate shim, and the response now includes the `executionId` for correlation. Global-context scripts record no events (no owning project stream). The shared runner is the function the upcoming codemode-drop PR points the inbound-MCP `exec_js` and agent execution paths at. ## Testing typecheck / lint / format green. New e2e test runs a script via `/api/itx/run` and asserts both events land on the `/itx` stream with the returned `executionId` and the correct outcome payload. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Touches dynamic code execution and stream audit payloads; auth stays at the existing `/api/itx/run` boundary, but execution records and API response shape change for all script runs. > > **Overview** > Introduces **record-only** script execution auditing: each project-context run appends **`execution-requested`** then **`execution-completed`** on the project **`/itx`** stream (replacing codemode’s six-event protocol), while callers still get the outcome from the HTTP/return value. > > The loader harness moves from **`fetch.ts`** into shared **`runItxScript()`** in **`run.ts`**; **`POST /api/itx/run`** only resolves access and delegates. Responses now include **`executionId`** (and on errors too) for correlation. The runner captures isolate **console** lines, **truncates** large results on the stream (64k), always emits **completed** even on loader failures, and skips stream writes for **global** (admin-only) scripts. > > A new e2e test runs a script via **`/api/itx/run`** and asserts both events match the returned **`executionId`** and result. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit f43fee8. 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-10T14:04:25.235Z", "headSha": "f43fee8b69ae70c6889cdd236fd568c1eab9b671", "message": null, "publicUrl": "https://os.iterate-preview-7.com", "runUrl": "https://github.com/iterate/iterate/actions/runs/27281391307", "shortSha": "f43fee8" }, "semaphore": { "appDisplayName": "Semaphore", "appSlug": "semaphore", "status": "deployed", "updatedAt": "2026-06-10T14:00:32.897Z", "headSha": "f43fee8b69ae70c6889cdd236fd568c1eab9b671", "message": null, "publicUrl": "https://semaphore.iterate-preview-7.com", "runUrl": "https://github.com/iterate/iterate/actions/runs/27281391307", "shortSha": "f43fee8" } }, "environmentConfigLease": { "dopplerConfig": "preview_7", "leasedUntil": 1781103456147, "leaseId": "2790f185-d4dc-4d47-8791-eab94eb3ac31", "slug": "preview-7", "type": "environment-config-lease" } } --> <!-- /CLOUDFLARE_PREVIEW_STATE --> Lease: `preview-7` Doppler config: `preview_7` Type: `environment-config-lease` Leased until: 2026-06-10T14:57:36.147Z ### OS Status: deployed Commit: `f43fee8` Preview: https://os.iterate-preview-7.com [Workflow run](https://github.com/iterate/iterate/actions/runs/27281391307) Updated: 2026-06-10T14:04:25.235Z ### Semaphore Status: deployed Commit: `f43fee8` Preview: https://semaphore.iterate-preview-7.com [Workflow run](https://github.com/iterate/iterate/actions/runs/27281391307) Updated: 2026-06-10T14:00:32.897Z <!-- /CLOUDFLARE_PREVIEW --> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
jonastemplestein
added a commit
that referenced
this pull request
Jun 10, 2026
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Stacks on the merged CapTarget kernel (#1436). Part of the codemode rip-out sequence (itx-next.md §4).
What
The first-party half of the §1 litmus test: MCP is a client implementation, not a transport — so it's an ordinary loopback-dialable RPC target, parameterized per server:
fetchoption), so header values may carrygetSecret()placeholders — substitution happens in the Project DO, the credential never exists in the isolate (Law 5).durable-objectref without changing callers (the connection-caching pattern of the oldOutboundMcpFromOurClientCapabilityDO).listTools()is an ordinary call, not a special discovery protocol — same convention the codemode provider used.DIALABLE_LOOPBACKS; result shaping reuses the existingoutbound-mcp-from-our-client-capability-corehelpers.In the upcoming codemode-drop PR this replaces
connectToMcpServerandcreateOutboundMcpFromOurClientToolProviderRegistration.Testing
typecheck / lint / format / unit green. New e2e test (
itx.e2e.test.ts) defines an McpClient cap, lists tools, and calls the first one — gated onOS_E2E_MCP_SERVER_URLorMOCK_PROVIDER_BASE_URL(skips when no MCP server is reachable).🤖 Generated with Claude Code
Note
Medium Risk
Expands dialable loopback surface and routes all MCP HTTP through project egress; behavior is gated by existing allowlists and attribution checks but affects outbound networking for defined caps.
Overview
Adds a first-party
McpClientloopback RPC capability so projects can define remote MCP servers as ordinarypath-callcaps (listTools()plus dotted tool calls). Each invocation is connect → call → close; transport HTTP is forced throughitx.fetch(project egress) so header secrets resolve in the Project DO, and tool listing/execution reuse the existing outbound MCP core helpers.McpClientis allowlisted inDIALABLE_LOOPBACKSand exported from the main worker. Several itx entrypoints switch fromReflect.get(this, "ctx")tothis.ctx.props. E2E gains a skipped-when-no-server test that defines anMcpClientcap and exercises list/call, pluscreatedProjectIdscleanup for the AI bindings test project.Reviewed by Cursor Bugbot for commit 236953d. Bugbot is set up for automated code reviews on this repo. Configure here.
Environment Config Lease
No active environment config lease.
OS
Status: released
Commit:
236953dPreview: https://os.iterate-preview-5.com
Summary: Preview app released.
Workflow run
Updated: 2026-06-10T13:56:08.912Z