itx kernel: CapTarget — capabilities are a name plus a typed target#1436
Merged
Conversation
Implements the first slice of itx-next.md §1/§2 (design of record:
src/itx/types.ts):
- protocol.ts: SerializableCapTarget (rpc | url) and WorkerRef (binding |
loopback | project-worker | durable-object | source). CapKind becomes
the target's type ("live" | "rpc" | "url"); legacy "worker"/"facet"
rows normalize on read. CapSource gains cacheKey (codeId kept as
deprecated alias) and exportType ("worker-entrypoint" |
"durable-object", replacing kind: "facet"). CapMeta is open metadata
with the `instructions` convention, lifted into CapDescription.
- registry.ts: borrowTarget is now the two-case switch — live table, or
resolve the stored target. Implemented refs: binding (env lookup via a
new host hook), loopback (entrypoint allowlist + attribution props),
source (existing loader/facet paths). url / durable-object /
project-worker refs fail with informative not-implemented errors at
define time. New target_json column (guarded ALTER); source_json kept
in sync for source targets so pre-CapTarget code can read rows written
by this version.
- Security: binding and loopback refs reach PLATFORM resources, so they
are gated on hardcoded allowlists (DIALABLE_BINDINGS = {AI},
DIALABLE_LOOPBACKS = {BindingCapability}) at define time (fail fast)
and dial time (authoritative). Config-driven lists are a follow-up.
- entrypoint.ts: BindingCapability — the thin policy wrapper for
platform bindings (itx-next §2): a path-call loopback that replays the
dotted path onto env[props.binding], with its own allowlist check
(props are definer-controlled). Gateway/quota policy slots in here.
- e2e: new test proving itx.ai.models() via a raw binding ref, the same
through the BindingCapability wrapper, allowlist refusals (DB binding,
ItxEntrypoint loopback), and describe() reporting new kinds +
instructions. Existing describe assertion updated ("worker" → "rpc").
Backward compatible: caps.define({ source, kind }) still works and
normalizes to an rpc/source target; existing stored rows keep working.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
jonastemplestein
added a commit
that referenced
this pull request
Jun 10, 2026
) 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: ```ts await itx.caps.define({ invoke: "path-call", name: "docs", target: { type: "rpc", worker: { type: "loopback" }, entrypoint: "McpClient", props: { serverUrl: "https://docs.example.com/mcp", headers: { authorization: 'Bearer getSecret({ key: "DOCS_TOKEN" })' }, }, }, }); await itx.docs.listTools(); await itx.docs.someToolName({ ... }); ``` - **All transport HTTP rides project egress** via the cap's own itx handle (the MCP SDK's custom-`fetch` option), so header values may carry `getSecret()` placeholders — substitution happens in the Project DO, the credential never exists in the isolate (Law 5). - **Stateless by design**: connect → call → close per invocation. When handshake latency matters, the same class becomes a `durable-object` ref without changing callers (the connection-caching pattern of the old `OutboundMcpFromOurClientCapability` DO). - `listTools()` is an ordinary call, not a special discovery protocol — same convention the codemode provider used. - Added to `DIALABLE_LOOPBACKS`; result shaping reuses the existing `outbound-mcp-from-our-client-capability-core` helpers. In the upcoming codemode-drop PR this replaces `connectToMcpServer` and `createOutboundMcpFromOurClientToolProviderRegistration`. ## 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 on `OS_E2E_MCP_SERVER_URL` or `MOCK_PROVIDER_BASE_URL` (skips when no MCP server is reachable). 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- CURSOR_SUMMARY --> --- > [!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 **`McpClient`** loopback RPC capability so projects can define remote MCP servers as ordinary `path-call` caps (`listTools()` plus dotted tool calls). Each invocation is **connect → call → close**; transport HTTP is forced through **`itx.fetch`** (project egress) so header secrets resolve in the Project DO, and tool listing/execution reuse the existing outbound MCP core helpers. > > **`McpClient`** is allowlisted in **`DIALABLE_LOOPBACKS`** and exported from the main worker. Several itx entrypoints switch from **`Reflect.get(this, "ctx")`** to **`this.ctx.props`**. E2E gains a skipped-when-no-server test that defines an **`McpClient`** cap and exercises list/call, plus **`createdProjectIds`** cleanup for the AI bindings test project. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 236953d. 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-10T13:52:03.377Z", "headSha": "236953da701399b35300d23cc79c0a728f536743", "message": null, "publicUrl": "https://os.iterate-preview-5.com", "runUrl": "https://github.com/iterate/iterate/actions/runs/27280672268", "shortSha": "236953d" } }, "environmentConfigLease": { "dopplerConfig": "preview_5", "leasedUntil": 1781102759817, "leaseId": "8f0c5c8c-96b3-43bd-9ed2-1126964f26ec", "slug": "preview-5", "type": "environment-config-lease" } } --> <!-- /CLOUDFLARE_PREVIEW_STATE --> Lease: `preview-5` Doppler config: `preview_5` Type: `environment-config-lease` Leased until: 2026-06-10T14:45:59.817Z ### OS Status: deployed Commit: `236953d` Preview: https://os.iterate-preview-5.com [Workflow run](https://github.com/iterate/iterate/actions/runs/27280672268) Updated: 2026-06-10T13:52:03.377Z <!-- /CLOUDFLARE_PREVIEW --> --------- 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.
Supersedes #1433, which GitHub auto-closed when its base branch (
medieval-fibre, merged as #1428) was deleted. Same commit, now based on main.What
First implementation slice of the CapTarget design from #1428 (design of record:
src/itx/types.ts).A capability is now a name plus a typed target.
caps.defineaccepts:registry.borrowTarget()is the two-case switch the design promised: live table, or resolve the stored target.binding,loopback,source(the old worker/facet paths, unchanged underneath).url/durable-object/project-workerfail with informative not-implemented errors at define time.BindingCapability: new loopback entrypoint, the thin policy wrapper for bindings — gateway/quota policy slots in there later.kind: "facet"is gone from the API (legacy input still accepted): statefulness issource.exportType: "worker-entrypoint" | "durable-object".cacheKeyreplacescodeId(deprecated alias kept).CapMetais open metadata with theinstructionsconvention, lifted intodescribe()output.Security
bindingandloopbackrefs reach platform resources (an open list would let any project handle reach the deployment D1, or mint itx handles on arbitrary projects via ItxEntrypoint props). They are gated on hardcoded allowlists —DIALABLE_BINDINGS = {AI},DIALABLE_LOOPBACKS = {BindingCapability}— checked at define time (fail fast) and again at dial time (authoritative).BindingCapabilityre-checks the binding allowlist itself since its props are definer-controlled. Config-driven lists are a follow-up.Compatibility
caps.define({ source, kind: "worker" | "facet" })still works — normalizes to an rpc/source target.source_jsonfor source targets so a rollback can still read them. Newtarget_jsoncolumn added via guarded ALTER.describe()now reportskind: "rpc"for stored-source caps (e2e assertion updated).Testing
pnpm typecheck/lint/format/test(190 unit tests) all green.pnpm e2e:itxsuite ran green against a live deployment (19 tests, 5 files, via the dev_jonas tunnel serving this branch):itx.ai.models()through the real AI binding raw + wrapped, allowlist refusals, and all pre-existing egress/facet/fork/HTTP tests — no regressions.🤖 Generated with Claude Code
Note
High Risk
Introduces platform binding and loopback dialing gated only on small hardcoded allowlists; misconfiguration or future allowlist expansion could expose env bindings like D1 or arbitrary loopback exports to project handles.
Overview
Capabilities are now a name plus a typed
SerializableCapTarget, not legacysource+kind: worker | facet.caps.defineacceptstarget(e.g.rpcwithbinding,loopback, orsourceworker refs); oldsource/kindinput still normalizes torpc/source.Registry dispatch is refactored to the two-case model: live in-memory connections, or
resolveTarget()at invoke time for stored targets. SQLite gainstarget_json(with legacy row normalization viatargetOf());describe()reportskind: "rpc"and liftsmeta.instructions.Platform binding exposure adds
BindingCapability(loopback path-call wrapper), hostbindingresolvers on Project/Context DOs, and allowlistsDIALABLE_BINDINGS/DIALABLE_LOOPBACKSenforced at define and dial time.Protocol updates:
CapKindbecomeslive | rpc | url;cacheKeyreplacescodeId;exportTypereplaces facet kind;url/project-worker/durable-objectrefs fail at define time until implemented.E2E covers raw
AIbinding caps, wrappedBindingCapability, and allowlist rejections.Reviewed by Cursor Bugbot for commit 13ab9e5. 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:
13ab9e5Preview: https://os.iterate-preview-2.com
Summary: Preview app released.
Workflow run
Updated: 2026-06-10T12:13:49.919Z