@nimblebrain/synapse 0.19.0 → 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 (100) hide show
  1. package/README.md +20 -25
  2. package/dist/check-JEM5PN64.cjs +435 -0
  3. package/dist/check-JEM5PN64.cjs.map +1 -0
  4. package/dist/check-YDZUQT6A.js +429 -0
  5. package/dist/check-YDZUQT6A.js.map +1 -0
  6. package/dist/{chunk-RU64F5RS.cjs → chunk-BNF352SA.cjs} +57 -146
  7. package/dist/chunk-BNF352SA.cjs.map +1 -0
  8. package/dist/{chunk-DRLVEHCD.js → chunk-DJS2FKBN.js} +21 -79
  9. package/dist/chunk-DJS2FKBN.js.map +1 -0
  10. package/dist/chunk-H3INUKA3.js +32 -0
  11. package/dist/chunk-H3INUKA3.js.map +1 -0
  12. package/dist/chunk-HQLSVUOR.cjs +667 -0
  13. package/dist/chunk-HQLSVUOR.cjs.map +1 -0
  14. package/dist/{chunk-NP7ROSH2.cjs → chunk-ISV6HFQE.cjs} +14 -7
  15. package/dist/chunk-ISV6HFQE.cjs.map +1 -0
  16. package/dist/chunk-TR2YNWZ5.js +658 -0
  17. package/dist/chunk-TR2YNWZ5.js.map +1 -0
  18. package/dist/{chunk-OYLZ5OCK.js → chunk-UKP4W6DI.js} +57 -146
  19. package/dist/chunk-UKP4W6DI.js.map +1 -0
  20. package/dist/{chunk-SZWHFTXV.cjs → chunk-YSL7KJNU.cjs} +21 -81
  21. package/dist/chunk-YSL7KJNU.cjs.map +1 -0
  22. package/dist/codegen/cli.cjs +41 -4
  23. package/dist/codegen/cli.cjs.map +1 -1
  24. package/dist/codegen/cli.js +41 -4
  25. package/dist/codegen/cli.js.map +1 -1
  26. package/dist/codegen/index.cjs +0 -1
  27. package/dist/codegen/index.d.cts +1 -1
  28. package/dist/codegen/index.d.ts +1 -1
  29. package/dist/codegen/index.js +0 -1
  30. package/dist/connect.iife.global.js +34 -34
  31. package/dist/{detect-DZMJsyvZ.d.cts → detect-DVGaL2bH.d.cts} +39 -33
  32. package/dist/{detect-DBwc8a21.d.ts → detect-DVGaL2bH.d.ts} +39 -33
  33. package/dist/host/index.cjs +17 -18
  34. package/dist/host/index.d.cts +11 -27
  35. package/dist/host/index.d.ts +11 -27
  36. package/dist/host/index.js +2 -3
  37. package/dist/index.cjs +30 -19
  38. package/dist/index.cjs.map +1 -1
  39. package/dist/index.d.cts +72 -26
  40. package/dist/index.d.ts +72 -26
  41. package/dist/index.js +8 -9
  42. package/dist/index.js.map +1 -1
  43. package/dist/react/index.cjs +9 -10
  44. package/dist/react/index.cjs.map +1 -1
  45. package/dist/react/index.d.cts +36 -8
  46. package/dist/react/index.d.ts +36 -8
  47. package/dist/react/index.js +3 -4
  48. package/dist/react/index.js.map +1 -1
  49. package/dist/schema-reader-776246MJ.js +3 -0
  50. package/dist/{schema-reader-GM4A4L44.js.map → schema-reader-776246MJ.js.map} +1 -1
  51. package/dist/{schema-reader-MWBRQ2TQ.cjs → schema-reader-B6LTGBMJ.cjs} +2 -3
  52. package/dist/{schema-reader-MWBRQ2TQ.cjs.map → schema-reader-B6LTGBMJ.cjs.map} +1 -1
  53. package/dist/{server-LR6T6C44.cjs → server-BR5MNFOB.cjs} +13 -14
  54. package/dist/server-BR5MNFOB.cjs.map +1 -0
  55. package/dist/{server-UHPEE55X.js → server-OOEEWAEG.js} +13 -14
  56. package/dist/server-OOEEWAEG.js.map +1 -0
  57. package/dist/synapse-runtime.iife.global.js +34 -34
  58. package/dist/synapse-ui.iife.global.js +4 -4
  59. package/dist/type-generator-MZ4X4DE5.js +3 -0
  60. package/dist/{type-generator-ZYSVFK3X.js.map → type-generator-MZ4X4DE5.js.map} +1 -1
  61. package/dist/{type-generator-YTPFWLVG.cjs → type-generator-UA3L4OIA.cjs} +2 -3
  62. package/dist/{type-generator-YTPFWLVG.cjs.map → type-generator-UA3L4OIA.cjs.map} +1 -1
  63. package/dist/{types-CG08aO82.d.cts → types-BxPfGHKO.d.cts} +35 -43
  64. package/dist/{types-CG08aO82.d.ts → types-BxPfGHKO.d.ts} +35 -43
  65. package/dist/ui/base.cjs +0 -1
  66. package/dist/ui/base.cjs.map +1 -1
  67. package/dist/ui/base.js +0 -1
  68. package/dist/ui/base.js.map +1 -1
  69. package/dist/ui/index.cjs +0 -1
  70. package/dist/ui/index.cjs.map +1 -1
  71. package/dist/ui/index.js +0 -1
  72. package/dist/ui/index.js.map +1 -1
  73. package/dist/vite/index.cjs +10 -10
  74. package/dist/vite/index.cjs.map +1 -1
  75. package/dist/vite/index.js +6 -6
  76. package/dist/vite/index.js.map +1 -1
  77. package/dist/{writer-HDRCQSX2.cjs → writer-NDK4U6C5.cjs} +3 -3
  78. package/dist/{writer-HDRCQSX2.cjs.map → writer-NDK4U6C5.cjs.map} +1 -1
  79. package/dist/{writer-QNDPMEYD.js → writer-ZNBXYUYC.js} +3 -3
  80. package/dist/{writer-QNDPMEYD.js.map → writer-ZNBXYUYC.js.map} +1 -1
  81. package/package.json +6 -3
  82. package/dist/chunk-54W6IAHM.js +0 -24
  83. package/dist/chunk-54W6IAHM.js.map +0 -1
  84. package/dist/chunk-DRLVEHCD.js.map +0 -1
  85. package/dist/chunk-NP7ROSH2.cjs.map +0 -1
  86. package/dist/chunk-OYLZ5OCK.js.map +0 -1
  87. package/dist/chunk-P5DBJFSQ.js +0 -8459
  88. package/dist/chunk-P5DBJFSQ.js.map +0 -1
  89. package/dist/chunk-PZ5AY32C.js +0 -9
  90. package/dist/chunk-PZ5AY32C.js.map +0 -1
  91. package/dist/chunk-Q7SFCCGT.cjs +0 -11
  92. package/dist/chunk-Q7SFCCGT.cjs.map +0 -1
  93. package/dist/chunk-QXDCA7QW.cjs +0 -8466
  94. package/dist/chunk-QXDCA7QW.cjs.map +0 -1
  95. package/dist/chunk-RU64F5RS.cjs.map +0 -1
  96. package/dist/chunk-SZWHFTXV.cjs.map +0 -1
  97. package/dist/schema-reader-GM4A4L44.js +0 -4
  98. package/dist/server-LR6T6C44.cjs.map +0 -1
  99. package/dist/server-UHPEE55X.js.map +0 -1
  100. package/dist/type-generator-ZYSVFK3X.js +0 -4
@@ -1,26 +1,23 @@
1
- import { f as FontFaceDescriptor } from './types-CG08aO82.cjs';
2
-
3
1
  /**
4
2
  * Cross-host UI client — types and wire constants.
5
3
  *
6
4
  * The cross-host client (`connectUI`) renders one Synapse-authored component in
7
- * hosts that each speak a different bridge: ChatGPT (OpenAI Apps SDK), Claude
8
- * (mcp-ui), and plain/standalone. Apps code against `synapse.*` and never touch
9
- * a host protocol. This is a **push-first** surface: the tool output that spawned
5
+ * any MCP Apps host (ChatGPT, Claude, NimbleBrain) and standalone. Apps code
6
+ * against `synapse.*` and never touch the bridge. This is a **push-first** surface: the tool output that spawned
10
7
  * the widget is delivered at render (`data()` / `onData()`); `callTool()` is the
11
8
  * pull escape hatch, advertised per host via `capabilities()`.
12
9
  *
13
10
  * This layer intentionally has ZERO dependency on `@modelcontextprotocol/*` — the
14
- * ChatGPT / mcp-ui / inline bridges are pure `window.openai` + `postMessage`, so
15
- * the IIFE that apps inline stays tiny (no Zod, no ext-apps schemas).
11
+ * bridge is JSON-RPC over `postMessage`, spoken by hand, so the IIFE that apps
12
+ * inline stays tiny (no Zod, no ext-apps schemas).
16
13
  */
17
14
  /**
18
- * The host the client resolved to, as reported by `synapse.host()`. An escape
19
- * hatch — apps should rarely branch on it; `capabilities()` is the supported way
20
- * to feature-detect. `"nimblebrain"` is reserved for the runtime adapter (P3).
15
+ * The bridge the client resolved to, as reported by `synapse.host()`:
16
+ * `"mcp-apps"` in a frame, `"generic"` standalone. It names the bridge, not the
17
+ * product — every framing host speaks the same one. Apps should rarely branch on
18
+ * it; `capabilities()` is the supported way to feature-detect.
21
19
  */
22
-
23
- type HostKind = "chatgpt" | "claude" | "nimblebrain" | "generic";
20
+ type HostKind = "mcp-apps" | "generic";
24
21
  /** Resolved theme. `mode` always resolves to light or dark. `tokens` are CSS
25
22
  * custom properties the host publishes — the MCP Apps adapter reads them from
26
23
  * `hostContext.styles.variables`; where a host sends none they stay empty and the
@@ -28,9 +25,6 @@ type HostKind = "chatgpt" | "claude" | "nimblebrain" | "generic";
28
25
  interface SynapseUITheme {
29
26
  mode: "light" | "dark";
30
27
  tokens: Record<string, string>;
31
- /** Font faces the host wants loaded. Absent where the host sends none, which
32
- * leaves the SDK's web-safe fallbacks in force. See `FontFaceDescriptor`. */
33
- fontFaces?: FontFaceDescriptor[];
34
28
  }
35
29
  /** What the active host actually supports. `data()`/`onData()`/`theme()`/
36
30
  * `resize()` work everywhere; these three vary. */
@@ -46,6 +40,18 @@ interface HostCapabilities {
46
40
  declare class HostUnsupportedError extends Error {
47
41
  constructor(feature: string, host: HostKind);
48
42
  }
43
+ /**
44
+ * Thrown by `callTool()` when the tool result says the call failed
45
+ * (`isError: true`). MCP reports a tool failure inside the result rather than as
46
+ * a JSON-RPC error, and a host reports a refusal the same way — ChatGPT answers a
47
+ * call to a tool the app may not see with an `isError` result — so without this a
48
+ * failure would resolve as if it were data. The message is the result's first
49
+ * text block; the whole result is on `result`.
50
+ */
51
+ declare class ToolCallError extends Error {
52
+ readonly result: unknown;
53
+ constructor(name: string, result: unknown);
54
+ }
49
55
  interface ConnectUIOptions {
50
56
  /** App name — informational; forwarded to hosts that accept an appInfo. */
51
57
  name?: string;
@@ -54,18 +60,17 @@ interface ConnectUIOptions {
54
60
  /**
55
61
  * Force a host adapter instead of auto-detecting. Used by preview harnesses,
56
62
  * SSR, and tests; production apps omit it and let the SDK feature-detect.
57
- * `"claude"` → MCP Apps standard adapter, `"chatgpt"` → OpenAI Apps adapter,
58
- * `"generic"` → inline adapter.
63
+ * `"mcp-apps"` → MCP Apps standard adapter, `"generic"` → inline adapter.
59
64
  */
60
65
  host?: HostKind;
61
66
  /**
62
67
  * `id` of the `<script type="application/json">` element carrying pushed data
63
- * baked into the HTML (the mcp-ui / SSR path). Defaults to
68
+ * baked into the HTML (the SSR / standalone path). Defaults to
64
69
  * {@link SYNAPSE_DATA_ELEMENT_ID}.
65
70
  */
66
71
  dataElementId?: string;
67
72
  /**
68
- * Auto-report content height to the host on layout changes (mcp-ui only).
73
+ * Auto-report content height to the host on layout changes (MCP Apps only).
69
74
  * Defaults to `true`. Set `false` to size manually via `resize()`.
70
75
  */
71
76
  autoResize?: boolean;
@@ -92,8 +97,10 @@ interface SynapseUIClient {
92
97
  /** Subscribe to theme changes. The client already applies the theme to the DOM
93
98
  * (`data-theme` attribute + CSS variables) before this fires. */
94
99
  onTheme(cb: (theme: SynapseUITheme) => void): () => void;
95
- /** Widget→server tool call. Rejects with {@link HostUnsupportedError} where the
96
- * host advertises no pull (`capabilities().pull === false`). */
100
+ /** Widget→server tool call. Resolves with the tool result. Rejects with
101
+ * {@link ToolCallError} when the result reports failure (`isError: true`), and
102
+ * with {@link HostUnsupportedError} where the host advertises no pull
103
+ * (`capabilities().pull === false`). */
97
104
  callTool<O = unknown>(name: string, args?: Record<string, unknown>): Promise<O>;
98
105
  /** Send a follow-up message to the agent conversation. No-op where unsupported. */
99
106
  sendPrompt(text: string): void;
@@ -132,9 +139,9 @@ interface HostAdapter {
132
139
  declare const SYNAPSE_DATA_ELEMENT_ID = "synapse-ui-data";
133
140
 
134
141
  /**
135
- * Connect a Synapse-authored component to whatever host it renders in — ChatGPT
136
- * (OpenAI Apps SDK), Claude (MCP Apps standard), or a plain/standalone page — behind one
137
- * push-first API. Feature-detects the host, selects an adapter, and applies the
142
+ * Connect a Synapse-authored component to whatever host it renders in — any MCP
143
+ * Apps host, or a plain/standalone page — behind one push-first API. Detects
144
+ * whether it is framed, selects an adapter, and applies the
138
145
  * host theme to the DOM before returning.
139
146
  *
140
147
  * Synchronous: `data()` is populated from baked-in data (where present) on
@@ -153,20 +160,19 @@ declare function connectUI(options?: ConnectUIOptions): SynapseUIClient;
153
160
  * unit-testable with a plain object — no full `Window` fake required.
154
161
  */
155
162
  interface DetectableWindow {
156
- openai?: unknown;
157
163
  parent?: unknown;
158
- self?: unknown;
159
164
  }
160
165
  /**
161
- * Feature-detect the host from the browsing context.
166
+ * Detect the bridge from the browsing context.
162
167
  *
163
- * - `window.openai` present → **chatgpt** (OpenAI Apps SDK).
164
- * - otherwise, a nested browsing context (`parent !== self`, or a cross-origin
165
- * access that throws) → **claude** (MCP Apps standard postMessage host).
168
+ * - a nested browsing context (`parent !== self`, or a cross-origin access that
169
+ * throws) → **mcp-apps**: every host that frames a component speaks the MCP
170
+ * Apps standard.
166
171
  * - top-level document → **generic** (inline / standalone).
167
172
  *
168
- * Returns the host *kind*; `chooseAdapter` maps it to a concrete adapter and
169
- * honors an explicit `options.host` override for previews/SSR/tests.
173
+ * Nothing on the window names the host. `window.openai` in particular does not:
174
+ * ChatGPT injects it into every frame it serves, whatever the component is.
175
+ * Which host answered is in the handshake, not here.
170
176
  */
171
177
  declare function detectHostKind(win: DetectableWindow): HostKind;
172
178
  /** Build the adapter for an explicit host kind (`options.host` or a detected one). */
@@ -174,4 +180,4 @@ declare function adapterForKind(kind: HostKind, win: Window & typeof globalThis,
174
180
  /** Select and construct the host adapter, honoring `options.host` when set. */
175
181
  declare function selectAdapter(win: Window & typeof globalThis, options: ConnectUIOptions): HostAdapter;
176
182
 
177
- export { type ConnectUIOptions as C, type HostAdapter as H, type SynapseUITheme as S, type HostCapabilities as a, type HostKind as b, HostUnsupportedError as c, SYNAPSE_DATA_ELEMENT_ID as d, type SynapseUIClient as e, adapterForKind as f, connectUI as g, detectHostKind as h, selectAdapter as s };
183
+ export { type ConnectUIOptions as C, type HostAdapter as H, type SynapseUITheme as S, ToolCallError as T, type HostCapabilities as a, type HostKind as b, HostUnsupportedError as c, SYNAPSE_DATA_ELEMENT_ID as d, type SynapseUIClient as e, adapterForKind as f, connectUI as g, detectHostKind as h, selectAdapter as s };
@@ -1,26 +1,23 @@
1
- import { f as FontFaceDescriptor } from './types-CG08aO82.js';
2
-
3
1
  /**
4
2
  * Cross-host UI client — types and wire constants.
5
3
  *
6
4
  * The cross-host client (`connectUI`) renders one Synapse-authored component in
7
- * hosts that each speak a different bridge: ChatGPT (OpenAI Apps SDK), Claude
8
- * (mcp-ui), and plain/standalone. Apps code against `synapse.*` and never touch
9
- * a host protocol. This is a **push-first** surface: the tool output that spawned
5
+ * any MCP Apps host (ChatGPT, Claude, NimbleBrain) and standalone. Apps code
6
+ * against `synapse.*` and never touch the bridge. This is a **push-first** surface: the tool output that spawned
10
7
  * the widget is delivered at render (`data()` / `onData()`); `callTool()` is the
11
8
  * pull escape hatch, advertised per host via `capabilities()`.
12
9
  *
13
10
  * This layer intentionally has ZERO dependency on `@modelcontextprotocol/*` — the
14
- * ChatGPT / mcp-ui / inline bridges are pure `window.openai` + `postMessage`, so
15
- * the IIFE that apps inline stays tiny (no Zod, no ext-apps schemas).
11
+ * bridge is JSON-RPC over `postMessage`, spoken by hand, so the IIFE that apps
12
+ * inline stays tiny (no Zod, no ext-apps schemas).
16
13
  */
17
14
  /**
18
- * The host the client resolved to, as reported by `synapse.host()`. An escape
19
- * hatch — apps should rarely branch on it; `capabilities()` is the supported way
20
- * to feature-detect. `"nimblebrain"` is reserved for the runtime adapter (P3).
15
+ * The bridge the client resolved to, as reported by `synapse.host()`:
16
+ * `"mcp-apps"` in a frame, `"generic"` standalone. It names the bridge, not the
17
+ * product — every framing host speaks the same one. Apps should rarely branch on
18
+ * it; `capabilities()` is the supported way to feature-detect.
21
19
  */
22
-
23
- type HostKind = "chatgpt" | "claude" | "nimblebrain" | "generic";
20
+ type HostKind = "mcp-apps" | "generic";
24
21
  /** Resolved theme. `mode` always resolves to light or dark. `tokens` are CSS
25
22
  * custom properties the host publishes — the MCP Apps adapter reads them from
26
23
  * `hostContext.styles.variables`; where a host sends none they stay empty and the
@@ -28,9 +25,6 @@ type HostKind = "chatgpt" | "claude" | "nimblebrain" | "generic";
28
25
  interface SynapseUITheme {
29
26
  mode: "light" | "dark";
30
27
  tokens: Record<string, string>;
31
- /** Font faces the host wants loaded. Absent where the host sends none, which
32
- * leaves the SDK's web-safe fallbacks in force. See `FontFaceDescriptor`. */
33
- fontFaces?: FontFaceDescriptor[];
34
28
  }
35
29
  /** What the active host actually supports. `data()`/`onData()`/`theme()`/
36
30
  * `resize()` work everywhere; these three vary. */
@@ -46,6 +40,18 @@ interface HostCapabilities {
46
40
  declare class HostUnsupportedError extends Error {
47
41
  constructor(feature: string, host: HostKind);
48
42
  }
43
+ /**
44
+ * Thrown by `callTool()` when the tool result says the call failed
45
+ * (`isError: true`). MCP reports a tool failure inside the result rather than as
46
+ * a JSON-RPC error, and a host reports a refusal the same way — ChatGPT answers a
47
+ * call to a tool the app may not see with an `isError` result — so without this a
48
+ * failure would resolve as if it were data. The message is the result's first
49
+ * text block; the whole result is on `result`.
50
+ */
51
+ declare class ToolCallError extends Error {
52
+ readonly result: unknown;
53
+ constructor(name: string, result: unknown);
54
+ }
49
55
  interface ConnectUIOptions {
50
56
  /** App name — informational; forwarded to hosts that accept an appInfo. */
51
57
  name?: string;
@@ -54,18 +60,17 @@ interface ConnectUIOptions {
54
60
  /**
55
61
  * Force a host adapter instead of auto-detecting. Used by preview harnesses,
56
62
  * SSR, and tests; production apps omit it and let the SDK feature-detect.
57
- * `"claude"` → MCP Apps standard adapter, `"chatgpt"` → OpenAI Apps adapter,
58
- * `"generic"` → inline adapter.
63
+ * `"mcp-apps"` → MCP Apps standard adapter, `"generic"` → inline adapter.
59
64
  */
60
65
  host?: HostKind;
61
66
  /**
62
67
  * `id` of the `<script type="application/json">` element carrying pushed data
63
- * baked into the HTML (the mcp-ui / SSR path). Defaults to
68
+ * baked into the HTML (the SSR / standalone path). Defaults to
64
69
  * {@link SYNAPSE_DATA_ELEMENT_ID}.
65
70
  */
66
71
  dataElementId?: string;
67
72
  /**
68
- * Auto-report content height to the host on layout changes (mcp-ui only).
73
+ * Auto-report content height to the host on layout changes (MCP Apps only).
69
74
  * Defaults to `true`. Set `false` to size manually via `resize()`.
70
75
  */
71
76
  autoResize?: boolean;
@@ -92,8 +97,10 @@ interface SynapseUIClient {
92
97
  /** Subscribe to theme changes. The client already applies the theme to the DOM
93
98
  * (`data-theme` attribute + CSS variables) before this fires. */
94
99
  onTheme(cb: (theme: SynapseUITheme) => void): () => void;
95
- /** Widget→server tool call. Rejects with {@link HostUnsupportedError} where the
96
- * host advertises no pull (`capabilities().pull === false`). */
100
+ /** Widget→server tool call. Resolves with the tool result. Rejects with
101
+ * {@link ToolCallError} when the result reports failure (`isError: true`), and
102
+ * with {@link HostUnsupportedError} where the host advertises no pull
103
+ * (`capabilities().pull === false`). */
97
104
  callTool<O = unknown>(name: string, args?: Record<string, unknown>): Promise<O>;
98
105
  /** Send a follow-up message to the agent conversation. No-op where unsupported. */
99
106
  sendPrompt(text: string): void;
@@ -132,9 +139,9 @@ interface HostAdapter {
132
139
  declare const SYNAPSE_DATA_ELEMENT_ID = "synapse-ui-data";
133
140
 
134
141
  /**
135
- * Connect a Synapse-authored component to whatever host it renders in — ChatGPT
136
- * (OpenAI Apps SDK), Claude (MCP Apps standard), or a plain/standalone page — behind one
137
- * push-first API. Feature-detects the host, selects an adapter, and applies the
142
+ * Connect a Synapse-authored component to whatever host it renders in — any MCP
143
+ * Apps host, or a plain/standalone page — behind one push-first API. Detects
144
+ * whether it is framed, selects an adapter, and applies the
138
145
  * host theme to the DOM before returning.
139
146
  *
140
147
  * Synchronous: `data()` is populated from baked-in data (where present) on
@@ -153,20 +160,19 @@ declare function connectUI(options?: ConnectUIOptions): SynapseUIClient;
153
160
  * unit-testable with a plain object — no full `Window` fake required.
154
161
  */
155
162
  interface DetectableWindow {
156
- openai?: unknown;
157
163
  parent?: unknown;
158
- self?: unknown;
159
164
  }
160
165
  /**
161
- * Feature-detect the host from the browsing context.
166
+ * Detect the bridge from the browsing context.
162
167
  *
163
- * - `window.openai` present → **chatgpt** (OpenAI Apps SDK).
164
- * - otherwise, a nested browsing context (`parent !== self`, or a cross-origin
165
- * access that throws) → **claude** (MCP Apps standard postMessage host).
168
+ * - a nested browsing context (`parent !== self`, or a cross-origin access that
169
+ * throws) → **mcp-apps**: every host that frames a component speaks the MCP
170
+ * Apps standard.
166
171
  * - top-level document → **generic** (inline / standalone).
167
172
  *
168
- * Returns the host *kind*; `chooseAdapter` maps it to a concrete adapter and
169
- * honors an explicit `options.host` override for previews/SSR/tests.
173
+ * Nothing on the window names the host. `window.openai` in particular does not:
174
+ * ChatGPT injects it into every frame it serves, whatever the component is.
175
+ * Which host answered is in the handshake, not here.
170
176
  */
171
177
  declare function detectHostKind(win: DetectableWindow): HostKind;
172
178
  /** Build the adapter for an explicit host kind (`options.host` or a detected one). */
@@ -174,4 +180,4 @@ declare function adapterForKind(kind: HostKind, win: Window & typeof globalThis,
174
180
  /** Select and construct the host adapter, honoring `options.host` when set. */
175
181
  declare function selectAdapter(win: Window & typeof globalThis, options: ConnectUIOptions): HostAdapter;
176
182
 
177
- export { type ConnectUIOptions as C, type HostAdapter as H, type SynapseUITheme as S, type HostCapabilities as a, type HostKind as b, HostUnsupportedError as c, SYNAPSE_DATA_ELEMENT_ID as d, type SynapseUIClient as e, adapterForKind as f, connectUI as g, detectHostKind as h, selectAdapter as s };
183
+ export { type ConnectUIOptions as C, type HostAdapter as H, type SynapseUITheme as S, ToolCallError as T, type HostCapabilities as a, type HostKind as b, HostUnsupportedError as c, SYNAPSE_DATA_ELEMENT_ID as d, type SynapseUIClient as e, adapterForKind as f, connectUI as g, detectHostKind as h, selectAdapter as s };
@@ -1,58 +1,57 @@
1
1
  'use strict';
2
2
 
3
- var chunkRU64F5RS_cjs = require('../chunk-RU64F5RS.cjs');
4
- require('../chunk-SZWHFTXV.cjs');
5
- require('../chunk-Q7SFCCGT.cjs');
3
+ var chunkBNF352SA_cjs = require('../chunk-BNF352SA.cjs');
4
+ require('../chunk-YSL7KJNU.cjs');
6
5
 
7
6
 
8
7
 
9
8
  Object.defineProperty(exports, "HostUnsupportedError", {
10
9
  enumerable: true,
11
- get: function () { return chunkRU64F5RS_cjs.HostUnsupportedError; }
10
+ get: function () { return chunkBNF352SA_cjs.HostUnsupportedError; }
12
11
  });
13
12
  Object.defineProperty(exports, "SYNAPSE_DATA_ELEMENT_ID", {
14
13
  enumerable: true,
15
- get: function () { return chunkRU64F5RS_cjs.SYNAPSE_DATA_ELEMENT_ID; }
14
+ get: function () { return chunkBNF352SA_cjs.SYNAPSE_DATA_ELEMENT_ID; }
15
+ });
16
+ Object.defineProperty(exports, "ToolCallError", {
17
+ enumerable: true,
18
+ get: function () { return chunkBNF352SA_cjs.ToolCallError; }
16
19
  });
17
20
  Object.defineProperty(exports, "adapterForKind", {
18
21
  enumerable: true,
19
- get: function () { return chunkRU64F5RS_cjs.adapterForKind; }
22
+ get: function () { return chunkBNF352SA_cjs.adapterForKind; }
20
23
  });
21
24
  Object.defineProperty(exports, "applyHostTheme", {
22
25
  enumerable: true,
23
- get: function () { return chunkRU64F5RS_cjs.applyHostTheme; }
26
+ get: function () { return chunkBNF352SA_cjs.applyHostTheme; }
24
27
  });
25
28
  Object.defineProperty(exports, "coerceMode", {
26
29
  enumerable: true,
27
- get: function () { return chunkRU64F5RS_cjs.coerceMode; }
30
+ get: function () { return chunkBNF352SA_cjs.coerceMode; }
28
31
  });
29
32
  Object.defineProperty(exports, "connectUI", {
30
33
  enumerable: true,
31
- get: function () { return chunkRU64F5RS_cjs.connectUI; }
32
- });
33
- Object.defineProperty(exports, "createChatGPTAdapter", {
34
- enumerable: true,
35
- get: function () { return chunkRU64F5RS_cjs.createChatGPTAdapter; }
34
+ get: function () { return chunkBNF352SA_cjs.connectUI; }
36
35
  });
37
36
  Object.defineProperty(exports, "createInlineAdapter", {
38
37
  enumerable: true,
39
- get: function () { return chunkRU64F5RS_cjs.createInlineAdapter; }
38
+ get: function () { return chunkBNF352SA_cjs.createInlineAdapter; }
40
39
  });
41
40
  Object.defineProperty(exports, "createMcpAppsAdapter", {
42
41
  enumerable: true,
43
- get: function () { return chunkRU64F5RS_cjs.createMcpAppsAdapter; }
42
+ get: function () { return chunkBNF352SA_cjs.createMcpAppsAdapter; }
44
43
  });
45
44
  Object.defineProperty(exports, "detectHostKind", {
46
45
  enumerable: true,
47
- get: function () { return chunkRU64F5RS_cjs.detectHostKind; }
46
+ get: function () { return chunkBNF352SA_cjs.detectHostKind; }
48
47
  });
49
48
  Object.defineProperty(exports, "preferredMode", {
50
49
  enumerable: true,
51
- get: function () { return chunkRU64F5RS_cjs.preferredMode; }
50
+ get: function () { return chunkBNF352SA_cjs.preferredMode; }
52
51
  });
53
52
  Object.defineProperty(exports, "selectAdapter", {
54
53
  enumerable: true,
55
- get: function () { return chunkRU64F5RS_cjs.selectAdapter; }
54
+ get: function () { return chunkBNF352SA_cjs.selectAdapter; }
56
55
  });
57
56
  //# sourceMappingURL=index.cjs.map
58
57
  //# sourceMappingURL=index.cjs.map
@@ -1,18 +1,5 @@
1
- import { C as ConnectUIOptions, H as HostAdapter, S as SynapseUITheme } from '../detect-DZMJsyvZ.cjs';
2
- export { a as HostCapabilities, b as HostKind, c as HostUnsupportedError, d as SYNAPSE_DATA_ELEMENT_ID, e as SynapseUIClient, f as adapterForKind, g as connectUI, h as detectHostKind, s as selectAdapter } from '../detect-DZMJsyvZ.cjs';
3
- import '../types-CG08aO82.cjs';
4
- import '@modelcontextprotocol/ext-apps';
5
- import '@modelcontextprotocol/sdk/types.js';
6
-
7
- /**
8
- * ChatGPT (OpenAI Apps SDK) adapter.
9
- *
10
- * Data arrives on `window.openai.toolOutput` (available synchronously before the
11
- * widget script runs) and updates via the `openai:set_globals` event. Theme is a
12
- * mode string on the same surface. The host auto-sizes the iframe, so `resize()`
13
- * is a no-op. `callTool` is available when the host exposes `window.openai.callTool`.
14
- */
15
- declare function createChatGPTAdapter(win: Window & typeof globalThis, _options: ConnectUIOptions): HostAdapter;
1
+ import { C as ConnectUIOptions, H as HostAdapter, S as SynapseUITheme } from '../detect-DVGaL2bH.cjs';
2
+ export { a as HostCapabilities, b as HostKind, c as HostUnsupportedError, d as SYNAPSE_DATA_ELEMENT_ID, e as SynapseUIClient, T as ToolCallError, f as adapterForKind, g as connectUI, h as detectHostKind, s as selectAdapter } from '../detect-DVGaL2bH.cjs';
16
3
 
17
4
  /**
18
5
  * Inline / standalone fallback adapter.
@@ -26,8 +13,8 @@ declare function createChatGPTAdapter(win: Window & typeof globalThis, _options:
26
13
  declare function createInlineAdapter(win: Window & typeof globalThis, options: ConnectUIOptions): HostAdapter;
27
14
 
28
15
  /**
29
- * MCP Apps standard (SEP-1865) adapter — the convergence bridge for Claude
30
- * Desktop and other MCP Apps hosts.
16
+ * MCP Apps standard (SEP-1865) adapter — the bridge for every framed host.
17
+ * ChatGPT, Claude and NimbleBrain all implement the standard, so there is one.
31
18
  *
32
19
  * The View iframe is an MCP client speaking JSON-RPC 2.0 to `window.parent`:
33
20
  *
@@ -39,9 +26,7 @@ declare function createInlineAdapter(win: Window & typeof globalThis, options: C
39
26
  * `ui/notifications/host-context-changed`.
40
27
  *
41
28
  * Actions go up as requests: `ui/open-link`, `ui/message` (follow-up), and
42
- * `tools/call` (pull). The legacy mcp-ui `ui-lifecycle-*` messages are sent and
43
- * accepted alongside so a pre-standard host still renders — a standard host drops
44
- * the non-JSON-RPC frames, and a legacy host ignores the JSON-RPC ones.
29
+ * `tools/call` (pull).
45
30
  */
46
31
  declare function createMcpAppsAdapter(win: Window & typeof globalThis, options: ConnectUIOptions): HostAdapter;
47
32
 
@@ -57,19 +42,18 @@ declare function createMcpAppsAdapter(win: Window & typeof globalThis, options:
57
42
  * `@nimblebrain/synapse/ui` token components consume theme, backed by the
58
43
  * SDK's neutral defaults so every referenced var resolves in both modes.
59
44
  *
60
- * Setting both means an app can use either convention (or a host that supplies
61
- * only a mode string, like the OpenAI Apps SDK, still themes correctly). SSR-safe.
45
+ * Setting both means an app can use either convention (and a host that supplies
46
+ * only a mode string still themes correctly). SSR-safe.
62
47
  *
63
- * Any `fontFaces` the host supplies are loaded here too — a token can name a
64
- * family but not load it, so the two travel together. A host that sends none
65
- * leaves the web-safe fallbacks in force.
48
+ * Fonts travel separately, as the host's `@font-face` CSS: see
49
+ * {@link injectHostFonts}.
66
50
  */
67
51
  declare function applyHostTheme(theme: SynapseUITheme): void;
68
52
  /** Read the OS-level color scheme as a sane default for hosts that don't push a
69
- * theme until later (mcp-ui) or ever (standalone). */
53
+ * theme until the handshake completes, or ever (standalone). */
70
54
  declare function preferredMode(win: Window | undefined): "light" | "dark";
71
55
  /** Coerce an arbitrary host-supplied theme signal to a mode. Accepts the string
72
56
  * form (`"dark"`) both hosts use; anything else falls back. */
73
57
  declare function coerceMode(value: unknown, fallback: "light" | "dark"): "light" | "dark";
74
58
 
75
- export { ConnectUIOptions, HostAdapter, SynapseUITheme, applyHostTheme, coerceMode, createChatGPTAdapter, createInlineAdapter, createMcpAppsAdapter, preferredMode };
59
+ export { ConnectUIOptions, HostAdapter, SynapseUITheme, applyHostTheme, coerceMode, createInlineAdapter, createMcpAppsAdapter, preferredMode };
@@ -1,18 +1,5 @@
1
- import { C as ConnectUIOptions, H as HostAdapter, S as SynapseUITheme } from '../detect-DBwc8a21.js';
2
- export { a as HostCapabilities, b as HostKind, c as HostUnsupportedError, d as SYNAPSE_DATA_ELEMENT_ID, e as SynapseUIClient, f as adapterForKind, g as connectUI, h as detectHostKind, s as selectAdapter } from '../detect-DBwc8a21.js';
3
- import '../types-CG08aO82.js';
4
- import '@modelcontextprotocol/ext-apps';
5
- import '@modelcontextprotocol/sdk/types.js';
6
-
7
- /**
8
- * ChatGPT (OpenAI Apps SDK) adapter.
9
- *
10
- * Data arrives on `window.openai.toolOutput` (available synchronously before the
11
- * widget script runs) and updates via the `openai:set_globals` event. Theme is a
12
- * mode string on the same surface. The host auto-sizes the iframe, so `resize()`
13
- * is a no-op. `callTool` is available when the host exposes `window.openai.callTool`.
14
- */
15
- declare function createChatGPTAdapter(win: Window & typeof globalThis, _options: ConnectUIOptions): HostAdapter;
1
+ import { C as ConnectUIOptions, H as HostAdapter, S as SynapseUITheme } from '../detect-DVGaL2bH.js';
2
+ export { a as HostCapabilities, b as HostKind, c as HostUnsupportedError, d as SYNAPSE_DATA_ELEMENT_ID, e as SynapseUIClient, T as ToolCallError, f as adapterForKind, g as connectUI, h as detectHostKind, s as selectAdapter } from '../detect-DVGaL2bH.js';
16
3
 
17
4
  /**
18
5
  * Inline / standalone fallback adapter.
@@ -26,8 +13,8 @@ declare function createChatGPTAdapter(win: Window & typeof globalThis, _options:
26
13
  declare function createInlineAdapter(win: Window & typeof globalThis, options: ConnectUIOptions): HostAdapter;
27
14
 
28
15
  /**
29
- * MCP Apps standard (SEP-1865) adapter — the convergence bridge for Claude
30
- * Desktop and other MCP Apps hosts.
16
+ * MCP Apps standard (SEP-1865) adapter — the bridge for every framed host.
17
+ * ChatGPT, Claude and NimbleBrain all implement the standard, so there is one.
31
18
  *
32
19
  * The View iframe is an MCP client speaking JSON-RPC 2.0 to `window.parent`:
33
20
  *
@@ -39,9 +26,7 @@ declare function createInlineAdapter(win: Window & typeof globalThis, options: C
39
26
  * `ui/notifications/host-context-changed`.
40
27
  *
41
28
  * Actions go up as requests: `ui/open-link`, `ui/message` (follow-up), and
42
- * `tools/call` (pull). The legacy mcp-ui `ui-lifecycle-*` messages are sent and
43
- * accepted alongside so a pre-standard host still renders — a standard host drops
44
- * the non-JSON-RPC frames, and a legacy host ignores the JSON-RPC ones.
29
+ * `tools/call` (pull).
45
30
  */
46
31
  declare function createMcpAppsAdapter(win: Window & typeof globalThis, options: ConnectUIOptions): HostAdapter;
47
32
 
@@ -57,19 +42,18 @@ declare function createMcpAppsAdapter(win: Window & typeof globalThis, options:
57
42
  * `@nimblebrain/synapse/ui` token components consume theme, backed by the
58
43
  * SDK's neutral defaults so every referenced var resolves in both modes.
59
44
  *
60
- * Setting both means an app can use either convention (or a host that supplies
61
- * only a mode string, like the OpenAI Apps SDK, still themes correctly). SSR-safe.
45
+ * Setting both means an app can use either convention (and a host that supplies
46
+ * only a mode string still themes correctly). SSR-safe.
62
47
  *
63
- * Any `fontFaces` the host supplies are loaded here too — a token can name a
64
- * family but not load it, so the two travel together. A host that sends none
65
- * leaves the web-safe fallbacks in force.
48
+ * Fonts travel separately, as the host's `@font-face` CSS: see
49
+ * {@link injectHostFonts}.
66
50
  */
67
51
  declare function applyHostTheme(theme: SynapseUITheme): void;
68
52
  /** Read the OS-level color scheme as a sane default for hosts that don't push a
69
- * theme until later (mcp-ui) or ever (standalone). */
53
+ * theme until the handshake completes, or ever (standalone). */
70
54
  declare function preferredMode(win: Window | undefined): "light" | "dark";
71
55
  /** Coerce an arbitrary host-supplied theme signal to a mode. Accepts the string
72
56
  * form (`"dark"`) both hosts use; anything else falls back. */
73
57
  declare function coerceMode(value: unknown, fallback: "light" | "dark"): "light" | "dark";
74
58
 
75
- export { ConnectUIOptions, HostAdapter, SynapseUITheme, applyHostTheme, coerceMode, createChatGPTAdapter, createInlineAdapter, createMcpAppsAdapter, preferredMode };
59
+ export { ConnectUIOptions, HostAdapter, SynapseUITheme, applyHostTheme, coerceMode, createInlineAdapter, createMcpAppsAdapter, preferredMode };
@@ -1,5 +1,4 @@
1
- export { HostUnsupportedError, SYNAPSE_DATA_ELEMENT_ID, adapterForKind, applyHostTheme, coerceMode, connectUI, createChatGPTAdapter, createInlineAdapter, createMcpAppsAdapter, detectHostKind, preferredMode, selectAdapter } from '../chunk-OYLZ5OCK.js';
2
- import '../chunk-DRLVEHCD.js';
3
- import '../chunk-PZ5AY32C.js';
1
+ export { HostUnsupportedError, SYNAPSE_DATA_ELEMENT_ID, ToolCallError, adapterForKind, applyHostTheme, coerceMode, connectUI, createInlineAdapter, createMcpAppsAdapter, detectHostKind, preferredMode, selectAdapter } from '../chunk-UKP4W6DI.js';
2
+ import '../chunk-DJS2FKBN.js';
4
3
  //# sourceMappingURL=index.js.map
5
4
  //# sourceMappingURL=index.js.map
package/dist/index.cjs CHANGED
@@ -1,18 +1,17 @@
1
1
  'use strict';
2
2
 
3
- var chunkRU64F5RS_cjs = require('./chunk-RU64F5RS.cjs');
4
- var chunkQXDCA7QW_cjs = require('./chunk-QXDCA7QW.cjs');
5
- var chunkSZWHFTXV_cjs = require('./chunk-SZWHFTXV.cjs');
6
- require('./chunk-NP7ROSH2.cjs');
7
- require('./chunk-Q7SFCCGT.cjs');
3
+ var chunkBNF352SA_cjs = require('./chunk-BNF352SA.cjs');
4
+ var chunkHQLSVUOR_cjs = require('./chunk-HQLSVUOR.cjs');
5
+ require('./chunk-YSL7KJNU.cjs');
6
+ var chunkISV6HFQE_cjs = require('./chunk-ISV6HFQE.cjs');
8
7
  var extApps = require('@modelcontextprotocol/ext-apps');
9
8
 
10
9
  var BASE64_CHUNK = 32768;
11
10
  async function downloadFile(app, filename, content, mimeType) {
12
- const internals = chunkQXDCA7QW_cjs.internalsFor(app);
13
- if (internals.hostDownloadFileCapability === void 0) {
14
- throw new Error("downloadFile is not supported in this host");
11
+ if (!app.hostCapabilities.downloadFile) {
12
+ throw new chunkHQLSVUOR_cjs.HostCapabilityError("downloadFile", "downloadFile");
15
13
  }
14
+ const internals = chunkHQLSVUOR_cjs.internalsFor(app);
16
15
  const resolvedMime = mimeType || (content instanceof Blob ? content.type : "") || "application/octet-stream";
17
16
  const uri = `file:///${filename}`;
18
17
  const resource = typeof content === "string" ? { uri, mimeType: resolvedMime, text: content } : { uri, mimeType: resolvedMime, blob: await toBase64(content) };
@@ -35,43 +34,55 @@ async function toBase64(blob) {
35
34
 
36
35
  Object.defineProperty(exports, "HostUnsupportedError", {
37
36
  enumerable: true,
38
- get: function () { return chunkRU64F5RS_cjs.HostUnsupportedError; }
37
+ get: function () { return chunkBNF352SA_cjs.HostUnsupportedError; }
39
38
  });
40
39
  Object.defineProperty(exports, "SYNAPSE_DATA_ELEMENT_ID", {
41
40
  enumerable: true,
42
- get: function () { return chunkRU64F5RS_cjs.SYNAPSE_DATA_ELEMENT_ID; }
41
+ get: function () { return chunkBNF352SA_cjs.SYNAPSE_DATA_ELEMENT_ID; }
42
+ });
43
+ Object.defineProperty(exports, "ToolCallError", {
44
+ enumerable: true,
45
+ get: function () { return chunkBNF352SA_cjs.ToolCallError; }
43
46
  });
44
47
  Object.defineProperty(exports, "connectUI", {
45
48
  enumerable: true,
46
- get: function () { return chunkRU64F5RS_cjs.connectUI; }
49
+ get: function () { return chunkBNF352SA_cjs.connectUI; }
47
50
  });
48
51
  Object.defineProperty(exports, "detectHostKind", {
49
52
  enumerable: true,
50
- get: function () { return chunkRU64F5RS_cjs.detectHostKind; }
53
+ get: function () { return chunkBNF352SA_cjs.detectHostKind; }
54
+ });
55
+ Object.defineProperty(exports, "HostCapabilityError", {
56
+ enumerable: true,
57
+ get: function () { return chunkHQLSVUOR_cjs.HostCapabilityError; }
51
58
  });
52
59
  Object.defineProperty(exports, "action", {
53
60
  enumerable: true,
54
- get: function () { return chunkQXDCA7QW_cjs.action; }
61
+ get: function () { return chunkHQLSVUOR_cjs.action; }
55
62
  });
56
63
  Object.defineProperty(exports, "callToolAsTask", {
57
64
  enumerable: true,
58
- get: function () { return chunkQXDCA7QW_cjs.callToolAsTask; }
65
+ get: function () { return chunkHQLSVUOR_cjs.callToolAsTask; }
59
66
  });
60
67
  Object.defineProperty(exports, "connect", {
61
68
  enumerable: true,
62
- get: function () { return chunkQXDCA7QW_cjs.connect; }
69
+ get: function () { return chunkHQLSVUOR_cjs.connect; }
70
+ });
71
+ Object.defineProperty(exports, "hostSupports", {
72
+ enumerable: true,
73
+ get: function () { return chunkHQLSVUOR_cjs.hostSupports; }
63
74
  });
64
75
  Object.defineProperty(exports, "pickFile", {
65
76
  enumerable: true,
66
- get: function () { return chunkQXDCA7QW_cjs.pickFile; }
77
+ get: function () { return chunkHQLSVUOR_cjs.pickFile; }
67
78
  });
68
79
  Object.defineProperty(exports, "pickFiles", {
69
80
  enumerable: true,
70
- get: function () { return chunkQXDCA7QW_cjs.pickFiles; }
81
+ get: function () { return chunkHQLSVUOR_cjs.pickFiles; }
71
82
  });
72
- Object.defineProperty(exports, "FONT_FACES_CONTEXT_KEY", {
83
+ Object.defineProperty(exports, "NIMBLEBRAIN_EXTENSIONS", {
73
84
  enumerable: true,
74
- get: function () { return chunkSZWHFTXV_cjs.FONT_FACES_CONTEXT_KEY; }
85
+ get: function () { return chunkISV6HFQE_cjs.NIMBLEBRAIN_EXTENSIONS; }
75
86
  });
76
87
  exports.downloadFile = downloadFile;
77
88
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/download-file.ts"],"names":["internalsFor","DOWNLOAD_FILE_METHOD"],"mappings":";;;;;;;;;AAqBA,IAAM,YAAA,GAAe,KAAA;AAqBrB,eAAsB,YAAA,CACpB,GAAA,EACA,QAAA,EACA,OAAA,EACA,QAAA,EACkC;AAClC,EAAA,MAAM,SAAA,GAAYA,+BAAa,GAAG,CAAA;AAClC,EAAA,IAAI,SAAA,CAAU,+BAA+B,MAAA,EAAW;AACtD,IAAA,MAAM,IAAI,MAAM,4CAA4C,CAAA;AAAA,EAC9D;AACA,EAAA,MAAM,eACJ,QAAA,KAAa,OAAA,YAAmB,IAAA,GAAO,OAAA,CAAQ,OAAO,EAAA,CAAA,IAAO,0BAAA;AAC/D,EAAA,MAAM,GAAA,GAAM,WAAW,QAAQ,CAAA,CAAA;AAC/B,EAAA,MAAM,WACJ,OAAO,OAAA,KAAY,WACf,EAAE,GAAA,EAAK,UAAU,YAAA,EAAc,IAAA,EAAM,SAAQ,GAC7C,EAAE,KAAK,QAAA,EAAU,YAAA,EAAc,MAAM,MAAM,QAAA,CAAS,OAAO,CAAA,EAAE;AACnE,EAAA,MAAM,MAAA,GAA6C;AAAA,IACjD,UAAU,CAAC,EAAE,IAAA,EAAM,UAAA,EAAY,UAAU;AAAA,GAC3C;AACA,EAAA,OAAQ,MAAM,SAAA,CAAU,OAAA;AAAA,IACtBC,4BAAA;AAAA,IACA;AAAA,GACF;AACF;AAEA,eAAe,SAAS,IAAA,EAA6B;AACnD,EAAA,MAAM,QAAQ,IAAI,UAAA,CAAW,MAAM,IAAA,CAAK,aAAa,CAAA;AACrD,EAAA,IAAI,MAAA,GAAS,EAAA;AACb,EAAA,KAAA,IAAS,IAAI,CAAA,EAAG,CAAA,GAAI,KAAA,CAAM,MAAA,EAAQ,KAAK,YAAA,EAAc;AACnD,IAAA,MAAA,IAAU,MAAA,CAAO,aAAa,GAAG,KAAA,CAAM,SAAS,CAAA,EAAG,CAAA,GAAI,YAAY,CAAC,CAAA;AAAA,EACtE;AACA,EAAA,OAAO,KAAK,MAAM,CAAA;AACpB","file":"index.cjs","sourcesContent":["/**\n * Hand the user a file to save, over the ext-apps `ui/download-file` request.\n *\n * A function over {@link App} rather than a method on it, like the helpers in\n * `extensions.ts` — but unlike them this is spec surface, so it is not gated on\n * the host identity. Any host that advertises `downloadFile` can answer it.\n *\n * The bytes always travel embedded. The spec also allows a `ResourceLink`, which\n * asks the host to fetch a URI the app names; a host holding the user's session\n * has every reason to refuse that, and the NimbleBrain host does.\n */\nimport {\n DOWNLOAD_FILE_METHOD,\n type McpUiDownloadFileRequest,\n type McpUiDownloadFileResult,\n} from \"@modelcontextprotocol/ext-apps\";\nimport type { EmbeddedResource } from \"@modelcontextprotocol/sdk/types.js\";\nimport { internalsFor } from \"./internals.js\";\nimport type { App } from \"./types.js\";\n\n/** Bytes per `String.fromCharCode` spread — well under any engine's argument limit. */\nconst BASE64_CHUNK = 0x8000;\n\n/**\n * Hand the user a file to save.\n *\n * A string is sent as the resource's `text`; a `Blob` is read and sent as\n * base64 `blob`. Reading a `Blob` is asynchronous, which is why this returns a\n * promise.\n *\n * Precedence for the MIME type: the explicit argument, then the Blob's own\n * type, then `application/octet-stream`. An empty-string argument falls\n * through — a `\"\"` MIME is effectively \"no type\".\n *\n * The filename is the last segment of the resource's `file:///` URI, because an\n * embedded resource has no name field of its own.\n *\n * Resolves with the host's result: `{ isError: true }` when the host declined\n * or the user cancelled. Rejects, without sending, when the host did not\n * advertise the `downloadFile` capability — a host that does not implement the\n * request may never answer it, and a request has no deadline.\n */\nexport async function downloadFile(\n app: App,\n filename: string,\n content: string | Blob,\n mimeType?: string,\n): Promise<McpUiDownloadFileResult> {\n const internals = internalsFor(app);\n if (internals.hostDownloadFileCapability === undefined) {\n throw new Error(\"downloadFile is not supported in this host\");\n }\n const resolvedMime =\n mimeType || (content instanceof Blob ? content.type : \"\") || \"application/octet-stream\";\n const uri = `file:///${filename}`;\n const resource: EmbeddedResource[\"resource\"] =\n typeof content === \"string\"\n ? { uri, mimeType: resolvedMime, text: content }\n : { uri, mimeType: resolvedMime, blob: await toBase64(content) };\n const params: McpUiDownloadFileRequest[\"params\"] = {\n contents: [{ type: \"resource\", resource }],\n };\n return (await internals.request(\n DOWNLOAD_FILE_METHOD,\n params as unknown as Record<string, unknown>,\n )) as McpUiDownloadFileResult;\n}\n\nasync function toBase64(blob: Blob): Promise<string> {\n const bytes = new Uint8Array(await blob.arrayBuffer());\n let binary = \"\";\n for (let i = 0; i < bytes.length; i += BASE64_CHUNK) {\n binary += String.fromCharCode(...bytes.subarray(i, i + BASE64_CHUNK));\n }\n return btoa(binary);\n}\n"]}
1
+ {"version":3,"sources":["../src/download-file.ts"],"names":["HostCapabilityError","internalsFor","DOWNLOAD_FILE_METHOD"],"mappings":";;;;;;;;AAsBA,IAAM,YAAA,GAAe,KAAA;AAqBrB,eAAsB,YAAA,CACpB,GAAA,EACA,QAAA,EACA,OAAA,EACA,QAAA,EACkC;AAClC,EAAA,IAAI,CAAC,GAAA,CAAI,gBAAA,CAAiB,YAAA,EAAc;AACtC,IAAA,MAAM,IAAIA,qCAAA,CAAoB,cAAA,EAAgB,cAAc,CAAA;AAAA,EAC9D;AACA,EAAA,MAAM,SAAA,GAAYC,+BAAa,GAAG,CAAA;AAClC,EAAA,MAAM,eACJ,QAAA,KAAa,OAAA,YAAmB,IAAA,GAAO,OAAA,CAAQ,OAAO,EAAA,CAAA,IAAO,0BAAA;AAC/D,EAAA,MAAM,GAAA,GAAM,WAAW,QAAQ,CAAA,CAAA;AAC/B,EAAA,MAAM,WACJ,OAAO,OAAA,KAAY,WACf,EAAE,GAAA,EAAK,UAAU,YAAA,EAAc,IAAA,EAAM,SAAQ,GAC7C,EAAE,KAAK,QAAA,EAAU,YAAA,EAAc,MAAM,MAAM,QAAA,CAAS,OAAO,CAAA,EAAE;AACnE,EAAA,MAAM,MAAA,GAA6C;AAAA,IACjD,UAAU,CAAC,EAAE,IAAA,EAAM,UAAA,EAAY,UAAU;AAAA,GAC3C;AACA,EAAA,OAAQ,MAAM,SAAA,CAAU,OAAA;AAAA,IACtBC,4BAAA;AAAA,IACA;AAAA,GACF;AACF;AAEA,eAAe,SAAS,IAAA,EAA6B;AACnD,EAAA,MAAM,QAAQ,IAAI,UAAA,CAAW,MAAM,IAAA,CAAK,aAAa,CAAA;AACrD,EAAA,IAAI,MAAA,GAAS,EAAA;AACb,EAAA,KAAA,IAAS,IAAI,CAAA,EAAG,CAAA,GAAI,KAAA,CAAM,MAAA,EAAQ,KAAK,YAAA,EAAc;AACnD,IAAA,MAAA,IAAU,MAAA,CAAO,aAAa,GAAG,KAAA,CAAM,SAAS,CAAA,EAAG,CAAA,GAAI,YAAY,CAAC,CAAA;AAAA,EACtE;AACA,EAAA,OAAO,KAAK,MAAM,CAAA;AACpB","file":"index.cjs","sourcesContent":["/**\n * Hand the user a file to save, over the ext-apps `ui/download-file` request.\n *\n * A function over {@link App} rather than a method on it, like the helpers in\n * `extensions.ts` — but unlike them this is spec surface, so it is not gated on\n * the host identity. Any host that advertises `downloadFile` can answer it.\n *\n * The bytes always travel embedded. The spec also allows a `ResourceLink`, which\n * asks the host to fetch a URI the app names; a host holding the user's session\n * has every reason to refuse that, and the NimbleBrain host does.\n */\nimport {\n DOWNLOAD_FILE_METHOD,\n type McpUiDownloadFileRequest,\n type McpUiDownloadFileResult,\n} from \"@modelcontextprotocol/ext-apps\";\nimport type { EmbeddedResource } from \"@modelcontextprotocol/sdk/types.js\";\nimport { HostCapabilityError } from \"./errors.js\";\nimport { internalsFor } from \"./internals.js\";\nimport type { App } from \"./types.js\";\n\n/** Bytes per `String.fromCharCode` spread — well under any engine's argument limit. */\nconst BASE64_CHUNK = 0x8000;\n\n/**\n * Hand the user a file to save.\n *\n * A string is sent as the resource's `text`; a `Blob` is read and sent as\n * base64 `blob`. Reading a `Blob` is asynchronous, which is why this returns a\n * promise.\n *\n * Precedence for the MIME type: the explicit argument, then the Blob's own\n * type, then `application/octet-stream`. An empty-string argument falls\n * through — a `\"\"` MIME is effectively \"no type\".\n *\n * The filename is the last segment of the resource's `file:///` URI, because an\n * embedded resource has no name field of its own.\n *\n * Resolves with the host's result: `{ isError: true }` when the host declined\n * or the user cancelled. Rejects with `HostCapabilityError`, without sending,\n * when the host did not declare `downloadFile` — a host that does not implement\n * the request may never answer it, and a request has no deadline.\n */\nexport async function downloadFile(\n app: App,\n filename: string,\n content: string | Blob,\n mimeType?: string,\n): Promise<McpUiDownloadFileResult> {\n if (!app.hostCapabilities.downloadFile) {\n throw new HostCapabilityError(\"downloadFile\", \"downloadFile\");\n }\n const internals = internalsFor(app);\n const resolvedMime =\n mimeType || (content instanceof Blob ? content.type : \"\") || \"application/octet-stream\";\n const uri = `file:///${filename}`;\n const resource: EmbeddedResource[\"resource\"] =\n typeof content === \"string\"\n ? { uri, mimeType: resolvedMime, text: content }\n : { uri, mimeType: resolvedMime, blob: await toBase64(content) };\n const params: McpUiDownloadFileRequest[\"params\"] = {\n contents: [{ type: \"resource\", resource }],\n };\n return (await internals.request(\n DOWNLOAD_FILE_METHOD,\n params as unknown as Record<string, unknown>,\n )) as McpUiDownloadFileResult;\n}\n\nasync function toBase64(blob: Blob): Promise<string> {\n const bytes = new Uint8Array(await blob.arrayBuffer());\n let binary = \"\";\n for (let i = 0; i < bytes.length; i += BASE64_CHUNK) {\n binary += String.fromCharCode(...bytes.subarray(i, i + BASE64_CHUNK));\n }\n return btoa(binary);\n}\n"]}