@maxhealth.tech/prefab 0.2.2 → 0.2.3

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/dist/app.d.ts CHANGED
@@ -9,7 +9,7 @@ import type { ComponentJSON } from './core/component.js';
9
9
  import type { Action, ActionJSON } from './actions/types.js';
10
10
  import type { PipeFn } from './rx/pipes.js';
11
11
  /** Package version — injected by build script, updated at release time. */
12
- export declare const VERSION = "0.2.2";
12
+ export declare const VERSION = "0.2.3";
13
13
  export interface Theme {
14
14
  light?: Record<string, string>;
15
15
  dark?: Record<string, string>;
package/dist/app.js CHANGED
@@ -7,7 +7,7 @@
7
7
  import {} from './core/component.js';
8
8
  import { drainAutoState } from './rx/state-collector.js';
9
9
  /** Package version — injected by build script, updated at release time. */
10
- export const VERSION = '0.2.2';
10
+ export const VERSION = '0.2.3';
11
11
  export class PrefabApp {
12
12
  title;
13
13
  view;
@@ -9,6 +9,11 @@
9
9
  * Designed for CSP-restricted environments (VS Code webviews, sandboxed iframes)
10
10
  * where inline `<script>` blocks are forbidden.
11
11
  *
12
+ * Supports all three Bridge protocols:
13
+ * - prefab:* (MistralOS)
14
+ * - ui/* JSON-RPC (VS Code, Claude, ChatGPT)
15
+ * - ext-apps SDK (legacy)
16
+ *
12
17
  * The host HTML only needs:
13
18
  * ```html
14
19
  * <div id="root"></div>
@@ -1 +1 @@
1
- {"version":3,"file":"auto.d.ts","sourceRoot":"","sources":["../../src/renderer/auto.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG"}
1
+ {"version":3,"file":"auto.d.ts","sourceRoot":"","sources":["../../src/renderer/auto.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG"}
@@ -9,6 +9,11 @@
9
9
  * Designed for CSP-restricted environments (VS Code webviews, sandboxed iframes)
10
10
  * where inline `<script>` blocks are forbidden.
11
11
  *
12
+ * Supports all three Bridge protocols:
13
+ * - prefab:* (MistralOS)
14
+ * - ui/* JSON-RPC (VS Code, Claude, ChatGPT)
15
+ * - ext-apps SDK (legacy)
16
+ *
12
17
  * The host HTML only needs:
13
18
  * ```html
14
19
  * <div id="root"></div>
@@ -24,20 +29,50 @@ function isPrefabWire(data) {
24
29
  function isPrefabUpdate(data) {
25
30
  return PrefabRenderer.isPrefabUpdate(data);
26
31
  }
32
+ function extractWireData(payload) {
33
+ if (isPrefabWire(payload))
34
+ return payload;
35
+ if (payload !== null && typeof payload === 'object') {
36
+ const obj = payload;
37
+ // MCP Apps tool-result: { content: [...], structuredContent: { $prefab, view } }
38
+ if (isPrefabWire(obj.structuredContent))
39
+ return obj.structuredContent;
40
+ // Try parsing text content items
41
+ if (Array.isArray(obj.content)) {
42
+ for (const raw of obj.content) {
43
+ const item = raw;
44
+ if (item.type === 'text' && typeof item.text === 'string') {
45
+ try {
46
+ const parsed = JSON.parse(item.text);
47
+ if (isPrefabWire(parsed))
48
+ return parsed;
49
+ }
50
+ catch { /* skip non-JSON */ }
51
+ }
52
+ }
53
+ }
54
+ }
55
+ return null;
56
+ }
27
57
  async function boot() {
28
58
  const root = document.querySelector(ROOT_SELECTOR);
29
59
  if (!root) {
30
60
  console.error(`[prefab:auto] Mount target "${ROOT_SELECTOR}" not found`);
31
61
  return;
32
62
  }
63
+ const el = root;
33
64
  const ui = await app();
34
65
  let mounted;
66
+ function mount(data) {
67
+ if (mounted)
68
+ mounted.destroy();
69
+ mounted = ui.mount(el, data);
70
+ }
35
71
  // ── Tool result handler (structuredContent from MCP) ───────────────────
36
72
  ui.onToolResult((result) => {
37
- if (isPrefabWire(result)) {
38
- if (mounted)
39
- mounted.destroy();
40
- mounted = ui.mount(root, result);
73
+ const wire = extractWireData(result);
74
+ if (wire) {
75
+ mount(wire);
41
76
  }
42
77
  else if (isPrefabUpdate(result) && mounted) {
43
78
  mounted.update(result);
@@ -45,10 +80,9 @@ async function boot() {
45
80
  });
46
81
  // ── Tool input handler (initial data or dynamic args) ──────────────────
47
82
  ui.onToolInput((args) => {
48
- if (isPrefabWire(args)) {
49
- if (mounted)
50
- mounted.destroy();
51
- mounted = ui.mount(root, args);
83
+ const wire = extractWireData(args);
84
+ if (wire) {
85
+ mount(wire);
52
86
  }
53
87
  else if (isPrefabUpdate(args) && mounted) {
54
88
  mounted.update(args);
@@ -1 +1 @@
1
- {"version":3,"file":"auto.js","sourceRoot":"","sources":["../../src/renderer/auto.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAA;AAC9B,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAG3C,MAAM,aAAa,GAAG,OAAO,CAAA;AAE7B,SAAS,YAAY,CAAC,IAAa;IACjC,OAAO,cAAc,CAAC,YAAY,CAAC,IAAI,CAAC,CAAA;AAC1C,CAAC;AAED,SAAS,cAAc,CAAC,IAAa;IACnC,OAAO,cAAc,CAAC,cAAc,CAAC,IAAI,CAAC,CAAA;AAC5C,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,MAAM,IAAI,GAAG,QAAQ,CAAC,aAAa,CAAc,aAAa,CAAC,CAAA;IAC/D,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,CAAC,KAAK,CAAC,+BAA+B,aAAa,aAAa,CAAC,CAAA;QACxE,OAAM;IACR,CAAC;IAED,MAAM,EAAE,GAAG,MAAM,GAAG,EAAE,CAAA;IAEtB,IAAI,OAA+B,CAAA;IAEnC,0EAA0E;IAC1E,EAAE,CAAC,YAAY,CAAC,CAAC,MAAM,EAAE,EAAE;QACzB,IAAI,YAAY,CAAC,MAAM,CAAC,EAAE,CAAC;YACzB,IAAI,OAAO;gBAAE,OAAO,CAAC,OAAO,EAAE,CAAA;YAC9B,OAAO,GAAG,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;QAClC,CAAC;aAAM,IAAI,cAAc,CAAC,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;YAC7C,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;QACxB,CAAC;IACH,CAAC,CAAC,CAAA;IAEF,0EAA0E;IAC1E,EAAE,CAAC,WAAW,CAAC,CAAC,IAAI,EAAE,EAAE;QACtB,IAAI,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC;YACvB,IAAI,OAAO;gBAAE,OAAO,CAAC,OAAO,EAAE,CAAA;YAC9B,OAAO,GAAG,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;QAChC,CAAC;aAAM,IAAI,cAAc,CAAC,IAAI,CAAC,IAAI,OAAO,EAAE,CAAC;YAC3C,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;QACtB,CAAC;IACH,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;IAC5B,OAAO,CAAC,KAAK,CAAC,4BAA4B,EAAE,GAAG,CAAC,CAAA;AAClD,CAAC,CAAC,CAAA"}
1
+ {"version":3,"file":"auto.js","sourceRoot":"","sources":["../../src/renderer/auto.ts"],"names":[],"mappings":"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"}
@@ -2,18 +2,27 @@
2
2
  * Bridge -- PostMessage-based communication between a prefab app
3
3
  * (running in an iframe) and its host (the parent window).
4
4
  *
5
- * Dual protocol:
6
- * Primary: prefab:* custom protocol (supported by VS Code Copilot, MistralOS)
7
- * Fallback: ext-apps JSON-RPC 2.0 (@modelcontextprotocol/ext-apps)
5
+ * Triple protocol:
6
+ * 1. prefab:* custom protocol (MistralOS, self-hosted)
7
+ * 2. ui/* — MCP Apps JSON-RPC 2.0 (VS Code, Claude, ChatGPT, Goose)
8
+ * 3. ext-apps — @modelcontextprotocol/ext-apps SDK (legacy fallback)
8
9
  *
9
- * Protocol detection: tries prefab:init first (1.5s timeout).
10
- * If no response, falls back to ext-apps.
10
+ * Protocol detection: tries prefab:init first (1s timeout),
11
+ * then ui/initialize JSON-RPC (1.5s), then ext-apps SDK.
11
12
  *
12
13
  * prefab:* messages:
13
14
  * App → Host: prefab:init, prefab:tool-call, prefab:send-message,
14
15
  * prefab:request-mode, prefab:open-link, prefab:update-context
15
16
  * Host → App: prefab:init-response, prefab:tool-input, prefab:tool-result,
16
17
  * prefab:tool-cancelled, prefab:theme-update, prefab:state-update
18
+ *
19
+ * ui/* JSON-RPC messages (MCP Apps spec 2026-01-26):
20
+ * App → Host: ui/initialize, ui/notifications/initialized,
21
+ * ui/notifications/size-changed, ui/open-link, ui/message,
22
+ * ui/request-display-mode, ui/update-model-context
23
+ * Host → App: ui/notifications/tool-input, ui/notifications/tool-result,
24
+ * ui/notifications/tool-cancelled, ui/notifications/tool-input-partial,
25
+ * ui/notifications/host-context-changed, ui/resource-teardown
17
26
  */
18
27
  import type { McpTransport } from './actions.js';
19
28
  export interface BridgeMessage<T extends string = string> {
@@ -54,12 +63,17 @@ export declare class Bridge {
54
63
  private cleanup;
55
64
  private pending;
56
65
  private callIdCounter;
66
+ private rpcPending;
67
+ private rpcIdCounter;
68
+ private sentRpcIds;
69
+ private postFn;
57
70
  private extApp;
58
71
  constructor(hostOrigin?: string);
59
- /** Start listening for prefab:* messages. */
72
+ /** Start listening for messages (prefab:* and JSON-RPC ui/*). */
60
73
  connect(): void;
61
74
  /**
62
- * Init handshake. Tries prefab:init first, falls back to ext-apps.
75
+ * Init handshake. Tries prefab:init first, then ui/initialize JSON-RPC,
76
+ * then falls back to ext-apps SDK.
63
77
  */
64
78
  initialize(appCapabilities: AppCapabilities): Promise<HostContext>;
65
79
  /** Create an McpTransport that routes through the active protocol. */
@@ -77,10 +91,22 @@ export declare class Bridge {
77
91
  /** Disconnect and clean up. */
78
92
  disconnect(): void;
79
93
  /** Which protocol is active after initialize(). */
80
- get activeProtocol(): 'prefab' | 'ext-apps';
94
+ get activeProtocol(): 'prefab' | 'jsonrpc' | 'ext-apps';
81
95
  private initPrefab;
82
96
  private createPrefabTransport;
83
97
  private sendPrefab;
98
+ /** Handle incoming JSON-RPC message. */
99
+ private handleJsonRpc;
100
+ /** Initialize via MCP Apps JSON-RPC ui/initialize handshake. */
101
+ private initJsonRpc;
102
+ /** Create transport that routes tool calls via JSON-RPC tools/call. */
103
+ private createJsonRpcTransport;
104
+ /** Send a JSON-RPC request and return a promise for the response. */
105
+ private sendRpcRequest;
106
+ /** Send a JSON-RPC notification (no id, no response expected). */
107
+ private sendRpcNotification;
108
+ /** Low-level: post a JSON-RPC envelope. Uses acquireVsCodeApi if available. */
109
+ private postJsonRpc;
84
110
  private initExtApps;
85
111
  private wireExtAppsEvents;
86
112
  private createExtAppsTransport;
@@ -1 +1 @@
1
- {"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../../src/renderer/bridge.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../../src/renderer/bridge.ts"],"names":[],"mappings":"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"}
@@ -2,19 +2,34 @@
2
2
  * Bridge -- PostMessage-based communication between a prefab app
3
3
  * (running in an iframe) and its host (the parent window).
4
4
  *
5
- * Dual protocol:
6
- * Primary: prefab:* custom protocol (supported by VS Code Copilot, MistralOS)
7
- * Fallback: ext-apps JSON-RPC 2.0 (@modelcontextprotocol/ext-apps)
5
+ * Triple protocol:
6
+ * 1. prefab:* custom protocol (MistralOS, self-hosted)
7
+ * 2. ui/* — MCP Apps JSON-RPC 2.0 (VS Code, Claude, ChatGPT, Goose)
8
+ * 3. ext-apps — @modelcontextprotocol/ext-apps SDK (legacy fallback)
8
9
  *
9
- * Protocol detection: tries prefab:init first (1.5s timeout).
10
- * If no response, falls back to ext-apps.
10
+ * Protocol detection: tries prefab:init first (1s timeout),
11
+ * then ui/initialize JSON-RPC (1.5s), then ext-apps SDK.
11
12
  *
12
13
  * prefab:* messages:
13
14
  * App → Host: prefab:init, prefab:tool-call, prefab:send-message,
14
15
  * prefab:request-mode, prefab:open-link, prefab:update-context
15
16
  * Host → App: prefab:init-response, prefab:tool-input, prefab:tool-result,
16
17
  * prefab:tool-cancelled, prefab:theme-update, prefab:state-update
18
+ *
19
+ * ui/* JSON-RPC messages (MCP Apps spec 2026-01-26):
20
+ * App → Host: ui/initialize, ui/notifications/initialized,
21
+ * ui/notifications/size-changed, ui/open-link, ui/message,
22
+ * ui/request-display-mode, ui/update-model-context
23
+ * Host → App: ui/notifications/tool-input, ui/notifications/tool-result,
24
+ * ui/notifications/tool-cancelled, ui/notifications/tool-input-partial,
25
+ * ui/notifications/host-context-changed, ui/resource-teardown
17
26
  */
27
+ /** Type guard: is this a JSON-RPC 2.0 envelope? */
28
+ function isJsonRpcEnvelope(msg) {
29
+ return (msg !== null &&
30
+ typeof msg === 'object' &&
31
+ msg.jsonrpc === '2.0');
32
+ }
18
33
  // ── Bridge Class ─────────────────────────────────────────────────────────────
19
34
  export class Bridge {
20
35
  hostOrigin;
@@ -24,58 +39,84 @@ export class Bridge {
24
39
  // prefab:* state
25
40
  pending = new Map();
26
41
  callIdCounter = 0;
42
+ // JSON-RPC state
43
+ rpcPending = new Map();
44
+ rpcIdCounter = 0;
45
+ sentRpcIds = new Set();
46
+ postFn;
27
47
  // ext-apps state (lazy)
28
48
  extApp;
29
49
  constructor(hostOrigin = '*') {
30
50
  this.hostOrigin = hostOrigin;
31
51
  }
32
- /** Start listening for prefab:* messages. */
52
+ /** Start listening for messages (prefab:* and JSON-RPC ui/*). */
33
53
  connect() {
34
54
  if (typeof window === 'undefined')
35
55
  return;
56
+ // Detect VS Code webview API (provides postMessage without origin restrictions)
57
+ const vscodeApi = detectVsCodeApi();
58
+ if (vscodeApi) {
59
+ this.postFn = (msg) => vscodeApi.postMessage(msg);
60
+ }
36
61
  const handler = (event) => {
37
62
  if (this.hostOrigin !== '*' && event.origin !== this.hostOrigin)
38
63
  return;
39
64
  const msg = event.data;
40
- if (!msg?.type.startsWith('prefab:'))
65
+ // ── JSON-RPC 2.0 messages (ui/* protocol) ───────────────────────────
66
+ if (isJsonRpcEnvelope(msg)) {
67
+ this.handleJsonRpc(msg);
68
+ return;
69
+ }
70
+ // ── prefab:* messages ───────────────────────────────────────────────
71
+ const bmsg = msg;
72
+ if (!bmsg?.type.startsWith('prefab:'))
41
73
  return;
42
74
  // Resolve pending tool-call promises
43
- if (msg.type === 'prefab:tool-call-response' && msg.id) {
44
- const p = this.pending.get(msg.id);
75
+ if (bmsg.type === 'prefab:tool-call-response' && bmsg.id) {
76
+ const p = this.pending.get(bmsg.id);
45
77
  if (p) {
46
- this.pending.delete(msg.id);
47
- if (msg.payload?.error != null) {
48
- p.reject(new Error(msg.payload.error));
78
+ this.pending.delete(bmsg.id);
79
+ if (bmsg.payload?.error != null) {
80
+ p.reject(new Error(bmsg.payload.error));
49
81
  }
50
82
  else {
51
- p.resolve(msg.payload?.result);
83
+ p.resolve(bmsg.payload?.result);
52
84
  }
53
85
  return;
54
86
  }
55
87
  }
56
88
  // Dispatch to registered listeners
57
- const handlers = this.listeners.get(msg.type);
89
+ const handlers = this.listeners.get(bmsg.type);
58
90
  if (handlers) {
59
91
  for (const fn of handlers)
60
- fn(msg.payload ?? {});
92
+ fn(bmsg.payload ?? {});
61
93
  }
62
94
  };
63
95
  window.addEventListener('message', handler);
64
96
  this.cleanup = () => window.removeEventListener('message', handler);
65
97
  }
66
98
  /**
67
- * Init handshake. Tries prefab:init first, falls back to ext-apps.
99
+ * Init handshake. Tries prefab:init first, then ui/initialize JSON-RPC,
100
+ * then falls back to ext-apps SDK.
68
101
  */
69
102
  async initialize(appCapabilities) {
70
103
  try {
71
104
  return await this.initPrefab(appCapabilities);
72
105
  }
73
106
  catch {
74
- return this.initExtApps(appCapabilities);
107
+ try {
108
+ return await this.initJsonRpc(appCapabilities);
109
+ }
110
+ catch {
111
+ return this.initExtApps(appCapabilities);
112
+ }
75
113
  }
76
114
  }
77
115
  /** Create an McpTransport that routes through the active protocol. */
78
116
  createTransport() {
117
+ if (this.protocol === 'jsonrpc') {
118
+ return this.createJsonRpcTransport();
119
+ }
79
120
  if (this.protocol === 'ext-apps' && this.extApp) {
80
121
  return this.createExtAppsTransport();
81
122
  }
@@ -83,7 +124,10 @@ export class Bridge {
83
124
  }
84
125
  /** Request a display mode change. */
85
126
  requestMode(mode) {
86
- if (this.protocol === 'ext-apps' && this.extApp) {
127
+ if (this.protocol === 'jsonrpc') {
128
+ void this.sendRpcRequest('ui/request-display-mode', { mode });
129
+ }
130
+ else if (this.protocol === 'ext-apps' && this.extApp) {
87
131
  void this.extApp.requestDisplayMode({ mode });
88
132
  }
89
133
  else {
@@ -92,7 +136,10 @@ export class Bridge {
92
136
  }
93
137
  /** Request the host to open a URL. */
94
138
  openLink(url, target) {
95
- if (this.protocol === 'ext-apps' && this.extApp) {
139
+ if (this.protocol === 'jsonrpc') {
140
+ void this.sendRpcRequest('ui/open-link', { url, target });
141
+ }
142
+ else if (this.protocol === 'ext-apps' && this.extApp) {
96
143
  void this.extApp.openLink({ url });
97
144
  }
98
145
  else {
@@ -101,7 +148,10 @@ export class Bridge {
101
148
  }
102
149
  /** Send context updates to the host. */
103
150
  updateContext(context) {
104
- if (this.protocol === 'ext-apps' && this.extApp) {
151
+ if (this.protocol === 'jsonrpc') {
152
+ void this.sendRpcRequest('ui/update-model-context', { structuredContent: context });
153
+ }
154
+ else if (this.protocol === 'ext-apps' && this.extApp) {
105
155
  void this.extApp.updateModelContext({ structuredContent: context });
106
156
  }
107
157
  else {
@@ -130,6 +180,11 @@ export class Bridge {
130
180
  p.reject(new Error('Bridge disconnected'));
131
181
  }
132
182
  this.pending.clear();
183
+ for (const [, p] of this.rpcPending) {
184
+ p.reject(new Error('Bridge disconnected'));
185
+ }
186
+ this.rpcPending.clear();
187
+ this.sentRpcIds.clear();
133
188
  }
134
189
  /** Which protocol is active after initialize(). */
135
190
  get activeProtocol() {
@@ -187,6 +242,194 @@ export class Bridge {
187
242
  const msg = { type, payload, id };
188
243
  target.postMessage(msg, this.hostOrigin);
189
244
  }
245
+ // ── JSON-RPC ui/* protocol (MCP Apps spec 2026-01-26) ────────────────
246
+ /** Handle incoming JSON-RPC message. */
247
+ handleJsonRpc(msg) {
248
+ // Response to a request we sent (has no 'method' → must be JsonRpcResponse)
249
+ if (!('method' in msg)) {
250
+ const p = this.rpcPending.get(msg.id);
251
+ if (p) {
252
+ this.rpcPending.delete(msg.id);
253
+ if (msg.error) {
254
+ p.reject(new Error(msg.error.message));
255
+ }
256
+ else {
257
+ p.resolve(msg.result);
258
+ }
259
+ }
260
+ return;
261
+ }
262
+ // Request or notification from host (JsonRpcRequest)
263
+ const method = msg.method;
264
+ const params = msg.params ?? {};
265
+ // Skip our own outgoing requests bounced back (same-window postMessage in tests)
266
+ if (msg.id != null && this.sentRpcIds.delete(msg.id))
267
+ return;
268
+ // Auto-acknowledge host requests (those with an id) so we don't deadlock
269
+ if (msg.id != null) {
270
+ this.postJsonRpc({ jsonrpc: '2.0', id: msg.id, result: {} });
271
+ }
272
+ // Map ui/* notifications to internal prefab:* events
273
+ switch (method) {
274
+ case 'ui/notifications/tool-input':
275
+ this.dispatch('prefab:tool-input', { args: params.arguments ?? params });
276
+ break;
277
+ case 'ui/notifications/tool-input-partial':
278
+ this.dispatch('prefab:tool-input-partial', { args: params.arguments ?? params });
279
+ break;
280
+ case 'ui/notifications/tool-result':
281
+ this.dispatch('prefab:tool-result', { result: params });
282
+ break;
283
+ case 'ui/notifications/tool-cancelled':
284
+ this.dispatch('prefab:tool-cancelled', params);
285
+ break;
286
+ case 'ui/notifications/host-context-changed': {
287
+ const theme = {};
288
+ if (typeof params.theme === 'string') {
289
+ theme.colorScheme = params.theme;
290
+ }
291
+ if (typeof params.styles === 'object' && params.styles !== null) {
292
+ const styles = params.styles;
293
+ if (styles.variables)
294
+ theme.variables = styles.variables;
295
+ }
296
+ this.dispatch('prefab:theme-update', theme);
297
+ break;
298
+ }
299
+ case 'ui/resource-teardown':
300
+ this.dispatch('prefab:teardown', params);
301
+ break;
302
+ }
303
+ }
304
+ /** Initialize via MCP Apps JSON-RPC ui/initialize handshake. */
305
+ initJsonRpc(appCapabilities) {
306
+ return new Promise((resolve, reject) => {
307
+ const id = ++this.rpcIdCounter;
308
+ let settled = false;
309
+ this.rpcPending.set(id, {
310
+ resolve: (result) => {
311
+ if (settled)
312
+ return;
313
+ settled = true;
314
+ this.protocol = 'jsonrpc';
315
+ const r = (result ?? {});
316
+ const hostInfo = (r.hostInfo ?? {});
317
+ const hostCtx = (r.hostContext ?? {});
318
+ const hostCaps = (r.hostCapabilities ?? {});
319
+ // Parse theme from hostContext
320
+ let theme;
321
+ if (typeof hostCtx.theme === 'string' || typeof hostCtx.styles === 'object') {
322
+ theme = {};
323
+ if (typeof hostCtx.theme === 'string') {
324
+ theme.colorScheme = hostCtx.theme;
325
+ }
326
+ if (typeof hostCtx.styles === 'object' && hostCtx.styles !== null) {
327
+ const styles = hostCtx.styles;
328
+ if (styles.variables)
329
+ theme.variables = styles.variables;
330
+ }
331
+ }
332
+ // Send ui/notifications/initialized to confirm readiness
333
+ this.postJsonRpc({
334
+ jsonrpc: '2.0',
335
+ method: 'ui/notifications/initialized',
336
+ params: {},
337
+ });
338
+ resolve({
339
+ hostName: hostInfo.name,
340
+ hostVersion: hostInfo.version,
341
+ capabilities: {
342
+ toast: true,
343
+ navigation: hostCaps.openLinks != null,
344
+ messaging: true,
345
+ displayModes: (hostCtx.availableDisplayModes ?? []),
346
+ },
347
+ theme,
348
+ toolInput: undefined,
349
+ meta: hostCtx,
350
+ });
351
+ },
352
+ reject: (err) => {
353
+ if (settled)
354
+ return;
355
+ settled = true;
356
+ reject(err);
357
+ },
358
+ });
359
+ // Send ui/initialize request
360
+ this.postJsonRpc({
361
+ jsonrpc: '2.0',
362
+ id,
363
+ method: 'ui/initialize',
364
+ params: {
365
+ protocolVersion: '2026-01-26',
366
+ capabilities: {},
367
+ clientInfo: { name: 'prefab', version: '0.2' },
368
+ appCapabilities: {
369
+ ...(appCapabilities.displayModes && {
370
+ availableDisplayModes: appCapabilities.displayModes,
371
+ }),
372
+ },
373
+ },
374
+ });
375
+ // Timeout — fall through to ext-apps
376
+ setTimeout(() => {
377
+ if (!settled) {
378
+ settled = true;
379
+ this.rpcPending.delete(id);
380
+ reject(new Error('ui/initialize timeout'));
381
+ }
382
+ }, 1500);
383
+ });
384
+ }
385
+ /** Create transport that routes tool calls via JSON-RPC tools/call. */
386
+ createJsonRpcTransport() {
387
+ return {
388
+ callTool: (name, args) => {
389
+ return this.sendRpcRequest('tools/call', { name, arguments: args });
390
+ },
391
+ sendMessage: (message) => {
392
+ return this.sendRpcRequest('ui/message', {
393
+ role: 'user',
394
+ content: { type: 'text', text: message },
395
+ });
396
+ },
397
+ };
398
+ }
399
+ /** Send a JSON-RPC request and return a promise for the response. */
400
+ sendRpcRequest(method, params) {
401
+ const id = ++this.rpcIdCounter;
402
+ return new Promise((resolve, reject) => {
403
+ this.rpcPending.set(id, { resolve, reject });
404
+ this.postJsonRpc({ jsonrpc: '2.0', id, method, params });
405
+ setTimeout(() => {
406
+ if (this.rpcPending.has(id)) {
407
+ this.rpcPending.delete(id);
408
+ reject(new Error(`JSON-RPC '${method}' timed out`));
409
+ }
410
+ }, 30000);
411
+ });
412
+ }
413
+ /** Send a JSON-RPC notification (no id, no response expected). */
414
+ sendRpcNotification(method, params) {
415
+ this.postJsonRpc({ jsonrpc: '2.0', method, params });
416
+ }
417
+ /** Low-level: post a JSON-RPC envelope. Uses acquireVsCodeApi if available. */
418
+ postJsonRpc(msg) {
419
+ if (typeof window === 'undefined')
420
+ return;
421
+ // Track outgoing request ids to filter self-messages in same-window envs
422
+ if (msg.id != null && typeof msg.method === 'string') {
423
+ this.sentRpcIds.add(msg.id);
424
+ }
425
+ if (this.postFn) {
426
+ this.postFn(msg);
427
+ }
428
+ else {
429
+ const target = window.parent !== window ? window.parent : window;
430
+ target.postMessage(msg, this.hostOrigin);
431
+ }
432
+ }
190
433
  // ── ext-apps fallback ──────────────────────────────────────────────────
191
434
  async initExtApps(appCapabilities) {
192
435
  const { App, PostMessageTransport } = await import('@modelcontextprotocol/ext-apps');
@@ -299,4 +542,22 @@ export function isIframe() {
299
542
  return true;
300
543
  }
301
544
  }
545
+ /** Detect VS Code webview API (acquireVsCodeApi). Returns the API object or undefined. */
546
+ function detectVsCodeApi() {
547
+ if (typeof window === 'undefined')
548
+ return undefined;
549
+ const win = window;
550
+ if (typeof win.acquireVsCodeApi === 'function') {
551
+ try {
552
+ const api = win.acquireVsCodeApi();
553
+ if (api !== null && typeof api === 'object' && 'postMessage' in api) {
554
+ return api;
555
+ }
556
+ }
557
+ catch {
558
+ return undefined;
559
+ }
560
+ }
561
+ return undefined;
562
+ }
302
563
  //# sourceMappingURL=bridge.js.map