@zackbart/connecta 0.10.6 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +8 -6
- package/CHANGELOG.md +80 -0
- package/README.md +5 -4
- package/bin/connecta.mjs +0 -7
- package/dist/activity.d.ts.map +1 -1
- package/dist/activity.js.map +1 -1
- package/dist/apps-shell.d.ts +38 -0
- package/dist/apps-shell.d.ts.map +1 -0
- package/dist/apps-shell.js +175 -0
- package/dist/apps-shell.js.map +1 -0
- package/dist/catalog-service.d.ts +2 -17
- package/dist/catalog-service.d.ts.map +1 -1
- package/dist/catalog-service.js +4 -6
- package/dist/catalog-service.js.map +1 -1
- package/dist/connectors/api.d.ts +2 -2
- package/dist/connectors/remote-mcp.d.ts +1 -1
- package/dist/errors.d.ts +1 -3
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/execute.d.ts +37 -8
- package/dist/execute.d.ts.map +1 -1
- package/dist/execute.js +137 -42
- package/dist/execute.js.map +1 -1
- package/dist/executor-admission.d.ts +8 -0
- package/dist/executor-admission.d.ts.map +1 -1
- package/dist/executor-admission.js +11 -0
- package/dist/executor-admission.js.map +1 -1
- package/dist/executors/quickjs.d.ts.map +1 -1
- package/dist/executors/quickjs.js +2 -2
- package/dist/executors/quickjs.js.map +1 -1
- package/dist/index.d.ts +15 -31
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +23 -37
- package/dist/index.js.map +1 -1
- package/dist/invocation.js +2 -2
- package/dist/invocation.js.map +1 -1
- package/dist/meta-tools.d.ts +19 -58
- package/dist/meta-tools.d.ts.map +1 -1
- package/dist/meta-tools.js +34 -431
- package/dist/meta-tools.js.map +1 -1
- package/dist/registry.d.ts +1 -10
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +3 -15
- package/dist/registry.js.map +1 -1
- package/dist/routes/mcp.d.ts.map +1 -1
- package/dist/routes/mcp.js +67 -30
- package/dist/routes/mcp.js.map +1 -1
- package/dist/routes/shared.d.ts +5 -11
- package/dist/routes/shared.d.ts.map +1 -1
- package/dist/routes/shared.js.map +1 -1
- package/dist/server.js +5 -4
- package/dist/server.js.map +1 -1
- package/dist/skills.d.ts +8 -18
- package/dist/skills.d.ts.map +1 -1
- package/dist/skills.js +13 -60
- package/dist/skills.js.map +1 -1
- package/dist/types.d.ts +6 -20
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/documentation/code-first-exploration.md +16 -16
- package/documentation/code-mode.md +130 -41
- package/documentation/connectors.md +1 -1
- package/documentation/mcp-2026-07-28.md +1 -1
- package/documentation/mcp-ui-design.md +382 -0
- package/documentation/meta-tools.md +30 -43
- package/documentation/rich-output-design.md +4 -4
- package/ethos.md +7 -2
- package/examples/node/README.md +1 -2
- package/examples/node/src/index.ts +1 -3
- package/examples/worker/README.md +8 -13
- package/examples/worker/src/index.ts +6 -14
- package/examples/worker/wrangler.jsonc +3 -6
- package/package.json +1 -1
- package/src/activity.ts +5 -0
- package/src/apps-shell.ts +179 -0
- package/src/catalog-service.ts +6 -26
- package/src/connectors/api.ts +2 -2
- package/src/connectors/remote-mcp.ts +1 -1
- package/src/errors.ts +2 -2
- package/src/execute.ts +150 -49
- package/src/executor-admission.ts +12 -0
- package/src/executors/quickjs.ts +2 -1
- package/src/index.ts +40 -69
- package/src/invocation.ts +2 -2
- package/src/meta-tools.ts +38 -565
- package/src/registry.ts +2 -33
- package/src/routes/mcp.ts +79 -30
- package/src/routes/shared.ts +4 -11
- package/src/server.ts +7 -7
- package/src/skills.ts +11 -74
- package/src/types.ts +6 -21
- package/src/version.ts +1 -1
- package/templates/node/README.md +2 -1
- package/templates/node/package.json +1 -1
- package/templates/node/src/index.ts +1 -1
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
# MCP UI from programs — design record
|
|
2
|
+
|
|
3
|
+
The decision record for
|
|
4
|
+
[#266](https://github.com/zackbart/connecta/issues/266): whether and how an
|
|
5
|
+
`execute_code` program may deliver a *rendered view* to the client, and what a
|
|
6
|
+
program is allowed to hand the host in order to get one. The verdicts live in
|
|
7
|
+
[`ethos.md`](../ethos.md); this document carries the argument, the contract
|
|
8
|
+
precisely enough to implement, and the shapes that were considered, refused, or
|
|
9
|
+
gated.
|
|
10
|
+
|
|
11
|
+
It extends the [rich-output design record](./rich-output-design.md)
|
|
12
|
+
([#267](https://github.com/zackbart/connecta/issues/267)) rather than restating
|
|
13
|
+
it. That record's contract clauses (`M1`–`M10`, now normative in
|
|
14
|
+
[`code-mode.md`](./code-mode.md)'s "Emitted output" section) are cited here,
|
|
15
|
+
not repeated; where the two overlap — provider-bridge delivery, budgets,
|
|
16
|
+
success-only delivery, payload-free diagnostics — this record follows the
|
|
17
|
+
precedent instead of inventing a parallel one. The implementation landed via
|
|
18
|
+
[#277](https://github.com/zackbart/connecta/issues/277): the clauses below
|
|
19
|
+
(`U1`–`U11`) are now normative in [`code-mode.md`](./code-mode.md)'s "Rendered
|
|
20
|
+
output" section, which wins where the two disagree. This document remains the
|
|
21
|
+
argument and the record of rejected shapes.
|
|
22
|
+
|
|
23
|
+
## The problem
|
|
24
|
+
|
|
25
|
+
`connecta.emit` gave programs pixels. It did not give them a *view*. A program
|
|
26
|
+
that assembles a comparison table, a chart, or a diffed record can emit it as
|
|
27
|
+
text the model re-reads, or as an image it can no longer inspect; what it
|
|
28
|
+
cannot do is hand the client something the human looks at directly while the
|
|
29
|
+
model keeps its cheap textual summary. That is a real gap for the surface
|
|
30
|
+
connecta calls primary: the program is where composition already happens, and
|
|
31
|
+
composed output is exactly the output worth rendering.
|
|
32
|
+
|
|
33
|
+
MCP grew an answer to this, and the ethos anticipated it. The gated row for
|
|
34
|
+
guest-emitted `resource` / `resource_link` blocks reserved MCP UI as the one
|
|
35
|
+
possible carve-out, with the constraint written in advance: *programs supply
|
|
36
|
+
only content, connecta mints the `ui://` address outside the sandbox.* The
|
|
37
|
+
lure defense is the whole reason for the gate — a guest-minted URI is an
|
|
38
|
+
address a helpful client may dereference, and generated code mints nothing.
|
|
39
|
+
|
|
40
|
+
### The spec moved
|
|
41
|
+
|
|
42
|
+
The gate was drafted against the shape mcp-ui had at the time: the server mints
|
|
43
|
+
a per-request `ui://` URI and embeds a `UIResource` block inline in the tool
|
|
44
|
+
result. That shape is now the *legacy* path, explicitly superseded upstream and
|
|
45
|
+
still rendered by hosts that also support the official path — Goose, Postman,
|
|
46
|
+
MCPJam — plus a few that do not: Nanobot, LibreChat. The claim that matters is
|
|
47
|
+
narrower and unaffected: Claude.ai, ChatGPT, and VS Code Copilot render only
|
|
48
|
+
the official shape.
|
|
49
|
+
|
|
50
|
+
The official replacement is the **MCP Apps extension**
|
|
51
|
+
(`io.modelcontextprotocol/ui`, launched 2026-01-26,
|
|
52
|
+
[specification](https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/2026-01-26/apps.mdx),
|
|
53
|
+
[overview](https://modelcontextprotocol.io/extensions/apps/overview)), and it
|
|
54
|
+
inverts the delivery model in a way that happens to suit a stateless
|
|
55
|
+
aggregator:
|
|
56
|
+
|
|
57
|
+
- A tool **pre-declares a static UI template** through tool metadata,
|
|
58
|
+
`_meta.ui.resourceUri`, pointing at a `ui://` URI. (The flat form
|
|
59
|
+
`_meta["ui/resourceUri"]` is deprecated.)
|
|
60
|
+
- The template's mimeType MUST be `text/html;profile=mcp-app` and its body MUST
|
|
61
|
+
be a valid HTML5 document. External URLs (`text/uri-list`) are the one type on
|
|
62
|
+
the spec's deferred list; remote-DOM was never adopted into the official spec
|
|
63
|
+
at all.
|
|
64
|
+
- The host MUST use `resources/read` to fetch the referenced URI, and MAY
|
|
65
|
+
prefetch and cache it. A permission the spec hands hosts is a permission to
|
|
66
|
+
assume they use, so a template change means a new URI — version the address.
|
|
67
|
+
- Servers MAY omit UI-only resources from `resources/list` entirely — the
|
|
68
|
+
omission is from the *listing*, not from the method. A handler that answers
|
|
69
|
+
exactly one static URI and lists nothing needs no state: the URI reaches the
|
|
70
|
+
host through tool metadata, so the listing has nothing to carry and there is
|
|
71
|
+
no session to keep.
|
|
72
|
+
- Per-call data reaches the rendered view over postMessage: the host delivers
|
|
73
|
+
the full tool result to the view via `ui/notifications/tool-result`. Result
|
|
74
|
+
`_meta` is delivered to the view and, per the spec's best practices, is data
|
|
75
|
+
"not intended for model context" — the established Apps convention, and how
|
|
76
|
+
ChatGPT's Apps SDK behaves too. It is guidance, not a MUST.
|
|
77
|
+
- Rendering is sandboxed by the host. Hosts MUST render in sandboxed iframes,
|
|
78
|
+
and a template that declares no CSP domains gets the host's restrictive
|
|
79
|
+
default: `default-src 'none'`, `connect-src 'none'`, `frame-src 'none'`. An
|
|
80
|
+
undeclared template has no network, and no nested frame it could load by URL.
|
|
81
|
+
- The extension MUST be explicitly negotiated. Servers declare it in their
|
|
82
|
+
capability declaration, and per the client matrix a client acts on an
|
|
83
|
+
extension only when both sides have declared it.
|
|
84
|
+
- Support for the official shape spans Claude.ai, Claude Desktop, ChatGPT, VS
|
|
85
|
+
Code Copilot, M365 Copilot, Goose, Postman, and Cursor, among others
|
|
86
|
+
([client matrix](https://modelcontextprotocol.io/extensions/client-matrix)).
|
|
87
|
+
|
|
88
|
+
So the gate's premise dissolved on both ends. The shape it envisioned is the
|
|
89
|
+
one nobody renders, and the shape everyone renders does not require connecta to
|
|
90
|
+
mint anything per request — which turns out to be a *stricter* posture than the
|
|
91
|
+
gate asked for, not a looser one.
|
|
92
|
+
|
|
93
|
+
## The shape: `connecta.ui(html)` and one static shell
|
|
94
|
+
|
|
95
|
+
One new provider function, `connecta.ui(html)`, plus one static, connecta-
|
|
96
|
+
authored MCP Apps shell template. The program supplies HTML content and nothing
|
|
97
|
+
else. The only `ui://` URI anywhere in the system is connecta's shell, minted
|
|
98
|
+
at build time and identical for every deployment and every request; the
|
|
99
|
+
program's HTML never becomes a URI at all. It rides the tool result in `_meta`,
|
|
100
|
+
which hosts keep out of model context, and the shell renders it inside the
|
|
101
|
+
client's sandboxed iframe.
|
|
102
|
+
|
|
103
|
+
That is the whole design, and it satisfies the gate's constraint more strongly
|
|
104
|
+
than the gate's own sketch did. The gate would have allowed connecta to mint an
|
|
105
|
+
address per request from program-supplied bytes; here the program's content
|
|
106
|
+
never becomes addressable at all. Nothing a client could dereference is derived
|
|
107
|
+
from anything a program said.
|
|
108
|
+
|
|
109
|
+
Two structural consequences follow, both borrowed rather than invented. First,
|
|
110
|
+
`connecta.ui` is a provider function in the same `fns` object as
|
|
111
|
+
`connecta.emit`, so `ExecuteResult` stays `{ result, error?, logs? }` and the
|
|
112
|
+
`Executor` contract is untouched — the parity guarantee `M8` describes applies
|
|
113
|
+
verbatim. Second, the shell ships as a build-time string constant in core, not
|
|
114
|
+
a file read at startup: import-graph purity means no `node:fs`, and the same
|
|
115
|
+
bytes have to serve on Workers.
|
|
116
|
+
|
|
117
|
+
### Contract (drafts for code-mode.md)
|
|
118
|
+
|
|
119
|
+
**U1.** `connecta.ui(html)` accepts exactly one argument: a non-empty string of
|
|
120
|
+
HTML. Anything else — a non-string, an empty string, an options bag, an MCP
|
|
121
|
+
block object — throws a catchable error and nothing is accepted. There is no
|
|
122
|
+
options parameter and no sugar form, for `M1`'s reason: sugar is how a
|
|
123
|
+
one-shape contract grows hair.
|
|
124
|
+
|
|
125
|
+
**U2.** At most one payload per run. A second `connecta.ui` call throws
|
|
126
|
+
catchably, naming the constraint; the first accepted payload stands. One tool
|
|
127
|
+
result renders one view, and last-wins would silently discard a payload the
|
|
128
|
+
program deliberately supplied.
|
|
129
|
+
|
|
130
|
+
**U3.** The payload is delivered on success only, and out of model context: the
|
|
131
|
+
tool result gains `_meta["connecta/ui"] = { html }`, and the JSON envelope
|
|
132
|
+
gains `ui: true` so the model learns a view rendered without seeing its bytes.
|
|
133
|
+
`_meta` is where the Apps spec's best practices put data "not intended for
|
|
134
|
+
model context", and it is how shipped hosts behave — guidance plus observed
|
|
135
|
+
behavior, not a protocol property; nothing in the spec makes it a MUST.
|
|
136
|
+
`structuredContent` remains the envelope alone. The wire key is a plain
|
|
137
|
+
single-label prefix rather than the reverse-DNS form MCP's SHOULD prefers, and
|
|
138
|
+
that is a choice: connecta has no domain to reverse, and fabricating one to
|
|
139
|
+
satisfy a SHOULD would be a worse answer than the single-label prefix the key
|
|
140
|
+
format's MUST already permits. A program that never calls `connecta.ui`
|
|
141
|
+
produces today's byte-for-byte response (`R6`). A program that ends in an error
|
|
142
|
+
delivers no payload; the error envelope reports `uiDiscarded: true` only when a
|
|
143
|
+
payload had been accepted — a field on the structured envelope, a trailing line
|
|
144
|
+
on the plain-text paths — and it coexists with `emittedDiscarded: N` when one
|
|
145
|
+
failure discards both.
|
|
146
|
+
|
|
147
|
+
**U4.** The payload spends the existing aggregate emit byte budget
|
|
148
|
+
(`ConnectaConfig.execute.maxEmittedBytes`, default 4,000,000), measured at the
|
|
149
|
+
call as the serialized bytes of `{ html }` — the same measurement `M5` applies
|
|
150
|
+
to a block. Over budget throws catchably, naming the budget and the room
|
|
151
|
+
remaining, with nothing partially accepted. It does not spend the block-count
|
|
152
|
+
budget (`maxEmittedBlocks`) — it is not a block — and it does not spend the
|
|
153
|
+
host-call budget (`L4`). One transport bound covers everything rich a program
|
|
154
|
+
delivers; no new knob.
|
|
155
|
+
|
|
156
|
+
**U5.** One static shell: a connecta-authored HTML5 document at
|
|
157
|
+
`ui://connecta/program-ui/v1`, mimeType `text/html;profile=mcp-app`, declared
|
|
158
|
+
on `execute_code` via `_meta.ui.resourceUri` together with an explicit
|
|
159
|
+
`_meta.ui.visibility: ["model"]`, and served by a `resources/read` handler that
|
|
160
|
+
answers exactly that URI and fails on any other. The handler registers on the
|
|
161
|
+
`McpServer` in `src/routes/mcp.ts`, which is what puts the `resources`
|
|
162
|
+
capability into the discovery response — so `resources/list` is served too, and
|
|
163
|
+
returns an empty list. That is the Apps spec's permitted omission of UI-only
|
|
164
|
+
resources from listing, taken exactly: the capability stays honest because the
|
|
165
|
+
method answers, and nothing downstream is ever listed or aggregated.
|
|
166
|
+
`visibility` is declared rather than left to default because the default
|
|
167
|
+
`["model","app"]` would tell hosts the view may call `execute_code` over
|
|
168
|
+
`tools/call` — the thing the view-initiated-calls gate defers. The version
|
|
169
|
+
segment bumps whenever the shell's bytes change, because hosts cache templates
|
|
170
|
+
by URI.
|
|
171
|
+
|
|
172
|
+
**U6.** The shell is display-only. It renders the payload in a nested iframe
|
|
173
|
+
(`srcdoc`, `sandbox="allow-scripts"`, no `allow-same-origin`) and declares no
|
|
174
|
+
CSP domains, so the host applies its restrictive default. That default includes
|
|
175
|
+
`frame-src 'none'`, which forbids nested frames loaded from a *URL* — which is
|
|
176
|
+
exactly why the payload frame is `srcdoc` and not an address. `about:srcdoc` is
|
|
177
|
+
not matched by `frame-src`; it inherits the embedding document's policy
|
|
178
|
+
instead. So the inner frame is *permitted* by the documented behavior of the
|
|
179
|
+
directive that would otherwise forbid it, and *offline* by the same
|
|
180
|
+
inheritance: it gets the shell's `default-src 'none'; connect-src 'none'`.
|
|
181
|
+
Program UI therefore gets scripts and local interactivity and nothing else: no
|
|
182
|
+
network, no tool calls, no conversation messages, no host-mediated links. The
|
|
183
|
+
shell participates in the Apps lifecycle — initialize, tool-result,
|
|
184
|
+
size-changed — and forwards no channel whatsoever from the inner frame to the
|
|
185
|
+
host.
|
|
186
|
+
|
|
187
|
+
**U7.** Structural executor parity, per `M8`. `connecta.ui` is a provider
|
|
188
|
+
function; `ExecuteResult` and the `Executor` interface are unchanged; QuickJS
|
|
189
|
+
and the Dynamic Worker get it through the bridge they already have, without
|
|
190
|
+
modification.
|
|
191
|
+
|
|
192
|
+
**U8.** Request-local and unstreamed, per `M9`. The payload exists only in the
|
|
193
|
+
finished response, and `connecta.ui` resolving means "accepted," never
|
|
194
|
+
"rendered."
|
|
195
|
+
|
|
196
|
+
**U9.** With `diagnostics: true` the diagnostics block gains a distinct `ui`
|
|
197
|
+
aggregate carrying the payload's byte size — a number, nothing else — present
|
|
198
|
+
only when a payload was accepted. UI bytes are not folded into the `emitted`
|
|
199
|
+
aggregate `M10` describes: that one pairs a block count with the bytes those
|
|
200
|
+
blocks cost, and adding bytes without a block would desync the pair. Activity
|
|
201
|
+
stays payload-free by construction (`R8`, `M10`).
|
|
202
|
+
|
|
203
|
+
**U10.** `_meta.ui.resourceUri` is declared on `execute_code` unconditionally.
|
|
204
|
+
A host without the extension ignores unknown `_meta` and sees the ordinary
|
|
205
|
+
envelope, which *is* the text fallback the Apps spec mandates. `connecta.ui`
|
|
206
|
+
never fails because a client cannot render: a stateless aggregator cannot
|
|
207
|
+
reliably know, and connecta is not a nanny.
|
|
208
|
+
|
|
209
|
+
**U11.** connecta declares `io.modelcontextprotocol/ui` in its server
|
|
210
|
+
capability declaration — `capabilities.extensions` on the `initialize` path,
|
|
211
|
+
the `server/discover` response on the 2026-07-28 one — and that is the one
|
|
212
|
+
extension it advertises. The Apps extension must be explicitly negotiated, and
|
|
213
|
+
a conforming client acts on an extension only when both sides declare it: with
|
|
214
|
+
no declaration from connecta, no host reads `_meta.ui.resourceUri`, no host
|
|
215
|
+
fetches the shell, and the whole design is inert. The declaration is part of
|
|
216
|
+
the contract, not an implementation option.
|
|
217
|
+
|
|
218
|
+
Negotiation runs in two directions, and this design takes one and refuses the
|
|
219
|
+
other. **Declaring** is required — `U11` — and it is what makes the
|
|
220
|
+
[revision inventory](./mcp-2026-07-28.md)'s decline of the versioned extensions
|
|
221
|
+
framework a decline of that framework *as a general surface*, not a blanket
|
|
222
|
+
refusal to name an extension. **Reading the client's** declaration in order to
|
|
223
|
+
register tool metadata conditionally stays refused ("Capability-conditional
|
|
224
|
+
tool metadata", below), and that refusal declines a spec SHOULD — servers
|
|
225
|
+
SHOULD check client capabilities before registering UI-enabled tools — which is
|
|
226
|
+
worth saying out loud rather than eliding. A per-request stateless aggregator
|
|
227
|
+
has no dependable place to hold the check; unknown `_meta` is ignored by spec;
|
|
228
|
+
and `U10`'s envelope is already the fallback the spec mandates. The SHOULD
|
|
229
|
+
would buy a guarantee the protocol gives away.
|
|
230
|
+
|
|
231
|
+
## Sizing rationale
|
|
232
|
+
|
|
233
|
+
The [rich-output record](./rich-output-design.md#sizing-rationale) draws the
|
|
234
|
+
line that matters here: the 24,000-character return bound is a *context*
|
|
235
|
+
budget, and the emit byte budget is a *transport* bound. A UI payload is
|
|
236
|
+
transport in the purest form yet — it rides `_meta`, which hosts deliver to the
|
|
237
|
+
view and, following the spec's best-practice guidance, keep out of the model's
|
|
238
|
+
window. On a host that behaves that way, the `ui: true` marker is the entire
|
|
239
|
+
context cost of a rendered view: one boolean.
|
|
240
|
+
|
|
241
|
+
Which is why it does not get its own budget. The reason `emit` has one is
|
|
242
|
+
transport, not context, and a UI payload competes for the same wire; giving
|
|
243
|
+
it a second knob would ask operators to reason about a distinction that does
|
|
244
|
+
not exist downstream of the response body. It spends the aggregate and leaves
|
|
245
|
+
the block count alone, because a payload that is not a block should not consume
|
|
246
|
+
a block.
|
|
247
|
+
|
|
248
|
+
## Security posture
|
|
249
|
+
|
|
250
|
+
- **Nothing is minted — more strictly than the gate demanded.** The gate would
|
|
251
|
+
have permitted connecta to mint a per-request URI over program bytes. Here
|
|
252
|
+
the program's content never becomes an addressable resource at all. The one
|
|
253
|
+
`ui://` URI in the system is a build-time constant with no program input in
|
|
254
|
+
it, so there is no address a program can influence and nothing for a client
|
|
255
|
+
to dereference on a program's behalf.
|
|
256
|
+
- **No bridge means no capability path.** `U6`'s shell forwards nothing from
|
|
257
|
+
the inner frame to the host. A program-authored UI cannot call tools, reach
|
|
258
|
+
the network, post conversation messages, or open host-mediated links, so
|
|
259
|
+
hostile emitted HTML is inert beyond its own pixels. The invariant holds by
|
|
260
|
+
construction rather than by validation: there is no channel to abuse.
|
|
261
|
+
- **The restrictive default CSP is the offline guarantee, and the inner frame
|
|
262
|
+
lives inside it rather than around it.** Declaring no CSP domains means
|
|
263
|
+
`default-src 'none'`, `connect-src 'none'`, and `frame-src 'none'` — that
|
|
264
|
+
last directive forbids the nested frame the design depends on if the frame
|
|
265
|
+
has a URL, which is why it has none. `about:srcdoc` is not matched by
|
|
266
|
+
`frame-src` and inherits the embedding document's policy, so the payload
|
|
267
|
+
frame is permitted and offline by the same rule. That is a dependency on
|
|
268
|
+
documented behavior, not something connecta enforces: hosts MAY tighten
|
|
269
|
+
further, and MAY override the inner `sandbox` attribute, either of which
|
|
270
|
+
could close the gap the shell rides through. If one does, the `v1` segment in
|
|
271
|
+
the shell URI is the escape hatch — a new shell at a new address is a version
|
|
272
|
+
bump, not a redesign. Program UI is offline because the host makes it
|
|
273
|
+
offline, not because connecta scanned the HTML for `fetch`.
|
|
274
|
+
- **Model-invisibility is host behavior, not a protocol guarantee.** The spec's
|
|
275
|
+
only statement on it is a non-normative best practice — `_meta` carries data
|
|
276
|
+
"not intended for model context" — with no MUST and no enforcement. Shipped
|
|
277
|
+
hosts honor it. A host that does not receives up to the full byte budget of
|
|
278
|
+
program-authored HTML as model context, which is an expensive turn rather
|
|
279
|
+
than a capability leak, but it is the honest failure mode of `U3` and the
|
|
280
|
+
reason the sizing argument rests on transport rather than on a promise of
|
|
281
|
+
free context.
|
|
282
|
+
- **HTML-borne injection is the existing class.** Hostile text inside a
|
|
283
|
+
rendered view is the same hazard as hostile text inside an emitted
|
|
284
|
+
screenshot (`M6`, and `call_tool`'s block passthrough before it). All program
|
|
285
|
+
output is untrusted input to the client; connecta claims no provenance and
|
|
286
|
+
adds no claim otherwise.
|
|
287
|
+
- **The lure defense stands.** Guest emission of `resource` and
|
|
288
|
+
`resource_link` blocks becomes refused — it was gated on exactly this
|
|
289
|
+
carve-out, and the carve-out did not need it. This design is not the
|
|
290
|
+
exception that opens that door; it is the demonstration that the door was not
|
|
291
|
+
needed.
|
|
292
|
+
|
|
293
|
+
## Considered, refused, and gated
|
|
294
|
+
|
|
295
|
+
**Legacy embedded `UIResource` delivery** (a per-request minted `ui://` URI
|
|
296
|
+
plus an inline resource block). Refused. It is superseded upstream and
|
|
297
|
+
unrendered by Claude.ai, ChatGPT, and VS Code Copilot — the clients connecta
|
|
298
|
+
deployments actually face — so it buys reach only with hosts that also support
|
|
299
|
+
the official path. It also fights the caching model: per-request URIs are
|
|
300
|
+
precisely what hosts are told they may prefetch and cache by URI. Building the
|
|
301
|
+
carve-out the gate imagined would mean minting addresses from guest content in
|
|
302
|
+
order to reach fewer clients.
|
|
303
|
+
|
|
304
|
+
**Extending `connecta.emit` with a `{ type: "ui", html }` shape.** Refused.
|
|
305
|
+
`M1` would need an asterisk (not an MCP block type), `M2` another (not appended
|
|
306
|
+
to `content`, delivered through `_meta` instead), and `M5` a third (its own
|
|
307
|
+
multiplicity, its own share of the budgets). Three exceptions to a settled
|
|
308
|
+
contract to avoid one new function is a bad trade; a separate function leaves
|
|
309
|
+
`M1`–`M10` exactly as they are.
|
|
310
|
+
|
|
311
|
+
**Guest-supplied external URL frames** (`text/uri-list`). Refused. A
|
|
312
|
+
guest-supplied URL is precisely the lure the gate exists to prevent — a program
|
|
313
|
+
choosing what the client loads is a program minting an address by another
|
|
314
|
+
route. The official spec deferred the type anyway, so the refusal costs
|
|
315
|
+
nothing today and would cost the whole posture tomorrow.
|
|
316
|
+
|
|
317
|
+
**Remote-DOM payloads.** Refused. Legacy-path-only, and never adopted into the
|
|
318
|
+
official spec at all — it survives there as background prose, not as a deferred
|
|
319
|
+
item with a queue position. Adopting a payload format the official spec never
|
|
320
|
+
took, on the path the official spec superseded, is two bets on one square.
|
|
321
|
+
|
|
322
|
+
**View-initiated tool calls from program UI.** Gated, not refused. The
|
|
323
|
+
host-mediated `tools/call` path exists in the Apps spec and would go through
|
|
324
|
+
the same admission, annotation, and consent path as any other call, so the
|
|
325
|
+
safety story is not the blocker. What is missing is the argument: a
|
|
326
|
+
program-authored UI that drives tools needs its own use case and its own
|
|
327
|
+
consent story, and neither exists yet. Display-only until one arrives, and
|
|
328
|
+
gated twice over: `U5` declares `_meta.ui.visibility: ["model"]` so no host is
|
|
329
|
+
ever told the view may call, and `U6`'s shell has no bridge to call with.
|
|
330
|
+
|
|
331
|
+
**Downstream MCP Apps template passthrough** (downstream connectors declaring
|
|
332
|
+
their own `ui://` templates, proxied through connecta's `resources/read`).
|
|
333
|
+
Gated. It is a coherent shape, and it earns its way in when a downstream
|
|
334
|
+
connector actually ships Apps templates. Until then it would be aggregation
|
|
335
|
+
machinery for a population of zero — and `U5`'s narrow handler is deliberately
|
|
336
|
+
narrow so that widening it later is a decision, not a diff.
|
|
337
|
+
|
|
338
|
+
**Delivering UI from failed programs.** Refused, for `M4`'s argument: a view
|
|
339
|
+
assembled before the failure may describe a world the error contradicts.
|
|
340
|
+
Failure delivers the error, the logs, and an honest `uiDiscarded: true`.
|
|
341
|
+
|
|
342
|
+
**Capability-conditional tool metadata** (declaring `_meta.ui.resourceUri` only
|
|
343
|
+
when the client negotiated the extension). Refused, and knowingly against a
|
|
344
|
+
spec SHOULD. Per-request statelessness makes the check unreliable, unknown
|
|
345
|
+
`_meta` is ignored by spec, and the JSON envelope is already the fallback the
|
|
346
|
+
Apps spec mandates. Conditional metadata would add a negotiation dependency to
|
|
347
|
+
buy a guarantee the protocol already gives away.
|
|
348
|
+
|
|
349
|
+
## Verification sketch
|
|
350
|
+
|
|
351
|
+
- Parity: one `connecta.ui` program through both vitest projects, identical
|
|
352
|
+
`_meta` payload and envelope.
|
|
353
|
+
- Validation: non-string, empty string, options bag, and a second call each
|
|
354
|
+
throw catchably and accept nothing.
|
|
355
|
+
- Budget: an over-budget payload fails at the call, naming the budget; emitted
|
|
356
|
+
blocks and the UI payload draw on one aggregate.
|
|
357
|
+
- Byte-for-byte: a program that never calls `connecta.ui` produces today's
|
|
358
|
+
exact response.
|
|
359
|
+
- Discard: a throwing program that had an accepted payload yields the error
|
|
360
|
+
envelope with `uiDiscarded: true` and no `_meta` payload; a throwing program
|
|
361
|
+
that never called `connecta.ui` reports no `uiDiscarded` at all.
|
|
362
|
+
- Diagnostics: an accepted payload adds a `ui` byte aggregate distinct from
|
|
363
|
+
`emitted`, and no aggregate appears when nothing was accepted.
|
|
364
|
+
- Independence: a truncated return value, emitted blocks, and a UI payload
|
|
365
|
+
coexist in one response.
|
|
366
|
+
- `resources/read` serves exactly the shell URI with the correct mimeType and a
|
|
367
|
+
valid HTML5 body, and fails on every other URI; `resources/list` is served
|
|
368
|
+
and returns an empty list.
|
|
369
|
+
- The server capability declaration carries exactly one extension identifier,
|
|
370
|
+
`io.modelcontextprotocol/ui`.
|
|
371
|
+
- `execute_code`'s metadata declares `_meta.ui.resourceUri` pointing at the
|
|
372
|
+
shell URI and `_meta.ui.visibility: ["model"]`.
|
|
373
|
+
- The shell renders payload HTML in a sandboxed inner frame and exposes no
|
|
374
|
+
bridge — asserted however the implementation can, at minimum that the shell
|
|
375
|
+
source carries the `srcdoc` and sandbox attributes and contains no
|
|
376
|
+
message-forwarding path from the inner frame to the host.
|
|
377
|
+
- `U1`–`U11` fold into `code-mode.md`, the `execute_code` description documents
|
|
378
|
+
`connecta.ui`, and the test-map row lands in
|
|
379
|
+
[`operations.md`](./operations.md) with that guide.
|
|
380
|
+
- `npm run check` passes.
|
|
381
|
+
|
|
382
|
+
The implementation issue (#277) carries these as acceptance criteria.
|
|
@@ -4,26 +4,20 @@ Connecta keeps one small tool surface in model context and resolves downstream
|
|
|
4
4
|
tools behind it. `search_tools` finds addresses, the call tools enforce safety
|
|
5
5
|
annotations, and `get_result` pages bounded results.
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## The deployment surface
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
| **executor configured** | seven: `execute_code`, `search_tools`, `call_tool`, `call_destructive_tool`, `authorize_connector`, `get_result`, `skills` | `connecta.search`, `connecta.describe`, `connecta.batch` inside a program |
|
|
15
|
-
| **no executor** | nine: the above minus `execute_code`, plus `list_connectors`, `describe_tools`, `batch_call` | those three top-level tools |
|
|
9
|
+
Every deployment requires an executor and `tools/list` is exactly seven:
|
|
10
|
+
`execute_code`, `search_tools`, `call_tool`, `call_destructive_tool`,
|
|
11
|
+
`authorize_connector`, `get_result`, and `skills`. Discovery breadth and
|
|
12
|
+
batching live in `connecta.search`, `connecta.describe`, and `connecta.batch`
|
|
13
|
+
inside a program ([#273](https://github.com/zackbart/connecta/issues/273)).
|
|
16
14
|
|
|
17
15
|
Code-first is what a model sees. Four overlapping ways to reach one connector
|
|
18
16
|
became two: `search_tools` then `call_tool` for a single cold read — measurably
|
|
19
17
|
cheaper direct than through a program — and `execute_code` for everything wider.
|
|
20
|
-
The
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
work. Note which baseline that is: the executor-free nine serialize to 7,207B,
|
|
24
|
-
so the seven-tool surface is *larger* than the row below it in that table. It
|
|
25
|
-
buys the program with those bytes. The [guest API contract](./code-mode.md) is
|
|
26
|
-
what a program is promised.
|
|
18
|
+
The consolidation removed overlapping routing choices while preserving the
|
|
19
|
+
cheaper direct path for one cold call. The [guest API contract](./code-mode.md)
|
|
20
|
+
is what a program is promised.
|
|
27
21
|
|
|
28
22
|
`execute_code` accepts optional `diagnostics: true` when a caller is measuring
|
|
29
23
|
a workflow. It adds only compact request-local timing and serialized-size
|
|
@@ -31,20 +25,11 @@ aggregates; normal calls carry no diagnostics block or response-context cost.
|
|
|
31
25
|
The measurements never contain program source, arguments, values, addresses,
|
|
32
26
|
credentials, logs, or raw error text.
|
|
33
27
|
|
|
34
|
-
Classic is the compatibility surface: what an executor-free deployment
|
|
35
|
-
necessarily serves, since the program surface the fold depends on is not there.
|
|
36
|
-
It is supported and tested, not an equal citizen in the docs. `surface:
|
|
37
|
-
"classic"` beside an executor is the only override; it produces the ten-tool
|
|
38
|
-
shape the [eval gate](../eval/code-first-gate/README.md)'s *incremental* arm
|
|
39
|
-
measures. That gate's control arm is executor-free classic and needs no
|
|
40
|
-
override.
|
|
41
|
-
|
|
42
28
|
Nothing became unreachable. `connecta.describe` takes the same addresses and
|
|
43
|
-
formats as
|
|
44
|
-
read-only calls
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
of it, and that is an operator concern: the operator pages and `/health` own it.
|
|
29
|
+
formats as the internal catalog service, `connecta.batch` runs 1–10 parallel
|
|
30
|
+
read-only calls and returns typed outcomes, and an unfiltered
|
|
31
|
+
`connecta.search({})` browses every catalog a program can reach. Live connector
|
|
32
|
+
probing is an operator concern: the operator pages and `/health` own it.
|
|
48
33
|
|
|
49
34
|
## Discovery context
|
|
50
35
|
|
|
@@ -61,8 +46,8 @@ shape. Bounded plain-object schemas also expose `inputKeys`,
|
|
|
61
46
|
list rather than repeating a large partial inventory. Matches carry declared
|
|
62
47
|
behavior annotations. When
|
|
63
48
|
that shape is sufficient, call the returned address directly. Reserve schema
|
|
64
|
-
expansion
|
|
65
|
-
|
|
49
|
+
expansion through `connecta.describe` for a search without schemas, an
|
|
50
|
+
ambiguous compact shape, or exact
|
|
66
51
|
constraints that require `format: "json"`.
|
|
67
52
|
|
|
68
53
|
Compact search is deliberately a routing view, not a second copy of connector
|
|
@@ -89,12 +74,11 @@ mode; they are not a duplicated Connecta object result. Newly stashed JSON and
|
|
|
89
74
|
downstream content envelopes use compact serialization, so `get_result` byte
|
|
90
75
|
offsets and totals refer to that exact compact text.
|
|
91
76
|
|
|
92
|
-
A `call_tool`
|
|
93
|
-
`
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
possible. Program results and oversized discovery responses carry no such
|
|
77
|
+
A `call_tool` truncation notice carries both the historical `resultId` and an
|
|
78
|
+
exact `nextAction: { tool: "get_result", arguments: { id, offset: 0 } }`. The
|
|
79
|
+
handle is therefore
|
|
80
|
+
directly actionable without copying an identifier out of prose; re-calling with
|
|
81
|
+
`fields` remains the smaller alternative when projection is possible. Program results and oversized discovery responses carry no such
|
|
98
82
|
route — paging a program's return value is a refused shape, because a program
|
|
99
83
|
can shrink anything before it returns.
|
|
100
84
|
|
|
@@ -186,7 +170,7 @@ read from the vault on the next call and needs no redeploy.
|
|
|
186
170
|
Predictable local refusals carry structured recovery on both result modes.
|
|
187
171
|
An unknown connector suggests an unscoped discovery query derived from the
|
|
188
172
|
attempted tool name; an unknown tool scopes the same query to the connector that
|
|
189
|
-
answered. The suggested route follows the caller
|
|
173
|
+
answered. The suggested route follows the route the caller took: `tool:
|
|
190
174
|
"search_tools"` for a top-level call, `function: "connecta.search"` with the same
|
|
191
175
|
arguments when the miss happened inside `execute_code`, which has no way to call
|
|
192
176
|
a tool. A read path that reaches an unannotated, write-capable, or destructive
|
|
@@ -197,9 +181,10 @@ That route echoes the caller's own arguments back only while they fit a
|
|
|
197
181
|
512-byte budget, and then whole — never clipped. An error envelope is not
|
|
198
182
|
size-guarded the way a result is, so an unbounded echo would let a large
|
|
199
183
|
argument object produce a refusal many times the deployment's result cap, on
|
|
200
|
-
both `call_tool` and
|
|
201
|
-
|
|
202
|
-
|
|
184
|
+
both `call_tool` and calls a program routes through `connecta.call` or
|
|
185
|
+
`connecta.batch`. Over budget, `args` is absent and the `purpose` says to
|
|
186
|
+
re-send what was just sent: the agent already holds its own arguments, and half
|
|
187
|
+
of them would describe a call nobody made.
|
|
203
188
|
|
|
204
189
|
The address gets the same budget and the opposite rule: 512 bytes, clamped
|
|
205
190
|
with a trailing `…` rather than dropped. It is caller-authored too — an
|
|
@@ -221,9 +206,11 @@ reason rather than as a reason to refuse the call.
|
|
|
221
206
|
Activity carries an optional coarse `friction` class: `tool_not_found`,
|
|
222
207
|
`schema_retry`, `destructive_reroute`, `auth_required`, or `result_too_large`.
|
|
223
208
|
It is derived from the typed error code, except on the one call that has no
|
|
224
|
-
error code to derive from: a
|
|
225
|
-
|
|
226
|
-
|
|
209
|
+
error code to derive from: a result too large to return inline is friction for
|
|
210
|
+
the agent while remaining `outcome: "success"`. That applies to a `call_tool`
|
|
211
|
+
result, the only source of `result_too_large` friction. (Activity stored by
|
|
212
|
+
older releases may still carry the retired `batch_call` source; nothing writes
|
|
213
|
+
it today.)
|
|
227
214
|
An oversized *discovery* response and an oversized program return are shaped
|
|
228
215
|
differently and produce none, and an `errorCode` is written only when the call
|
|
229
216
|
actually failed. The category adds no arguments, results, search text,
|
|
@@ -20,13 +20,13 @@ program cannot "return less" of a screenshot — so any rich block a downstream
|
|
|
20
20
|
tool produces dies at the exit guard, converted to a truncation envelope whose
|
|
21
21
|
preview is the head of a base64 string, which is of use to no one.
|
|
22
22
|
|
|
23
|
-
The asymmetry is already visible in the
|
|
23
|
+
The asymmetry is already visible in the direct-call surface. `call_tool` forwards
|
|
24
24
|
non-text downstream blocks to the client untouched when they fit the result cap
|
|
25
25
|
(the fix for issue #43 made the guard measure every block, then pass them
|
|
26
26
|
through in original order). And on the intake side, a host call **inside** a
|
|
27
27
|
program is not size-capped at all: `unwrapMcpResult` hands mixed content
|
|
28
28
|
through as a raw object, base64 and all. So today a downstream image survives
|
|
29
|
-
the trip *into* the sandbox and survives the
|
|
29
|
+
the trip *into* the sandbox and survives the explicit tool boundary — the one
|
|
30
30
|
place it cannot survive is the exit of the surface connecta calls primary.
|
|
31
31
|
|
|
32
32
|
## The shape: `connecta.emit(block)`
|
|
@@ -103,8 +103,8 @@ uncapped) and emits it. The host attaches no attribution.
|
|
|
103
103
|
**M7.** `emit` does not spend the host-call budget (`L4`). Its bounds are
|
|
104
104
|
`M5`'s and only `M5`'s.
|
|
105
105
|
|
|
106
|
-
**M8.** `ExecuteResult` is unchanged.
|
|
107
|
-
|
|
106
|
+
**M8.** `ExecuteResult` is unchanged. Neither QuickJS, Dynamic Worker, nor a
|
|
107
|
+
third-party executor needs modification, and the parity suite runs the same
|
|
108
108
|
emitting program through both vitest projects and asserts identical delivered
|
|
109
109
|
content.
|
|
110
110
|
|
package/ethos.md
CHANGED
|
@@ -67,7 +67,7 @@ proposing one without a new argument is not.
|
|
|
67
67
|
| Policy engine, approvals, pauses | refused | the host asks the human; connecta only annotates |
|
|
68
68
|
| Runtime connector registration | refused | config-as-code is the security model |
|
|
69
69
|
| Protocol sessions & server push | refused | stateless per request |
|
|
70
|
-
| Resources & prompts aggregation | refused | tools only |
|
|
70
|
+
| Resources & prompts aggregation | refused | tools only; connecta's own Apps shell is the one `resources/read` carve-out ([#266](https://github.com/zackbart/connecta/issues/266)) |
|
|
71
71
|
| Elicitation passthrough | refused | no route through a stateless aggregator |
|
|
72
72
|
| Repository formatter | refused | style is authored, not enforced |
|
|
73
73
|
| Toolkits (scoped views) | removed | never earned its keep; deploy per audience ([#178](https://github.com/zackbart/connecta/issues/178)) |
|
|
@@ -90,8 +90,13 @@ proposing one without a new argument is not.
|
|
|
90
90
|
| Downstream `ttlMs` cache hints | gated | fixed TTL + fingerprint is battle-tested and catalog reads are ~3 ms; earns its way in with refresh-churn evidence ([#176](https://github.com/zackbart/connecta/issues/176)) |
|
|
91
91
|
| Rich program output (`connecta.emit`) | accepted | one host-collected emission channel: programs emit strictly validated text/image/audio blocks, delivered after the result envelope on success only; budgets fail loudly at the emit call ([design record](./documentation/rich-output-design.md), [#267](https://github.com/zackbart/connecta/issues/267)) |
|
|
92
92
|
| Result-channel widening of the `Executor` contract | refused | `ExecuteResult` stays `{ result, error?, logs? }` — structural compatibility with `@cloudflare/codemode` is the parity guarantee; emission rides the provider bridge instead ([#267](https://github.com/zackbart/connecta/issues/267)) |
|
|
93
|
-
| Guest-emitted `resource` / `resource_link` blocks |
|
|
93
|
+
| Guest-emitted `resource` / `resource_link` blocks | refused | a program can never mint a URI a client may dereference, and the carve-out this row waited on does not need one: `connecta.ui` takes HTML content, and the only `ui://` URI is connecta's build-time shell ([design record](./documentation/mcp-ui-design.md), [#266](https://github.com/zackbart/connecta/issues/266), [#267](https://github.com/zackbart/connecta/issues/267)) |
|
|
94
94
|
| Provenance tracking for emitted content | refused | everything a program emits is program output; handles or attribution labels are capability-shaped machinery that changes no client's trust posture ([#267](https://github.com/zackbart/connecta/issues/267)) |
|
|
95
|
+
| Program-generated UI (`connecta.ui` + the Apps shell) | accepted | one MCP Apps view per successful run: the program supplies HTML only, delivered in result `_meta`, which hosts keep out of model context, and rendered by connecta's static shell inside the host's sandboxed frame ([design record](./documentation/mcp-ui-design.md), [#266](https://github.com/zackbart/connecta/issues/266)) |
|
|
96
|
+
| Serving connecta's own UI template via `resources/read` | accepted | a narrow carve-out from the resources-aggregation refusal, not a reversal of it: one static build-time shell at one URI, an empty `resources/list`, nothing downstream ever listed or aggregated ([#266](https://github.com/zackbart/connecta/issues/266)) |
|
|
97
|
+
| Downstream MCP Apps template passthrough | gated | proxying downstream `resources/read` earns its way in when a downstream connector actually ships an Apps template ([#266](https://github.com/zackbart/connecta/issues/266)) |
|
|
98
|
+
| View-initiated tool calls from program UI | gated | the host-mediated path exists and would take the ordinary audit and consent route, but a program-authored UI driving tools needs its own argument; display-only until one arrives ([#266](https://github.com/zackbart/connecta/issues/266)) |
|
|
99
|
+
| Legacy embedded `UIResource` delivery | refused | superseded upstream and rendered by none of the clients connecta faces; per-request minted URIs also fight the caching the Apps spec assumes ([#266](https://github.com/zackbart/connecta/issues/266)) |
|
|
95
100
|
|
|
96
101
|
## Invariants
|
|
97
102
|
|
package/examples/node/README.md
CHANGED
|
@@ -32,8 +32,7 @@ value outside this local example.
|
|
|
32
32
|
Keep this deployment small:
|
|
33
33
|
|
|
34
34
|
- Edit `src/index.ts` to change connectors, auth, storage, and the public URL.
|
|
35
|
-
- Keep `executor: quickJsExecutor()`
|
|
36
|
-
compatibility surface.
|
|
35
|
+
- Keep the required `executor: quickJsExecutor()` configuration.
|
|
37
36
|
- Keep secrets in environment variables or an external secret store. Never put
|
|
38
37
|
tokens in `src/index.ts`, connector guides, or committed JSON.
|
|
39
38
|
- Add application code only when implementing a deliberate `api()` connector.
|
|
@@ -30,9 +30,7 @@ const connecta = createConnecta({
|
|
|
30
30
|
auth: bearerToken(token, { subjectId: "operator" }),
|
|
31
31
|
// Downstream OAuth callbacks use this deployment origin.
|
|
32
32
|
publicUrl: `http://localhost:${port}`,
|
|
33
|
-
//
|
|
34
|
-
// This line is also what selects the seven-tool code-first surface; remove it
|
|
35
|
-
// to serve the nine classic meta-tools instead.
|
|
33
|
+
// Required: QuickJS runs model-written JS in a bounded disposable child.
|
|
36
34
|
executor: quickJsExecutor(),
|
|
37
35
|
connectors: [
|
|
38
36
|
api("time", {
|
|
@@ -2,9 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
A deployable Worker that aggregates a downstream remote MCP and an in-code HTTP
|
|
4
4
|
API connector, guarded by Clerk OAuth *and* a static bearer token, with state in
|
|
5
|
-
a KV namespace.
|
|
6
|
-
|
|
7
|
-
without it, serving the nine classic meta-tools on the Workers Free plan.
|
|
5
|
+
a KV namespace. Its required Worker Loader binding backs the seven-tool surface
|
|
6
|
+
and requires the Workers Paid plan.
|
|
8
7
|
|
|
9
8
|
This is also the **starting template for a deployment**: a real deployment
|
|
10
9
|
should be its own repository that pins an exact `@zackbart/connecta` version and
|
|
@@ -56,21 +55,17 @@ Connections. Credentials is at `/credentials`, named MCP access tokens are at
|
|
|
56
55
|
|
|
57
56
|
## Code mode
|
|
58
57
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
The Worker Loader binding is
|
|
62
|
-
environment variable is needed. Add this block to `wrangler.jsonc` (and a comma
|
|
63
|
-
after the preceding property):
|
|
58
|
+
The Dynamic Worker sandbox requires the
|
|
59
|
+
[Workers Paid plan](https://developers.cloudflare.com/dynamic-workers/pricing/).
|
|
60
|
+
The required Worker Loader binding is checked into `wrangler.jsonc`:
|
|
64
61
|
|
|
65
62
|
```jsonc
|
|
66
63
|
"worker_loaders": [{ "binding": "LOADER" }]
|
|
67
64
|
```
|
|
68
65
|
|
|
69
|
-
`src/index.ts`
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
the nine classic meta-tools. A deployment copied into its own repository must
|
|
73
|
-
also install the executor package before enabling the binding:
|
|
66
|
+
`src/index.ts` constructs `DynamicWorkerExecutor` from `env.LOADER` and serves
|
|
67
|
+
the seven-tool surface. A deployment copied into its own repository must also
|
|
68
|
+
install the executor package:
|
|
74
69
|
|
|
75
70
|
```sh
|
|
76
71
|
npm install @cloudflare/codemode
|