@zackbart/connecta 0.18.3 → 0.20.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.
Files changed (76) hide show
  1. package/CHANGELOG.md +131 -4
  2. package/dist/apps-shell.d.ts +10 -12
  3. package/dist/apps-shell.js +29 -220
  4. package/dist/catalog-service.d.ts +16 -13
  5. package/dist/catalog-service.js +106 -115
  6. package/dist/catalog.js +29 -46
  7. package/dist/connector-scope.js +2 -7
  8. package/dist/connectors/api.d.ts +4 -16
  9. package/dist/connectors/api.js +19 -46
  10. package/dist/connectors/guarded-fetch.d.ts +9 -23
  11. package/dist/connectors/guarded-fetch.js +38 -76
  12. package/dist/connectors/remote-mcp.js +36 -79
  13. package/dist/errors.d.ts +6 -27
  14. package/dist/errors.js +8 -5
  15. package/dist/execute.d.ts +23 -28
  16. package/dist/execute.js +101 -257
  17. package/dist/executor-result.d.ts +1 -0
  18. package/dist/executor-result.js +4 -11
  19. package/dist/executors/quickjs-child.js +1 -3
  20. package/dist/executors/quickjs-runtime.js +1 -3
  21. package/dist/executors/quickjs.js +1 -3
  22. package/dist/index.js +134 -123
  23. package/dist/invocation.d.ts +1 -1
  24. package/dist/invocation.js +113 -183
  25. package/dist/meta-tools.d.ts +15 -29
  26. package/dist/meta-tools.js +41 -582
  27. package/dist/operator-ui/generated.d.ts +2 -2
  28. package/dist/providers/cloudflare.d.ts +2 -18
  29. package/dist/providers/cloudflare.js +1460 -2451
  30. package/dist/providers/linear.d.ts +4 -41
  31. package/dist/providers/linear.js +8 -39
  32. package/dist/providers/mixpanel.d.ts +3 -25
  33. package/dist/providers/mixpanel.js +7 -22
  34. package/dist/providers/notion.d.ts +1 -15
  35. package/dist/providers/notion.js +44 -173
  36. package/dist/providers/revenuecat.d.ts +4 -57
  37. package/dist/providers/revenuecat.js +10 -93
  38. package/dist/providers/stripe.d.ts +1 -12
  39. package/dist/providers/stripe.js +7 -45
  40. package/dist/registry.d.ts +9 -34
  41. package/dist/registry.js +9 -103
  42. package/dist/routes/mcp.js +1 -1
  43. package/dist/routes/oauth.js +3 -3
  44. package/dist/routes/shared.d.ts +15 -15
  45. package/dist/routes/shared.js +1 -3
  46. package/dist/skills.d.ts +1 -1
  47. package/dist/skills.js +5 -5
  48. package/dist/timeout.d.ts +8 -7
  49. package/dist/timeout.js +47 -38
  50. package/dist/types.d.ts +3 -3
  51. package/dist/ui.d.ts +1 -25
  52. package/dist/ui.js +18 -45
  53. package/dist/version.d.ts +1 -1
  54. package/dist/version.js +1 -1
  55. package/documentation/call-admission.md +1 -1
  56. package/documentation/cloudflare.md +1 -1
  57. package/documentation/code-mode.md +25 -25
  58. package/documentation/connectors.md +24 -1
  59. package/documentation/linear.md +1 -1
  60. package/documentation/meta-tools.md +4 -30
  61. package/documentation/mixpanel.md +1 -1
  62. package/documentation/notion.md +1 -1
  63. package/documentation/operations.md +29 -25
  64. package/documentation/provider-conventions.md +4 -5
  65. package/documentation/revenuecat.md +1 -1
  66. package/documentation/stripe.md +1 -1
  67. package/documentation/upgrading.md +43 -8
  68. package/ethos.md +75 -121
  69. package/package.json +3 -4
  70. package/templates/node/package.json +1 -1
  71. package/documentation/code-first-exploration.md +0 -292
  72. package/documentation/mcp-2026-07-28.md +0 -46
  73. package/documentation/mcp-ui-design.md +0 -382
  74. package/documentation/program-ui-read-calls.md +0 -213
  75. package/documentation/provider-audit.md +0 -198
  76. package/documentation/rich-output-design.md +0 -211
package/CHANGELOG.md CHANGED
@@ -2,6 +2,126 @@
2
2
 
3
3
  All notable changes to this package are documented here.
4
4
 
5
+ ## 0.20.0 — 2026-08-26
6
+
7
+ This release removes the two side languages that had grown around the seven
8
+ tools: direct-call field projection and live reads from program-rendered HTML.
9
+ Agents now shape data in JavaScript, page a genuinely large direct result with
10
+ `get_result`, and render a display-only view. Configuration becomes strict at
11
+ the same boundary, so a typo fails at startup instead of surviving as inert
12
+ deployment state. Existing deployments using current documented options need
13
+ only bump the pin and reconcile generation B; callers that send `fields`,
14
+ programs that bind UI reads, and JavaScript deployments carrying retired or
15
+ misspelled options must migrate. The implementation stack deletes 2,866 lines
16
+ and adds 1,063 before these release notes, while preserving exactly seven MCP
17
+ tools and the Node and Worker deployment shapes.
18
+
19
+ ### Changed
20
+
21
+ - **One data-shaping language.** `call_tool` and `call_destructive_tool` no
22
+ longer advertise or accept `fields`; the path resolver, schema walker,
23
+ projection recovery records, guidance, and tests are gone. `execute_code`
24
+ remains the projection surface, while `get_result` still pages oversized
25
+ direct reads byte-exactly. The 21-scenario current-version audit passes, fixed
26
+ tool definitions fall from 1,625 to 1,587 tokens, and a deterministic
27
+ 52,396-byte document succeeds 3/3 through `call_tool` then `get_result`
28
+ (#482).
29
+ - **Display-only program UI.** `connecta.ui(html)` keeps success-only Apps
30
+ delivery, sandboxing, one shared rich-output budget, executor parity, and the
31
+ compact mirrored return. Its read manifest, host-call bridge, and second
32
+ argument are removed. All seven tools explicitly remain model-only, and only
33
+ `execute_code` advertises the cache-busted v3 display resource, so neither a
34
+ current nor cached shell can call Connecta tools (#484).
35
+ - **Strict configuration.** `createConnecta` rejects unknown own options at
36
+ every closed configuration path without reading or quoting their values.
37
+ Each closed schema is compile-time exhaustive against its public type;
38
+ connector, auth, storage, activity-store, logger, deployment-metadata, and
39
+ executor implementations remain open leaves (#485).
40
+ - **Smaller package and tests.** Generated operator assets publish explicit
41
+ 95-byte string declarations instead of embedding about 95 KB of literals,
42
+ with byte-identical runtime output and a packed-package size guard (#486).
43
+ The remaining copied UI credential and meta-tool fixtures are shared without
44
+ changing any test or assertion count, removing another 127 repository lines
45
+ (#479).
46
+
47
+ ### Removed
48
+
49
+ - The direct-call `fields` projection option and its dot/array path language.
50
+ - Program UI read bindings, `connecta.read`, and the Apps-to-host tool-call
51
+ bridge.
52
+
53
+ ### Declined with evidence
54
+
55
+ - Discovery filtering did not ship: its sealed qualification retained complete
56
+ scenario coverage but missed top-1, recall, default-page recall, and negative
57
+ false-positive gates. The holdout cases were not inspected or tuned (#481).
58
+ - Lean object-result text did not ship: Codex and Claude read ordinary
59
+ structured values, but Claude's error route took extra recovery actions and
60
+ Cursor was unavailable for the required matrix. The compatibility JSON copy
61
+ remains (#483).
62
+
63
+ ## 0.19.0 — 2026-08-25
64
+
65
+ This release is a smaller, simpler package with no behavioral change for a
66
+ deployment. A deployment can ignore everything here. The one packaging change
67
+ is to maintainer history: six finished design records now live in the repository
68
+ at their GitHub URLs instead of shipping in the npm package, cutting 91 KB from
69
+ the package. The constitution is a fifth of its former size and keeps the same
70
+ decisions. The core shrank from 28,756 to 27,417 lines and the tests from 42,341
71
+ to 40,375 before the final pure-suite splits, which put 198 lines back — a
72
+ copied preamble the next pass moves into a fixture (#479). Two dead paths
73
+ finally left with the rest: write-only health accessors on `RegistryView`,
74
+ residue from #179, and the v1 persisted-catalog reader, unreachable since
75
+ 2026-07-28.
76
+
77
+ ### Changed
78
+
79
+ - **Providers.** Every Cloudflare tool is built through `cfTool()` and
80
+ `compact()`, so the repeated schema headers, scope properties, and
81
+ conditional-spread projections are written once; the paged-list, delete-ack,
82
+ and passthrough handlers are shared while every tool keeps its own literal
83
+ name, description, annotations, and schemas; the authentication `Symbol` is
84
+ gone because the scheme is fixed per connector (#465, #467). Cloudflare and
85
+ Notion share one Retry-After parser and one guarded JSON accessor, and
86
+ `callCloudflareContent` still classifies an unreadable error body by status
87
+ (#468). The hosted providers' comment blocks point at their guides instead of
88
+ restating them, and `api()` omits undefined options once (#463).
89
+ - **Core.** Internal option types accept `undefined`, so `createConnecta` and
90
+ the registration helpers forward configuration directly (#462). One deadline,
91
+ sleep, and message helper serve the whole call path; `invocation.ts` lost its
92
+ hand-rolled controller, timer, and listener choreography with Retry-After,
93
+ per-phase timing, and health exclusion unchanged (#466). The catalog,
94
+ execution, remote-MCP, and operator-data paths share their failure envelopes,
95
+ close helpers, and result shapers (#472).
96
+ - **Package and docs.** Six finished design records moved to
97
+ [`records/`](https://github.com/zackbart/connecta/tree/main/records), outside
98
+ the published files, and packed-link validation folded into the documentation
99
+ checker: −1,342 shipped lines, −91 KB (#464). `ethos.md` is the constitution
100
+ again — refusals, invariants, and what this is, at 1,188 words instead of
101
+ 3,174, with every decision intact and a word cap replacing the line cap
102
+ (#471).
103
+ - **Tests.** Shared fixtures replace 122 hand-rolled connector literals, five
104
+ fake downstream MCP servers, four fake Clerk auths, and the per-suite JSON-RPC
105
+ readers, deferreds, and logger spies (#473, #474, #475); the six
106
+ provider-registry suites are one `describe.each` and the provider error
107
+ mappings are tables (#476); repeated cases are `it.each` tables and the
108
+ duplicated assertion layers are gone (#477); the two largest suites are split
109
+ by subject (#460). Every `it()` and `expect()` that pins behavior survived —
110
+ 3,400 fewer lines, no assertion dropped.
111
+ - **Declined with numbers**, so nobody re-runs the experiment: a shared bounded
112
+ queue under both admission controllers measured −17 lines for a
113
+ hook-parameterised abstraction (#453); Effect as the core effect system
114
+ measured −4% of the core for +75 KB gzip and a second async paradigm (#470).
115
+
116
+ ### Removed
117
+
118
+ - The write-only `HealthLog` and its `RegistryView` accessors: `healthFor`,
119
+ `hasObservedSuccess`, `observedSuccessAt`, and `peekTools`. `refreshTools` is
120
+ now private. This was residue from the removed proactive credential-liveness
121
+ design (#179).
122
+ - The v1 persisted-catalog reader, unreachable since 2026-07-28. Its fixtures
123
+ now use the v2 manifest-and-chunks format.
124
+
5
125
  ## 0.18.3 — 2026-08-25
6
126
 
7
127
  This change gives code mode memory where downstream MCP catalogs are usually
@@ -29,6 +149,13 @@ one or several real results from masquerading as a provider contract.
29
149
  live deployment audits: BePresent had 246/378 tools without output schemas,
30
150
  while OneMany's maintained connectors declared all 90 (#442).
31
151
 
152
+ ### Changed
153
+
154
+ - Removed the write-only per-connector call health log. Payload-free call
155
+ outcomes remain available through the activity sink.
156
+ - Persisted catalog reads now require the version 2 manifest-and-chunks shape;
157
+ version 1 single-value catalogs are ignored and refreshed.
158
+
32
159
  ## 0.18.2 — 2026-08-18
33
160
 
34
161
  This patch closes the gap the RevenueCat rollout exposed on the same day 0.18.1
@@ -752,7 +879,7 @@ it back byte-identical after each operator mutation.
752
879
  constructor, schemas, validation path, handlers, and catalog service run, and
753
880
  only `fetch` is a probe that records the request (#350).
754
881
  - **Five provider audit reports** in
755
- [`documentation/provider-audit.md`](./documentation/provider-audit.md), with
882
+ [`records/provider-audit.md`](https://github.com/zackbart/connecta/blob/main/records/provider-audit.md), with
756
883
  a verdict per convention, the fix for every miss, and every accepted
757
884
  exception recorded with its argument (#342).
758
885
  - **A convention test over the shipped surface.**
@@ -1535,7 +1662,7 @@ The channel is additive — a program that never calls `connecta.ui` produces th
1535
1662
  byte-for-byte prior response, no executor changed to carry it, and there is no
1536
1663
  new budget knob. Deployments do gain a `resources` capability and one extension
1537
1664
  declaration, both of which the design requires before any host will render.
1538
- The design record is `documentation/mcp-ui-design.md` (#266); the contract is
1665
+ The design record is [`records/mcp-ui-design.md`](https://github.com/zackbart/connecta/blob/main/records/mcp-ui-design.md) (#266); the contract is
1539
1666
  `code-mode.md`'s "Rendered output" clauses (#277).
1540
1667
 
1541
1668
  ### Added
@@ -1611,7 +1738,7 @@ that cannot be projected, like a screenshot a downstream tool returned. The
1611
1738
  channel is additive: a program that never emits produces the byte-for-byte
1612
1739
  prior response, no executor changed to carry it, and deployments that do
1613
1740
  nothing get sensible budgets. The design record is
1614
- `documentation/rich-output-design.md` (#267); the contract is `code-mode.md`'s
1741
+ [`records/rich-output-design.md`](https://github.com/zackbart/connecta/blob/main/records/rich-output-design.md) (#267); the contract is `code-mode.md`'s
1615
1742
  "Emitted output" clauses (#270).
1616
1743
 
1617
1744
  Agent recovery is now executable data instead of prose at the remaining local
@@ -2024,7 +2151,7 @@ tests confirm SDK v2 client close does not make it redundant.
2024
2151
  advances the existing generation fence before the SDK starts a fresh grant;
2025
2152
  pre-upgrade credentials bind in place on their first validated read.
2026
2153
  - **A complete in-repo disposition of the revision** in
2027
- [`documentation/mcp-2026-07-28.md`](./documentation/mcp-2026-07-28.md),
2154
+ [`records/mcp-2026-07-28.md`](https://github.com/zackbart/connecta/blob/main/records/mcp-2026-07-28.md),
2028
2155
  including the declined and gated surfaces.
2029
2156
 
2030
2157
  ### Changed
@@ -3,16 +3,16 @@
3
3
  *
4
4
  * A build-time string constant, not a file read at startup: the core is
5
5
  * Web-API-only so it runs unchanged on Workers, and the same bytes have to
6
- * serve everywhere. It renders whatever HTML a program handed `connecta.ui`
7
- * inside a nested `srcdoc` frame. The one-argument form forwards no channel;
8
- * an explicitly bound view gets only named read calls through the trusted
9
- * shell, never a raw host channel.
6
+ * serve everywhere. The shell is display-only: it renders whatever HTML a
7
+ * program handed `connecta.ui` inside a nested `srcdoc` frame and forwards no
8
+ * channel back from that frame to the host, so program-authored markup is
9
+ * inert beyond its own pixels.
10
10
  *
11
11
  * The address carries a version segment because hosts are permitted to
12
12
  * prefetch and cache templates by URI: change these bytes, bump the version.
13
13
  */
14
14
  /** The only `ui://` URI in the system. No program input reaches it. */
15
- export declare const PROGRAM_UI_RESOURCE_URI = "ui://connecta/program-ui/v2";
15
+ export declare const PROGRAM_UI_RESOURCE_URI = "ui://connecta/program-ui/v3";
16
16
  /** The mimeType the Apps spec requires of an HTML template. */
17
17
  export declare const PROGRAM_UI_MIME_TYPE = "text/html;profile=mcp-app";
18
18
  /**
@@ -29,11 +29,9 @@ export declare const MCP_APPS_EXTENSION = "io.modelcontextprotocol/ui";
29
29
  * Apps postMessage dialect (`ui/initialize`, `ui/notifications/initialized`,
30
30
  * `ui/notifications/tool-result`, `ui/notifications/size-changed`,
31
31
  * `ui/resource-teardown`), lifts `_meta["connecta/ui"].html` out of the
32
- * delivered tool result, and puts it in a frame. An optional read manifest
33
- * installs one narrow `connecta.read(name, args)` bridge in that inner frame;
34
- * the outer shell maps declared names to the existing `call_tool` meta-tool.
35
- * It declares no CSP domains, so the host applies its restrictive default and
36
- * the `srcdoc` frame inherits `default-src 'none'` — program markup still gets
37
- * no direct network.
32
+ * delivered tool result, and puts it in a frame. It declares no CSP domains,
33
+ * so the host applies its restrictive default and the `srcdoc` frame inherits
34
+ * `default-src 'none'`. The payload gets scripts and local interactivity, and
35
+ * no network.
38
36
  */
39
- export declare const PROGRAM_UI_SHELL_HTML = "<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <title>connecta program view</title>\n <style>\n html,\n body {\n margin: 0;\n padding: 0;\n background: transparent;\n }\n #program-view {\n display: block;\n width: 100%;\n min-height: 480px;\n border: 0;\n }\n </style>\n </head>\n <body>\n <iframe\n id=\"program-view\"\n title=\"Program-rendered view\"\n sandbox=\"allow-scripts\"\n srcdoc=\"\"\n ></iframe>\n <script>\n (function () {\n \"use strict\";\n // The outer shell is the only host peer. The payload frame is\n // sandboxed to scripts alone, with no same-origin escape. Its one\n // optional message dialect is handled below and translated into\n // bounded call_tool requests; raw JSON-RPC is never forwarded.\n var host = window.parent;\n var view = document.getElementById(\"program-view\");\n var initializeId = \"connecta-ui-initialize\";\n var lastWidth = 0;\n var lastHeight = 0;\n var reads = null;\n var hostCanCallTools = false;\n var nextHostRequestId = 0;\n var pendingHostReads = Object.create(null);\n var activeHostReads = 0;\n var maxActiveHostReads = 8;\n\n function send(message) {\n if (!host || host === window) return;\n host.postMessage(message, \"*\");\n }\n\n function notify(method, params) {\n send({ jsonrpc: \"2.0\", method: method, params: params });\n }\n\n // Program views are fixed-height by construction. The shell has no\n // content-height bridge to the payload frame, so it can never learn\n // the payload's content height, and\n // what it reports here is its own box: the min-height above, unless\n // the host has given it more. Taller content scrolls inside the inner\n // frame rather than growing the view. Raising the min-height is the\n // only lever; a content-height signal would cost the isolation.\n function reportSize() {\n var width = Math.ceil(document.documentElement.clientWidth);\n var height = Math.ceil(document.documentElement.scrollHeight);\n if (width === lastWidth && height === lastHeight) return;\n lastWidth = width;\n lastHeight = height;\n notify(\"ui/notifications/size-changed\", {\n width: width,\n height: height\n });\n }\n\n function payload(result) {\n if (!result || typeof result !== \"object\") return null;\n var meta = result._meta;\n if (!meta || typeof meta !== \"object\") return null;\n var value = meta[\"connecta/ui\"];\n if (!value || typeof value !== \"object\") return null;\n return typeof value.html === \"string\" && value.html.length > 0\n ? value\n : null;\n }\n\n // This function is serialized into the opaque-origin payload frame.\n // It knows no addresses and has no host channel of its own: one named\n // read request goes to the trusted outer shell and one correlated\n // result comes back.\n function payloadReadBridge() {\n \"use strict\";\n var pending = Object.create(null);\n var nextId = 0;\n\n function read(name, args) {\n return new Promise(function (resolve, reject) {\n var id = String(++nextId);\n pending[id] = { resolve: resolve, reject: reject };\n try {\n window.parent.postMessage({\n type: \"connecta/read\",\n id: id,\n name: name,\n args: args === undefined ? {} : args\n }, \"*\");\n } catch (error) {\n delete pending[id];\n reject(error);\n }\n });\n }\n\n Object.defineProperty(globalThis, \"connecta\", {\n value: Object.freeze({ read: read }),\n configurable: false,\n enumerable: true,\n writable: false\n });\n\n window.addEventListener(\"message\", function (event) {\n if (event.source !== window.parent) return;\n var message = event.data;\n if (!message || message.type !== \"connecta/read-result\") return;\n var waiter = pending[message.id];\n if (!waiter) return;\n delete pending[message.id];\n if (message.ok) waiter.resolve(message.value);\n else waiter.reject(new Error(message.error || \"Read failed\"));\n });\n }\n\n function htmlWithReadBridge(html) {\n var script =\n \"<scr\" + \"ipt>(\" + payloadReadBridge.toString() + \")();</scr\" + \"ipt>\";\n var head = /<head(?:\\s[^>]*)?>/i.exec(html);\n if (head) {\n var at = (head.index || 0) + head[0].length;\n return html.slice(0, at) + script + html.slice(at);\n }\n var document = /<html(?:\\s[^>]*)?>/i.exec(html);\n if (document) {\n var afterHtml = (document.index || 0) + document[0].length;\n return html.slice(0, afterHtml) + \"<head>\" + script + \"</head>\" + html.slice(afterHtml);\n }\n return script + html;\n }\n\n function render(params) {\n var value =\n payload(params) ||\n payload(params && params.result) ||\n payload(params && params.toolResult);\n if (value === null) return;\n reads = value.reads && typeof value.reads === \"object\"\n ? value.reads\n : null;\n view.srcdoc = reads\n ? htmlWithReadBridge(value.html)\n : value.html;\n reportSize();\n }\n\n function readError(message, fallback) {\n if (message && typeof message.message === \"string\") return message.message;\n if (message && message.data && typeof message.data.message === \"string\") {\n return message.data.message;\n }\n return fallback;\n }\n\n function finishInnerRead(innerId, ok, value) {\n if (!view.contentWindow) return;\n view.contentWindow.postMessage(ok\n ? { type: \"connecta/read-result\", id: innerId, ok: true, value: value }\n : { type: \"connecta/read-result\", id: innerId, ok: false, error: value }, \"*\");\n }\n\n function beginInnerRead(message) {\n if (!hostCanCallTools) {\n finishInnerRead(message && message.id, false, \"This host does not support app-initiated server tool calls\");\n return;\n }\n if (!message || typeof message.id !== \"string\" || typeof message.name !== \"string\") return;\n if (!reads || !Object.prototype.hasOwnProperty.call(reads, message.name)) {\n finishInnerRead(message.id, false, \"Unknown read binding\");\n return;\n }\n if (activeHostReads >= maxActiveHostReads) {\n finishInnerRead(message.id, false, \"Too many concurrent reads\");\n return;\n }\n var binding = reads[message.name];\n if (!binding || typeof binding !== \"object\" || typeof binding.address !== \"string\") {\n finishInnerRead(message.id, false, \"Invalid read binding\");\n return;\n }\n var supplied = message.args;\n if (!supplied || typeof supplied !== \"object\" || Array.isArray(supplied)) {\n finishInnerRead(message.id, false, \"Read arguments must be an object\");\n return;\n }\n var allowed = Array.isArray(binding.viewArgs) ? binding.viewArgs : [];\n var suppliedKeys = Object.keys(supplied);\n for (var i = 0; i < suppliedKeys.length; i++) {\n var key = suppliedKeys[i];\n if (allowed.indexOf(key) === -1) {\n finishInnerRead(message.id, false, \"Undeclared read argument \" + JSON.stringify(key));\n return;\n }\n }\n var args = Object.create(null);\n var fixed = binding.fixedArgs && typeof binding.fixedArgs === \"object\"\n ? binding.fixedArgs\n : {};\n Object.keys(fixed).forEach(function (key) { args[key] = fixed[key]; });\n suppliedKeys.forEach(function (key) { args[key] = supplied[key]; });\n\n var hostId = \"connecta-ui-read-\" + String(++nextHostRequestId);\n pendingHostReads[hostId] = { innerId: message.id };\n activeHostReads++;\n send({\n jsonrpc: \"2.0\",\n id: hostId,\n method: \"tools/call\",\n params: {\n name: \"call_tool\",\n arguments: {\n address: binding.address,\n args: args,\n resultMode: \"value\"\n }\n }\n });\n }\n\n function finishHostRead(message) {\n var pending = pendingHostReads[message.id];\n if (!pending) return false;\n delete pendingHostReads[message.id];\n activeHostReads--;\n if (message.error) {\n finishInnerRead(pending.innerId, false, readError(message.error, \"Host rejected read\"));\n return true;\n }\n var toolResult = message.result;\n if (!toolResult || typeof toolResult !== \"object\") {\n finishInnerRead(pending.innerId, false, \"Host returned an invalid tool result\");\n return true;\n }\n var structured = toolResult.structuredContent;\n if (toolResult.isError || (structured && structured.ok === false)) {\n var detail = structured && structured.error;\n var content = Array.isArray(toolResult.content)\n ? toolResult.content.find(function (block) { return block && block.type === \"text\"; })\n : null;\n finishInnerRead(\n pending.innerId,\n false,\n readError(detail, content && content.text ? content.text : \"Read failed\")\n );\n return true;\n }\n var value = structured && structured.ok === true &&\n Object.prototype.hasOwnProperty.call(structured, \"data\")\n ? structured.data\n : structured !== undefined\n ? structured\n : toolResult;\n finishInnerRead(pending.innerId, true, value);\n return true;\n }\n\n window.addEventListener(\"message\", function (event) {\n var message = event.data;\n if (event.source === view.contentWindow) {\n if (message && message.type === \"connecta/read\") beginInnerRead(message);\n return;\n }\n if (event.source !== host) return;\n if (!message || message.jsonrpc !== \"2.0\") return;\n if (message.id !== undefined && finishHostRead(message)) return;\n if (message.method === \"ui/notifications/tool-result\") {\n render(message.params);\n return;\n }\n if (message.method === \"ui/resource-teardown\") {\n // A host->view request, not a notification: the host waits for\n // this reply before it tears the view down. There is nothing to\n // release, so answer immediately rather than make it time out.\n if (message.id !== undefined && message.id !== null) {\n send({ jsonrpc: \"2.0\", id: message.id, result: {} });\n }\n return;\n }\n // Only a completed handshake earns \"initialized\". A JSON-RPC error\n // response carries the same id, and announcing initialization on one\n // would assert a handshake that never happened.\n if (message.id === initializeId && message.result !== undefined) {\n var capabilities = message.result.hostCapabilities;\n hostCanCallTools = Boolean(capabilities && capabilities.serverTools);\n notify(\"ui/notifications/initialized\", {});\n }\n });\n\n window.addEventListener(\"resize\", reportSize);\n view.addEventListener(\"load\", reportSize);\n\n // Every field here is required by the Apps initialize schema, and a\n // conforming host rejects the request outright when one is missing \u2014\n // which would strand the shell before any tool result arrives.\n send({\n jsonrpc: \"2.0\",\n id: initializeId,\n method: \"ui/initialize\",\n params: {\n appInfo: { name: \"connecta program view\", version: \"1\" },\n appCapabilities: {},\n protocolVersion: \"2026-01-26\"\n }\n });\n reportSize();\n })();\n </script>\n </body>\n</html>\n";
37
+ export declare const PROGRAM_UI_SHELL_HTML = "<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <title>connecta program view</title>\n <style>\n html,\n body {\n margin: 0;\n padding: 0;\n background: transparent;\n }\n #program-view {\n display: block;\n width: 100%;\n min-height: 480px;\n border: 0;\n }\n </style>\n </head>\n <body>\n <iframe\n id=\"program-view\"\n title=\"Program-rendered view\"\n sandbox=\"allow-scripts\"\n srcdoc=\"\"\n ></iframe>\n <script>\n (function () {\n \"use strict\";\n // The host frame is the only peer this shell speaks to, in either\n // direction. The payload frame below is sandboxed to scripts alone,\n // with no same-origin escape, and is never handed a reply path:\n // anything it posts fails the source check and is dropped. There is\n // no bridge from program HTML to the host, by construction rather\n // than by validation.\n var host = window.parent;\n var view = document.getElementById(\"program-view\");\n var initializeId = \"connecta-ui-initialize\";\n var lastWidth = 0;\n var lastHeight = 0;\n\n function send(message) {\n if (!host || host === window) return;\n host.postMessage(message, \"*\");\n }\n\n function notify(method, params) {\n send({ jsonrpc: \"2.0\", method: method, params: params });\n }\n\n // Program views are fixed-height by construction. The shell has no\n // bridge to the payload frame \u2014 that is the security posture, not an\n // omission \u2014 so it can never learn the payload's content height, and\n // what it reports here is its own box: the min-height above, unless\n // the host has given it more. Taller content scrolls inside the inner\n // frame rather than growing the view. Raising the min-height is the\n // only lever; a content-height signal would cost the isolation.\n function reportSize() {\n var width = Math.ceil(document.documentElement.clientWidth);\n var height = Math.ceil(document.documentElement.scrollHeight);\n if (width === lastWidth && height === lastHeight) return;\n lastWidth = width;\n lastHeight = height;\n notify(\"ui/notifications/size-changed\", {\n width: width,\n height: height\n });\n }\n\n function payloadHtml(result) {\n if (!result || typeof result !== \"object\") return null;\n var meta = result._meta;\n if (!meta || typeof meta !== \"object\") return null;\n var payload = meta[\"connecta/ui\"];\n if (!payload || typeof payload !== \"object\") return null;\n var html = payload.html;\n return typeof html === \"string\" && html.length > 0 ? html : null;\n }\n\n function render(params) {\n var html =\n payloadHtml(params) ||\n payloadHtml(params && params.result) ||\n payloadHtml(params && params.toolResult);\n if (html === null) return;\n view.srcdoc = html;\n reportSize();\n }\n\n window.addEventListener(\"message\", function (event) {\n if (event.source !== host) return;\n var message = event.data;\n if (!message || message.jsonrpc !== \"2.0\") return;\n if (message.method === \"ui/notifications/tool-result\") {\n render(message.params);\n return;\n }\n if (message.method === \"ui/resource-teardown\") {\n // A host->view request, not a notification: the host waits for\n // this reply before it tears the view down. There is nothing to\n // release, so answer immediately rather than make it time out.\n if (message.id !== undefined && message.id !== null) {\n send({ jsonrpc: \"2.0\", id: message.id, result: {} });\n }\n return;\n }\n // Only a completed handshake earns \"initialized\". A JSON-RPC error\n // response carries the same id, and announcing initialization on one\n // would assert a handshake that never happened.\n if (message.id === initializeId && message.result !== undefined) {\n notify(\"ui/notifications/initialized\", {});\n }\n });\n\n window.addEventListener(\"resize\", reportSize);\n view.addEventListener(\"load\", reportSize);\n\n // Every field here is required by the Apps initialize schema, and a\n // conforming host rejects the request outright when one is missing \u2014\n // which would strand the shell before any tool result arrives.\n send({\n jsonrpc: \"2.0\",\n id: initializeId,\n method: \"ui/initialize\",\n params: {\n appInfo: { name: \"connecta program view\", version: \"1\" },\n appCapabilities: {},\n protocolVersion: \"2026-01-26\"\n }\n });\n reportSize();\n })();\n </script>\n </body>\n</html>\n";
@@ -3,16 +3,16 @@
3
3
  *
4
4
  * A build-time string constant, not a file read at startup: the core is
5
5
  * Web-API-only so it runs unchanged on Workers, and the same bytes have to
6
- * serve everywhere. It renders whatever HTML a program handed `connecta.ui`
7
- * inside a nested `srcdoc` frame. The one-argument form forwards no channel;
8
- * an explicitly bound view gets only named read calls through the trusted
9
- * shell, never a raw host channel.
6
+ * serve everywhere. The shell is display-only: it renders whatever HTML a
7
+ * program handed `connecta.ui` inside a nested `srcdoc` frame and forwards no
8
+ * channel back from that frame to the host, so program-authored markup is
9
+ * inert beyond its own pixels.
10
10
  *
11
11
  * The address carries a version segment because hosts are permitted to
12
12
  * prefetch and cache templates by URI: change these bytes, bump the version.
13
13
  */
14
14
  /** The only `ui://` URI in the system. No program input reaches it. */
15
- export const PROGRAM_UI_RESOURCE_URI = "ui://connecta/program-ui/v2";
15
+ export const PROGRAM_UI_RESOURCE_URI = "ui://connecta/program-ui/v3";
16
16
  /** The mimeType the Apps spec requires of an HTML template. */
17
17
  export const PROGRAM_UI_MIME_TYPE = "text/html;profile=mcp-app";
18
18
  /**
@@ -29,12 +29,10 @@ export const MCP_APPS_EXTENSION = "io.modelcontextprotocol/ui";
29
29
  * Apps postMessage dialect (`ui/initialize`, `ui/notifications/initialized`,
30
30
  * `ui/notifications/tool-result`, `ui/notifications/size-changed`,
31
31
  * `ui/resource-teardown`), lifts `_meta["connecta/ui"].html` out of the
32
- * delivered tool result, and puts it in a frame. An optional read manifest
33
- * installs one narrow `connecta.read(name, args)` bridge in that inner frame;
34
- * the outer shell maps declared names to the existing `call_tool` meta-tool.
35
- * It declares no CSP domains, so the host applies its restrictive default and
36
- * the `srcdoc` frame inherits `default-src 'none'` — program markup still gets
37
- * no direct network.
32
+ * delivered tool result, and puts it in a frame. It declares no CSP domains,
33
+ * so the host applies its restrictive default and the `srcdoc` frame inherits
34
+ * `default-src 'none'`. The payload gets scripts and local interactivity, and
35
+ * no network.
38
36
  */
39
37
  export const PROGRAM_UI_SHELL_HTML = `<!doctype html>
40
38
  <html lang="en">
@@ -67,21 +65,17 @@ export const PROGRAM_UI_SHELL_HTML = `<!doctype html>
67
65
  <script>
68
66
  (function () {
69
67
  "use strict";
70
- // The outer shell is the only host peer. The payload frame is
71
- // sandboxed to scripts alone, with no same-origin escape. Its one
72
- // optional message dialect is handled below and translated into
73
- // bounded call_tool requests; raw JSON-RPC is never forwarded.
68
+ // The host frame is the only peer this shell speaks to, in either
69
+ // direction. The payload frame below is sandboxed to scripts alone,
70
+ // with no same-origin escape, and is never handed a reply path:
71
+ // anything it posts fails the source check and is dropped. There is
72
+ // no bridge from program HTML to the host, by construction rather
73
+ // than by validation.
74
74
  var host = window.parent;
75
75
  var view = document.getElementById("program-view");
76
76
  var initializeId = "connecta-ui-initialize";
77
77
  var lastWidth = 0;
78
78
  var lastHeight = 0;
79
- var reads = null;
80
- var hostCanCallTools = false;
81
- var nextHostRequestId = 0;
82
- var pendingHostReads = Object.create(null);
83
- var activeHostReads = 0;
84
- var maxActiveHostReads = 8;
85
79
 
86
80
  function send(message) {
87
81
  if (!host || host === window) return;
@@ -93,8 +87,8 @@ export const PROGRAM_UI_SHELL_HTML = `<!doctype html>
93
87
  }
94
88
 
95
89
  // Program views are fixed-height by construction. The shell has no
96
- // content-height bridge to the payload frame, so it can never learn
97
- // the payload's content height, and
90
+ // bridge to the payload frame that is the security posture, not an
91
+ // omission — so it can never learn the payload's content height, and
98
92
  // what it reports here is its own box: the min-height above, unless
99
93
  // the host has given it more. Taller content scrolls inside the inner
100
94
  // frame rather than growing the view. Raising the min-height is the
@@ -111,213 +105,30 @@ export const PROGRAM_UI_SHELL_HTML = `<!doctype html>
111
105
  });
112
106
  }
113
107
 
114
- function payload(result) {
108
+ function payloadHtml(result) {
115
109
  if (!result || typeof result !== "object") return null;
116
110
  var meta = result._meta;
117
111
  if (!meta || typeof meta !== "object") return null;
118
- var value = meta["connecta/ui"];
119
- if (!value || typeof value !== "object") return null;
120
- return typeof value.html === "string" && value.html.length > 0
121
- ? value
122
- : null;
123
- }
124
-
125
- // This function is serialized into the opaque-origin payload frame.
126
- // It knows no addresses and has no host channel of its own: one named
127
- // read request goes to the trusted outer shell and one correlated
128
- // result comes back.
129
- function payloadReadBridge() {
130
- "use strict";
131
- var pending = Object.create(null);
132
- var nextId = 0;
133
-
134
- function read(name, args) {
135
- return new Promise(function (resolve, reject) {
136
- var id = String(++nextId);
137
- pending[id] = { resolve: resolve, reject: reject };
138
- try {
139
- window.parent.postMessage({
140
- type: "connecta/read",
141
- id: id,
142
- name: name,
143
- args: args === undefined ? {} : args
144
- }, "*");
145
- } catch (error) {
146
- delete pending[id];
147
- reject(error);
148
- }
149
- });
150
- }
151
-
152
- Object.defineProperty(globalThis, "connecta", {
153
- value: Object.freeze({ read: read }),
154
- configurable: false,
155
- enumerable: true,
156
- writable: false
157
- });
158
-
159
- window.addEventListener("message", function (event) {
160
- if (event.source !== window.parent) return;
161
- var message = event.data;
162
- if (!message || message.type !== "connecta/read-result") return;
163
- var waiter = pending[message.id];
164
- if (!waiter) return;
165
- delete pending[message.id];
166
- if (message.ok) waiter.resolve(message.value);
167
- else waiter.reject(new Error(message.error || "Read failed"));
168
- });
169
- }
170
-
171
- function htmlWithReadBridge(html) {
172
- var script =
173
- "<scr" + "ipt>(" + payloadReadBridge.toString() + ")();</scr" + "ipt>";
174
- var head = /<head(?:\\s[^>]*)?>/i.exec(html);
175
- if (head) {
176
- var at = (head.index || 0) + head[0].length;
177
- return html.slice(0, at) + script + html.slice(at);
178
- }
179
- var document = /<html(?:\\s[^>]*)?>/i.exec(html);
180
- if (document) {
181
- var afterHtml = (document.index || 0) + document[0].length;
182
- return html.slice(0, afterHtml) + "<head>" + script + "</head>" + html.slice(afterHtml);
183
- }
184
- return script + html;
112
+ var payload = meta["connecta/ui"];
113
+ if (!payload || typeof payload !== "object") return null;
114
+ var html = payload.html;
115
+ return typeof html === "string" && html.length > 0 ? html : null;
185
116
  }
186
117
 
187
118
  function render(params) {
188
- var value =
189
- payload(params) ||
190
- payload(params && params.result) ||
191
- payload(params && params.toolResult);
192
- if (value === null) return;
193
- reads = value.reads && typeof value.reads === "object"
194
- ? value.reads
195
- : null;
196
- view.srcdoc = reads
197
- ? htmlWithReadBridge(value.html)
198
- : value.html;
119
+ var html =
120
+ payloadHtml(params) ||
121
+ payloadHtml(params && params.result) ||
122
+ payloadHtml(params && params.toolResult);
123
+ if (html === null) return;
124
+ view.srcdoc = html;
199
125
  reportSize();
200
126
  }
201
127
 
202
- function readError(message, fallback) {
203
- if (message && typeof message.message === "string") return message.message;
204
- if (message && message.data && typeof message.data.message === "string") {
205
- return message.data.message;
206
- }
207
- return fallback;
208
- }
209
-
210
- function finishInnerRead(innerId, ok, value) {
211
- if (!view.contentWindow) return;
212
- view.contentWindow.postMessage(ok
213
- ? { type: "connecta/read-result", id: innerId, ok: true, value: value }
214
- : { type: "connecta/read-result", id: innerId, ok: false, error: value }, "*");
215
- }
216
-
217
- function beginInnerRead(message) {
218
- if (!hostCanCallTools) {
219
- finishInnerRead(message && message.id, false, "This host does not support app-initiated server tool calls");
220
- return;
221
- }
222
- if (!message || typeof message.id !== "string" || typeof message.name !== "string") return;
223
- if (!reads || !Object.prototype.hasOwnProperty.call(reads, message.name)) {
224
- finishInnerRead(message.id, false, "Unknown read binding");
225
- return;
226
- }
227
- if (activeHostReads >= maxActiveHostReads) {
228
- finishInnerRead(message.id, false, "Too many concurrent reads");
229
- return;
230
- }
231
- var binding = reads[message.name];
232
- if (!binding || typeof binding !== "object" || typeof binding.address !== "string") {
233
- finishInnerRead(message.id, false, "Invalid read binding");
234
- return;
235
- }
236
- var supplied = message.args;
237
- if (!supplied || typeof supplied !== "object" || Array.isArray(supplied)) {
238
- finishInnerRead(message.id, false, "Read arguments must be an object");
239
- return;
240
- }
241
- var allowed = Array.isArray(binding.viewArgs) ? binding.viewArgs : [];
242
- var suppliedKeys = Object.keys(supplied);
243
- for (var i = 0; i < suppliedKeys.length; i++) {
244
- var key = suppliedKeys[i];
245
- if (allowed.indexOf(key) === -1) {
246
- finishInnerRead(message.id, false, "Undeclared read argument " + JSON.stringify(key));
247
- return;
248
- }
249
- }
250
- var args = Object.create(null);
251
- var fixed = binding.fixedArgs && typeof binding.fixedArgs === "object"
252
- ? binding.fixedArgs
253
- : {};
254
- Object.keys(fixed).forEach(function (key) { args[key] = fixed[key]; });
255
- suppliedKeys.forEach(function (key) { args[key] = supplied[key]; });
256
-
257
- var hostId = "connecta-ui-read-" + String(++nextHostRequestId);
258
- pendingHostReads[hostId] = { innerId: message.id };
259
- activeHostReads++;
260
- send({
261
- jsonrpc: "2.0",
262
- id: hostId,
263
- method: "tools/call",
264
- params: {
265
- name: "call_tool",
266
- arguments: {
267
- address: binding.address,
268
- args: args,
269
- resultMode: "value"
270
- }
271
- }
272
- });
273
- }
274
-
275
- function finishHostRead(message) {
276
- var pending = pendingHostReads[message.id];
277
- if (!pending) return false;
278
- delete pendingHostReads[message.id];
279
- activeHostReads--;
280
- if (message.error) {
281
- finishInnerRead(pending.innerId, false, readError(message.error, "Host rejected read"));
282
- return true;
283
- }
284
- var toolResult = message.result;
285
- if (!toolResult || typeof toolResult !== "object") {
286
- finishInnerRead(pending.innerId, false, "Host returned an invalid tool result");
287
- return true;
288
- }
289
- var structured = toolResult.structuredContent;
290
- if (toolResult.isError || (structured && structured.ok === false)) {
291
- var detail = structured && structured.error;
292
- var content = Array.isArray(toolResult.content)
293
- ? toolResult.content.find(function (block) { return block && block.type === "text"; })
294
- : null;
295
- finishInnerRead(
296
- pending.innerId,
297
- false,
298
- readError(detail, content && content.text ? content.text : "Read failed")
299
- );
300
- return true;
301
- }
302
- var value = structured && structured.ok === true &&
303
- Object.prototype.hasOwnProperty.call(structured, "data")
304
- ? structured.data
305
- : structured !== undefined
306
- ? structured
307
- : toolResult;
308
- finishInnerRead(pending.innerId, true, value);
309
- return true;
310
- }
311
-
312
128
  window.addEventListener("message", function (event) {
313
- var message = event.data;
314
- if (event.source === view.contentWindow) {
315
- if (message && message.type === "connecta/read") beginInnerRead(message);
316
- return;
317
- }
318
129
  if (event.source !== host) return;
130
+ var message = event.data;
319
131
  if (!message || message.jsonrpc !== "2.0") return;
320
- if (message.id !== undefined && finishHostRead(message)) return;
321
132
  if (message.method === "ui/notifications/tool-result") {
322
133
  render(message.params);
323
134
  return;
@@ -335,8 +146,6 @@ export const PROGRAM_UI_SHELL_HTML = `<!doctype html>
335
146
  // response carries the same id, and announcing initialization on one
336
147
  // would assert a handshake that never happened.
337
148
  if (message.id === initializeId && message.result !== undefined) {
338
- var capabilities = message.result.hostCapabilities;
339
- hostCanCallTools = Boolean(capabilities && capabilities.serverTools);
340
149
  notify("ui/notifications/initialized", {});
341
150
  }
342
151
  });
@@ -151,13 +151,13 @@ export declare class CatalogService {
151
151
  private readonly loaded;
152
152
  private readonly loading;
153
153
  constructor(registry: RegistryView, baseUrl: string, options?: {
154
- requestScope?: object;
155
- probeTimeoutMs?: number;
156
- concurrency?: number;
154
+ requestScope?: object | undefined;
155
+ probeTimeoutMs?: number | undefined;
156
+ concurrency?: number | undefined;
157
157
  /** The discovery route recovery records name. Default `search_tools`. */
158
- searchRoute?: SearchRoute;
158
+ searchRoute?: SearchRoute | undefined;
159
159
  /** Runtime-owned tail for stale-while-revalidate catalog reads. */
160
- defer?: DeferredWork;
160
+ defer?: DeferredWork | undefined;
161
161
  });
162
162
  /**
163
163
  * Send a caller back to discovery through the surface it can actually reach.
@@ -177,6 +177,9 @@ export declare class CatalogService {
177
177
  loadConnector(id: string, callOptions?: ConnectorOperationOptions): Promise<ToolDef[]>;
178
178
  private loadForDiscovery;
179
179
  private outputSchema;
180
+ private unknownAddressFailure;
181
+ private catalogLoadFailure;
182
+ private unknownToolFailure;
180
183
  resolveTool(address: string, callOptions?: ConnectorOperationOptions): Promise<CatalogResolution>;
181
184
  /**
182
185
  * Resolve the JavaScript-safe property used by a lazy code-mode namespace
@@ -206,6 +209,10 @@ export declare function groupedSearchResult(page: CatalogSearchPage): {
206
209
  };
207
210
  matchMode?: "partial";
208
211
  nextOffset?: number;
212
+ total: number;
213
+ offset: number;
214
+ limit: number;
215
+ hasMore: boolean;
209
216
  connectors: {
210
217
  id: string;
211
218
  title?: string;
@@ -213,10 +220,6 @@ export declare function groupedSearchResult(page: CatalogSearchPage): {
213
220
  guideSummary?: string;
214
221
  tools: CatalogSearchEntry["tool"][];
215
222
  }[];
216
- total: number;
217
- offset: number;
218
- limit: number;
219
- hasMore: boolean;
220
223
  };
221
224
  export declare function flatSearchResult(page: CatalogSearchPage): {
222
225
  queryAnalysis?: {
@@ -237,6 +240,10 @@ export declare function flatSearchResult(page: CatalogSearchPage): {
237
240
  };
238
241
  matchMode?: "partial";
239
242
  nextOffset?: number;
243
+ total: number;
244
+ offset: number;
245
+ limit: number;
246
+ hasMore: boolean;
240
247
  tools: {
241
248
  guideSummary?: string;
242
249
  guide?: string;
@@ -255,9 +262,5 @@ export declare function flatSearchResult(page: CatalogSearchPage): {
255
262
  guideRequired?: true;
256
263
  guideRequiredReasons?: GuideRequiredReason[];
257
264
  }[];
258
- total: number;
259
- offset: number;
260
- limit: number;
261
- hasMore: boolean;
262
265
  };
263
266
  export {};