ADR 0108: Built-in web portal for external web AI and tool bridge via Host Object (WebView → IDE)¶
Status: Accepted · Implemented
Date: 2026-05-10
Fixed parameters (2026-05-10): format executeIdeCommand(string json), PoC-allowlist for reading, Read / Write-confirm modes, WebView2 model below the page (see §2) - agreed for the first integration into CascadeIDE. Sidenote: the Atlas person sometimes gives “sounding” working names in the discussion - this is rhetoric, not individual entities in the repository archive, unless they are clearly listed as a product.
Related ADRs¶
| ADR | Role |
|---|---|
| 0035 | basic boundary "web ≠ automatic MCP"; this ADR specifies an explicit, consistent channel on top of it |
| 0093 | MFD/browser/launch URL |
| 0008 | IdeCommands, MCP, IDE Executable Circuit |
| 0038 | provider facade and tool orchestration |
| 0016 | external agents; orthogonal |
| 0048 | body surface / parity |
| ## Summary |
- Web AI portal on MFD: WebView2, Host Object →
IdeCommands/MCP. - Allowlist, user consent; PoC (Atlas/Search AI).
- Trust boundary - 0035.
1. Context¶
Powers:
- The host editor (for example Cursor) periodically goes into Reconnect or crashes - long sessions and stable access to tools suffer.
- Local LLMs on a typical workstation give a long track of response; Not everyone has the option of purchasing a dedicated GPU in the near future.
- At the same time, cloud web interfaces from vendors (including “AI in search” modes) can provide acceptable speed and quality of reasoning without being tied to the release cycle of the local stack.
Limitation: Web AI lives in the origin sandbox of someone else's domain; The page doesn't have the same API as the native agent with MCP in the IDE process. In 0035 §2–3 it is fixed: arbitrary HTTPS-origin does not automatically gain access to MCP, workspace files and secrets. In 0035 §5 it is noted that connecting the web with local tools is a separate line with a consent model and threat analysis.
Observation: for the scenario “the web model reasons, and the edits/assembly/navigation is done in the IDE,” a narrow, auditable bridge is sufficient: the page does not call arbitrary host code, but a stable contract (execute_ide_command / command_id + JSON args), which converges with an existing one inside the IDE IdeMcpCommandExecutor and canon MCP-PROTOCOL.md.
Empirics: carried out PoC together with Atlas on the web AI side (including targeting Google Search AI / web shell without a full-fledged local tool runtime on the page). The transport link has been proven: the signal from the web reaches the native bridge (Host Object), then in the PoC the output goes echo to a separate console application listening to port :8080 - without calling real IDE tools and without IdeMcpCommandExecutor. That is, the idea of bridge and injection was tested, and not the end of the “command → workspace” chain. Connecting to the canon MCP-PROTOCOL.md and whitelist §2.2 is the next step (M1).
2. Decision (intention)¶
2.1. Single call with JS: executeIdeCommand(string json)¶
-
One method on the Host Object boundary - without a mirror “one method for each
ide_*tool.” Recommended signature for AddHostObjectToScript:executeIdeCommand(string json)(or equivalent with the same request body). -
JSON body - the same contract as the MCP manager: an object with
command_idandargs(as in MCP-PROTOCOL.md / parsingIdeMcpServer: top-level merge with nestedargsis possible). Parsing, validation and routing remain C# only; on the web page side there is one simple interface. -
JSON transport is required: field expansion without rebuilding the bridge; new commands are added through
IdeCommandsand allowlist, rather than through new COM methods.
Recommended model output: fenced json-cascade¶
Ready text of instructions for attachment to the operator’s external web chat: AiPrompts/web-ai-portal-bridge-adr0108.prompts.md (sections ## web_portal_bridge_*). If the web layer spoils Markdown when copied from the chat history - a flat version without markup and with one-line JSON like the bridge buffer: AiPrompts/web-ai-portal-bridge-adr0108.chat-paste.txt.
To separate the command to the IDE from the arbitrary fenced json in the same answer, the web layer negotiates with the model: one object { "command_id", "args" } (same contract as ide_execute_command) is placed in a fenced block with info string json-cascade:markdown
{ "command_id": "codebase_index_search", "args": { "query": "Foo", "top_n": 10 } }
IDE parser: WebAiPortalJsonCascadeFence.TryExtractFirst → JSON string → invokeCSharpAction / bridge. JSON must not contain the fence closing sequence (usually not an issue).
2.2. PoC allowlist (command_id whitelist)¶
-
Primary safety guard — a stable whitelist of permitted
command_idvalues. Until the loop is debugged, commands outside the list are rejected in the IDE before the executor runs. -
Initial PoC set (eyes only, no destructive actions): three semantics; CascadeIDE code uses canonical identifiers from
IdeCommands:
| Semantics (discussion shorthand) | Canonical command_id in CascadeIDE |
|---|---|
| read / view editor context (read analog) | get_editor_content_range (with get_editor_state when active-file metadata is needed) |
| search local code index | codebase_index_search · for stable web-chat flow, read-only codebase_index_status, codebase_index_explain are on the bridge whitelist |
| diagnostics for current file | get_current_file_diagnostics |
Aliases such as read_file / search_index / get_diagnostics are not a second source of truth in the product: if needed, only a thin alias in the JS layer mapping to the table above.
2.3. Modes: Read-only and Write-confirm¶
-
Read-only: commands classified as read/observe (in PoC — entire whitelist §2.2) run without extra dialog (after initial bridge enable and allowlist consent).
-
Write-confirm (default): any command classified as mutation (file write, git with side effects, build, delete, etc.) does not run silently: IDE shows explicit confirmation (modal or equivalent toast with Allow / Deny) with clear intent description (“agent requests X”). This fixes proposed → human confirmed model.
-
Write without per-step confirmation (must-have for flow): confirming every write separately is unacceptable for long sessions. There must be a separate, explicit option “allow all writes for this bridge session” / “trust writes from this bridge (until revoked)” — like Run everything / Cursor analog: one conscious opt-in, then allowlisted write commands run without modal per call until user disables mode, revokes bridge, or closes session (UX details — product; time/scope limits — settings). Default for newcomers remains confirm each write or narrow mode.
-
Read vs write classification — in bridge configuration (table or attribute on
command_id); whitelist extension must include classification, else default — write-confirm (safe fallback).
2.4. Technical assumptions: WebView2 and “ordinary web” limits¶
-
Embedding and placement: built-in portal = controlled WebView2 (or equivalent) inside IDE. Canonical slot for long-lived “web chat + bridge” — MFD (secondary attention contour per 0021; aligns with 0035 — embedded browser as deliberate zone, not fourth “forward” semantic anchor). PFD not reserved for permanent web chat. Alternative — separate window / secondary surface per 0017 and UI presets (0010).
-
Bridge injection: IDE tool interaction via native object published to JavaScript (e.g.
AddHostObjectToScript) — call goes to host-added object, notpostMessagebetween windows or page network stack. From page view this is direct JS → C# in IDE process. -
Same-Origin Policy and third-party page CSP do not define host object access: origin policy still limits page network, but does not remove IDE responsibility for what is exported on
windowand which whitelist applies on C# side. “SOP bypass” for network does not justify weakening allowlist.
2.5. Deferred: operator presence (IsHumanPresent and similar)¶
- Out of scope for implementation in this ADR. Separate product/engineering line: “human at machine” detection, simplicity, peripherals, link to 0034 — large effort; not in M1–M2 roadmap §7. Revisit after stable bridge (whitelist + Write-confirm). Presence heuristics do not replace explicit confirmation for write commands.
2.6. Consent, audit, executor¶
-
Before first bridge use — explicit user consent on allowlist and modes; revoke and pause bridge are mandatory.
-
Where consent is shown (and chat): main scenario — dialog with web AI runs in embedded WebView2, so bridge onboarding, mode banner, and buttons like “allow writes for session” belong in the same attention zone. Allowed implementations: injection into document (script / overlay / startup wrapper URL under IDE control), or native strip or modal around
WebViewin same slot (typically MFD). In all cases fact “bridge active / write-all for session” is stored and checked by IDE process; WV2 rendering is UX, not source of authority. -
Execution: each call goes through same contour as MCP:
command_idvalidation, whitelist, read/write class, UI marshaling (0004), quotas/timeouts, logging without leaking secrets to page console. -
Source of truth for arguments —
IdeCommands+ ProtocolDocGen; no need to duplicate semantics in JS.
3. Relation to ADR 0035¶
- 0035 remains: arbitrary page without bridge is still not an MCP client.
- This ADR describes designed exception: bridge enabled deliberately, with limited surface and same command executor as IDE agent — without mixing web provider origin with full process privileges.
4. Threats and mitigation (minimum)¶
| Threat | Mitigation |
|---|---|
| XSS on web provider side runs bridge calls as user | Narrow allowlist; optional confirmation on sensitive command_id; bridge revoke |
| API spoofing or extension via third-party script load | Bridge only on designated pages/presets; do not export arbitrary COM objects |
| Path/content leak to vendor cloud | Aligned with 0035 §3: context to web — only explicit user actions + product policy |
| “All writes without prompt” mode (§2.3 item 8) increases blast radius on XSS/page session hijack | Only after explicit opt-in; visible UI indicator; revoke in one action; allowlist still limits command set |
Full threat model and pentest plan — outside this document; with M1+ rollout allow appendix or separate security ADR.
5. Rejected / deferred options¶
- Rely only on copy-paste between web tab and IDE — enough for “second opinion”, not enough for “direct tool control” without manual relay each step.
- Give page direct socket to local MCP server — mixes browser and MCP trust models; rejected in favor of single executor inside IDE process.
- Unify web AI and native agent into one indistinguishable UX entity — rejected; user must know who executes command (bridge under IDE control vs external host).
IsHumanPresent/ auto read-only when operator absent — deferred (separate line, see §2.5); does not block bridge from this ADR.
6. Consequences¶
- Product feature: “web persona + IDE tools” without mandatory host-editor dependency or local GPU.
- Implementation needs WebView, bridge, consent UI, contract tests bridge ↔
IdeCommands(snapshots or table tests). - User docs must clearly distinguish: native chat / MCP in IDE vs web portal with bridge vs ordinary external browser.
7. Roadmap (normative minimum)¶
- M0 — transport PoC: done (web → bridge → echo to console listener :8080; see §1); optionally record artifacts outside ADR.
- M1 — product integration:
executeIdeCommand(string json)→IdeMcpCommandExecutor+ whitelist §2.2 + Read / Write-confirm + “all writes for session” mode (§2.3 item 8) + consent + logging (first real IDE tool call from bridge). - M2 — hardening: abuse metrics, whitelist review on
IdeCommandsupdates. Presence policy (§2.5) — not in M2.
8. Implementation slice (M1)¶
- WebView: package
Avalonia.Controls.WebView, controlNativeWebView(Win — WebView2 underneath), no WinForms. - MFD:
MfdShellPage.WebAiPortal, viewViews/WebAiPortalMfdPageView.axaml. - Bridge:
Features/WebAiPortal/Application/WebAiPortalCommandBridge.cs→IIdeMcpActions.ExecuteCommandAsyncwith whitelist §2.2; transport from page — AvaloniaWebMessageReceived/invokeCSharpAction(body — same JSON withcommand_idandargs). AddHostObjectToScript for sync JS↔C# on Windows if needed — viaTryGetPlatformHandle/ICoreWebView2(Avalonia “Embedding web content” docs), does not block M1. - Navigation: IDE command
show_web_ai_portal_page→ MFD region +WebAiPortalpage. - Manual execution (buffer/button): button “Execute command: buffer → last on page” (
WebAiPortalMfdPageView) — (1) clipboard: fencedjson-cascadeor bare JSON{ "command_id", … }(common “copy” in chat UI without backticks); (2) elseNativeWebView.InvokeScript: last matching block inpre/ barecodein DOM (WebAiPortalLastCommandDomProbe). Then same path asinvokeCSharpAction; result inWebAiPortalLastBridgeResult. - Hands-free without copy-paste and without that button: checkbox “Auto: last json-cascade on page → bridge (poll…)” when consent and bridge enabled (
WebAiPortalMfdPageView):DispatcherTimer~1.1s runs same DOM probe (WebAiPortalLastCommandDomProbe); successful execution deduped by canonical JSON (WebAiPortalBridgePayloadDedup); on navigation dedup resets — same command can run again on new page. Probe handles fenced``json-cascade and **bare** markerjson-cascade+ newline + JSON (typical Gemini / Google AI search without triple backticks); search usesdocument.body.innerText, not onlypre/code`. - Vendor “Send” click is not automated: “Inject into composer” or manual send in site chat field remains (DOM/policy limit, not bridge blocker).
- Inject IDE response into web chat (after successful bridge): flags — (1) text to system clipboard; (2) optional
InvokeScript/WebAiPortalComposerInjectScriptinto page focus. If response very long (typicalget_editor_statewith large preview) and “under chat limit (~1200)” enabled (default): buffer/composer gets compact with readyjson-cascadefor HCI (codebase_index_search,codebase_index_status,codebase_index_explain) and narrow read (get_editor_statewithmax_preview_chars: 0,get_editor_content_rangenear caret). Bridge whitelist includescodebase_index_statusandcodebase_index_explain(read-only).