@nimblebrain/synapse 0.2.1 → 0.3.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 (36) hide show
  1. package/README.md +181 -45
  2. package/dist/{chunk-Y4ZDNAYQ.cjs → chunk-B3T6NB32.cjs} +349 -80
  3. package/dist/chunk-B3T6NB32.cjs.map +1 -0
  4. package/dist/{chunk-7KEYXJWD.js → chunk-GQ4L63CL.js} +349 -81
  5. package/dist/chunk-GQ4L63CL.js.map +1 -0
  6. package/dist/codegen/cli.cjs +1 -1
  7. package/dist/codegen/cli.js +1 -1
  8. package/dist/codegen/index.d.cts +1 -1
  9. package/dist/codegen/index.d.ts +1 -1
  10. package/dist/connect.iife.global.js +1 -0
  11. package/dist/index.cjs +8 -4
  12. package/dist/index.cjs.map +1 -1
  13. package/dist/index.d.cts +11 -3
  14. package/dist/index.d.ts +11 -3
  15. package/dist/index.js +3 -3
  16. package/dist/index.js.map +1 -1
  17. package/dist/react/index.cjs +119 -3
  18. package/dist/react/index.cjs.map +1 -1
  19. package/dist/react/index.d.cts +18 -2
  20. package/dist/react/index.d.ts +18 -2
  21. package/dist/react/index.js +114 -5
  22. package/dist/react/index.js.map +1 -1
  23. package/dist/{server-NNW54YW5.js → server-7BRGSPT3.js} +13 -13
  24. package/dist/{server-NNW54YW5.js.map → server-7BRGSPT3.js.map} +1 -1
  25. package/dist/{server-3BDZ5S72.cjs → server-SRE7E3G3.cjs} +13 -13
  26. package/dist/{server-3BDZ5S72.cjs.map → server-SRE7E3G3.cjs.map} +1 -1
  27. package/dist/synapse-runtime.iife.global.js +1 -1
  28. package/dist/{types-DElq_otH.d.cts → types-DJ32F5EL.d.cts} +79 -4
  29. package/dist/{types-DElq_otH.d.ts → types-DJ32F5EL.d.ts} +79 -4
  30. package/dist/vite/index.cjs +6 -6
  31. package/dist/vite/index.cjs.map +1 -1
  32. package/dist/vite/index.js +6 -6
  33. package/dist/vite/index.js.map +1 -1
  34. package/package.json +4 -1
  35. package/dist/chunk-7KEYXJWD.js.map +0 -1
  36. package/dist/chunk-Y4ZDNAYQ.cjs.map +0 -1
package/README.md CHANGED
@@ -7,12 +7,13 @@
7
7
  [![TypeScript](https://img.shields.io/badge/TypeScript-strict-blue)](https://www.typescriptlang.org/)
8
8
  [![Node.js](https://img.shields.io/badge/node-%3E%3D20-brightgreen)](https://nodejs.org/)
9
9
 
10
- Agent-aware app SDK for the [MCP ext-apps](https://modelcontextprotocol.io/specification/2025-06-18/user-interaction/ext-apps) protocol. Typed tool calls, reactive data sync, and React hooks works in any host that implements ext-apps (Claude Desktop, VS Code, ChatGPT, [NimbleBrain](https://nimblebrain.ai), or your own runtime).
10
+ Agent-aware app SDK for the [MCP ext-apps](https://modelcontextprotocol.io/specification/2025-06-18/user-interaction/ext-apps) protocol. One `await connect()` and you're live — typed tool calls, reactive data sync, and React hooks that work in any host implementing ext-apps (Claude Desktop, VS Code, ChatGPT, [NimbleBrain](https://nimblebrain.ai), or your own runtime).
11
11
 
12
12
  ## What is Synapse?
13
13
 
14
14
  Synapse is an optional enhancement layer over `@modelcontextprotocol/ext-apps`. It wraps the ext-apps protocol handshake and adds:
15
15
 
16
+ - **Zero-config handshake** — `await connect()` resolves when the host is ready. You never see `ui/initialize`.
16
17
  - **Typed tool calls** — call MCP tools with full TypeScript input/output types
17
18
  - **Reactive data sync** — subscribe to data change events from the agent
18
19
  - **Theme tracking** — automatic light/dark mode and custom design tokens
@@ -40,36 +41,36 @@ npm install @nimblebrain/synapse
40
41
 
41
42
  | Entry Point | Description |
42
43
  |-------------|-------------|
43
- | `@nimblebrain/synapse` | Vanilla JS core (no framework dependency) |
44
- | `@nimblebrain/synapse/react` | React hooks and provider |
44
+ | `@nimblebrain/synapse` | Vanilla JS core — `connect()`, `createSynapse()`, `createStore()` |
45
+ | `@nimblebrain/synapse/react` | React hooks and providers (`AppProvider`, `SynapseProvider`) |
45
46
  | `@nimblebrain/synapse/vite` | Vite plugin for dev mode |
46
47
  | `@nimblebrain/synapse/codegen` | CLI + programmatic code generation |
48
+ | `@nimblebrain/synapse/iife` | Pre-built IIFE bundle for `<script>` tags (`window.Synapse`) |
47
49
 
48
50
  ## Quick Start
49
51
 
50
52
  ### Vanilla JS
51
53
 
52
54
  ```typescript
53
- import { createSynapse } from "@nimblebrain/synapse";
55
+ import { connect } from "@nimblebrain/synapse";
54
56
 
55
- const synapse = createSynapse({
56
- name: "my-app",
57
- version: "1.0.0",
58
- });
57
+ const app = await connect({ name: "my-app", version: "1.0.0" });
59
58
 
60
- await synapse.ready;
59
+ // Theme, host info, and tool context are available immediately
60
+ console.log(app.theme.mode); // "dark"
61
+ console.log(app.hostInfo); // { name: "nimblebrain", version: "2.0.0" }
62
+
63
+ // Subscribe to tool results from the agent
64
+ app.on("tool-result", (data) => {
65
+ console.log(data.content); // parsed JSON or raw string
66
+ });
61
67
 
62
68
  // Call an MCP tool
63
- const result = await synapse.callTool("get_items", { limit: 10 });
69
+ const result = await app.callTool("get_items", { limit: 10 });
64
70
  console.log(result.data);
65
71
 
66
- // React to data changes from the agent
67
- synapse.onDataChanged((event) => {
68
- console.log(`${event.tool} was called on ${event.server}`);
69
- });
70
-
71
- // Push state visible to the LLM
72
- synapse.setVisibleState(
72
+ // Tell the agent what the user sees
73
+ app.updateModelContext(
73
74
  { selectedItem: "item-42" },
74
75
  "User is viewing item 42",
75
76
  );
@@ -78,31 +79,44 @@ synapse.setVisibleState(
78
79
  ### React
79
80
 
80
81
  ```tsx
81
- import { SynapseProvider, useCallTool, useTheme } from "@nimblebrain/synapse/react";
82
+ import { AppProvider, useToolResult, useCallTool, useResize } from "@nimblebrain/synapse/react";
82
83
 
83
84
  function App() {
84
85
  return (
85
- <SynapseProvider name="my-app" version="1.0.0">
86
+ <AppProvider name="my-app" version="1.0.0">
86
87
  <ItemList />
87
- </SynapseProvider>
88
+ </AppProvider>
88
89
  );
89
90
  }
90
91
 
91
92
  function ItemList() {
92
- const { call, data, isPending } = useCallTool<Item[]>("list_items");
93
- const theme = useTheme();
93
+ const result = useToolResult();
94
+ const { call, data, isPending } = useCallTool("list_items");
95
+ const resize = useResize();
94
96
 
95
- return (
96
- <div style={{ colorScheme: theme.mode }}>
97
- <button onClick={() => call()} disabled={isPending}>
98
- Load Items
99
- </button>
100
- {data?.map((item) => <div key={item.id}>{item.name}</div>)}
101
- </div>
102
- );
97
+ useEffect(() => { if (result) resize(); }, [result, resize]);
98
+
99
+ if (!result) return <p>Waiting for data...</p>;
100
+ return result.content.items.map((item) => <div key={item.id}>{item.name}</div>);
103
101
  }
104
102
  ```
105
103
 
104
+ ### Script Tag (IIFE)
105
+
106
+ Drop a single `<script>` tag — no bundler required:
107
+
108
+ ```html
109
+ <script src="https://unpkg.com/@nimblebrain/synapse/dist/connect.iife.global.js"></script>
110
+ <script>
111
+ Synapse.connect({ name: "widget", version: "1.0.0", autoResize: true })
112
+ .then(app => {
113
+ app.on("tool-result", (data) => {
114
+ document.getElementById("root").innerHTML = render(data.content);
115
+ });
116
+ });
117
+ </script>
118
+ ```
119
+
106
120
  ### Vite Plugin
107
121
 
108
122
  ```typescript
@@ -138,6 +152,54 @@ Or from a directory of `.schema.json` files (generates CRUD tool types):
138
152
  npx synapse --from-schema ./schemas --out src/generated/types.ts
139
153
  ```
140
154
 
155
+ ## Handling Events
156
+
157
+ The `App` object returned by `connect()` uses a unified `on()` method for all events. Each call returns an unsubscribe function.
158
+
159
+ ```typescript
160
+ const app = await connect({ name: "my-app", version: "1.0.0" });
161
+
162
+ // Tool results from the agent (parsed content, not raw JSON-RPC)
163
+ const unsub = app.on("tool-result", (data) => {
164
+ console.log(data.content); // parsed JSON or raw string
165
+ console.log(data.structuredContent); // structuredContent if host sent it
166
+ console.log(data.raw); // original params for advanced use
167
+ });
168
+
169
+ // Tool input arguments (what the agent is calling with)
170
+ app.on("tool-input", (args) => {
171
+ console.log(args); // Record<string, unknown>
172
+ });
173
+
174
+ // Theme changes
175
+ app.on("theme-changed", (theme) => {
176
+ document.body.classList.toggle("dark", theme.mode === "dark");
177
+ });
178
+
179
+ // Lifecycle — clean up when the host tears down the view
180
+ app.on("teardown", () => {
181
+ saveState();
182
+ });
183
+
184
+ // NimbleBrain extensions work as passthrough event names
185
+ app.on("synapse/data-changed", (params) => {
186
+ refreshData();
187
+ });
188
+
189
+ // Unsubscribe when done
190
+ unsub();
191
+ ```
192
+
193
+ | `on()` Event | Spec Method | Data |
194
+ |---|---|---|
195
+ | `"tool-result"` | `ui/notifications/tool-result` | `ToolResultData` (parsed) |
196
+ | `"tool-input"` | `ui/notifications/tool-input` | `Record<string, unknown>` |
197
+ | `"tool-input-partial"` | `ui/notifications/tool-input-partial` | `Record<string, unknown>` |
198
+ | `"tool-cancelled"` | `ui/notifications/tool-cancelled` | — |
199
+ | `"theme-changed"` | `ui/notifications/host-context-changed` | `Theme` |
200
+ | `"teardown"` | `ui/resource-teardown` | — |
201
+ | Any custom string | Passed through as-is | `unknown` |
202
+
141
203
  ## State Store
142
204
 
143
205
  Create a typed, reactive store with optional persistence and agent visibility:
@@ -178,9 +240,53 @@ function Counter() {
178
240
 
179
241
  ## API Reference
180
242
 
181
- ### `createSynapse(options)`
243
+ ### `connect(options)` — Recommended
182
244
 
183
- Creates a Synapse instance. Returns a `Synapse` object.
245
+ Creates a connected `App` instance. The returned promise resolves after the ext-apps handshake completes — theme, host info, and tool context are available immediately.
246
+
247
+ ```typescript
248
+ import { connect } from "@nimblebrain/synapse";
249
+
250
+ const app = await connect({ name: "my-app", version: "1.0.0" });
251
+ ```
252
+
253
+ | Option | Type | Description |
254
+ |--------|------|-------------|
255
+ | `name` | `string` | App name (must match registered bundle name) |
256
+ | `version` | `string` | Semver version |
257
+ | `autoResize` | `boolean?` | Observe `document.body` and auto-send `size-changed`. Default: `false` |
258
+
259
+ ### `App` Properties
260
+
261
+ | Property | Type | Description |
262
+ |----------|------|-------------|
263
+ | `theme` | `Theme` | Current theme (`mode`, `tokens`) |
264
+ | `hostInfo` | `{ name, version }` | Host identity |
265
+ | `toolInfo` | `{ tool } \| null` | Tool context if launched from a tool call |
266
+ | `containerDimensions` | `Dimensions \| null` | Container size constraints from host |
267
+
268
+ ### `App` Methods
269
+
270
+ | Method | Description |
271
+ |--------|-------------|
272
+ | `on(event, handler)` | Subscribe to events. Returns unsubscribe function. |
273
+ | `resize(width?, height?)` | Send size to host. Auto-measures `document.body` if no args. |
274
+ | `openLink(url)` | Open a URL (host-aware) |
275
+ | `updateModelContext(state, summary?)` | Push LLM-visible state |
276
+ | `callTool(name, args?)` | Call an MCP tool and get typed result |
277
+ | `sendMessage(text, context?)` | Send a chat message to the agent |
278
+ | `destroy()` | Clean up all listeners, observers, and timers |
279
+
280
+ ### `createSynapse(options)` — Advanced / Legacy
281
+
282
+ The original Synapse API. Still fully supported — use it when you need the state store, agent actions, file operations, or NimbleBrain-specific features not yet surfaced in `connect()`.
283
+
284
+ ```typescript
285
+ import { createSynapse } from "@nimblebrain/synapse";
286
+
287
+ const synapse = createSynapse({ name: "my-app", version: "1.0.0" });
288
+ await synapse.ready;
289
+ ```
184
290
 
185
291
  | Option | Type | Description |
186
292
  |--------|------|-------------|
@@ -197,27 +303,57 @@ Creates a Synapse instance. Returns a `Synapse` object.
197
303
  | `isNimbleBrainHost` | Whether the host is a NimbleBrain platform |
198
304
  | `callTool(name, args?)` | Call an MCP tool and get typed result |
199
305
  | `onDataChanged(cb)` | Subscribe to data change events |
306
+ | `onAction(cb)` | Subscribe to agent actions (typed, declarative) |
200
307
  | `getTheme()` | Get current theme |
201
308
  | `onThemeChanged(cb)` | Subscribe to theme changes |
202
309
  | `action(name, params?)` | Dispatch a NB platform action |
203
310
  | `chat(message, context?)` | Send a chat message to the agent |
204
311
  | `setVisibleState(state, summary?)` | Push LLM-visible state (debounced 250ms) |
205
- | `downloadFile(name, content, mime?)` | Trigger a file download |
312
+ | `saveFile(name, content, mime?)` | Trigger a file save (NB-only) |
313
+ | `pickFile(options?)` | Open native file picker, single file (NB-only) |
314
+ | `pickFiles(options?)` | Open native file picker, multiple files (NB-only) |
206
315
  | `openLink(url)` | Open a URL (host-aware) |
207
316
  | `destroy()` | Clean up all listeners and timers |
208
317
 
209
- ### React Hooks
210
-
211
- | Hook | Description |
212
- |------|-------------|
213
- | `useSynapse()` | Access the Synapse instance |
214
- | `useCallTool(name)` | `{ call, data, isPending, error }` for a tool |
215
- | `useDataSync(cb)` | Subscribe to data change events |
216
- | `useTheme()` | Reactive theme object |
217
- | `useAction()` | Dispatch platform actions |
218
- | `useChat()` | Send chat messages |
219
- | `useVisibleState()` | Push LLM-visible state |
220
- | `useStore(store)` | `{ state, dispatch }` for a store |
318
+ ## React Hooks
319
+
320
+ ### `AppProvider`-based (Recommended)
321
+
322
+ Wrap your app with `<AppProvider>` and use these hooks. Each is a thin wrapper over `connect()`.
323
+
324
+ ```tsx
325
+ import { AppProvider, useApp, useToolResult, useToolInput, useResize, useCallTool } from "@nimblebrain/synapse/react";
326
+ ```
327
+
328
+ | Hook | Returns | Description |
329
+ |------|---------|-------------|
330
+ | `useApp()` | `App` | Access the connected `App` instance |
331
+ | `useToolResult()` | `ToolResultData \| null` | Re-renders on every `tool-result` event |
332
+ | `useToolInput()` | `Record<string, unknown> \| null` | Re-renders on every `tool-input` event |
333
+ | `useConnectTheme()` | `Theme` | Reactive theme from `connect()` |
334
+ | `useResize()` | `(w?, h?) => void` | Resize helper — auto-measures body if no args |
335
+ | `useCallTool(name)` | `{ call, data, isPending, error }` | Call a tool with loading/error state |
336
+
337
+ ### `SynapseProvider`-based (Legacy)
338
+
339
+ For existing apps using `createSynapse()`. Still fully supported.
340
+
341
+ ```tsx
342
+ import { SynapseProvider, useSynapse, useCallTool, useTheme } from "@nimblebrain/synapse/react";
343
+ ```
344
+
345
+ | Hook | Returns | Description |
346
+ |------|---------|-------------|
347
+ | `useSynapse()` | `Synapse` | Access the Synapse instance |
348
+ | `useCallTool(name)` | `{ call, data, isPending, error }` | Call a tool with loading/error state |
349
+ | `useDataSync(cb)` | — | Subscribe to data change events |
350
+ | `useTheme()` | `SynapseTheme` | Reactive theme object |
351
+ | `useAction()` | `(name, params?) => void` | Dispatch platform actions |
352
+ | `useAgentAction(cb)` | — | Subscribe to agent actions |
353
+ | `useChat()` | `(msg, ctx?) => void` | Send chat messages |
354
+ | `useVisibleState()` | `(state, summary?) => void` | Push LLM-visible state |
355
+ | `useFileUpload()` | File picker helpers | File upload (NB-only) |
356
+ | `useStore(store)` | `{ state, dispatch }` | Bind a store to React |
221
357
 
222
358
  ## Development
223
359