@nimblebrain/synapse 0.1.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 (44) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +248 -0
  3. package/dist/chunk-AW3YIXLE.cjs +248 -0
  4. package/dist/chunk-AW3YIXLE.cjs.map +1 -0
  5. package/dist/chunk-JZC3VC2C.js +349 -0
  6. package/dist/chunk-JZC3VC2C.js.map +1 -0
  7. package/dist/chunk-M4I222LB.js +243 -0
  8. package/dist/chunk-M4I222LB.js.map +1 -0
  9. package/dist/chunk-Q7OSHSGZ.cjs +351 -0
  10. package/dist/chunk-Q7OSHSGZ.cjs.map +1 -0
  11. package/dist/codegen/cli.cjs +85 -0
  12. package/dist/codegen/cli.cjs.map +1 -0
  13. package/dist/codegen/cli.d.cts +1 -0
  14. package/dist/codegen/cli.d.ts +1 -0
  15. package/dist/codegen/cli.js +83 -0
  16. package/dist/codegen/cli.js.map +1 -0
  17. package/dist/codegen/index.cjs +24 -0
  18. package/dist/codegen/index.cjs.map +1 -0
  19. package/dist/codegen/index.d.cts +24 -0
  20. package/dist/codegen/index.d.ts +24 -0
  21. package/dist/codegen/index.js +3 -0
  22. package/dist/codegen/index.js.map +1 -0
  23. package/dist/index.cjs +87 -0
  24. package/dist/index.cjs.map +1 -0
  25. package/dist/index.d.cts +26 -0
  26. package/dist/index.d.ts +26 -0
  27. package/dist/index.js +81 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/react/index.cjs +123 -0
  30. package/dist/react/index.cjs.map +1 -0
  31. package/dist/react/index.d.cts +30 -0
  32. package/dist/react/index.d.ts +30 -0
  33. package/dist/react/index.js +113 -0
  34. package/dist/react/index.js.map +1 -0
  35. package/dist/synapse-runtime.iife.global.js +1 -0
  36. package/dist/types-BP0SNrpo.d.cts +96 -0
  37. package/dist/types-BP0SNrpo.d.ts +96 -0
  38. package/dist/vite/index.cjs +49 -0
  39. package/dist/vite/index.cjs.map +1 -0
  40. package/dist/vite/index.d.cts +21 -0
  41. package/dist/vite/index.d.ts +21 -0
  42. package/dist/vite/index.js +47 -0
  43. package/dist/vite/index.js.map +1 -0
  44. package/package.json +89 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 NimbleBrain, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,248 @@
1
+ # @nimblebrain/synapse
2
+
3
+ [![CI](https://github.com/NimbleBrainInc/synapse/actions/workflows/ci.yml/badge.svg)](https://github.com/NimbleBrainInc/synapse/actions/workflows/ci.yml)
4
+ [![npm version](https://img.shields.io/npm/v/@nimblebrain/synapse)](https://www.npmjs.com/package/@nimblebrain/synapse)
5
+ [![npm downloads](https://img.shields.io/npm/dm/@nimblebrain/synapse)](https://www.npmjs.com/package/@nimblebrain/synapse)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
7
+ [![TypeScript](https://img.shields.io/badge/TypeScript-strict-blue)](https://www.typescriptlang.org/)
8
+ [![Node.js](https://img.shields.io/badge/node-%3E%3D20-brightgreen)](https://nodejs.org/)
9
+
10
+ Agent-aware app SDK for the [NimbleBrain](https://nimblebrain.ai) platform. Typed tool calls, reactive state, and React hooks over the [MCP ext-apps](https://modelcontextprotocol.io/specification/2025-06-18/user-interaction/ext-apps) protocol.
11
+
12
+ ## What is Synapse?
13
+
14
+ Synapse is an optional enhancement layer over `@modelcontextprotocol/ext-apps`. It wraps the ext-apps protocol handshake and adds:
15
+
16
+ - **Typed tool calls** — call MCP tools with full TypeScript input/output types
17
+ - **Reactive data sync** — subscribe to data change events from the agent
18
+ - **Theme tracking** — automatic light/dark mode and custom design tokens
19
+ - **State store** — Redux-like store with optional persistence and LLM visibility
20
+ - **Keyboard forwarding** — forward shortcuts from sandboxed iframes to the host
21
+ - **Code generation** — generate TypeScript types from manifests, running servers, or JSON schemas
22
+
23
+ In non-NimbleBrain hosts (Claude Desktop, VS Code, ChatGPT), NB-specific features degrade gracefully to no-ops while ext-apps baseline behavior is preserved.
24
+
25
+ ## Install
26
+
27
+ ```bash
28
+ npm install @nimblebrain/synapse
29
+ ```
30
+
31
+ **Peer dependency:** `@modelcontextprotocol/ext-apps@^1.3.1`
32
+
33
+ ## Package Exports
34
+
35
+ | Entry Point | Description |
36
+ |-------------|-------------|
37
+ | `@nimblebrain/synapse` | Vanilla JS core (no framework dependency) |
38
+ | `@nimblebrain/synapse/react` | React hooks and provider |
39
+ | `@nimblebrain/synapse/vite` | Vite plugin for dev mode |
40
+ | `@nimblebrain/synapse/codegen` | CLI + programmatic code generation |
41
+
42
+ ## Quick Start
43
+
44
+ ### Vanilla JS
45
+
46
+ ```typescript
47
+ import { createSynapse } from "@nimblebrain/synapse";
48
+
49
+ const synapse = createSynapse({
50
+ name: "my-app",
51
+ version: "1.0.0",
52
+ });
53
+
54
+ await synapse.ready;
55
+
56
+ // Call an MCP tool
57
+ const result = await synapse.callTool("get_items", { limit: 10 });
58
+ console.log(result.data);
59
+
60
+ // React to data changes from the agent
61
+ synapse.onDataChanged((event) => {
62
+ console.log(`${event.tool} was called on ${event.server}`);
63
+ });
64
+
65
+ // Push state visible to the LLM
66
+ synapse.setVisibleState(
67
+ { selectedItem: "item-42" },
68
+ "User is viewing item 42",
69
+ );
70
+ ```
71
+
72
+ ### React
73
+
74
+ ```tsx
75
+ import { SynapseProvider, useCallTool, useTheme } from "@nimblebrain/synapse/react";
76
+
77
+ function App() {
78
+ return (
79
+ <SynapseProvider name="my-app" version="1.0.0">
80
+ <ItemList />
81
+ </SynapseProvider>
82
+ );
83
+ }
84
+
85
+ function ItemList() {
86
+ const { call, data, isPending } = useCallTool<Item[]>("list_items");
87
+ const theme = useTheme();
88
+
89
+ return (
90
+ <div style={{ colorScheme: theme.mode }}>
91
+ <button onClick={() => call()} disabled={isPending}>
92
+ Load Items
93
+ </button>
94
+ {data?.map((item) => <div key={item.id}>{item.name}</div>)}
95
+ </div>
96
+ );
97
+ }
98
+ ```
99
+
100
+ ### Vite Plugin
101
+
102
+ ```typescript
103
+ // vite.config.ts
104
+ import { synapseVite } from "@nimblebrain/synapse/vite";
105
+
106
+ export default {
107
+ plugins: [
108
+ synapseVite({
109
+ appName: "my-app",
110
+ }),
111
+ ],
112
+ };
113
+ ```
114
+
115
+ ### Code Generation
116
+
117
+ Generate TypeScript types from an app manifest:
118
+
119
+ ```bash
120
+ npx synapse --from-manifest ./manifest.json --out src/generated/types.ts
121
+ ```
122
+
123
+ Or from a running MCP server:
124
+
125
+ ```bash
126
+ npx synapse --from-server http://localhost:3000 --out src/generated/types.ts
127
+ ```
128
+
129
+ Or from a directory of `.schema.json` files (generates CRUD tool types):
130
+
131
+ ```bash
132
+ npx synapse --from-schema ./schemas --out src/generated/types.ts
133
+ ```
134
+
135
+ ## State Store
136
+
137
+ Create a typed, reactive store with optional persistence and agent visibility:
138
+
139
+ ```typescript
140
+ import { createSynapse, createStore } from "@nimblebrain/synapse";
141
+
142
+ const synapse = createSynapse({ name: "my-app", version: "1.0.0" });
143
+
144
+ const store = createStore(synapse, {
145
+ initialState: { count: 0, items: [] },
146
+ actions: {
147
+ increment: (state) => ({ ...state, count: state.count + 1 }),
148
+ addItem: (state, item: string) => ({
149
+ ...state,
150
+ items: [...state.items, item],
151
+ }),
152
+ },
153
+ persist: true,
154
+ visibleToAgent: true,
155
+ summarize: (state) => `${state.items.length} items, count=${state.count}`,
156
+ });
157
+
158
+ store.dispatch.increment();
159
+ store.dispatch.addItem("hello");
160
+ ```
161
+
162
+ Use `useStore` in React:
163
+
164
+ ```tsx
165
+ import { useStore } from "@nimblebrain/synapse/react";
166
+
167
+ function Counter() {
168
+ const { state, dispatch } = useStore(store);
169
+ return <button onClick={() => dispatch.increment()}>{state.count}</button>;
170
+ }
171
+ ```
172
+
173
+ ## API Reference
174
+
175
+ ### `createSynapse(options)`
176
+
177
+ Creates a Synapse instance. Returns a `Synapse` object.
178
+
179
+ | Option | Type | Description |
180
+ |--------|------|-------------|
181
+ | `name` | `string` | App name (must match registered bundle name) |
182
+ | `version` | `string` | Semver version |
183
+ | `internal` | `boolean?` | Enable cross-server tool calls (NB internal only) |
184
+ | `forwardKeys` | `KeyForwardConfig[]?` | Custom keyboard forwarding rules |
185
+
186
+ ### `Synapse` Methods
187
+
188
+ | Method | Description |
189
+ |--------|-------------|
190
+ | `ready` | Promise that resolves after the ext-apps handshake |
191
+ | `isNimbleBrainHost` | Whether the host is a NimbleBrain platform |
192
+ | `callTool(name, args?)` | Call an MCP tool and get typed result |
193
+ | `onDataChanged(cb)` | Subscribe to data change events |
194
+ | `getTheme()` | Get current theme |
195
+ | `onThemeChanged(cb)` | Subscribe to theme changes |
196
+ | `action(name, params?)` | Dispatch a NB platform action |
197
+ | `chat(message, context?)` | Send a chat message to the agent |
198
+ | `setVisibleState(state, summary?)` | Push LLM-visible state (debounced 250ms) |
199
+ | `downloadFile(name, content, mime?)` | Trigger a file download |
200
+ | `openLink(url)` | Open a URL (host-aware) |
201
+ | `destroy()` | Clean up all listeners and timers |
202
+
203
+ ### React Hooks
204
+
205
+ | Hook | Description |
206
+ |------|-------------|
207
+ | `useSynapse()` | Access the Synapse instance |
208
+ | `useCallTool(name)` | `{ call, data, isPending, error }` for a tool |
209
+ | `useDataSync(cb)` | Subscribe to data change events |
210
+ | `useTheme()` | Reactive theme object |
211
+ | `useAction()` | Dispatch platform actions |
212
+ | `useChat()` | Send chat messages |
213
+ | `useVisibleState()` | Push LLM-visible state |
214
+ | `useStore(store)` | `{ state, dispatch }` for a store |
215
+
216
+ ## Development
217
+
218
+ ```bash
219
+ npm install
220
+ npm run build # Build ESM + CJS + IIFE
221
+ npm test # Run tests
222
+ npm run typecheck # Type-check
223
+ npm run lint # Lint with Biome
224
+ npm run lint:fix # Auto-fix lint issues
225
+ npm run ci # Run full CI pipeline locally (lint → typecheck → build → test)
226
+ ```
227
+
228
+ ## Publishing
229
+
230
+ Requires npm login with access to the `@nimblebrain` org.
231
+
232
+ ```bash
233
+ # First time: log in to npm
234
+ npm login
235
+
236
+ # Bump version (updates package.json and creates a git tag)
237
+ npm version patch # or minor / major
238
+
239
+ # Publish (build runs automatically via prepublishOnly)
240
+ npm publish --access public
241
+
242
+ # Push the version tag
243
+ git push origin main --tags
244
+ ```
245
+
246
+ ## License
247
+
248
+ [MIT](LICENSE)
@@ -0,0 +1,248 @@
1
+ 'use strict';
2
+
3
+ var fs = require('fs');
4
+ var path = require('path');
5
+
6
+ // src/codegen/schema-reader.ts
7
+ function readFromManifest(manifestPath) {
8
+ if (!fs.existsSync(manifestPath)) {
9
+ throw new Error(`Manifest not found: ${manifestPath}`);
10
+ }
11
+ const raw = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
12
+ const tools = raw.tools ?? [];
13
+ if (!Array.isArray(tools)) {
14
+ return [];
15
+ }
16
+ return tools.map((t) => ({
17
+ name: t.name,
18
+ description: t.description,
19
+ inputSchema: t.inputSchema ?? {},
20
+ outputSchema: t.outputSchema
21
+ }));
22
+ }
23
+ async function readFromServer(url) {
24
+ const response = await fetch(url, {
25
+ method: "POST",
26
+ headers: { "Content-Type": "application/json" },
27
+ body: JSON.stringify({
28
+ jsonrpc: "2.0",
29
+ method: "tools/list",
30
+ id: "codegen-1",
31
+ params: {}
32
+ })
33
+ });
34
+ if (!response.ok) {
35
+ throw new Error(`Server responded with ${response.status}: ${response.statusText}`);
36
+ }
37
+ const result = await response.json();
38
+ if (result.error) {
39
+ throw new Error(`Server error: ${result.error.message}`);
40
+ }
41
+ const tools = result.result?.tools ?? [];
42
+ return tools.map((t) => ({
43
+ name: t.name,
44
+ description: t.description,
45
+ inputSchema: t.inputSchema ?? {},
46
+ outputSchema: t.outputSchema
47
+ }));
48
+ }
49
+ function readFromSchemaDir(dirPath) {
50
+ if (!fs.existsSync(dirPath)) {
51
+ throw new Error(`Schema directory not found: ${dirPath}`);
52
+ }
53
+ const files = fs.readdirSync(dirPath).filter((f) => f.endsWith(".schema.json"));
54
+ const tools = [];
55
+ for (const file of files) {
56
+ const schema = JSON.parse(fs.readFileSync(path.join(dirPath, file), "utf-8"));
57
+ const entityName = path.basename(file, ".schema.json");
58
+ tools.push(
59
+ {
60
+ name: `create_${entityName}`,
61
+ description: `Create a new ${entityName}`,
62
+ inputSchema: schema,
63
+ outputSchema: schema
64
+ },
65
+ {
66
+ name: `read_${entityName}`,
67
+ description: `Read a ${entityName} by ID`,
68
+ inputSchema: {
69
+ type: "object",
70
+ properties: { id: { type: "string" } },
71
+ required: ["id"]
72
+ },
73
+ outputSchema: schema
74
+ },
75
+ {
76
+ name: `update_${entityName}`,
77
+ description: `Update an existing ${entityName}`,
78
+ inputSchema: schema,
79
+ outputSchema: schema
80
+ },
81
+ {
82
+ name: `delete_${entityName}`,
83
+ description: `Delete a ${entityName} by ID`,
84
+ inputSchema: {
85
+ type: "object",
86
+ properties: { id: { type: "string" } },
87
+ required: ["id"]
88
+ }
89
+ },
90
+ {
91
+ name: `list_${entityName}s`,
92
+ description: `List all ${entityName}s`,
93
+ inputSchema: {
94
+ type: "object",
95
+ properties: {
96
+ filter: { type: "string" },
97
+ limit: { type: "number" }
98
+ }
99
+ },
100
+ outputSchema: {
101
+ type: "object",
102
+ properties: {
103
+ items: { type: "array", items: schema },
104
+ total: { type: "number" }
105
+ },
106
+ required: ["items", "total"]
107
+ }
108
+ }
109
+ );
110
+ }
111
+ return tools;
112
+ }
113
+
114
+ // src/codegen/type-generator.ts
115
+ function generateTypes(tools, appName) {
116
+ const lines = [];
117
+ const mapName = `${toPascalCase(appName)}ToolMap`;
118
+ lines.push("/**");
119
+ lines.push(` * Auto-generated by @nimblebrain/synapse codegen`);
120
+ lines.push(` * Source: ${appName}`);
121
+ lines.push(` *`);
122
+ lines.push(
123
+ ` * DO NOT EDIT \u2014 regenerate with: npx synapse codegen --from-manifest ./manifest.json`
124
+ );
125
+ lines.push(` */`);
126
+ lines.push("");
127
+ const mapEntries = [];
128
+ for (const tool of tools) {
129
+ const baseName = toPascalCase(tool.name);
130
+ const inputName = `${baseName}Input`;
131
+ lines.push(schemaToInterface(inputName, tool.inputSchema));
132
+ lines.push("");
133
+ let outputName = "unknown";
134
+ if (tool.outputSchema) {
135
+ outputName = `${baseName}Output`;
136
+ lines.push(schemaToInterface(outputName, tool.outputSchema));
137
+ lines.push("");
138
+ }
139
+ mapEntries.push(` ${tool.name}: { input: ${inputName}; output: ${outputName} };`);
140
+ }
141
+ lines.push(`/** Tool type map for typed useCallTool */`);
142
+ lines.push(`export interface ${mapName} {`);
143
+ for (const entry of mapEntries) {
144
+ lines.push(entry);
145
+ }
146
+ lines.push(`}`);
147
+ lines.push("");
148
+ return lines.join("\n");
149
+ }
150
+ function schemaToInterface(name, schema) {
151
+ const type = schemaToType(schema, name);
152
+ if (schema.type === "object" || schema.properties) {
153
+ return generateInterface(name, schema);
154
+ }
155
+ return `export type ${name} = ${type};`;
156
+ }
157
+ function generateInterface(name, schema) {
158
+ const lines = [];
159
+ lines.push(`export interface ${name} {`);
160
+ const props = schema.properties ?? {};
161
+ const required = new Set(schema.required ?? []);
162
+ for (const [key, propSchema] of Object.entries(props)) {
163
+ const isRequired = required.has(key);
164
+ const type = schemaToType(propSchema, name + toPascalCase(key));
165
+ const desc = propSchema.description;
166
+ if (desc) {
167
+ lines.push(` /** ${desc} */`);
168
+ }
169
+ lines.push(` ${key}${isRequired ? "" : "?"}: ${type};`);
170
+ }
171
+ lines.push(`}`);
172
+ return lines.join("\n");
173
+ }
174
+ function schemaToType(schema, context) {
175
+ if (schema.enum && Array.isArray(schema.enum)) {
176
+ return schema.enum.map((v) => typeof v === "string" ? `"${v}"` : String(v)).join(" | ");
177
+ }
178
+ if (schema.oneOf && Array.isArray(schema.oneOf)) {
179
+ return schema.oneOf.map((s, i) => schemaToType(s, `${context}Option${i}`)).join(" | ");
180
+ }
181
+ if (schema.anyOf && Array.isArray(schema.anyOf)) {
182
+ return schema.anyOf.map((s, i) => schemaToType(s, `${context}Option${i}`)).join(" | ");
183
+ }
184
+ if (schema.allOf && Array.isArray(schema.allOf)) {
185
+ return schema.allOf.map((s, i) => schemaToType(s, `${context}Part${i}`)).join(" & ");
186
+ }
187
+ const type = schema.type;
188
+ if (Array.isArray(type)) {
189
+ return type.map((t) => primitiveType(t)).join(" | ");
190
+ }
191
+ switch (type) {
192
+ case "string":
193
+ return "string";
194
+ case "number":
195
+ case "integer":
196
+ return "number";
197
+ case "boolean":
198
+ return "boolean";
199
+ case "null":
200
+ return "null";
201
+ case "array": {
202
+ const items = schema.items;
203
+ if (items) {
204
+ return `${schemaToType(items, `${context}Item`)}[]`;
205
+ }
206
+ return "unknown[]";
207
+ }
208
+ case "object": {
209
+ if (schema.properties) {
210
+ const props = schema.properties;
211
+ const required = new Set(schema.required ?? []);
212
+ const fields = Object.entries(props).map(([key, propSchema]) => {
213
+ const t = schemaToType(propSchema, context + toPascalCase(key));
214
+ return `${key}${required.has(key) ? "" : "?"}: ${t}`;
215
+ }).join("; ");
216
+ return `{ ${fields} }`;
217
+ }
218
+ return "Record<string, unknown>";
219
+ }
220
+ default:
221
+ return "unknown";
222
+ }
223
+ }
224
+ function primitiveType(t) {
225
+ switch (t) {
226
+ case "string":
227
+ return "string";
228
+ case "number":
229
+ case "integer":
230
+ return "number";
231
+ case "boolean":
232
+ return "boolean";
233
+ case "null":
234
+ return "null";
235
+ default:
236
+ return "unknown";
237
+ }
238
+ }
239
+ function toPascalCase(str) {
240
+ return str.split(/[-_@/]/).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
241
+ }
242
+
243
+ exports.generateTypes = generateTypes;
244
+ exports.readFromManifest = readFromManifest;
245
+ exports.readFromSchemaDir = readFromSchemaDir;
246
+ exports.readFromServer = readFromServer;
247
+ //# sourceMappingURL=chunk-AW3YIXLE.cjs.map
248
+ //# sourceMappingURL=chunk-AW3YIXLE.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/codegen/schema-reader.ts","../src/codegen/type-generator.ts"],"names":["existsSync","readFileSync","readdirSync","join","basename"],"mappings":"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","file":"chunk-AW3YIXLE.cjs","sourcesContent":["import { existsSync, readdirSync, readFileSync } from \"node:fs\";\nimport { basename, join } from \"node:path\";\nimport type { ToolDefinition } from \"../types.js\";\n\n/**\n * Read tool definitions from a manifest.json file.\n * Extracts tools from the MCP standard `tools` array.\n */\nexport function readFromManifest(manifestPath: string): ToolDefinition[] {\n if (!existsSync(manifestPath)) {\n throw new Error(`Manifest not found: ${manifestPath}`);\n }\n\n const raw = JSON.parse(readFileSync(manifestPath, \"utf-8\"));\n const tools = raw.tools ?? [];\n\n if (!Array.isArray(tools)) {\n return [];\n }\n\n return tools.map((t: any) => ({\n name: t.name as string,\n description: t.description as string | undefined,\n inputSchema: (t.inputSchema ?? {}) as Record<string, unknown>,\n outputSchema: t.outputSchema as Record<string, unknown> | undefined,\n }));\n}\n\n/**\n * Read tool definitions from a running MCP server via tools/list.\n * Connects to the server's HTTP endpoint and calls the JSON-RPC method.\n */\nexport async function readFromServer(url: string): Promise<ToolDefinition[]> {\n const response = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n jsonrpc: \"2.0\",\n method: \"tools/list\",\n id: \"codegen-1\",\n params: {},\n }),\n });\n\n if (!response.ok) {\n throw new Error(`Server responded with ${response.status}: ${response.statusText}`);\n }\n\n const result = await response.json();\n if (result.error) {\n throw new Error(`Server error: ${result.error.message}`);\n }\n\n const tools = result.result?.tools ?? [];\n return tools.map((t: any) => ({\n name: t.name as string,\n description: t.description as string | undefined,\n inputSchema: (t.inputSchema ?? {}) as Record<string, unknown>,\n outputSchema: t.outputSchema as Record<string, unknown> | undefined,\n }));\n}\n\n/**\n * Read entity schemas from a directory and generate CRUD tool definitions.\n * Each .schema.json file becomes create/read/update/delete/list tools.\n */\nexport function readFromSchemaDir(dirPath: string): ToolDefinition[] {\n if (!existsSync(dirPath)) {\n throw new Error(`Schema directory not found: ${dirPath}`);\n }\n\n const files = readdirSync(dirPath).filter((f: string) => f.endsWith(\".schema.json\"));\n const tools: ToolDefinition[] = [];\n\n for (const file of files) {\n const schema = JSON.parse(readFileSync(join(dirPath, file), \"utf-8\"));\n const entityName = basename(file, \".schema.json\");\n\n tools.push(\n {\n name: `create_${entityName}`,\n description: `Create a new ${entityName}`,\n inputSchema: schema,\n outputSchema: schema,\n },\n {\n name: `read_${entityName}`,\n description: `Read a ${entityName} by ID`,\n inputSchema: {\n type: \"object\",\n properties: { id: { type: \"string\" } },\n required: [\"id\"],\n },\n outputSchema: schema,\n },\n {\n name: `update_${entityName}`,\n description: `Update an existing ${entityName}`,\n inputSchema: schema,\n outputSchema: schema,\n },\n {\n name: `delete_${entityName}`,\n description: `Delete a ${entityName} by ID`,\n inputSchema: {\n type: \"object\",\n properties: { id: { type: \"string\" } },\n required: [\"id\"],\n },\n },\n {\n name: `list_${entityName}s`,\n description: `List all ${entityName}s`,\n inputSchema: {\n type: \"object\",\n properties: {\n filter: { type: \"string\" },\n limit: { type: \"number\" },\n },\n },\n outputSchema: {\n type: \"object\",\n properties: {\n items: { type: \"array\", items: schema },\n total: { type: \"number\" },\n },\n required: [\"items\", \"total\"],\n },\n },\n );\n }\n\n return tools;\n}\n","import type { ToolDefinition } from \"../types.js\";\n\n/**\n * Generate TypeScript interfaces from tool definitions.\n */\nexport function generateTypes(tools: ToolDefinition[], appName: string): string {\n const lines: string[] = [];\n const mapName = `${toPascalCase(appName)}ToolMap`;\n\n lines.push(\"/**\");\n lines.push(` * Auto-generated by @nimblebrain/synapse codegen`);\n lines.push(` * Source: ${appName}`);\n lines.push(` *`);\n lines.push(\n ` * DO NOT EDIT — regenerate with: npx synapse codegen --from-manifest ./manifest.json`,\n );\n lines.push(` */`);\n lines.push(\"\");\n\n const mapEntries: string[] = [];\n\n for (const tool of tools) {\n const baseName = toPascalCase(tool.name);\n\n // Input type\n const inputName = `${baseName}Input`;\n lines.push(schemaToInterface(inputName, tool.inputSchema));\n lines.push(\"\");\n\n // Output type\n let outputName = \"unknown\";\n if (tool.outputSchema) {\n outputName = `${baseName}Output`;\n lines.push(schemaToInterface(outputName, tool.outputSchema));\n lines.push(\"\");\n }\n\n mapEntries.push(` ${tool.name}: { input: ${inputName}; output: ${outputName} };`);\n }\n\n // Tool map\n lines.push(`/** Tool type map for typed useCallTool */`);\n lines.push(`export interface ${mapName} {`);\n for (const entry of mapEntries) {\n lines.push(entry);\n }\n lines.push(`}`);\n lines.push(\"\");\n\n return lines.join(\"\\n\");\n}\n\n// ---------------------------------------------------------------------------\n// JSON Schema -> TypeScript conversion\n// ---------------------------------------------------------------------------\n\nfunction schemaToInterface(name: string, schema: Record<string, unknown>): string {\n const type = schemaToType(schema, name);\n\n // If the schema is an object, generate a named interface\n if (schema.type === \"object\" || schema.properties) {\n return generateInterface(name, schema);\n }\n\n // Otherwise, generate a type alias\n return `export type ${name} = ${type};`;\n}\n\nfunction generateInterface(name: string, schema: Record<string, unknown>): string {\n const lines: string[] = [];\n lines.push(`export interface ${name} {`);\n\n const props = (schema.properties ?? {}) as Record<string, Record<string, unknown>>;\n const required = new Set((schema.required ?? []) as string[]);\n\n for (const [key, propSchema] of Object.entries(props)) {\n const isRequired = required.has(key);\n const type = schemaToType(propSchema, name + toPascalCase(key));\n const desc = propSchema.description;\n if (desc) {\n lines.push(` /** ${desc} */`);\n }\n lines.push(` ${key}${isRequired ? \"\" : \"?\"}: ${type};`);\n }\n\n lines.push(`}`);\n return lines.join(\"\\n\");\n}\n\nfunction schemaToType(schema: Record<string, unknown>, context: string): string {\n // Handle enum\n if (schema.enum && Array.isArray(schema.enum)) {\n return (schema.enum as unknown[])\n .map((v) => (typeof v === \"string\" ? `\"${v}\"` : String(v)))\n .join(\" | \");\n }\n\n // Handle oneOf / anyOf\n if (schema.oneOf && Array.isArray(schema.oneOf)) {\n return (schema.oneOf as Record<string, unknown>[])\n .map((s, i) => schemaToType(s, `${context}Option${i}`))\n .join(\" | \");\n }\n if (schema.anyOf && Array.isArray(schema.anyOf)) {\n return (schema.anyOf as Record<string, unknown>[])\n .map((s, i) => schemaToType(s, `${context}Option${i}`))\n .join(\" | \");\n }\n\n // Handle allOf (intersection)\n if (schema.allOf && Array.isArray(schema.allOf)) {\n return (schema.allOf as Record<string, unknown>[])\n .map((s, i) => schemaToType(s, `${context}Part${i}`))\n .join(\" & \");\n }\n\n // Handle type-based conversion\n const type = schema.type as string | string[] | undefined;\n\n if (Array.isArray(type)) {\n // Multi-type (e.g., [\"string\", \"null\"])\n return type.map((t) => primitiveType(t)).join(\" | \");\n }\n\n switch (type) {\n case \"string\":\n return \"string\";\n case \"number\":\n case \"integer\":\n return \"number\";\n case \"boolean\":\n return \"boolean\";\n case \"null\":\n return \"null\";\n case \"array\": {\n const items = schema.items as Record<string, unknown> | undefined;\n if (items) {\n return `${schemaToType(items, `${context}Item`)}[]`;\n }\n return \"unknown[]\";\n }\n case \"object\": {\n if (schema.properties) {\n // Inline object — generate property types\n const props = schema.properties as Record<string, Record<string, unknown>>;\n const required = new Set((schema.required ?? []) as string[]);\n const fields = Object.entries(props)\n .map(([key, propSchema]) => {\n const t = schemaToType(propSchema, context + toPascalCase(key));\n return `${key}${required.has(key) ? \"\" : \"?\"}: ${t}`;\n })\n .join(\"; \");\n return `{ ${fields} }`;\n }\n return \"Record<string, unknown>\";\n }\n default:\n return \"unknown\";\n }\n}\n\nfunction primitiveType(t: string): string {\n switch (t) {\n case \"string\":\n return \"string\";\n case \"number\":\n case \"integer\":\n return \"number\";\n case \"boolean\":\n return \"boolean\";\n case \"null\":\n return \"null\";\n default:\n return \"unknown\";\n }\n}\n\nfunction toPascalCase(str: string): string {\n return str\n .split(/[-_@/]/)\n .filter(Boolean)\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1))\n .join(\"\");\n}\n"]}