@netpad/mcp-server 2.1.0 → 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -14105,6 +14105,59 @@ var FIELD_TYPES = [
14105
14105
  included: true,
14106
14106
  layout: { type: "image", imageUrl: "https://example.com/logo.png", alignment: "center" }
14107
14107
  }
14108
+ },
14109
+ // File upload fields
14110
+ {
14111
+ type: "file",
14112
+ category: "file",
14113
+ description: "File upload field for images and documents",
14114
+ muiComponent: "FileUpload (custom)",
14115
+ example: {
14116
+ path: "attachment",
14117
+ label: "Upload File",
14118
+ type: "file",
14119
+ included: true,
14120
+ validation: { maxSize: 10485760 }
14121
+ // 10MB
14122
+ }
14123
+ },
14124
+ {
14125
+ type: "file_upload",
14126
+ category: "file",
14127
+ description: "File upload field (alias for file)",
14128
+ muiComponent: "FileUpload (custom)",
14129
+ example: {
14130
+ path: "document",
14131
+ label: "Upload Document",
14132
+ type: "file_upload",
14133
+ included: true
14134
+ }
14135
+ },
14136
+ {
14137
+ type: "image_upload",
14138
+ category: "file",
14139
+ description: "Image-specific upload field with preview",
14140
+ muiComponent: "ImageUpload (custom)",
14141
+ example: {
14142
+ path: "profilePhoto",
14143
+ label: "Profile Photo",
14144
+ type: "image_upload",
14145
+ included: true,
14146
+ validation: { accept: "image/*" }
14147
+ }
14148
+ },
14149
+ {
14150
+ type: "document_upload",
14151
+ category: "file",
14152
+ description: "Document upload field (PDF, Word, Excel)",
14153
+ muiComponent: "DocumentUpload (custom)",
14154
+ example: {
14155
+ path: "resume",
14156
+ label: "Resume",
14157
+ type: "document_upload",
14158
+ included: true,
14159
+ validation: { accept: ".pdf,.doc,.docx" }
14160
+ }
14108
14161
  }
14109
14162
  ];
14110
14163
  var OPERATORS = [
@@ -14441,7 +14494,10 @@ var FIELD_TYPE_KEYWORDS = {
14441
14494
  rating: ["rating", "stars", "rate", "review"],
14442
14495
  nps: ["nps", "recommend", "likelihood", "net promoter"],
14443
14496
  autocomplete: ["search", "autocomplete", "typeahead"],
14444
- tags: ["tags", "keywords", "labels"]
14497
+ tags: ["tags", "keywords", "labels"],
14498
+ file: ["file", "upload", "attachment", "attach"],
14499
+ image_upload: ["photo", "picture", "image upload", "profile photo", "avatar", "thumbnail"],
14500
+ document_upload: ["document", "resume", "cv", "pdf", "contract", "agreement"]
14445
14501
  };
14446
14502
  function inferFieldType(label, description) {
14447
14503
  const text = `${label} ${description || ""}`.toLowerCase();
@@ -14988,6 +15044,342 @@ try {
14988
15044
  }
14989
15045
  }
14990
15046
  \`\`\`
15047
+ `,
15048
+ extensions: `# NetPad Extensions
15049
+
15050
+ NetPad's extension system allows you to add custom functionality to your NetPad installation through modular, independently deployable packages. Extensions can provide API routes, UI components, services, middleware, and more.
15051
+
15052
+ ## What Are Extensions?
15053
+
15054
+ Extensions are npm packages that follow the \`NetPadExtension\` interface. They integrate seamlessly with NetPad's core functionality while remaining isolated and independently maintainable.
15055
+
15056
+ \`\`\`
15057
+ \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
15058
+ \u2502 NetPad Core \u2502
15059
+ \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524
15060
+ \u2502 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502
15061
+ \u2502 \u2502 Extension \u2502 \u2502 Extension \u2502 \u2502 Extension \u2502 ... \u2502
15062
+ \u2502 \u2502 (Cloud) \u2502 \u2502(Collaborate)\u2502 \u2502 (Custom) \u2502 \u2502
15063
+ \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502
15064
+ \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
15065
+ \`\`\`
15066
+
15067
+ ## Extension Capabilities
15068
+
15069
+ Extensions can provide:
15070
+
15071
+ | Capability | Description |
15072
+ | ------------------- | --------------------------------------------------------- |
15073
+ | **Routes** | Custom API endpoints under /api/ext/{extension-name}/ |
15074
+ | **Services** | Shared business logic accessible throughout the extension |
15075
+ | **Middleware** | Request/response processing with priority ordering |
15076
+ | **Components** | React UI components for use in pages |
15077
+ | **Features** | Feature flags that enable/disable functionality |
15078
+ | **Lifecycle Hooks** | Initialize and cleanup logic |
15079
+ | **Workflow Nodes** | Custom workflow node types for automation |
15080
+
15081
+ ## Built-in Extensions
15082
+
15083
+ NetPad includes several built-in extensions:
15084
+
15085
+ ### @netpad/cloud-features
15086
+
15087
+ The cloud features extension provides premium functionality for NetPad Cloud:
15088
+
15089
+ * **Billing & Subscriptions** - Stripe integration for payments
15090
+ * **Atlas Provisioning** - Automatic MongoDB cluster creation
15091
+ * **Premium AI Features** - Advanced RAG and AI capabilities
15092
+ * **Usage Analytics** - Detailed usage tracking and reporting
15093
+
15094
+ ### @netpad/collaborate
15095
+
15096
+ Community collaboration features:
15097
+
15098
+ * **Gallery** - Community showcase of forms, workflows, and integrations
15099
+ * **Contributors** - Leaderboard and contributor profiles
15100
+ * **Submissions** - Collaboration application system
15101
+ * **UI Components** - Pre-built React components
15102
+
15103
+ ### @netpad/demo-node
15104
+
15105
+ A simple demonstration extension that shows how to create custom workflow nodes:
15106
+
15107
+ * **Log Message Node** - A custom workflow node that logs messages with configurable levels
15108
+ * **Template for Extension Development** - Use as a starting point for your own extensions
15109
+ * **Complete Example** - Shows node definition, handler implementation, and extension structure
15110
+
15111
+ **Node Details:**
15112
+ - Type: demo:log-message
15113
+ - Category: Custom
15114
+ - Config Fields: message (textarea), level (select: info/warn/error), label (text), passthrough (boolean)
15115
+ - Outputs: Success output with log data and optional passthrough of input data
15116
+
15117
+ This extension serves as the canonical example for creating workflow node extensions. See the package README for detailed instructions on using it as a template.
15118
+
15119
+ ## Extension Architecture
15120
+
15121
+ Extensions follow a clear architectural pattern:
15122
+
15123
+ \`\`\`typescript
15124
+ // Extension structure
15125
+ const myExtension: NetPadExtension = {
15126
+ metadata: {
15127
+ id: 'my-extension',
15128
+ name: 'My Extension',
15129
+ version: '1.0.0',
15130
+ description: 'Description of what this extension does',
15131
+ },
15132
+
15133
+ // Features this extension provides
15134
+ features: ['custom:my_feature'],
15135
+
15136
+ // API routes
15137
+ routes: [
15138
+ { path: '/api/ext/my-extension/data', method: 'GET', handler: handleGet },
15139
+ { path: '/api/ext/my-extension/data', method: 'POST', handler: handlePost },
15140
+ ],
15141
+
15142
+ // Request middleware
15143
+ middleware: [
15144
+ { path: '/api/ext/my-extension/*', handler: authMiddleware, priority: 10 },
15145
+ ],
15146
+
15147
+ // Shared services
15148
+ services: {
15149
+ myService: myServiceInstance,
15150
+ },
15151
+
15152
+ // Custom workflow nodes
15153
+ workflowNodes: [
15154
+ {
15155
+ definition: {
15156
+ type: 'my-custom-node',
15157
+ name: 'My Custom Node',
15158
+ category: 'custom',
15159
+ description: 'Does something custom',
15160
+ configSchema: { /* ... */ },
15161
+ },
15162
+ handler: async (config, context) => { /* ... */ },
15163
+ },
15164
+ ],
15165
+
15166
+ // Lifecycle hooks
15167
+ initialize: async () => { /* Setup logic */ },
15168
+ cleanup: async () => { /* Cleanup logic */ },
15169
+ };
15170
+ \`\`\`
15171
+
15172
+ ## Quick Start
15173
+
15174
+ ### 1. Enable an Extension
15175
+
15176
+ Add the extension package name to your \`.env.local\`:
15177
+
15178
+ \`\`\`
15179
+ # Enable single extension
15180
+ NETPAD_EXTENSIONS=@netpad/collaborate
15181
+
15182
+ # Enable multiple extensions
15183
+ NETPAD_EXTENSIONS=@netpad/collaborate,@myorg/custom-extension
15184
+ \`\`\`
15185
+
15186
+ ### 2. Install the Package
15187
+
15188
+ \`\`\`bash
15189
+ npm install @netpad/collaborate
15190
+ \`\`\`
15191
+
15192
+ ### 3. Restart NetPad
15193
+
15194
+ Extensions are loaded during application startup.
15195
+
15196
+ ## Creating Custom Extensions
15197
+
15198
+ To create your own extension, use **@netpad/demo-node** as a template:
15199
+
15200
+ 1. **Copy the demo-node package** to a new directory
15201
+ 2. **Update package.json** with your extension name and metadata
15202
+ 3. **Modify src/index.ts**:
15203
+ - Update extension metadata (id, name, version, description)
15204
+ - Define your workflow nodes (type, label, category, config fields)
15205
+ - Implement your node handlers (business logic)
15206
+ - Export the extension as default
15207
+ 4. **Install and enable**:
15208
+ - Run: npm install @myorg/my-extension
15209
+ - Add to .env.local: NETPAD_EXTENSIONS=@myorg/my-extension
15210
+ 5. **Restart NetPad** - Your extension will be loaded automatically
15211
+
15212
+ ### Example: Creating a Custom Workflow Node
15213
+
15214
+ Based on the demo-node structure:
15215
+
15216
+ \`\`\`typescript
15217
+ // 1. Define your node configuration interface
15218
+ interface MyNodeConfig {
15219
+ action: string;
15220
+ target: string;
15221
+ }
15222
+
15223
+ // 2. Implement the node handler
15224
+ const myNodeHandler: NodeHandler = async (context) => {
15225
+ const config = context.resolvedConfig as MyNodeConfig;
15226
+ const inputs = context.inputs;
15227
+
15228
+ // Your business logic here
15229
+ const result = await performAction(config.action, config.target);
15230
+
15231
+ return {
15232
+ success: true,
15233
+ data: { result },
15234
+ metadata: { durationMs: Date.now() - startTime },
15235
+ };
15236
+ };
15237
+
15238
+ // 3. Define the node appearance
15239
+ const myNodeDefinition = {
15240
+ type: 'myext:my-node', // Unique type (convention: extid:nodename)
15241
+ label: 'My Custom Node',
15242
+ description: 'Does something custom',
15243
+ category: 'custom' as const,
15244
+ color: '#FF6B35',
15245
+ icon: 'Extension', // MUI icon name
15246
+ version: '1.0.0',
15247
+ configFields: [
15248
+ {
15249
+ name: 'action',
15250
+ label: 'Action',
15251
+ type: 'select' as const,
15252
+ options: [
15253
+ { label: 'Create', value: 'create' },
15254
+ { label: 'Update', value: 'update' },
15255
+ ],
15256
+ },
15257
+ {
15258
+ name: 'target',
15259
+ label: 'Target',
15260
+ type: 'text' as const,
15261
+ required: true,
15262
+ },
15263
+ ],
15264
+ outputs: [{ id: 'output', label: 'Success', primary: true }],
15265
+ };
15266
+
15267
+ // 4. Export the extension
15268
+ export const myExtension: NetPadExtension = {
15269
+ metadata: {
15270
+ id: 'my-extension',
15271
+ name: 'My Extension',
15272
+ version: '1.0.0',
15273
+ },
15274
+ workflowNodes: [
15275
+ {
15276
+ definition: myNodeDefinition,
15277
+ handler: myNodeHandler,
15278
+ },
15279
+ ],
15280
+ initialize: async () => {
15281
+ console.log('[My Extension] Initialized!');
15282
+ },
15283
+ };
15284
+
15285
+ export default myExtension;
15286
+ \`\`\`
15287
+
15288
+ ### Node Handler Context
15289
+
15290
+ The handler receives a \`NodeExecutionContext\` with:
15291
+
15292
+ - **config**: Raw configuration from the editor
15293
+ - **resolvedConfig**: Configuration with variables like \`{{formData.email}}\` already resolved
15294
+ - **inputs**: Data from previous nodes in the workflow
15295
+ - **trigger**: Information about what triggered the workflow
15296
+ - **getConnection()**: Helper to get MongoDB connection details
15297
+ - **getEmailCredentials()**: Helper to get email service credentials
15298
+
15299
+ ### Node Categories
15300
+
15301
+ When defining your node, choose an appropriate category:
15302
+
15303
+ - **triggers**: Workflow entry points (form submission, webhook, schedule)
15304
+ - **logic**: Control flow (condition, switch, loop, delay)
15305
+ - **integrations**: External services (Slack, email, HTTP)
15306
+ - **actions**: Data operations (MongoDB queries, transforms)
15307
+ - **data**: Data manipulation (set variable, code execution)
15308
+ - **ai**: AI-powered operations (generate, classify, extract)
15309
+ - **forms**: Form-specific operations
15310
+ - **custom**: Custom extensions (use this for your own nodes)
15311
+ - **annotations**: Non-executable nodes (notes, labels)
15312
+
15313
+ ## Extension Loading
15314
+
15315
+ Extensions are loaded in this order:
15316
+
15317
+ 1. **Cloud Extension** - \`@netpad/cloud-features\` (if \`NETPAD_CLOUD=true\`)
15318
+ 2. **Plugin Extensions** - Packages listed in \`NETPAD_EXTENSIONS\`
15319
+ 3. **Programmatic Extensions** - Registered via \`registerExtensionManually()\`
15320
+
15321
+ Each extension goes through:
15322
+
15323
+ 1. Package resolution and import
15324
+ 2. Registration in the extension registry
15325
+ 3. Initialization via the \`initialize()\` hook
15326
+
15327
+ ## Feature Checking
15328
+
15329
+ Extensions can declare features that other parts of the application can check:
15330
+
15331
+ \`\`\`typescript
15332
+ import { isFeatureAvailable } from '@/lib/extensions/registry';
15333
+
15334
+ // Check if a feature is available
15335
+ if (isFeatureAvailable('billing')) {
15336
+ // Show billing UI
15337
+ }
15338
+
15339
+ // Custom extension features use the custom: prefix
15340
+ if (isFeatureAvailable('custom:collaborate')) {
15341
+ // Show collaborate features
15342
+ }
15343
+ \`\`\`
15344
+
15345
+ ## Best Practices
15346
+
15347
+ 1. **Use descriptive metadata** - Clear names and descriptions help users understand your extension
15348
+ 2. **Namespace your routes** - Always use \`/api/ext/{your-extension}/\` for routes
15349
+ 3. **Handle errors gracefully** - Extensions should not crash the main application
15350
+ 4. **Clean up resources** - Implement the \`cleanup()\` hook for proper teardown
15351
+ 5. **Document your extension** - Provide clear usage instructions and API documentation
15352
+ 6. **Test thoroughly** - Extensions run in production, so ensure they're well-tested
15353
+
15354
+ ## Extension Types
15355
+
15356
+ ### Service Extensions
15357
+
15358
+ Provide business logic services:
15359
+ - Billing service (Stripe integration)
15360
+ - Atlas provisioning service
15361
+ - Usage tracking service
15362
+ - Admin service
15363
+
15364
+ ### Feature Extensions
15365
+
15366
+ Add new capabilities:
15367
+ - Custom workflow nodes
15368
+ - Additional form field types
15369
+ - UI components
15370
+ - Integration connectors
15371
+
15372
+ ### Middleware Extensions
15373
+
15374
+ Intercept and modify requests:
15375
+ - Authentication middleware
15376
+ - Rate limiting
15377
+ - Request logging
15378
+ - Data transformation
15379
+
15380
+ ## API Reference
15381
+
15382
+ See the full extension API documentation at: https://docs.netpad.io/docs/extensions/api-reference
14991
15383
  `
14992
15384
  };
14993
15385
  var QUICK_START_GUIDE = `# Quick Start Guide
@@ -16567,104 +16959,6 @@ var APPLICATION_TEMPLATES = {
16567
16959
  }
16568
16960
  }
16569
16961
  };
16570
- function generateCreateApplicationCode(options) {
16571
- const { name, description, slug, templateId, icon, color, tags, projectId, organizationId } = options;
16572
- const template = templateId ? APPLICATION_TEMPLATES[templateId] : null;
16573
- let code = `// Create a new NetPad application
16574
- // Using: NetPad Platform API
16575
-
16576
- const applicationData = {
16577
- name: '${name}',
16578
- ${description ? `description: '${description}',` : ""}
16579
- ${slug ? `slug: '${slug}',` : ""}
16580
- ${icon ? `icon: '${icon}',` : ""}
16581
- ${color ? `color: '${color}',` : ""}
16582
- ${tags && tags.length > 0 ? `tags: ${JSON.stringify(tags)},` : ""}
16583
- projectId: '${projectId}',
16584
- organizationId: '${organizationId}',
16585
- };
16586
-
16587
- // API call to create application
16588
- const response = await fetch('/api/applications', {
16589
- method: 'POST',
16590
- headers: {
16591
- 'Content-Type': 'application/json',
16592
- 'Authorization': \`Bearer \${process.env.NETPAD_API_KEY}\`,
16593
- },
16594
- body: JSON.stringify(applicationData),
16595
- });
16596
-
16597
- const { application } = await response.json();
16598
- console.log('Created application:', application.applicationId);
16599
- `;
16600
- if (template && template.structure.forms.length > 0) {
16601
- code += `
16602
- // Create forms from template: ${template.name}
16603
- `;
16604
- for (const form of template.structure.forms) {
16605
- code += `
16606
- // Create form: ${form.name}
16607
- const ${form.slug.replace(/-/g, "")}FormData = {
16608
- name: '${form.name}',
16609
- slug: '${form.slug}',
16610
- applicationId: application.applicationId,
16611
- projectId: '${projectId}',
16612
- organizationId: '${organizationId}',
16613
- fieldConfigs: ${JSON.stringify(form.fields, null, 2)},
16614
- ${form.multiPage ? `multiPage: { enabled: true, pages: ${JSON.stringify(form.pages)} },` : ""}
16615
- };
16616
-
16617
- await fetch('/api/forms', {
16618
- method: 'POST',
16619
- headers: {
16620
- 'Content-Type': 'application/json',
16621
- 'Authorization': \`Bearer \${process.env.NETPAD_API_KEY}\`,
16622
- },
16623
- body: JSON.stringify(${form.slug.replace(/-/g, "")}FormData),
16624
- });
16625
- `;
16626
- }
16627
- }
16628
- if (template && template.structure.workflows.length > 0) {
16629
- code += `
16630
- // Create workflows from template
16631
- `;
16632
- for (const workflow of template.structure.workflows) {
16633
- code += `
16634
- // Workflow: ${workflow.name}
16635
- // Trigger: ${workflow.trigger}
16636
- // Steps: ${workflow.steps.join(" -> ")}
16637
- // Note: Create workflow in NetPad UI or via API with full node configuration
16638
- `;
16639
- }
16640
- }
16641
- return code;
16642
- }
16643
- function generateApplicationConfig(options) {
16644
- const { name, description, slug, templateId, icon, color, tags } = options;
16645
- const template = templateId ? APPLICATION_TEMPLATES[templateId] : null;
16646
- const config2 = {
16647
- application: {
16648
- name,
16649
- description: description || (template ? template.description : ""),
16650
- slug: slug || name.toLowerCase().replace(/[^a-z0-9]+/g, "-"),
16651
- icon,
16652
- color,
16653
- tags: tags || (template ? template.tags : []),
16654
- version: "1.0.0"
16655
- }
16656
- };
16657
- if (template) {
16658
- config2.template = {
16659
- id: template.id,
16660
- name: template.name,
16661
- category: template.category
16662
- };
16663
- config2.forms = template.structure.forms;
16664
- config2.workflows = template.structure.workflows;
16665
- }
16666
- return config2;
16667
- }
16668
16962
  function generateApplicationContract(options) {
16669
16963
  const {
16670
16964
  applicationId,
@@ -17898,92 +18192,6 @@ var WORKFLOW_TEMPLATES = {
17898
18192
  ]
17899
18193
  }
17900
18194
  };
17901
- function generateCreateWorkflowCode(options) {
17902
- const { name, description, templateId, applicationId, projectId, organizationId, tags } = options;
17903
- const template = templateId ? WORKFLOW_TEMPLATES[templateId] : null;
17904
- let code = `// Create a new NetPad workflow
17905
- // Using: NetPad Platform API
17906
-
17907
- const workflowData = {
17908
- name: '${name}',
17909
- ${description ? `description: '${description}',` : ""}
17910
- projectId: '${projectId}',
17911
- organizationId: '${organizationId}',
17912
- ${applicationId ? `applicationId: '${applicationId}',` : ""}
17913
- ${tags && tags.length > 0 ? `tags: ${JSON.stringify(tags)},` : ""}
17914
- `;
17915
- if (template) {
17916
- code += `
17917
- // Using template: ${template.name}
17918
- canvas: {
17919
- nodes: ${JSON.stringify(template.nodes, null, 4)},
17920
- edges: ${JSON.stringify(template.edges, null, 4)},
17921
- viewport: { x: 0, y: 0, zoom: 1 },
17922
- },
17923
- `;
17924
- } else {
17925
- code += `
17926
- // Empty canvas - add nodes in the visual editor
17927
- canvas: {
17928
- nodes: [],
17929
- edges: [],
17930
- viewport: { x: 0, y: 0, zoom: 1 },
17931
- },
17932
- `;
17933
- }
17934
- code += `
17935
- settings: {
17936
- executionMode: 'auto',
17937
- maxExecutionTime: 300000,
17938
- retryPolicy: {
17939
- maxRetries: 3,
17940
- backoffMultiplier: 2,
17941
- initialDelayMs: 1000,
17942
- },
17943
- errorHandling: 'stop',
17944
- timezone: 'UTC',
17945
- },
17946
- variables: [],
17947
- };
17948
-
17949
- const response = await fetch('/api/workflows', {
17950
- method: 'POST',
17951
- headers: {
17952
- 'Content-Type': 'application/json',
17953
- 'Authorization': \`Bearer \${process.env.NETPAD_API_KEY}\`,
17954
- },
17955
- body: JSON.stringify(workflowData),
17956
- });
17957
-
17958
- const { workflow } = await response.json();
17959
- console.log('Created workflow:', workflow.id);
17960
- `;
17961
- return code;
17962
- }
17963
- function generateWorkflowConfig(options) {
17964
- const { name, description, templateId, tags } = options;
17965
- const template = templateId ? WORKFLOW_TEMPLATES[templateId] : null;
17966
- return {
17967
- workflow: {
17968
- name,
17969
- description: description || (template ? template.description : ""),
17970
- tags: tags || (template ? template.tags : []),
17971
- status: "draft"
17972
- },
17973
- template: template ? {
17974
- id: template.id,
17975
- name: template.name,
17976
- category: template.category
17977
- } : null,
17978
- canvas: template ? {
17979
- nodes: template.nodes,
17980
- edges: template.edges
17981
- } : {
17982
- nodes: [],
17983
- edges: []
17984
- }
17985
- };
17986
- }
17987
18195
  function generateAddNodeCode(workflowId, node) {
17988
18196
  return `// Add node to workflow
17989
18197
  const nodeData = ${JSON.stringify(node, null, 2)};
@@ -20115,23 +20323,647 @@ collections.forEach((coll: any) => {
20115
20323
  `;
20116
20324
  }
20117
20325
 
20118
- // src/index.ts
20119
- var server = new McpServer({
20120
- name: "@netpad/mcp-server",
20121
- version: "2.0.0"
20122
- });
20123
- server.resource(
20124
- "netpad-docs",
20125
- "netpad://docs/readme",
20126
- async () => ({
20127
- contents: [
20128
- {
20129
- uri: "netpad://docs/readme",
20130
- mimeType: "text/markdown",
20131
- text: DOCUMENTATION.readme
20132
- }
20133
- ]
20134
- })
20326
+ // src/validation.ts
20327
+ import ts from "typescript";
20328
+ var INLINE_TYPES = `
20329
+ // ============================================================================
20330
+ // Types (inline - no external dependencies)
20331
+ // ============================================================================
20332
+
20333
+ interface NetPadConfig {
20334
+ baseUrl: string;
20335
+ apiKey: string;
20336
+ organizationId?: string;
20337
+ projectId?: string;
20338
+ }
20339
+
20340
+ interface FormFieldValidation {
20341
+ min?: number;
20342
+ max?: number;
20343
+ minLength?: number;
20344
+ maxLength?: number;
20345
+ pattern?: string;
20346
+ errorMessage?: string;
20347
+ }
20348
+
20349
+ interface FormFieldOption {
20350
+ label: string;
20351
+ value: string;
20352
+ }
20353
+
20354
+ interface ConditionalLogicCondition {
20355
+ field: string;
20356
+ operator: string;
20357
+ value?: string | number | boolean;
20358
+ }
20359
+
20360
+ interface ConditionalLogic {
20361
+ action: 'show' | 'hide';
20362
+ logicType: 'all' | 'any';
20363
+ conditions: ConditionalLogicCondition[];
20364
+ }
20365
+
20366
+ interface FormField {
20367
+ path: string;
20368
+ label: string;
20369
+ type: string;
20370
+ included?: boolean;
20371
+ required?: boolean;
20372
+ disabled?: boolean;
20373
+ readOnly?: boolean;
20374
+ placeholder?: string;
20375
+ helpText?: string;
20376
+ options?: FormFieldOption[];
20377
+ validation?: FormFieldValidation;
20378
+ fieldWidth?: 'full' | 'half' | 'third' | 'quarter';
20379
+ defaultValue?: unknown;
20380
+ conditionalLogic?: ConditionalLogic;
20381
+ }
20382
+
20383
+ interface FormTheme {
20384
+ primaryColor?: string;
20385
+ backgroundColor?: string;
20386
+ surfaceColor?: string;
20387
+ textColor?: string;
20388
+ errorColor?: string;
20389
+ successColor?: string;
20390
+ borderRadius?: number;
20391
+ spacing?: 'compact' | 'comfortable' | 'spacious';
20392
+ inputStyle?: 'outlined' | 'filled' | 'standard';
20393
+ mode?: 'light' | 'dark';
20394
+ }
20395
+
20396
+ interface FormPage {
20397
+ id: string;
20398
+ title: string;
20399
+ description?: string;
20400
+ fields: string[];
20401
+ }
20402
+
20403
+ interface MultiPageConfig {
20404
+ enabled: boolean;
20405
+ pages: FormPage[];
20406
+ showProgressBar?: boolean;
20407
+ showPageTitles?: boolean;
20408
+ allowSkip?: boolean;
20409
+ showReview?: boolean;
20410
+ }
20411
+
20412
+ interface FormConfig {
20413
+ name: string;
20414
+ slug?: string;
20415
+ description?: string;
20416
+ fieldConfigs: FormField[];
20417
+ submitButtonText?: string;
20418
+ successMessage?: string;
20419
+ theme?: FormTheme;
20420
+ multiPage?: MultiPageConfig;
20421
+ }
20422
+
20423
+ interface SubmitResult {
20424
+ success: boolean;
20425
+ submissionId?: string;
20426
+ error?: string;
20427
+ }
20428
+
20429
+ interface ApiResponse<T> {
20430
+ success: boolean;
20431
+ data?: T;
20432
+ error?: string;
20433
+ }
20434
+ `;
20435
+ var INLINE_WORKFLOW_TYPES = `
20436
+ interface WorkflowNode {
20437
+ id: string;
20438
+ type: string;
20439
+ label: string;
20440
+ position: { x: number; y: number };
20441
+ config: Record<string, unknown>;
20442
+ enabled?: boolean;
20443
+ }
20444
+
20445
+ interface WorkflowEdge {
20446
+ id: string;
20447
+ source: string;
20448
+ sourceHandle: string;
20449
+ target: string;
20450
+ targetHandle: string;
20451
+ condition?: {
20452
+ expression: string;
20453
+ label?: string;
20454
+ };
20455
+ }
20456
+
20457
+ interface WorkflowSettings {
20458
+ executionMode?: 'auto' | 'manual';
20459
+ maxExecutionTime?: number;
20460
+ retryPolicy?: {
20461
+ maxRetries: number;
20462
+ backoffMultiplier: number;
20463
+ initialDelayMs: number;
20464
+ };
20465
+ }
20466
+
20467
+ interface WorkflowConfig {
20468
+ name: string;
20469
+ description?: string;
20470
+ tags?: string[];
20471
+ nodes: WorkflowNode[];
20472
+ edges: WorkflowEdge[];
20473
+ settings?: WorkflowSettings;
20474
+ }
20475
+
20476
+ interface CreateWorkflowResult {
20477
+ success: boolean;
20478
+ workflowId?: string;
20479
+ error?: string;
20480
+ }
20481
+ `;
20482
+ var SKIPPED_ERROR_PATTERNS = [
20483
+ "Cannot find module",
20484
+ // Global types (not available in isolated compilation)
20485
+ "Cannot find global type",
20486
+ "Cannot find name 'Array'",
20487
+ "Cannot find name 'String'",
20488
+ "Cannot find name 'Number'",
20489
+ "Cannot find name 'Boolean'",
20490
+ "Cannot find name 'Object'",
20491
+ "Cannot find name 'Function'",
20492
+ "Cannot find name 'Symbol'",
20493
+ "Cannot find name 'Error'",
20494
+ "Cannot find name 'Promise'",
20495
+ "Cannot find name 'Record'",
20496
+ "Cannot find name 'Partial'",
20497
+ "Cannot find name 'Required'",
20498
+ "Cannot find name 'Readonly'",
20499
+ "Cannot find name 'Pick'",
20500
+ "Cannot find name 'Omit'",
20501
+ "Cannot find name 'Exclude'",
20502
+ "Cannot find name 'Extract'",
20503
+ "Cannot find name 'Map'",
20504
+ "Cannot find name 'Set'",
20505
+ "Cannot find name 'WeakMap'",
20506
+ "Cannot find name 'WeakSet'",
20507
+ "Cannot find name 'JSON'",
20508
+ "Cannot find name 'Date'",
20509
+ "Cannot find name 'RegExp'",
20510
+ "Cannot find name 'Math'",
20511
+ // Runtime globals
20512
+ "Cannot find name 'process'",
20513
+ "Cannot find name 'fetch'",
20514
+ "Cannot find name 'console'",
20515
+ "Cannot find name 'RequestInit'",
20516
+ "Cannot find name 'Response'",
20517
+ "Cannot find name 'Headers'",
20518
+ "Cannot find name 'URL'",
20519
+ "Cannot find name 'URLSearchParams'",
20520
+ "Cannot find name 'FormData'",
20521
+ "Cannot find name 'Blob'",
20522
+ "Cannot find name 'File'",
20523
+ "Cannot find name 'AbortController'",
20524
+ "Cannot find name 'AbortSignal'",
20525
+ "Cannot find name 'setTimeout'",
20526
+ "Cannot find name 'clearTimeout'",
20527
+ "Cannot find name 'setInterval'",
20528
+ "Cannot find name 'clearInterval'",
20529
+ // React-specific
20530
+ "Cannot find name 'React'",
20531
+ "Cannot find namespace 'React'",
20532
+ "Cannot find name 'JSX'",
20533
+ // Node.js specific
20534
+ "Cannot find name 'Buffer'",
20535
+ "Cannot find name '__dirname'",
20536
+ "Cannot find name '__filename'",
20537
+ "Cannot find name 'require'",
20538
+ "Cannot find name 'module'",
20539
+ "Cannot find name 'exports'",
20540
+ // DOM types
20541
+ "Cannot find name 'document'",
20542
+ "Cannot find name 'window'",
20543
+ "Cannot find name 'HTMLElement'",
20544
+ "Cannot find name 'Event'",
20545
+ "Cannot find name 'EventTarget'",
20546
+ // Library suggestions
20547
+ "Do you need to change your target library",
20548
+ // Strict mode catch clause errors (handled by instanceof checks at runtime)
20549
+ "is of type 'unknown'"
20550
+ ];
20551
+ function validateTypeScript(code) {
20552
+ const result = validateTypeScriptDetailed(code);
20553
+ return result.errors.map(
20554
+ (e) => e.line ? `Line ${e.line}:${e.column}: ${e.message}` : e.message
20555
+ );
20556
+ }
20557
+ function validateTypeScriptDetailed(code) {
20558
+ const errors = [];
20559
+ const warnings = [];
20560
+ const sourceFile = ts.createSourceFile(
20561
+ "generated.ts",
20562
+ code,
20563
+ ts.ScriptTarget.ES2020,
20564
+ true,
20565
+ ts.ScriptKind.TS
20566
+ );
20567
+ const compilerOptions = {
20568
+ target: ts.ScriptTarget.ES2020,
20569
+ module: ts.ModuleKind.ESNext,
20570
+ moduleResolution: ts.ModuleResolutionKind.Bundler,
20571
+ strict: true,
20572
+ noEmit: true,
20573
+ esModuleInterop: true,
20574
+ skipLibCheck: true,
20575
+ // Don't require external type definitions
20576
+ types: [],
20577
+ lib: ["ES2020", "DOM"],
20578
+ // JSX support for React components
20579
+ jsx: ts.JsxEmit.React,
20580
+ jsxFactory: "React.createElement",
20581
+ jsxFragmentFactory: "React.Fragment"
20582
+ };
20583
+ const defaultCompilerHost = ts.createCompilerHost(compilerOptions);
20584
+ const customCompilerHost = {
20585
+ ...defaultCompilerHost,
20586
+ getSourceFile: (fileName, languageVersion) => {
20587
+ if (fileName === "generated.ts") {
20588
+ return sourceFile;
20589
+ }
20590
+ if (fileName.endsWith(".ts") || fileName.endsWith(".tsx")) {
20591
+ return ts.createSourceFile(
20592
+ fileName,
20593
+ "",
20594
+ languageVersion,
20595
+ true
20596
+ );
20597
+ }
20598
+ return defaultCompilerHost.getSourceFile(fileName, languageVersion);
20599
+ },
20600
+ fileExists: (fileName) => {
20601
+ if (fileName === "generated.ts") return true;
20602
+ return defaultCompilerHost.fileExists(fileName);
20603
+ },
20604
+ readFile: (fileName) => {
20605
+ if (fileName === "generated.ts") return code;
20606
+ return defaultCompilerHost.readFile(fileName);
20607
+ }
20608
+ };
20609
+ const program = ts.createProgram(
20610
+ ["generated.ts"],
20611
+ compilerOptions,
20612
+ customCompilerHost
20613
+ );
20614
+ const allDiagnostics = ts.getPreEmitDiagnostics(program);
20615
+ for (const diagnostic of allDiagnostics) {
20616
+ const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
20617
+ const shouldSkip = SKIPPED_ERROR_PATTERNS.some((pattern) => message.includes(pattern));
20618
+ if (shouldSkip) {
20619
+ continue;
20620
+ }
20621
+ if (diagnostic.category === ts.DiagnosticCategory.Warning) {
20622
+ warnings.push(message);
20623
+ continue;
20624
+ }
20625
+ if (diagnostic.category === ts.DiagnosticCategory.Error) {
20626
+ const error48 = {
20627
+ message,
20628
+ code: diagnostic.code,
20629
+ fixable: isFixableError(diagnostic.code, message),
20630
+ fixSuggestion: getFixSuggestion(diagnostic.code, message)
20631
+ };
20632
+ if (diagnostic.file && diagnostic.start !== void 0) {
20633
+ const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
20634
+ error48.line = line + 1;
20635
+ error48.column = character + 1;
20636
+ }
20637
+ errors.push(error48);
20638
+ }
20639
+ }
20640
+ return {
20641
+ valid: errors.length === 0,
20642
+ errors,
20643
+ warnings,
20644
+ fixable: errors.some((e) => e.fixable)
20645
+ };
20646
+ }
20647
+ function isFixableError(code, message) {
20648
+ if (code === 1005) return true;
20649
+ if (code === 7006) return true;
20650
+ if (code === 1064) return true;
20651
+ if (code === 2304 && message.includes("Cannot find name")) return false;
20652
+ if (code === 2345) return false;
20653
+ if (code === 2339) return false;
20654
+ return false;
20655
+ }
20656
+ function getFixSuggestion(code, message) {
20657
+ if (code === 1005) {
20658
+ return "Add missing semicolon";
20659
+ }
20660
+ if (code === 7006) {
20661
+ return "Add type annotation to parameter (e.g., ': unknown' or ': string')";
20662
+ }
20663
+ if (code === 1064) {
20664
+ return "Add Promise return type to async function";
20665
+ }
20666
+ if (code === 2304) {
20667
+ const match = message.match(/Cannot find name '(\w+)'/);
20668
+ if (match) {
20669
+ return `Ensure '${match[1]}' is defined or imported`;
20670
+ }
20671
+ }
20672
+ if (code === 2345) {
20673
+ return "Check that the argument type matches the expected parameter type";
20674
+ }
20675
+ if (code === 2339) {
20676
+ const match = message.match(/Property '(\w+)' does not exist/);
20677
+ if (match) {
20678
+ return `Add '${match[1]}' property to the type definition or use type assertion`;
20679
+ }
20680
+ }
20681
+ return void 0;
20682
+ }
20683
+ function attemptAutoFix(code, _errors) {
20684
+ let fixedCode = code;
20685
+ const result = validateTypeScriptDetailed(code);
20686
+ for (const error48 of result.errors) {
20687
+ if (!error48.fixable) continue;
20688
+ if (error48.code === 1005 && error48.message.includes("';' expected")) {
20689
+ fixedCode = fixedCode.replace(
20690
+ /(\})\s*(\n\s*(?:const|let|var|function|export|import|class|interface|type|async|if|for|while|switch|try))/g,
20691
+ "$1;\n$2"
20692
+ );
20693
+ fixedCode = fixedCode.replace(
20694
+ /(\s=\s[^;{]+)\s*(\n\s*(?:const|let|var|function|export|import|class|interface|type|async))/g,
20695
+ "$1;$2"
20696
+ );
20697
+ }
20698
+ if (error48.code === 7006 && error48.message.includes("implicitly has an")) {
20699
+ fixedCode = fixedCode.replace(
20700
+ /\(([a-zA-Z_][a-zA-Z0-9_]*)\s*\)/g,
20701
+ "($1: unknown)"
20702
+ );
20703
+ fixedCode = fixedCode.replace(
20704
+ /\(([a-zA-Z_][a-zA-Z0-9_]*)\s*,\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\)/g,
20705
+ "($1: unknown, $2: unknown)"
20706
+ );
20707
+ }
20708
+ if (error48.code === 1064) {
20709
+ fixedCode = fixedCode.replace(
20710
+ /async function (\w+)\(([^)]*)\)\s*\{/g,
20711
+ (match, name, params) => {
20712
+ if (match.includes(":")) return match;
20713
+ return `async function ${name}(${params}): Promise<unknown> {`;
20714
+ }
20715
+ );
20716
+ fixedCode = fixedCode.replace(
20717
+ /const (\w+) = async \(([^)]*)\)\s*=>/g,
20718
+ (match, name, params) => {
20719
+ if (match.includes(":")) return match;
20720
+ return `const ${name} = async (${params}): Promise<unknown> =>`;
20721
+ }
20722
+ );
20723
+ }
20724
+ }
20725
+ fixedCode = fixedCode.replace(/,(\s*[}\]])/g, "$1");
20726
+ fixedCode = fixedCode.replace(
20727
+ /export \{ ([^}]+) \}(\s*\n)/g,
20728
+ "export { $1 };$2"
20729
+ );
20730
+ return fixedCode;
20731
+ }
20732
+ function attemptAutoFixIterative(code, maxIterations = 3) {
20733
+ let currentCode = code;
20734
+ let iterations = 0;
20735
+ let previousErrorCount = Infinity;
20736
+ while (iterations < maxIterations) {
20737
+ const errors = validateTypeScript(currentCode);
20738
+ if (errors.length === 0 || errors.length >= previousErrorCount) {
20739
+ return {
20740
+ code: currentCode,
20741
+ fixed: errors.length === 0,
20742
+ iterations,
20743
+ remainingErrors: errors
20744
+ };
20745
+ }
20746
+ previousErrorCount = errors.length;
20747
+ currentCode = attemptAutoFix(currentCode, errors);
20748
+ iterations++;
20749
+ }
20750
+ const finalErrors = validateTypeScript(currentCode);
20751
+ return {
20752
+ code: currentCode,
20753
+ fixed: finalErrors.length === 0,
20754
+ iterations,
20755
+ remainingErrors: finalErrors
20756
+ };
20757
+ }
20758
+ function getSyntaxErrors(code) {
20759
+ const sourceFile = ts.createSourceFile(
20760
+ "check.ts",
20761
+ code,
20762
+ ts.ScriptTarget.ES2020,
20763
+ true,
20764
+ ts.ScriptKind.TS
20765
+ );
20766
+ const syntaxErrors = sourceFile.parseDiagnostics || [];
20767
+ return syntaxErrors.map((diagnostic) => {
20768
+ const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
20769
+ if (diagnostic.start !== void 0) {
20770
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(diagnostic.start);
20771
+ return `Line ${line + 1}:${character + 1}: ${message}`;
20772
+ }
20773
+ return message;
20774
+ });
20775
+ }
20776
+ var STANDARD_ENV_VARS = [
20777
+ {
20778
+ name: "NETPAD_URL",
20779
+ description: "NetPad API URL",
20780
+ example: "https://api.netpad.io"
20781
+ },
20782
+ {
20783
+ name: "NETPAD_API_KEY",
20784
+ description: "Your NetPad API key",
20785
+ example: "np_live_xxxxx"
20786
+ },
20787
+ {
20788
+ name: "NETPAD_ORG_ID",
20789
+ description: "Your organization ID",
20790
+ example: "org_xxxxx"
20791
+ },
20792
+ {
20793
+ name: "NETPAD_PROJECT_ID",
20794
+ description: "Your project ID",
20795
+ example: "proj_xxxxx"
20796
+ }
20797
+ ];
20798
+ function generateSelfContainedCode(options) {
20799
+ const {
20800
+ title,
20801
+ description,
20802
+ includeFormTypes = true,
20803
+ includeWorkflowTypes = false,
20804
+ configCode,
20805
+ functionsCode,
20806
+ mainCode
20807
+ } = options;
20808
+ let code = `/**
20809
+ * ${title}
20810
+ * ${description || "Generated by NetPad"}
20811
+ *
20812
+ * Run with: npx tsx ${title.toLowerCase().replace(/\s+/g, "-")}.ts
20813
+ */
20814
+
20815
+ `;
20816
+ if (includeFormTypes) {
20817
+ code += INLINE_TYPES + "\n";
20818
+ }
20819
+ if (includeWorkflowTypes) {
20820
+ code += INLINE_WORKFLOW_TYPES + "\n";
20821
+ }
20822
+ code += `// ============================================================================
20823
+ // Configuration
20824
+ // ============================================================================
20825
+
20826
+ const CONFIG = {
20827
+ baseUrl: process.env.NETPAD_URL ?? 'https://api.netpad.io',
20828
+ apiKey: process.env.NETPAD_API_KEY ?? '',
20829
+ organizationId: process.env.NETPAD_ORG_ID ?? '',
20830
+ projectId: process.env.NETPAD_PROJECT_ID ?? '',
20831
+ };
20832
+
20833
+ ${configCode}
20834
+
20835
+ // ============================================================================
20836
+ // API Functions
20837
+ // ============================================================================
20838
+
20839
+ ${functionsCode}
20840
+ `;
20841
+ if (mainCode) {
20842
+ code += `
20843
+ // ============================================================================
20844
+ // Main
20845
+ // ============================================================================
20846
+
20847
+ ${mainCode}
20848
+ `;
20849
+ }
20850
+ return code;
20851
+ }
20852
+ function createToolOutput(options) {
20853
+ const { code, filename, additionalFiles, envVars, dependencies, skipValidation } = options;
20854
+ if (skipValidation) {
20855
+ return {
20856
+ code,
20857
+ filename,
20858
+ additionalFiles,
20859
+ envVars: envVars || STANDARD_ENV_VARS,
20860
+ dependencies,
20861
+ validated: true
20862
+ // Assumed valid
20863
+ };
20864
+ }
20865
+ const syntaxErrors = getSyntaxErrors(code);
20866
+ if (syntaxErrors.length > 0) {
20867
+ return {
20868
+ code,
20869
+ filename,
20870
+ additionalFiles,
20871
+ envVars: envVars || STANDARD_ENV_VARS,
20872
+ dependencies,
20873
+ validated: false,
20874
+ validationErrors: syntaxErrors
20875
+ };
20876
+ }
20877
+ const fixResult = attemptAutoFixIterative(code);
20878
+ return {
20879
+ code: fixResult.code,
20880
+ filename,
20881
+ additionalFiles,
20882
+ envVars: envVars || STANDARD_ENV_VARS,
20883
+ dependencies,
20884
+ validated: fixResult.fixed,
20885
+ validationErrors: fixResult.remainingErrors.length > 0 ? fixResult.remainingErrors : void 0
20886
+ };
20887
+ }
20888
+ function formatToolOutput(output) {
20889
+ let result = "";
20890
+ if (output.validated) {
20891
+ result += "\u2705 **Code validated successfully** - Ready to use!\n\n";
20892
+ } else {
20893
+ result += "\u26A0\uFE0F **Code validation warnings:**\n\n";
20894
+ result += "The generated code has some issues that may need manual review:\n\n";
20895
+ for (const error48 of output.validationErrors || []) {
20896
+ result += `- ${error48}
20897
+ `;
20898
+ }
20899
+ result += "\n> Note: The code may still work in most environments. These warnings are from strict TypeScript checking.\n\n";
20900
+ }
20901
+ result += "## Quick Start\n\n";
20902
+ result += "```bash\n";
20903
+ result += `# Save the code below to ${output.filename}
20904
+ `;
20905
+ result += `# Then run:
20906
+ `;
20907
+ result += `npx tsx ${output.filename}
20908
+ `;
20909
+ result += "```\n\n";
20910
+ if (output.envVars.length > 0) {
20911
+ result += "## Required Environment Variables\n\n";
20912
+ result += "Create a `.env` file or export these variables:\n\n";
20913
+ result += "```bash\n";
20914
+ for (const env of output.envVars) {
20915
+ result += `# ${env.description}
20916
+ `;
20917
+ result += `export ${env.name}="${env.example}"
20918
+ `;
20919
+ }
20920
+ result += "```\n\n";
20921
+ }
20922
+ if (output.dependencies && output.dependencies.length > 0) {
20923
+ result += "## Dependencies\n\n";
20924
+ result += "```bash\n";
20925
+ result += `npm install ${output.dependencies.join(" ")}
20926
+ `;
20927
+ result += "```\n\n";
20928
+ }
20929
+ result += `## ${output.filename}
20930
+
20931
+ `;
20932
+ result += "```typescript\n";
20933
+ result += output.code;
20934
+ result += "\n```\n";
20935
+ if (output.additionalFiles && output.additionalFiles.length > 0) {
20936
+ result += "\n## Additional Files\n";
20937
+ for (const file2 of output.additionalFiles) {
20938
+ result += `
20939
+ ### ${file2.filename}
20940
+
20941
+ `;
20942
+ result += "```typescript\n";
20943
+ result += file2.code;
20944
+ result += "\n```\n";
20945
+ }
20946
+ }
20947
+ return result;
20948
+ }
20949
+
20950
+ // src/index.ts
20951
+ var server = new McpServer({
20952
+ name: "@netpad/mcp-server",
20953
+ version: "2.0.0"
20954
+ });
20955
+ server.resource(
20956
+ "netpad-docs",
20957
+ "netpad://docs/readme",
20958
+ async () => ({
20959
+ contents: [
20960
+ {
20961
+ uri: "netpad://docs/readme",
20962
+ mimeType: "text/markdown",
20963
+ text: DOCUMENTATION.readme
20964
+ }
20965
+ ]
20966
+ })
20135
20967
  );
20136
20968
  server.resource(
20137
20969
  "netpad-architecture",
@@ -20211,25 +21043,261 @@ server.resource(
20211
21043
  ]
20212
21044
  })
20213
21045
  );
21046
+ server.resource(
21047
+ "netpad-extensions",
21048
+ "netpad://docs/extensions",
21049
+ async () => ({
21050
+ contents: [
21051
+ {
21052
+ uri: "netpad://docs/extensions",
21053
+ mimeType: "text/markdown",
21054
+ text: DOCUMENTATION.extensions
21055
+ }
21056
+ ]
21057
+ })
21058
+ );
21059
+ server.resource(
21060
+ "netpad-demo-node",
21061
+ "netpad://examples/demo-node",
21062
+ async () => ({
21063
+ contents: [
21064
+ {
21065
+ uri: "netpad://examples/demo-node",
21066
+ mimeType: "text/markdown",
21067
+ text: `# @netpad/demo-node Extension Example
21068
+
21069
+ The demo-node extension is a complete, working example of how to create NetPad workflow node extensions. Use it as a template for your own extensions.
21070
+
21071
+ ## What It Provides
21072
+
21073
+ A single workflow node called **"Log Message"** that:
21074
+ - Logs configurable messages to the console
21075
+ - Supports different log levels (info, warn, error)
21076
+ - Can pass through input data to downstream nodes
21077
+ - Demonstrates all key extension concepts
21078
+
21079
+ ## Node Details
21080
+
21081
+ **Type:** \`demo:log-message\`
21082
+ **Category:** Custom
21083
+ **Icon:** Terminal (MUI icon)
21084
+ **Color:** #FF6B35 (orange)
21085
+
21086
+ ### Configuration Fields
21087
+
21088
+ | Field | Type | Description |
21089
+ |-------|------|-------------|
21090
+ | Message | textarea | The message to log. Supports \`{{variable}}\` syntax for dynamic values |
21091
+ | Log Level | select | info, warn, or error |
21092
+ | Label | text | Custom label for the log entry |
21093
+ | Pass Through | boolean | Include input data in output (default: true) |
21094
+
21095
+ ### Example Usage
21096
+
21097
+ 1. Drag the "Log Message" node onto the workflow canvas
21098
+ 2. Connect it after a form trigger or other node
21099
+ 3. Configure the message: \`New submission from {{formData.email}}\`
21100
+ 4. The node will log the message and pass data to the next node
21101
+
21102
+ ## Using as a Template
21103
+
21104
+ 1. **Copy the package** to a new directory
21105
+ 2. **Update package.json** with your extension name
21106
+ 3. **Modify src/index.ts**:
21107
+ - Change extension metadata (id, name, version)
21108
+ - Update node definition (type, label, icon, color, config fields)
21109
+ - Implement your business logic in the handler
21110
+ 4. **Export the extension** as default export
21111
+
21112
+ ## Key Concepts Demonstrated
21113
+
21114
+ - **Extension Metadata**: Identifies your extension in the system
21115
+ - **Workflow Nodes**: Custom nodes that appear in the workflow editor palette
21116
+ - **Node Definition**: Describes the node's appearance and configuration UI
21117
+ - **Node Handler**: The function that executes when the node runs
21118
+ - **Configuration Fields**: UI fields for node configuration
21119
+ - **Output Handles**: Connection points for downstream nodes
21120
+ - **Lifecycle Hooks**: initialize() and cleanup() functions
21121
+
21122
+ ## File Structure
21123
+
21124
+ \`\`\`
21125
+ packages/demo-node/
21126
+ \u251C\u2500\u2500 package.json # Package metadata
21127
+ \u251C\u2500\u2500 README.md # Documentation
21128
+ \u2514\u2500\u2500 src/
21129
+ \u2514\u2500\u2500 index.ts # Extension + node definition + handler
21130
+ \`\`\`
21131
+
21132
+ ## Extension Structure
21133
+
21134
+ \`\`\`typescript
21135
+ export const demoNodeExtension: NetPadExtension = {
21136
+ metadata: {
21137
+ id: 'netpad-demo-node',
21138
+ name: 'Demo Node Extension',
21139
+ version: '1.0.0',
21140
+ },
21141
+ features: ['custom:demo-node'],
21142
+ workflowNodes: [
21143
+ {
21144
+ definition: {
21145
+ type: 'demo:log-message',
21146
+ label: 'Log Message',
21147
+ category: 'custom',
21148
+ // ... node appearance config
21149
+ },
21150
+ handler: async (context) => {
21151
+ // ... execution logic
21152
+ return { success: true, data: {...} };
21153
+ },
21154
+ },
21155
+ ],
21156
+ initialize: async () => { /* setup */ },
21157
+ cleanup: async () => { /* teardown */ },
21158
+ };
21159
+ \`\`\`
21160
+
21161
+ ## Handler Implementation
21162
+
21163
+ The handler receives a \`NodeExecutionContext\` with:
21164
+ - \`resolvedConfig\`: Configuration with variables resolved
21165
+ - \`inputs\`: Data from previous nodes
21166
+ - \`trigger\`: Workflow trigger information
21167
+ - Helper functions for connections and credentials
21168
+
21169
+ See the full source code in \`packages/demo-node/src/index.ts\` for complete implementation details.
21170
+ `
21171
+ }
21172
+ ]
21173
+ })
21174
+ );
20214
21175
  server.tool(
20215
21176
  "generate_form",
20216
- "Generate a complete NetPad form configuration from a description. Provide a natural language description of the form you want to create, and this tool will generate the full FormConfiguration object.",
21177
+ "Generate a complete NetPad form configuration from a description. Returns validated TypeScript code by default (can optionally return JSON). The TypeScript output includes inline types, form config, and API functions - ready to run with `npx tsx`.",
20217
21178
  {
20218
21179
  description: external_exports.string().describe("Natural language description of the form to generate"),
20219
21180
  formName: external_exports.string().describe("Name of the form"),
20220
21181
  includeMultiPage: external_exports.boolean().optional().describe("Whether to organize fields into multiple pages"),
20221
- includeTheme: external_exports.boolean().optional().describe("Whether to include theme configuration")
21182
+ includeTheme: external_exports.boolean().optional().describe("Whether to include theme configuration"),
21183
+ outputFormat: external_exports.enum(["typescript", "json"]).optional().describe('Output format: "typescript" (default) for complete working code, "json" for raw config')
20222
21184
  },
20223
- async ({ description, formName, includeMultiPage, includeTheme }) => {
21185
+ async ({ description, formName, includeMultiPage, includeTheme, outputFormat = "typescript" }) => {
20224
21186
  const schema = generateFormSchema(description, formName, {
20225
21187
  multiPage: includeMultiPage,
20226
21188
  theme: includeTheme
20227
21189
  });
21190
+ if (outputFormat === "json") {
21191
+ return {
21192
+ content: [
21193
+ {
21194
+ type: "text",
21195
+ text: JSON.stringify(schema, null, 2)
21196
+ }
21197
+ ]
21198
+ };
21199
+ }
21200
+ const slug = formName.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
21201
+ const configCode = `export const formConfig: FormConfig = ${JSON.stringify({
21202
+ ...schema,
21203
+ slug
21204
+ }, null, 2)};`;
21205
+ const functionsCode = `/**
21206
+ * Submit form data to the NetPad API.
21207
+ */
21208
+ export async function submitForm(data: Record<string, unknown>): Promise<SubmitResult> {
21209
+ try {
21210
+ const response = await fetch(
21211
+ \`\${CONFIG.baseUrl}/api/forms/\${formConfig.slug}/submit\`,
21212
+ {
21213
+ method: 'POST',
21214
+ headers: {
21215
+ 'Content-Type': 'application/json',
21216
+ 'Authorization': \`Bearer \${CONFIG.apiKey}\`,
21217
+ },
21218
+ body: JSON.stringify({ data }),
21219
+ }
21220
+ );
21221
+
21222
+ if (!response.ok) {
21223
+ const error = await response.text();
21224
+ return { success: false, error };
21225
+ }
21226
+
21227
+ const result = await response.json() as { submissionId: string };
21228
+ return { success: true, submissionId: result.submissionId };
21229
+ } catch (error) {
21230
+ return {
21231
+ success: false,
21232
+ error: error instanceof Error ? error.message : 'Unknown error',
21233
+ };
21234
+ }
21235
+ }
21236
+
21237
+ /**
21238
+ * Create the form in NetPad (run once to set up).
21239
+ */
21240
+ export async function createForm(): Promise<{ formId: string } | { error: string }> {
21241
+ try {
21242
+ const response = await fetch(\`\${CONFIG.baseUrl}/api/forms\`, {
21243
+ method: 'POST',
21244
+ headers: {
21245
+ 'Content-Type': 'application/json',
21246
+ 'Authorization': \`Bearer \${CONFIG.apiKey}\`,
21247
+ },
21248
+ body: JSON.stringify({
21249
+ ...formConfig,
21250
+ organizationId: CONFIG.organizationId,
21251
+ projectId: CONFIG.projectId,
21252
+ }),
21253
+ });
21254
+
21255
+ if (!response.ok) {
21256
+ return { error: await response.text() };
21257
+ }
21258
+
21259
+ const result = await response.json() as { form: { formId: string } };
21260
+ return { formId: result.form.formId };
21261
+ } catch (error) {
21262
+ return { error: error instanceof Error ? error.message : 'Unknown error' };
21263
+ }
21264
+ }`;
21265
+ const mainCode = `// Example usage
21266
+ async function main() {
21267
+ console.log('Creating form:', formConfig.name);
21268
+
21269
+ // Create the form (run once)
21270
+ const createResult = await createForm();
21271
+ if ('error' in createResult) {
21272
+ console.error('Failed to create form:', createResult.error);
21273
+ return;
21274
+ }
21275
+
21276
+ console.log('\u2705 Form created with ID:', createResult.formId);
21277
+ console.log('Form slug:', formConfig.slug);
21278
+ }
21279
+
21280
+ // Uncomment to run:
21281
+ // main().catch(console.error);
21282
+ `;
21283
+ const code = generateSelfContainedCode({
21284
+ title: formName,
21285
+ description: schema.description,
21286
+ includeFormTypes: true,
21287
+ configCode,
21288
+ functionsCode,
21289
+ mainCode
21290
+ });
21291
+ const output = createToolOutput({
21292
+ code,
21293
+ filename: `${slug}.ts`,
21294
+ envVars: STANDARD_ENV_VARS
21295
+ });
20228
21296
  return {
20229
21297
  content: [
20230
21298
  {
20231
21299
  type: "text",
20232
- text: JSON.stringify(schema, null, 2)
21300
+ text: formatToolOutput(output)
20233
21301
  }
20234
21302
  ]
20235
21303
  };
@@ -20381,9 +21449,451 @@ server.tool(
20381
21449
  }
20382
21450
  }
20383
21451
  );
21452
+ server.tool(
21453
+ "get_reference",
21454
+ "Get NetPad reference information: field types, operators, formula functions, validation options, theme options, or documentation. This is the recommended way to access all reference data.",
21455
+ {
21456
+ type: external_exports.enum([
21457
+ "field_types",
21458
+ "operators",
21459
+ "formula_functions",
21460
+ "validation_options",
21461
+ "theme_options",
21462
+ "documentation"
21463
+ ]).describe("The type of reference to retrieve"),
21464
+ category: external_exports.string().optional().describe('Filter by category (for field_types: "text", "selection", "date"; for formula_functions: "math", "string", "date")'),
21465
+ topic: external_exports.enum(["readme", "architecture", "quick-start", "examples", "api-client"]).optional().describe('Documentation topic (required when type is "documentation")')
21466
+ },
21467
+ async ({ type, category, topic }) => {
21468
+ switch (type) {
21469
+ case "field_types": {
21470
+ let types = FIELD_TYPES;
21471
+ if (category) {
21472
+ types = types.filter((t) => t.category.toLowerCase() === category.toLowerCase());
21473
+ }
21474
+ return {
21475
+ content: [{
21476
+ type: "text",
21477
+ text: JSON.stringify({
21478
+ referenceType: "field_types",
21479
+ count: types.length,
21480
+ categories: [...new Set(FIELD_TYPES.map((t) => t.category))],
21481
+ data: types
21482
+ }, null, 2)
21483
+ }]
21484
+ };
21485
+ }
21486
+ case "operators": {
21487
+ return {
21488
+ content: [{
21489
+ type: "text",
21490
+ text: JSON.stringify({
21491
+ referenceType: "operators",
21492
+ count: OPERATORS.length,
21493
+ data: OPERATORS
21494
+ }, null, 2)
21495
+ }]
21496
+ };
21497
+ }
21498
+ case "formula_functions": {
21499
+ let functions = FORMULA_FUNCTIONS;
21500
+ if (category) {
21501
+ functions = functions.filter((f) => f.category.toLowerCase() === category.toLowerCase());
21502
+ }
21503
+ return {
21504
+ content: [{
21505
+ type: "text",
21506
+ text: JSON.stringify({
21507
+ referenceType: "formula_functions",
21508
+ count: functions.length,
21509
+ categories: [...new Set(FORMULA_FUNCTIONS.map((f) => f.category))],
21510
+ data: functions
21511
+ }, null, 2)
21512
+ }]
21513
+ };
21514
+ }
21515
+ case "validation_options": {
21516
+ return {
21517
+ content: [{
21518
+ type: "text",
21519
+ text: JSON.stringify({
21520
+ referenceType: "validation_options",
21521
+ data: VALIDATION_OPTIONS
21522
+ }, null, 2)
21523
+ }]
21524
+ };
21525
+ }
21526
+ case "theme_options": {
21527
+ return {
21528
+ content: [{
21529
+ type: "text",
21530
+ text: JSON.stringify({
21531
+ referenceType: "theme_options",
21532
+ data: THEME_OPTIONS
21533
+ }, null, 2)
21534
+ }]
21535
+ };
21536
+ }
21537
+ case "documentation": {
21538
+ const docs = {
21539
+ "readme": DOCUMENTATION.readme,
21540
+ "architecture": ARCHITECTURE_GUIDE,
21541
+ "quick-start": QUICK_START_GUIDE,
21542
+ "examples": EXAMPLES,
21543
+ "api-client": DOCUMENTATION.apiClient
21544
+ };
21545
+ const selectedTopic = topic || "readme";
21546
+ return {
21547
+ content: [{
21548
+ type: "text",
21549
+ text: `# NetPad Documentation: ${selectedTopic}
21550
+
21551
+ ${docs[selectedTopic] || "Documentation not found"}
21552
+
21553
+ ---
21554
+ Available topics: ${Object.keys(docs).join(", ")}`
21555
+ }]
21556
+ };
21557
+ }
21558
+ default:
21559
+ return {
21560
+ content: [{
21561
+ type: "text",
21562
+ text: "Invalid reference type. Use: field_types, operators, formula_functions, validation_options, theme_options, or documentation"
21563
+ }]
21564
+ };
21565
+ }
21566
+ }
21567
+ );
21568
+ server.tool(
21569
+ "browse_templates",
21570
+ "Browse all NetPad templates: forms (25+), applications (7), workflows (5), conversational (4), and query templates. This is the recommended way to discover and access all templates.",
21571
+ {
21572
+ templateType: external_exports.enum([
21573
+ "form",
21574
+ "application",
21575
+ "workflow",
21576
+ "conversational",
21577
+ "query",
21578
+ "use_case",
21579
+ "all"
21580
+ ]).describe("Type of templates to browse"),
21581
+ action: external_exports.enum(["list", "get", "categories"]).optional().describe('Action: "list" (default) returns summaries, "get" returns full details, "categories" lists available categories'),
21582
+ templateId: external_exports.string().optional().describe('Template ID (required when action is "get")'),
21583
+ category: external_exports.string().optional().describe("Filter templates by category"),
21584
+ search: external_exports.string().optional().describe("Search templates by name, description, or tags (form templates only)")
21585
+ },
21586
+ async ({ templateType, action = "list", templateId, category, search }) => {
21587
+ const formatSummary = (id, name, desc, cat, extra = {}) => ({
21588
+ id,
21589
+ name,
21590
+ description: desc,
21591
+ category: cat,
21592
+ ...extra
21593
+ });
21594
+ switch (templateType) {
21595
+ case "form": {
21596
+ if (action === "categories") {
21597
+ return {
21598
+ content: [{
21599
+ type: "text",
21600
+ text: JSON.stringify({
21601
+ templateType: "form",
21602
+ categories: TEMPLATE_CATEGORIES,
21603
+ totalTemplates: Object.keys(FORM_TEMPLATES).length
21604
+ }, null, 2)
21605
+ }]
21606
+ };
21607
+ }
21608
+ if (action === "get" && templateId) {
21609
+ const template = getTemplateById(templateId);
21610
+ if (!template) {
21611
+ return {
21612
+ content: [{
21613
+ type: "text",
21614
+ text: JSON.stringify({
21615
+ error: `Form template "${templateId}" not found`,
21616
+ availableTemplates: Object.keys(FORM_TEMPLATES)
21617
+ }, null, 2)
21618
+ }]
21619
+ };
21620
+ }
21621
+ return { content: [{ type: "text", text: JSON.stringify(template, null, 2) }] };
21622
+ }
21623
+ let templates;
21624
+ if (search) {
21625
+ templates = searchTemplates(search);
21626
+ } else if (category) {
21627
+ templates = getTemplatesByCategory(category);
21628
+ } else {
21629
+ templates = Object.values(FORM_TEMPLATES);
21630
+ }
21631
+ const summary = templates.map((t) => formatSummary(t.id, t.name, t.description, t.category, {
21632
+ tags: t.tags,
21633
+ icon: t.icon,
21634
+ fieldCount: t.fields.length,
21635
+ hasMultiPage: !!t.multiPage?.enabled
21636
+ }));
21637
+ return {
21638
+ content: [{
21639
+ type: "text",
21640
+ text: JSON.stringify({
21641
+ templateType: "form",
21642
+ templates: summary,
21643
+ total: summary.length,
21644
+ categories: [...new Set(templates.map((t) => t.category))]
21645
+ }, null, 2)
21646
+ }]
21647
+ };
21648
+ }
21649
+ case "application": {
21650
+ if (action === "categories") {
21651
+ return {
21652
+ content: [{
21653
+ type: "text",
21654
+ text: JSON.stringify({
21655
+ templateType: "application",
21656
+ categories: [...new Set(Object.values(APPLICATION_TEMPLATES).map((t) => t.category))],
21657
+ totalTemplates: Object.keys(APPLICATION_TEMPLATES).length
21658
+ }, null, 2)
21659
+ }]
21660
+ };
21661
+ }
21662
+ if (action === "get" && templateId) {
21663
+ const template = APPLICATION_TEMPLATES[templateId];
21664
+ if (!template) {
21665
+ return {
21666
+ content: [{
21667
+ type: "text",
21668
+ text: JSON.stringify({
21669
+ error: `Application template "${templateId}" not found`,
21670
+ availableTemplates: Object.keys(APPLICATION_TEMPLATES)
21671
+ }, null, 2)
21672
+ }]
21673
+ };
21674
+ }
21675
+ return { content: [{ type: "text", text: JSON.stringify(template, null, 2) }] };
21676
+ }
21677
+ let templates = Object.values(APPLICATION_TEMPLATES);
21678
+ if (category) {
21679
+ templates = templates.filter((t) => t.category.toLowerCase() === category.toLowerCase());
21680
+ }
21681
+ const summary = templates.map((t) => formatSummary(t.id, t.name, t.description, t.category, {
21682
+ tags: t.tags,
21683
+ formsCount: t.structure.forms.length,
21684
+ workflowsCount: t.structure.workflows.length
21685
+ }));
21686
+ return {
21687
+ content: [{
21688
+ type: "text",
21689
+ text: JSON.stringify({
21690
+ templateType: "application",
21691
+ templates: summary,
21692
+ total: summary.length,
21693
+ categories: [...new Set(Object.values(APPLICATION_TEMPLATES).map((t) => t.category))]
21694
+ }, null, 2)
21695
+ }]
21696
+ };
21697
+ }
21698
+ case "workflow": {
21699
+ if (action === "categories") {
21700
+ return {
21701
+ content: [{
21702
+ type: "text",
21703
+ text: JSON.stringify({
21704
+ templateType: "workflow",
21705
+ categories: [...new Set(Object.values(WORKFLOW_TEMPLATES).map((t) => t.category))],
21706
+ totalTemplates: Object.keys(WORKFLOW_TEMPLATES).length
21707
+ }, null, 2)
21708
+ }]
21709
+ };
21710
+ }
21711
+ if (action === "get" && templateId) {
21712
+ const template = WORKFLOW_TEMPLATES[templateId];
21713
+ if (!template) {
21714
+ return {
21715
+ content: [{
21716
+ type: "text",
21717
+ text: JSON.stringify({
21718
+ error: `Workflow template "${templateId}" not found`,
21719
+ availableTemplates: Object.keys(WORKFLOW_TEMPLATES)
21720
+ }, null, 2)
21721
+ }]
21722
+ };
21723
+ }
21724
+ const workflowForUI = {
21725
+ id: template.id,
21726
+ name: template.name,
21727
+ description: template.description,
21728
+ category: template.category,
21729
+ tags: template.tags,
21730
+ canvas: {
21731
+ nodes: template.nodes,
21732
+ edges: template.edges
21733
+ }
21734
+ };
21735
+ return { content: [{ type: "text", text: JSON.stringify(workflowForUI, null, 2) }] };
21736
+ }
21737
+ let templates = Object.values(WORKFLOW_TEMPLATES);
21738
+ if (category) {
21739
+ templates = templates.filter((t) => t.category.toLowerCase() === category.toLowerCase());
21740
+ }
21741
+ const summary = templates.map((t) => formatSummary(t.id, t.name, t.description, t.category, {
21742
+ tags: t.tags,
21743
+ nodesCount: t.nodes.length,
21744
+ edgesCount: t.edges.length
21745
+ }));
21746
+ return {
21747
+ content: [{
21748
+ type: "text",
21749
+ text: JSON.stringify({
21750
+ templateType: "workflow",
21751
+ templates: summary,
21752
+ total: summary.length,
21753
+ categories: [...new Set(Object.values(WORKFLOW_TEMPLATES).map((t) => t.category))]
21754
+ }, null, 2)
21755
+ }]
21756
+ };
21757
+ }
21758
+ case "conversational": {
21759
+ if (action === "categories") {
21760
+ return {
21761
+ content: [{
21762
+ type: "text",
21763
+ text: JSON.stringify({
21764
+ templateType: "conversational",
21765
+ categories: [...new Set(Object.values(CONVERSATIONAL_TEMPLATES).map((t) => t.category))],
21766
+ totalTemplates: Object.keys(CONVERSATIONAL_TEMPLATES).length
21767
+ }, null, 2)
21768
+ }]
21769
+ };
21770
+ }
21771
+ if (action === "get" && templateId) {
21772
+ const template = CONVERSATIONAL_TEMPLATES[templateId];
21773
+ if (!template) {
21774
+ return {
21775
+ content: [{
21776
+ type: "text",
21777
+ text: JSON.stringify({
21778
+ error: `Conversational template "${templateId}" not found`,
21779
+ availableTemplates: Object.keys(CONVERSATIONAL_TEMPLATES)
21780
+ }, null, 2)
21781
+ }]
21782
+ };
21783
+ }
21784
+ return { content: [{ type: "text", text: JSON.stringify(template, null, 2) }] };
21785
+ }
21786
+ let templates = Object.values(CONVERSATIONAL_TEMPLATES);
21787
+ if (category) {
21788
+ templates = templates.filter((t) => t.category.toLowerCase() === category.toLowerCase());
21789
+ }
21790
+ const summary = templates.map((t) => formatSummary(t.id, t.name, t.description, t.category, {
21791
+ tags: t.tags,
21792
+ topicsCount: t.defaultConfig.topics.length,
21793
+ extractionFieldsCount: t.defaultConfig.extractionSchema.length
21794
+ }));
21795
+ return {
21796
+ content: [{
21797
+ type: "text",
21798
+ text: JSON.stringify({
21799
+ templateType: "conversational",
21800
+ templates: summary,
21801
+ total: summary.length,
21802
+ categories: [...new Set(Object.values(CONVERSATIONAL_TEMPLATES).map((t) => t.category))]
21803
+ }, null, 2)
21804
+ }]
21805
+ };
21806
+ }
21807
+ case "query": {
21808
+ if (action === "get" && templateId) {
21809
+ const template = getQueryTemplate(templateId);
21810
+ if (!template) {
21811
+ return {
21812
+ content: [{
21813
+ type: "text",
21814
+ text: JSON.stringify({
21815
+ error: `Query template "${templateId}" not found`,
21816
+ availableTemplates: listQueryTemplates()
21817
+ }, null, 2)
21818
+ }]
21819
+ };
21820
+ }
21821
+ return { content: [{ type: "text", text: JSON.stringify(template, null, 2) }] };
21822
+ }
21823
+ const templates = listQueryTemplates();
21824
+ return {
21825
+ content: [{
21826
+ type: "text",
21827
+ text: JSON.stringify({
21828
+ templateType: "query",
21829
+ templates,
21830
+ total: templates.length
21831
+ }, null, 2)
21832
+ }]
21833
+ };
21834
+ }
21835
+ case "use_case": {
21836
+ if (action === "get" && templateId) {
21837
+ const template = USE_CASE_TEMPLATES[templateId];
21838
+ if (!template) {
21839
+ return {
21840
+ content: [{
21841
+ type: "text",
21842
+ text: JSON.stringify({
21843
+ error: `Use case template "${templateId}" not found`,
21844
+ availableTemplates: Object.keys(USE_CASE_TEMPLATES)
21845
+ }, null, 2)
21846
+ }]
21847
+ };
21848
+ }
21849
+ return { content: [{ type: "text", text: JSON.stringify(template, null, 2) }] };
21850
+ }
21851
+ const useCaseIds = Object.keys(USE_CASE_TEMPLATES);
21852
+ return {
21853
+ content: [{
21854
+ type: "text",
21855
+ text: JSON.stringify({
21856
+ templateType: "use_case",
21857
+ templates: useCaseIds,
21858
+ total: useCaseIds.length,
21859
+ note: 'Use action="get" with templateId to retrieve full template details'
21860
+ }, null, 2)
21861
+ }]
21862
+ };
21863
+ }
21864
+ case "all": {
21865
+ return {
21866
+ content: [{
21867
+ type: "text",
21868
+ text: JSON.stringify({
21869
+ overview: "NetPad Template Catalog",
21870
+ templateTypes: [
21871
+ { type: "form", count: Object.keys(FORM_TEMPLATES).length, categories: TEMPLATE_CATEGORIES.length, description: "Pre-built form configurations with fields, validation, and styling" },
21872
+ { type: "application", count: Object.keys(APPLICATION_TEMPLATES).length, description: "Complete applications with forms, workflows, and settings" },
21873
+ { type: "workflow", count: Object.keys(WORKFLOW_TEMPLATES).length, description: "Automated workflow patterns with triggers, conditions, and actions" },
21874
+ { type: "conversational", count: Object.keys(CONVERSATIONAL_TEMPLATES).length, description: "AI-powered conversational form templates" },
21875
+ { type: "query", count: listQueryTemplates().length, description: "MongoDB query patterns for common operations" },
21876
+ { type: "use_case", count: Object.keys(USE_CASE_TEMPLATES).length, description: "Industry use case blueprints" }
21877
+ ],
21878
+ totalTemplates: Object.keys(FORM_TEMPLATES).length + Object.keys(APPLICATION_TEMPLATES).length + Object.keys(WORKFLOW_TEMPLATES).length + Object.keys(CONVERSATIONAL_TEMPLATES).length + listQueryTemplates().length + Object.keys(USE_CASE_TEMPLATES).length,
21879
+ usage: 'Use templateType to filter, action="get" with templateId to get details, or action="categories" to see available categories'
21880
+ }, null, 2)
21881
+ }]
21882
+ };
21883
+ }
21884
+ default:
21885
+ return {
21886
+ content: [{
21887
+ type: "text",
21888
+ text: "Invalid templateType. Use: form, application, workflow, conversational, query, use_case, or all"
21889
+ }]
21890
+ };
21891
+ }
21892
+ }
21893
+ );
20384
21894
  server.tool(
20385
21895
  "list_field_types",
20386
- "List all supported field types in @netpad/forms with their descriptions and usage.",
21896
+ '[DEPRECATED - use get_reference with type="field_types" instead] List all supported field types in @netpad/forms with their descriptions and usage.',
20387
21897
  {
20388
21898
  category: external_exports.string().optional().describe('Filter by category (e.g., "text", "selection", "date")')
20389
21899
  },
@@ -20396,7 +21906,9 @@ server.tool(
20396
21906
  content: [
20397
21907
  {
20398
21908
  type: "text",
20399
- text: JSON.stringify(types, null, 2)
21909
+ text: `DEPRECATED: This tool is deprecated. Use get_reference with type="field_types" instead.
21910
+
21911
+ ${JSON.stringify(types, null, 2)}`
20400
21912
  }
20401
21913
  ]
20402
21914
  };
@@ -20404,14 +21916,16 @@ server.tool(
20404
21916
  );
20405
21917
  server.tool(
20406
21918
  "list_operators",
20407
- "List all available conditional logic operators with descriptions.",
21919
+ '[DEPRECATED - use get_reference with type="operators" instead] List all available conditional logic operators with descriptions.',
20408
21920
  {},
20409
21921
  async () => {
20410
21922
  return {
20411
21923
  content: [
20412
21924
  {
20413
21925
  type: "text",
20414
- text: JSON.stringify(OPERATORS, null, 2)
21926
+ text: `DEPRECATED: This tool is deprecated. Use get_reference with type="operators" instead.
21927
+
21928
+ ${JSON.stringify(OPERATORS, null, 2)}`
20415
21929
  }
20416
21930
  ]
20417
21931
  };
@@ -20419,7 +21933,7 @@ server.tool(
20419
21933
  );
20420
21934
  server.tool(
20421
21935
  "list_formula_functions",
20422
- "List all available formula functions for computed fields.",
21936
+ '[DEPRECATED - use get_reference with type="formula_functions" instead] List all available formula functions for computed fields.',
20423
21937
  {
20424
21938
  category: external_exports.string().optional().describe('Filter by category (e.g., "math", "string", "date")')
20425
21939
  },
@@ -20432,7 +21946,9 @@ server.tool(
20432
21946
  content: [
20433
21947
  {
20434
21948
  type: "text",
20435
- text: JSON.stringify(functions, null, 2)
21949
+ text: `DEPRECATED: This tool is deprecated. Use get_reference with type="formula_functions" instead.
21950
+
21951
+ ${JSON.stringify(functions, null, 2)}`
20436
21952
  }
20437
21953
  ]
20438
21954
  };
@@ -20440,14 +21956,16 @@ server.tool(
20440
21956
  );
20441
21957
  server.tool(
20442
21958
  "list_validation_options",
20443
- "List all available validation options for form fields.",
21959
+ '[DEPRECATED - use get_reference with type="validation_options" instead] List all available validation options for form fields.',
20444
21960
  {},
20445
21961
  async () => {
20446
21962
  return {
20447
21963
  content: [
20448
21964
  {
20449
21965
  type: "text",
20450
- text: JSON.stringify(VALIDATION_OPTIONS, null, 2)
21966
+ text: `DEPRECATED: This tool is deprecated. Use get_reference with type="validation_options" instead.
21967
+
21968
+ ${JSON.stringify(VALIDATION_OPTIONS, null, 2)}`
20451
21969
  }
20452
21970
  ]
20453
21971
  };
@@ -20455,14 +21973,16 @@ server.tool(
20455
21973
  );
20456
21974
  server.tool(
20457
21975
  "list_theme_options",
20458
- "List all available theme customization options.",
21976
+ '[DEPRECATED - use get_reference with type="theme_options" instead] List all available theme customization options.',
20459
21977
  {},
20460
21978
  async () => {
20461
21979
  return {
20462
21980
  content: [
20463
21981
  {
20464
21982
  type: "text",
20465
- text: JSON.stringify(THEME_OPTIONS, null, 2)
21983
+ text: `DEPRECATED: This tool is deprecated. Use get_reference with type="theme_options" instead.
21984
+
21985
+ ${JSON.stringify(THEME_OPTIONS, null, 2)}`
20466
21986
  }
20467
21987
  ]
20468
21988
  };
@@ -20470,7 +21990,7 @@ server.tool(
20470
21990
  );
20471
21991
  server.tool(
20472
21992
  "get_documentation",
20473
- "Get NetPad forms documentation. Use this to learn about features, APIs, and best practices.",
21993
+ '[DEPRECATED - use get_reference with type="documentation" instead] Get NetPad forms documentation. Use this to learn about features, APIs, and best practices.',
20474
21994
  {
20475
21995
  topic: external_exports.enum(["readme", "architecture", "quick-start", "examples", "api-client"]).describe("The documentation topic to retrieve")
20476
21996
  },
@@ -20486,7 +22006,9 @@ server.tool(
20486
22006
  content: [
20487
22007
  {
20488
22008
  type: "text",
20489
- text: docs[topic] || "Documentation not found"
22009
+ text: `DEPRECATED: This tool is deprecated. Use get_reference with type="documentation" and topic="${topic}" instead.
22010
+
22011
+ ${docs[topic] || "Documentation not found"}`
20490
22012
  }
20491
22013
  ]
20492
22014
  };
@@ -20494,73 +22016,404 @@ server.tool(
20494
22016
  );
20495
22017
  server.tool(
20496
22018
  "generate_react_code",
20497
- "Generate React component code that uses @netpad/forms to render a form.",
22019
+ "Generate a complete, self-contained React component with inline types and fetch-based API calls. No external @netpad/* imports required - ready to copy-paste and use.",
20498
22020
  {
20499
22021
  formConfig: external_exports.string().describe("The form configuration JSON"),
20500
22022
  componentName: external_exports.string().optional().describe("Name of the React component"),
20501
22023
  includeSubmitHandler: external_exports.boolean().optional().describe("Whether to include a submit handler"),
20502
- useNetPadClient: external_exports.boolean().optional().describe("Whether to use NetPad API client for submission")
22024
+ includeApiSubmission: external_exports.boolean().optional().describe("Whether to submit to NetPad API via fetch (default: true)")
20503
22025
  },
20504
- async ({ formConfig, componentName = "MyForm", includeSubmitHandler = true, useNetPadClient = false }) => {
20505
- let code = `import { FormRenderer } from '@netpad/forms';
20506
- `;
20507
- if (useNetPadClient) {
20508
- code += `import { createNetPadClient } from '@netpad/forms';
20509
- `;
22026
+ async ({ formConfig, componentName = "MyForm", includeSubmitHandler = true, includeApiSubmission = true }) => {
22027
+ let parsedConfig;
22028
+ try {
22029
+ parsedConfig = JSON.parse(formConfig);
22030
+ } catch {
22031
+ parsedConfig = { name: componentName, slug: componentName.toLowerCase().replace(/\s+/g, "-") };
22032
+ }
22033
+ const formSlug = parsedConfig.slug || parsedConfig.name?.toLowerCase().replace(/\s+/g, "-") || "form";
22034
+ const code = `/**
22035
+ * ${componentName} - React Form Component
22036
+ * Generated by NetPad
22037
+ *
22038
+ * This is a self-contained component with inline types.
22039
+ * No @netpad/* imports required.
22040
+ */
22041
+
22042
+ 'use client';
22043
+
22044
+ import React, { useState, FormEvent, ChangeEvent } from 'react';
22045
+
22046
+ // ============================================================================
22047
+ // Types (inline - no external dependencies)
22048
+ // ============================================================================
22049
+
22050
+ interface FormFieldOption {
22051
+ label: string;
22052
+ value: string;
22053
+ }
22054
+
22055
+ interface FormFieldValidation {
22056
+ min?: number;
22057
+ max?: number;
22058
+ minLength?: number;
22059
+ maxLength?: number;
22060
+ pattern?: string;
22061
+ errorMessage?: string;
22062
+ }
22063
+
22064
+ interface FormField {
22065
+ path: string;
22066
+ label: string;
22067
+ type: string;
22068
+ included?: boolean;
22069
+ required?: boolean;
22070
+ placeholder?: string;
22071
+ helpText?: string;
22072
+ options?: FormFieldOption[];
22073
+ validation?: FormFieldValidation;
22074
+ fieldWidth?: 'full' | 'half' | 'third' | 'quarter';
22075
+ }
22076
+
22077
+ interface FormConfig {
22078
+ name: string;
22079
+ slug?: string;
22080
+ description?: string;
22081
+ fieldConfigs: FormField[];
22082
+ submitButtonText?: string;
22083
+ successMessage?: string;
22084
+ }
22085
+
22086
+ interface SubmitResult {
22087
+ success: boolean;
22088
+ submissionId?: string;
22089
+ error?: string;
22090
+ }
22091
+
22092
+ // ============================================================================
22093
+ // Configuration
22094
+ // ============================================================================
22095
+
22096
+ const CONFIG = {
22097
+ baseUrl: process.env.NEXT_PUBLIC_NETPAD_URL ?? 'https://api.netpad.io',
22098
+ apiKey: process.env.NEXT_PUBLIC_NETPAD_API_KEY ?? '',
22099
+ };
22100
+
22101
+ const formConfig: FormConfig = ${formConfig};
22102
+
22103
+ // ============================================================================
22104
+ // API Functions (using fetch - no SDK required)
22105
+ // ============================================================================
22106
+
22107
+ ${includeApiSubmission ? `async function submitToApi(data: Record<string, unknown>): Promise<SubmitResult> {
22108
+ try {
22109
+ const response = await fetch(
22110
+ \`\${CONFIG.baseUrl}/api/forms/${formSlug}/submit\`,
22111
+ {
22112
+ method: 'POST',
22113
+ headers: {
22114
+ 'Content-Type': 'application/json',
22115
+ 'Authorization': \`Bearer \${CONFIG.apiKey}\`,
22116
+ },
22117
+ body: JSON.stringify({ data }),
22118
+ }
22119
+ );
22120
+
22121
+ if (!response.ok) {
22122
+ const errorText = await response.text();
22123
+ return { success: false, error: errorText };
20510
22124
  }
20511
- code += `import type { FormConfiguration } from '@netpad/forms';
20512
22125
 
20513
- `;
20514
- code += `const formConfig: FormConfiguration = ${formConfig};
22126
+ const result = await response.json() as { submissionId: string };
22127
+ return { success: true, submissionId: result.submissionId };
22128
+ } catch (error) {
22129
+ return {
22130
+ success: false,
22131
+ error: error instanceof Error ? error.message : 'Unknown error',
22132
+ };
22133
+ }
22134
+ }` : ""}
22135
+
22136
+ // ============================================================================
22137
+ // Component
22138
+ // ============================================================================
22139
+
22140
+ export function ${componentName}() {
22141
+ const [formData, setFormData] = useState<Record<string, string>>({});
22142
+ const [isSubmitting, setIsSubmitting] = useState(false);
22143
+ const [submitResult, setSubmitResult] = useState<SubmitResult | null>(null);
22144
+ const [errors, setErrors] = useState<Record<string, string>>({});
22145
+
22146
+ const handleChange = (e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) => {
22147
+ const { name, value } = e.target;
22148
+ setFormData(prev => ({ ...prev, [name]: value }));
22149
+ // Clear error when field is edited
22150
+ if (errors[name]) {
22151
+ setErrors(prev => {
22152
+ const newErrors = { ...prev };
22153
+ delete newErrors[name];
22154
+ return newErrors;
22155
+ });
22156
+ }
22157
+ };
20515
22158
 
20516
- `;
20517
- if (useNetPadClient) {
20518
- code += `const client = createNetPadClient({
20519
- baseUrl: process.env.NEXT_PUBLIC_NETPAD_URL || 'https://your-netpad-instance.com',
20520
- apiKey: process.env.NETPAD_API_KEY || '',
20521
- });
22159
+ const validateForm = (): boolean => {
22160
+ const newErrors: Record<string, string> = {};
20522
22161
 
20523
- `;
22162
+ for (const field of formConfig.fieldConfigs) {
22163
+ if (!field.included) continue;
22164
+
22165
+ const value = formData[field.path] || '';
22166
+
22167
+ if (field.required && !value) {
22168
+ newErrors[field.path] = \`\${field.label} is required\`;
22169
+ }
22170
+
22171
+ if (field.validation) {
22172
+ if (field.validation.minLength && value.length < field.validation.minLength) {
22173
+ newErrors[field.path] = field.validation.errorMessage || \`Minimum \${field.validation.minLength} characters required\`;
22174
+ }
22175
+ if (field.validation.maxLength && value.length > field.validation.maxLength) {
22176
+ newErrors[field.path] = field.validation.errorMessage || \`Maximum \${field.validation.maxLength} characters allowed\`;
22177
+ }
22178
+ if (field.validation.pattern && !new RegExp(field.validation.pattern).test(value)) {
22179
+ newErrors[field.path] = field.validation.errorMessage || 'Invalid format';
22180
+ }
22181
+ }
20524
22182
  }
20525
- code += `export function ${componentName}() {
20526
- `;
20527
- if (includeSubmitHandler) {
20528
- if (useNetPadClient) {
20529
- code += ` const handleSubmit = async (data: Record<string, unknown>) => {
22183
+
22184
+ setErrors(newErrors);
22185
+ return Object.keys(newErrors).length === 0;
22186
+ };
22187
+
22188
+ ${includeSubmitHandler ? ` const handleSubmit = async (e: FormEvent) => {
22189
+ e.preventDefault();
22190
+
22191
+ if (!validateForm()) return;
22192
+
22193
+ setIsSubmitting(true);
22194
+ setSubmitResult(null);
22195
+
20530
22196
  try {
20531
- const result = await client.submitForm(formConfig.formId || formConfig.slug || '', data);
20532
- console.log('Submission successful:', result);
20533
- // Handle success (e.g., show notification, redirect)
22197
+ ${includeApiSubmission ? ` const result = await submitToApi(formData);
22198
+ setSubmitResult(result);` : ` // TODO: Implement your submission logic
22199
+ console.log('Form data:', formData);
22200
+ setSubmitResult({ success: true });`}
20534
22201
  } catch (error) {
20535
- console.error('Submission failed:', error);
20536
- // Handle error
22202
+ setSubmitResult({
22203
+ success: false,
22204
+ error: error instanceof Error ? error.message : 'Submission failed',
22205
+ });
22206
+ } finally {
22207
+ setIsSubmitting(false);
20537
22208
  }
20538
- };
22209
+ };` : ""}
20539
22210
 
20540
- `;
20541
- } else {
20542
- code += ` const handleSubmit = async (data: Record<string, unknown>) => {
20543
- console.log('Form submitted:', data);
20544
- // TODO: Handle form submission
22211
+ const renderField = (field: FormField) => {
22212
+ if (!field.included) return null;
22213
+
22214
+ const commonProps = {
22215
+ id: field.path,
22216
+ name: field.path,
22217
+ value: formData[field.path] || '',
22218
+ onChange: handleChange,
22219
+ required: field.required,
22220
+ placeholder: field.placeholder,
22221
+ disabled: isSubmitting,
22222
+ className: \`form-field \${errors[field.path] ? 'error' : ''}\`,
22223
+ };
22224
+
22225
+ let input;
22226
+
22227
+ switch (field.type) {
22228
+ case 'long_text':
22229
+ input = <textarea {...commonProps} rows={4} />;
22230
+ break;
22231
+ case 'email':
22232
+ input = <input {...commonProps} type="email" />;
22233
+ break;
22234
+ case 'number':
22235
+ input = <input {...commonProps} type="number" />;
22236
+ break;
22237
+ case 'phone':
22238
+ input = <input {...commonProps} type="tel" />;
22239
+ break;
22240
+ case 'date':
22241
+ input = <input {...commonProps} type="date" />;
22242
+ break;
22243
+ case 'dropdown':
22244
+ case 'select':
22245
+ input = (
22246
+ <select {...commonProps}>
22247
+ <option value="">Select...</option>
22248
+ {field.options?.map(opt => (
22249
+ <option key={opt.value} value={opt.value}>{opt.label}</option>
22250
+ ))}
22251
+ </select>
22252
+ );
22253
+ break;
22254
+ default:
22255
+ input = <input {...commonProps} type="text" />;
22256
+ }
22257
+
22258
+ return (
22259
+ <div key={field.path} className={\`form-group field-width-\${field.fieldWidth || 'full'}\`}>
22260
+ <label htmlFor={field.path}>
22261
+ {field.label}
22262
+ {field.required && <span className="required">*</span>}
22263
+ </label>
22264
+ {input}
22265
+ {field.helpText && <small className="help-text">{field.helpText}</small>}
22266
+ {errors[field.path] && <span className="error-message">{errors[field.path]}</span>}
22267
+ </div>
22268
+ );
20545
22269
  };
20546
22270
 
20547
- `;
20548
- }
20549
- }
20550
- code += ` return (
20551
- <FormRenderer
20552
- config={formConfig}
20553
- onSubmit={${includeSubmitHandler ? "handleSubmit" : "undefined"}}
20554
- mode="create"
20555
- />
22271
+ if (submitResult?.success) {
22272
+ return (
22273
+ <div className="form-success">
22274
+ <h3>\u2713 {formConfig.successMessage || 'Thank you for your submission!'}</h3>
22275
+ {submitResult.submissionId && (
22276
+ <p>Confirmation ID: {submitResult.submissionId}</p>
22277
+ )}
22278
+ <button onClick={() => {
22279
+ setSubmitResult(null);
22280
+ setFormData({});
22281
+ }}>
22282
+ Submit Another
22283
+ </button>
22284
+ </div>
22285
+ );
22286
+ }
22287
+
22288
+ return (
22289
+ <div className="form-container">
22290
+ <h2>{formConfig.name}</h2>
22291
+ {formConfig.description && <p className="form-description">{formConfig.description}</p>}
22292
+
22293
+ <form onSubmit={${includeSubmitHandler ? "handleSubmit" : "(e) => e.preventDefault()"}}>
22294
+ {formConfig.fieldConfigs.map(renderField)}
22295
+
22296
+ {submitResult?.error && (
22297
+ <div className="form-error">
22298
+ <p>Error: {submitResult.error}</p>
22299
+ </div>
22300
+ )}
22301
+
22302
+ <button type="submit" disabled={isSubmitting} className="submit-button">
22303
+ {isSubmitting ? 'Submitting...' : (formConfig.submitButtonText || 'Submit')}
22304
+ </button>
22305
+ </form>
22306
+
22307
+ <style>{\`
22308
+ .form-container {
22309
+ max-width: 600px;
22310
+ margin: 0 auto;
22311
+ padding: 20px;
22312
+ }
22313
+ .form-group {
22314
+ margin-bottom: 16px;
22315
+ }
22316
+ .form-group label {
22317
+ display: block;
22318
+ margin-bottom: 4px;
22319
+ font-weight: 500;
22320
+ }
22321
+ .form-group input,
22322
+ .form-group textarea,
22323
+ .form-group select {
22324
+ width: 100%;
22325
+ padding: 8px 12px;
22326
+ border: 1px solid #ddd;
22327
+ border-radius: 4px;
22328
+ font-size: 14px;
22329
+ }
22330
+ .form-group input:focus,
22331
+ .form-group textarea:focus,
22332
+ .form-group select:focus {
22333
+ outline: none;
22334
+ border-color: #1976d2;
22335
+ }
22336
+ .form-group .error {
22337
+ border-color: #d32f2f;
22338
+ }
22339
+ .required {
22340
+ color: #d32f2f;
22341
+ margin-left: 4px;
22342
+ }
22343
+ .help-text {
22344
+ display: block;
22345
+ color: #666;
22346
+ margin-top: 4px;
22347
+ }
22348
+ .error-message {
22349
+ display: block;
22350
+ color: #d32f2f;
22351
+ font-size: 12px;
22352
+ margin-top: 4px;
22353
+ }
22354
+ .submit-button {
22355
+ background: #1976d2;
22356
+ color: white;
22357
+ padding: 12px 24px;
22358
+ border: none;
22359
+ border-radius: 4px;
22360
+ cursor: pointer;
22361
+ font-size: 16px;
22362
+ }
22363
+ .submit-button:hover {
22364
+ background: #1565c0;
22365
+ }
22366
+ .submit-button:disabled {
22367
+ background: #ccc;
22368
+ cursor: not-allowed;
22369
+ }
22370
+ .form-success {
22371
+ text-align: center;
22372
+ padding: 40px;
22373
+ }
22374
+ .form-error {
22375
+ background: #ffebee;
22376
+ color: #c62828;
22377
+ padding: 12px;
22378
+ border-radius: 4px;
22379
+ margin-bottom: 16px;
22380
+ }
22381
+ .field-width-half {
22382
+ display: inline-block;
22383
+ width: calc(50% - 8px);
22384
+ margin-right: 8px;
22385
+ }
22386
+ .field-width-third {
22387
+ display: inline-block;
22388
+ width: calc(33.33% - 8px);
22389
+ margin-right: 8px;
22390
+ }
22391
+ .field-width-quarter {
22392
+ display: inline-block;
22393
+ width: calc(25% - 8px);
22394
+ margin-right: 8px;
22395
+ }
22396
+ \`}</style>
22397
+ </div>
20556
22398
  );
20557
22399
  }
22400
+
22401
+ export default ${componentName};
20558
22402
  `;
22403
+ const output = createToolOutput({
22404
+ code,
22405
+ filename: `${componentName}.tsx`,
22406
+ envVars: [
22407
+ { name: "NEXT_PUBLIC_NETPAD_URL", description: "NetPad API URL (client-side accessible)", example: "https://api.netpad.io" },
22408
+ { name: "NEXT_PUBLIC_NETPAD_API_KEY", description: "NetPad API key (client-side accessible)", example: "np_live_xxxxx" }
22409
+ ],
22410
+ dependencies: ["react"]
22411
+ });
20559
22412
  return {
20560
22413
  content: [
20561
22414
  {
20562
22415
  type: "text",
20563
- text: code
22416
+ text: formatToolOutput(output)
20564
22417
  }
20565
22418
  ]
20566
22419
  };
@@ -20661,7 +22514,7 @@ server.tool(
20661
22514
  );
20662
22515
  server.tool(
20663
22516
  "get_use_case_template",
20664
- "Get a pre-built template for common form use cases including form configuration and workflow setup.",
22517
+ '[DEPRECATED - use browse_templates with templateType="use_case" and action="get"] Get a pre-built template for common form use cases including form configuration and workflow setup.',
20665
22518
  {
20666
22519
  useCase: external_exports.enum(["leadCapture", "eventRegistration", "feedbackSurvey"]).describe("The use case template to retrieve")
20667
22520
  },
@@ -21050,7 +22903,7 @@ ${context ? `
21050
22903
  }
21051
22904
  server.tool(
21052
22905
  "list_application_templates",
21053
- "List all available application templates for creating new NetPad applications. Templates include pre-configured forms, workflows, and settings.",
22906
+ '[DEPRECATED - use browse_templates with templateType="application"] List all available application templates for creating new NetPad applications. Templates include pre-configured forms, workflows, and settings.',
21054
22907
  {
21055
22908
  category: external_exports.string().optional().describe('Filter by category (e.g., "lead-generation", "events", "surveys", "hr", "ecommerce")')
21056
22909
  },
@@ -21082,7 +22935,7 @@ server.tool(
21082
22935
  );
21083
22936
  server.tool(
21084
22937
  "get_application_template",
21085
- "Get detailed information about a specific application template including its forms, workflows, and field configurations.",
22938
+ '[DEPRECATED - use browse_templates with templateType="application" and action="get"] Get detailed information about a specific application template including its forms, workflows, and field configurations.',
21086
22939
  {
21087
22940
  templateId: external_exports.enum(["contact-form", "lead-capture", "event-registration", "feedback-survey", "job-application", "order-form", "blank"]).describe("The template ID")
21088
22941
  },
@@ -21106,7 +22959,7 @@ server.tool(
21106
22959
  );
21107
22960
  server.tool(
21108
22961
  "create_application",
21109
- "Generate code to create a new NetPad application. Can use a template or start from scratch. Returns API code and configuration.",
22962
+ "Generate a single, complete TypeScript file that creates a NetPad application with all forms and workflows. Run with `npx tsx` - no SDK required.",
21110
22963
  {
21111
22964
  name: external_exports.string().describe("Name of the application"),
21112
22965
  description: external_exports.string().optional().describe("Description of the application"),
@@ -21119,20 +22972,264 @@ server.tool(
21119
22972
  organizationId: external_exports.string().describe("Organization ID")
21120
22973
  },
21121
22974
  async (options) => {
21122
- const code = generateCreateApplicationCode(options);
21123
- const config2 = generateApplicationConfig(options);
21124
- return {
21125
- content: [{
21126
- type: "text",
21127
- text: `## Application Creation Code
22975
+ const { name, description, templateId, icon, color, tags, projectId, organizationId } = options;
22976
+ const slug = options.slug || name.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
22977
+ const template = templateId ? APPLICATION_TEMPLATES[templateId] : null;
22978
+ const formConfigs = template?.structure.forms.map((form) => ({
22979
+ name: form.name,
22980
+ slug: form.slug,
22981
+ fieldConfigs: form.fields.map((f) => ({ ...f, included: true })),
22982
+ submitButtonText: "Submit",
22983
+ successMessage: "Thank you for your submission!"
22984
+ })) || [];
22985
+ const workflowConfigs = template?.structure.workflows.map((workflow) => ({
22986
+ name: workflow.name,
22987
+ description: `Triggered on ${workflow.trigger}`,
22988
+ nodes: [
22989
+ {
22990
+ id: "trigger_1",
22991
+ type: workflow.trigger === "form_submission" ? "form-trigger" : "manual-trigger",
22992
+ label: "Trigger",
22993
+ position: { x: 100, y: 200 },
22994
+ config: { formSlug: formConfigs[0]?.slug || "" },
22995
+ enabled: true
22996
+ },
22997
+ ...workflow.steps.map((step, stepIdx) => ({
22998
+ id: `step_${stepIdx + 1}`,
22999
+ type: step.includes("email") ? "email-send" : step.includes("database") ? "mongodb-write" : "transform",
23000
+ label: step.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()),
23001
+ position: { x: 100 + (stepIdx + 1) * 250, y: 200 },
23002
+ config: {},
23003
+ enabled: true
23004
+ }))
23005
+ ],
23006
+ edges: [
23007
+ { id: "edge_1", source: "trigger_1", sourceHandle: "form_data", target: "step_1", targetHandle: "input" }
23008
+ ]
23009
+ })) || [];
23010
+ const configCode = `// Application Configuration
23011
+ const APPLICATION_CONFIG = {
23012
+ name: ${JSON.stringify(name)},
23013
+ description: ${JSON.stringify(description || `Application created from ${templateId || "scratch"}`)},
23014
+ slug: ${JSON.stringify(slug)},
23015
+ icon: ${JSON.stringify(icon || "\u{1F4CB}")},
23016
+ color: ${JSON.stringify(color || "#00ED64")},
23017
+ tags: ${JSON.stringify(tags || [])},
23018
+ projectId: CONFIG.projectId,
23019
+ organizationId: CONFIG.organizationId,
23020
+ };
21128
23021
 
21129
- ${code}
23022
+ // Form Configurations
23023
+ const FORM_CONFIGS: FormConfig[] = ${JSON.stringify(formConfigs, null, 2)};
21130
23024
 
21131
- ## Application Configuration
23025
+ // Workflow Configurations
23026
+ const WORKFLOW_CONFIGS: WorkflowConfig[] = ${JSON.stringify(workflowConfigs, null, 2)};`;
23027
+ const functionsCode = `// ============================================================================
23028
+ // API Helper Functions
23029
+ // ============================================================================
21132
23030
 
21133
- \`\`\`json
21134
- ${JSON.stringify(config2, null, 2)}
21135
- \`\`\``
23031
+ async function apiCall<T>(
23032
+ endpoint: string,
23033
+ options: RequestInit = {}
23034
+ ): Promise<{ success: boolean; data?: T; error?: string }> {
23035
+ try {
23036
+ const response = await fetch(\`\${CONFIG.baseUrl}\${endpoint}\`, {
23037
+ ...options,
23038
+ headers: {
23039
+ 'Content-Type': 'application/json',
23040
+ 'Authorization': \`Bearer \${CONFIG.apiKey}\`,
23041
+ ...options.headers,
23042
+ },
23043
+ });
23044
+
23045
+ if (!response.ok) {
23046
+ const error = await response.text();
23047
+ return { success: false, error };
23048
+ }
23049
+
23050
+ const data = await response.json() as T;
23051
+ return { success: true, data };
23052
+ } catch (error) {
23053
+ return { success: false, error: error instanceof Error ? error.message : 'Unknown error' };
23054
+ }
23055
+ }
23056
+
23057
+ // ============================================================================
23058
+ // Application Setup Functions
23059
+ // ============================================================================
23060
+
23061
+ async function createApplication(): Promise<string | null> {
23062
+ console.log('\u{1F4F1} Creating application:', APPLICATION_CONFIG.name);
23063
+
23064
+ const result = await apiCall<{ application: { applicationId: string } }>('/api/applications', {
23065
+ method: 'POST',
23066
+ body: JSON.stringify(APPLICATION_CONFIG),
23067
+ });
23068
+
23069
+ if (!result.success || !result.data) {
23070
+ console.error('\u274C Failed to create application:', result.error);
23071
+ return null;
23072
+ }
23073
+
23074
+ console.log('\u2705 Application created:', result.data.application.applicationId);
23075
+ return result.data.application.applicationId;
23076
+ }
23077
+
23078
+ async function createForms(applicationId: string): Promise<string[]> {
23079
+ const formIds: string[] = [];
23080
+
23081
+ for (const formConfig of FORM_CONFIGS) {
23082
+ console.log('\u{1F4DD} Creating form:', formConfig.name);
23083
+
23084
+ const result = await apiCall<{ form: { formId: string } }>('/api/forms', {
23085
+ method: 'POST',
23086
+ body: JSON.stringify({
23087
+ ...formConfig,
23088
+ applicationId,
23089
+ projectId: CONFIG.projectId,
23090
+ organizationId: CONFIG.organizationId,
23091
+ }),
23092
+ });
23093
+
23094
+ if (result.success && result.data) {
23095
+ console.log('\u2705 Form created:', result.data.form.formId);
23096
+ formIds.push(result.data.form.formId);
23097
+ } else {
23098
+ console.error('\u274C Failed to create form:', formConfig.name, result.error);
23099
+ }
23100
+ }
23101
+
23102
+ return formIds;
23103
+ }
23104
+
23105
+ async function createWorkflows(applicationId: string, formIds: string[]): Promise<string[]> {
23106
+ const workflowIds: string[] = [];
23107
+
23108
+ for (const workflowConfig of WORKFLOW_CONFIGS) {
23109
+ console.log('\u26A1 Creating workflow:', workflowConfig.name);
23110
+
23111
+ // Update trigger with actual form ID if available
23112
+ const updatedNodes = workflowConfig.nodes.map(node => {
23113
+ if (node.type === 'form-trigger' && formIds.length > 0) {
23114
+ return { ...node, config: { ...node.config, formId: formIds[0] } };
23115
+ }
23116
+ return node;
23117
+ });
23118
+
23119
+ const result = await apiCall<{ workflow: { id: string } }>('/api/workflows', {
23120
+ method: 'POST',
23121
+ body: JSON.stringify({
23122
+ ...workflowConfig,
23123
+ nodes: updatedNodes,
23124
+ applicationId,
23125
+ projectId: CONFIG.projectId,
23126
+ organizationId: CONFIG.organizationId,
23127
+ }),
23128
+ });
23129
+
23130
+ if (result.success && result.data) {
23131
+ console.log('\u2705 Workflow created:', result.data.workflow.id);
23132
+ workflowIds.push(result.data.workflow.id);
23133
+ } else {
23134
+ console.error('\u274C Failed to create workflow:', workflowConfig.name, result.error);
23135
+ }
23136
+ }
23137
+
23138
+ return workflowIds;
23139
+ }
23140
+
23141
+ async function activateWorkflows(workflowIds: string[]): Promise<void> {
23142
+ for (const workflowId of workflowIds) {
23143
+ console.log('\u{1F504} Activating workflow:', workflowId);
23144
+
23145
+ const result = await apiCall(\`/api/workflows/\${workflowId}/activate\`, {
23146
+ method: 'POST',
23147
+ });
23148
+
23149
+ if (result.success) {
23150
+ console.log('\u2705 Workflow activated');
23151
+ } else {
23152
+ console.warn('\u26A0\uFE0F Failed to activate workflow:', result.error);
23153
+ }
23154
+ }
23155
+ }`;
23156
+ const mainCode = `// ============================================================================
23157
+ // Main Setup Script
23158
+ // ============================================================================
23159
+
23160
+ async function setup() {
23161
+ console.log('\\n\u{1F680} Setting up ${name}...\\n');
23162
+ console.log('Template: ${templateId || "blank"}');
23163
+ console.log('Project: ${projectId}');
23164
+ console.log('Organization: ${organizationId}\\n');
23165
+
23166
+ // Validate configuration
23167
+ if (!CONFIG.apiKey) {
23168
+ console.error('\u274C Error: NETPAD_API_KEY environment variable is required');
23169
+ console.log('\\nSet it in your environment or .env file:');
23170
+ console.log(' export NETPAD_API_KEY="np_live_xxxxx"\\n');
23171
+ process.exit(1);
23172
+ }
23173
+
23174
+ // Step 1: Create application
23175
+ const applicationId = await createApplication();
23176
+ if (!applicationId) {
23177
+ process.exit(1);
23178
+ }
23179
+
23180
+ // Step 2: Create forms
23181
+ const formIds = await createForms(applicationId);
23182
+ console.log(\`\\n\u{1F4CA} Created \${formIds.length} form(s)\\n\`);
23183
+
23184
+ // Step 3: Create workflows
23185
+ const workflowIds = await createWorkflows(applicationId, formIds);
23186
+ console.log(\`\\n\u26A1 Created \${workflowIds.length} workflow(s)\\n\`);
23187
+
23188
+ // Step 4: Activate workflows
23189
+ if (workflowIds.length > 0) {
23190
+ await activateWorkflows(workflowIds);
23191
+ }
23192
+
23193
+ // Summary
23194
+ console.log('\\n' + '='.repeat(50));
23195
+ console.log('\u2705 Setup Complete!');
23196
+ console.log('='.repeat(50));
23197
+ console.log(\`
23198
+ Application: ${name}
23199
+ Application ID: \${applicationId}
23200
+ Forms: \${formIds.length}
23201
+ Workflows: \${workflowIds.length}
23202
+
23203
+ Next steps:
23204
+ 1. Visit your NetPad dashboard to customize forms and workflows
23205
+ 2. Test form submissions
23206
+ 3. Configure email templates and integrations
23207
+ \`);
23208
+ }
23209
+
23210
+ // Run setup
23211
+ setup().catch((error) => {
23212
+ console.error('\u274C Setup failed:', error);
23213
+ process.exit(1);
23214
+ });`;
23215
+ const code = generateSelfContainedCode({
23216
+ title: `${name} Setup`,
23217
+ description: description || `Complete setup script for ${name}`,
23218
+ includeFormTypes: true,
23219
+ includeWorkflowTypes: true,
23220
+ configCode,
23221
+ functionsCode,
23222
+ mainCode
23223
+ });
23224
+ const output = createToolOutput({
23225
+ code,
23226
+ filename: `setup-${slug}.ts`,
23227
+ envVars: STANDARD_ENV_VARS
23228
+ });
23229
+ return {
23230
+ content: [{
23231
+ type: "text",
23232
+ text: formatToolOutput(output)
21136
23233
  }]
21137
23234
  };
21138
23235
  }
@@ -21591,7 +23688,7 @@ server.resource(
21591
23688
  );
21592
23689
  server.tool(
21593
23690
  "list_workflow_templates",
21594
- "List all available workflow templates for creating automated workflows.",
23691
+ '[DEPRECATED - use browse_templates with templateType="workflow"] List all available workflow templates for creating automated workflows.',
21595
23692
  {
21596
23693
  category: external_exports.string().optional().describe('Filter by category (e.g., "notifications", "data", "sales")')
21597
23694
  },
@@ -21623,7 +23720,7 @@ server.tool(
21623
23720
  );
21624
23721
  server.tool(
21625
23722
  "get_workflow_template",
21626
- "Get detailed information about a specific workflow template including its nodes, edges, and configuration.",
23723
+ '[DEPRECATED - use browse_templates with templateType="workflow" and action="get"] Get detailed information about a specific workflow template including its nodes, edges, and configuration.',
21627
23724
  {
21628
23725
  templateId: external_exports.enum(["form-to-email", "form-to-database", "lead-qualification", "webhook-to-database", "scheduled-report"]).describe("The template ID")
21629
23726
  },
@@ -21637,10 +23734,21 @@ server.tool(
21637
23734
  }]
21638
23735
  };
21639
23736
  }
23737
+ const workflowForUI = {
23738
+ id: template.id,
23739
+ name: template.name,
23740
+ description: template.description,
23741
+ category: template.category,
23742
+ tags: template.tags,
23743
+ canvas: {
23744
+ nodes: template.nodes,
23745
+ edges: template.edges
23746
+ }
23747
+ };
21640
23748
  return {
21641
23749
  content: [{
21642
23750
  type: "text",
21643
- text: JSON.stringify(template, null, 2)
23751
+ text: JSON.stringify(workflowForUI, null, 2)
21644
23752
  }]
21645
23753
  };
21646
23754
  }
@@ -21688,33 +23796,242 @@ server.tool(
21688
23796
  );
21689
23797
  server.tool(
21690
23798
  "create_workflow_from_template",
21691
- "Generate code to create a new workflow, optionally using a template.",
23799
+ "Generate a complete, self-contained TypeScript file to create a workflow. Returns validated code with inline types - run with `npx tsx`.",
21692
23800
  {
21693
23801
  name: external_exports.string().describe("Name of the workflow"),
21694
23802
  description: external_exports.string().optional().describe("Description of the workflow"),
21695
23803
  templateId: external_exports.enum(["form-to-email", "form-to-database", "lead-qualification", "webhook-to-database", "scheduled-report"]).optional().describe("Template to use"),
23804
+ formId: external_exports.string().optional().describe("Form ID for form-triggered workflows"),
23805
+ formSlug: external_exports.string().optional().describe("Form slug for form-triggered workflows"),
21696
23806
  applicationId: external_exports.string().optional().describe("Application ID to attach workflow to"),
21697
23807
  projectId: external_exports.string().describe("Project ID"),
21698
23808
  organizationId: external_exports.string().describe("Organization ID"),
21699
- tags: external_exports.array(external_exports.string()).optional().describe("Tags for categorization")
23809
+ tags: external_exports.array(external_exports.string()).optional().describe("Tags for categorization"),
23810
+ salesEmail: external_exports.string().optional().describe("Email address for sales notifications")
21700
23811
  },
21701
23812
  async (options) => {
21702
- const code = generateCreateWorkflowCode(options);
21703
- const config2 = generateWorkflowConfig(options);
23813
+ const { name, description, templateId, formId, formSlug, applicationId, tags, salesEmail } = options;
23814
+ const slug = name.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
23815
+ const template = templateId ? WORKFLOW_TEMPLATES[templateId] : null;
23816
+ const workflowNodes = template?.nodes || [
23817
+ {
23818
+ id: "trigger_1",
23819
+ type: "manual-trigger",
23820
+ label: "Manual Trigger",
23821
+ position: { x: 100, y: 200 },
23822
+ config: {},
23823
+ enabled: true
23824
+ }
23825
+ ];
23826
+ const workflowEdges = template?.edges || [];
23827
+ const updatedNodes = workflowNodes.map((node) => {
23828
+ if (node.type === "form-trigger") {
23829
+ return {
23830
+ ...node,
23831
+ config: {
23832
+ ...node.config,
23833
+ formId: formId || node.config.formId || "",
23834
+ formSlug: formSlug || node.config.formSlug || ""
23835
+ }
23836
+ };
23837
+ }
23838
+ if (node.type === "email-send" && salesEmail) {
23839
+ return {
23840
+ ...node,
23841
+ config: {
23842
+ ...node.config,
23843
+ to: salesEmail
23844
+ }
23845
+ };
23846
+ }
23847
+ return node;
23848
+ });
23849
+ const configCode = `// Workflow Configuration
23850
+ const WORKFLOW_CONFIG: WorkflowConfig = {
23851
+ name: ${JSON.stringify(name)},
23852
+ description: ${JSON.stringify(description || template?.description || "Custom workflow")},
23853
+ tags: ${JSON.stringify(tags || template?.tags || [])},
23854
+ canvas: {
23855
+ nodes: ${JSON.stringify(updatedNodes, null, 2)},
23856
+ edges: ${JSON.stringify(workflowEdges, null, 2)},
23857
+ },
23858
+ settings: {
23859
+ executionMode: 'auto',
23860
+ maxExecutionTime: 300000,
23861
+ retryPolicy: {
23862
+ maxRetries: 3,
23863
+ backoffMultiplier: 2,
23864
+ initialDelayMs: 1000,
23865
+ },
23866
+ },
23867
+ };`;
23868
+ const functionsCode = `/**
23869
+ * Create the workflow via NetPad API.
23870
+ */
23871
+ export async function createWorkflow(): Promise<CreateWorkflowResult> {
23872
+ try {
23873
+ const response = await fetch(\`\${CONFIG.baseUrl}/api/workflows\`, {
23874
+ method: 'POST',
23875
+ headers: {
23876
+ 'Content-Type': 'application/json',
23877
+ 'Authorization': \`Bearer \${CONFIG.apiKey}\`,
23878
+ },
23879
+ body: JSON.stringify({
23880
+ ...WORKFLOW_CONFIG,
23881
+ ${applicationId ? `applicationId: '${applicationId}',` : ""}
23882
+ projectId: CONFIG.projectId,
23883
+ organizationId: CONFIG.organizationId,
23884
+ }),
23885
+ });
23886
+
23887
+ if (!response.ok) {
23888
+ return { success: false, error: await response.text() };
23889
+ }
23890
+
23891
+ const result = await response.json() as { workflow: { id: string } };
23892
+ return { success: true, workflowId: result.workflow.id };
23893
+ } catch (error) {
21704
23894
  return {
21705
- content: [{
21706
- type: "text",
21707
- text: `## Workflow Creation Code
23895
+ success: false,
23896
+ error: error instanceof Error ? error.message : 'Unknown error',
23897
+ };
23898
+ }
23899
+ }
21708
23900
 
21709
- \`\`\`typescript
21710
- ${code}
21711
- \`\`\`
23901
+ /**
23902
+ * Activate the workflow to start processing.
23903
+ */
23904
+ export async function activateWorkflow(workflowId: string): Promise<{ success: boolean; error?: string }> {
23905
+ try {
23906
+ const response = await fetch(
23907
+ \`\${CONFIG.baseUrl}/api/workflows/\${workflowId}/activate\`,
23908
+ {
23909
+ method: 'POST',
23910
+ headers: {
23911
+ 'Authorization': \`Bearer \${CONFIG.apiKey}\`,
23912
+ },
23913
+ }
23914
+ );
21712
23915
 
21713
- ## Workflow Configuration
23916
+ if (!response.ok) {
23917
+ return { success: false, error: await response.text() };
23918
+ }
21714
23919
 
21715
- \`\`\`json
21716
- ${JSON.stringify(config2, null, 2)}
21717
- \`\`\``
23920
+ return { success: true };
23921
+ } catch (error) {
23922
+ return {
23923
+ success: false,
23924
+ error: error instanceof Error ? error.message : 'Unknown error',
23925
+ };
23926
+ }
23927
+ }
23928
+
23929
+ /**
23930
+ * Test the workflow with sample data.
23931
+ */
23932
+ export async function testWorkflow(workflowId: string, testData: Record<string, unknown>): Promise<{ success: boolean; executionId?: string; error?: string }> {
23933
+ try {
23934
+ const response = await fetch(
23935
+ \`\${CONFIG.baseUrl}/api/workflows/\${workflowId}/execute\`,
23936
+ {
23937
+ method: 'POST',
23938
+ headers: {
23939
+ 'Content-Type': 'application/json',
23940
+ 'Authorization': \`Bearer \${CONFIG.apiKey}\`,
23941
+ },
23942
+ body: JSON.stringify({ payload: testData }),
23943
+ }
23944
+ );
23945
+
23946
+ if (!response.ok) {
23947
+ return { success: false, error: await response.text() };
23948
+ }
23949
+
23950
+ const result = await response.json() as { executionId: string };
23951
+ return { success: true, executionId: result.executionId };
23952
+ } catch (error) {
23953
+ return {
23954
+ success: false,
23955
+ error: error instanceof Error ? error.message : 'Unknown error',
23956
+ };
23957
+ }
23958
+ }`;
23959
+ const mainCode = `// ============================================================================
23960
+ // Main Setup
23961
+ // ============================================================================
23962
+
23963
+ async function setup() {
23964
+ console.log('\u26A1 Creating workflow:', WORKFLOW_CONFIG.name);
23965
+ console.log('Template: ${templateId || "custom"}');
23966
+ ${formId ? `console.log('Form ID: ${formId}');` : ""}
23967
+ ${formSlug ? `console.log('Form Slug: ${formSlug}');` : ""}
23968
+ console.log('');
23969
+
23970
+ // Validate configuration
23971
+ if (!CONFIG.apiKey) {
23972
+ console.error('\u274C Error: NETPAD_API_KEY environment variable is required');
23973
+ process.exit(1);
23974
+ }
23975
+
23976
+ // Create the workflow
23977
+ const createResult = await createWorkflow();
23978
+ if (!createResult.success) {
23979
+ console.error('\u274C Failed to create workflow:', createResult.error);
23980
+ process.exit(1);
23981
+ }
23982
+
23983
+ console.log('\u2705 Workflow created:', createResult.workflowId);
23984
+
23985
+ // Activate the workflow
23986
+ console.log('\\n\u{1F504} Activating workflow...');
23987
+ const activateResult = await activateWorkflow(createResult.workflowId!);
23988
+ if (activateResult.success) {
23989
+ console.log('\u2705 Workflow activated');
23990
+ } else {
23991
+ console.warn('\u26A0\uFE0F Failed to activate:', activateResult.error);
23992
+ }
23993
+
23994
+ // Summary
23995
+ console.log('\\n' + '='.repeat(50));
23996
+ console.log('\u2705 Workflow Setup Complete!');
23997
+ console.log('='.repeat(50));
23998
+ console.log(\`
23999
+ Workflow: ${name}
24000
+ Workflow ID: \${createResult.workflowId}
24001
+ Nodes: ${updatedNodes.length}
24002
+ Edges: ${workflowEdges.length}
24003
+ ${templateId ? `Template: ${templateId}` : ""}
24004
+
24005
+ Next steps:
24006
+ 1. Configure node settings in the NetPad dashboard
24007
+ 2. Test with sample data
24008
+ 3. Monitor executions
24009
+ \`);
24010
+ }
24011
+
24012
+ // Uncomment to run:
24013
+ // setup().catch(console.error);
24014
+
24015
+ // Export for use as module
24016
+ export { WORKFLOW_CONFIG, createWorkflow, activateWorkflow, testWorkflow };`;
24017
+ const code = generateSelfContainedCode({
24018
+ title: name,
24019
+ description: description || `Workflow created from ${templateId || "scratch"}`,
24020
+ includeFormTypes: false,
24021
+ includeWorkflowTypes: true,
24022
+ configCode,
24023
+ functionsCode,
24024
+ mainCode
24025
+ });
24026
+ const output = createToolOutput({
24027
+ code,
24028
+ filename: `${slug}-workflow.ts`,
24029
+ envVars: STANDARD_ENV_VARS
24030
+ });
24031
+ return {
24032
+ content: [{
24033
+ type: "text",
24034
+ text: formatToolOutput(output)
21718
24035
  }]
21719
24036
  };
21720
24037
  }
@@ -21874,15 +24191,33 @@ ${code}
21874
24191
  server.resource(
21875
24192
  "netpad-workflow-templates",
21876
24193
  "netpad://reference/workflow-templates",
21877
- async () => ({
21878
- contents: [
21879
- {
21880
- uri: "netpad://reference/workflow-templates",
21881
- mimeType: "application/json",
21882
- text: JSON.stringify(WORKFLOW_TEMPLATES, null, 2)
21883
- }
21884
- ]
21885
- })
24194
+ async () => {
24195
+ const templatesForUI = Object.fromEntries(
24196
+ Object.entries(WORKFLOW_TEMPLATES).map(([key, template]) => [
24197
+ key,
24198
+ {
24199
+ id: template.id,
24200
+ name: template.name,
24201
+ description: template.description,
24202
+ category: template.category,
24203
+ tags: template.tags,
24204
+ canvas: {
24205
+ nodes: template.nodes,
24206
+ edges: template.edges
24207
+ }
24208
+ }
24209
+ ])
24210
+ );
24211
+ return {
24212
+ contents: [
24213
+ {
24214
+ uri: "netpad://reference/workflow-templates",
24215
+ mimeType: "application/json",
24216
+ text: JSON.stringify(templatesForUI, null, 2)
24217
+ }
24218
+ ]
24219
+ };
24220
+ }
21886
24221
  );
21887
24222
  server.resource(
21888
24223
  "netpad-workflow-nodes",
@@ -21899,7 +24234,7 @@ server.resource(
21899
24234
  );
21900
24235
  server.tool(
21901
24236
  "list_conversational_templates",
21902
- "List all available conversational form templates for AI-powered data collection.",
24237
+ '[DEPRECATED - use browse_templates with templateType="conversational"] List all available conversational form templates for AI-powered data collection.',
21903
24238
  {
21904
24239
  category: external_exports.string().optional().describe('Filter by category (e.g., "support", "feedback", "intake")')
21905
24240
  },
@@ -21931,7 +24266,7 @@ server.tool(
21931
24266
  );
21932
24267
  server.tool(
21933
24268
  "get_conversational_template",
21934
- "Get detailed information about a specific conversational form template including topics, extraction schema, and persona configuration.",
24269
+ '[DEPRECATED - use browse_templates with templateType="conversational" and action="get"] Get detailed information about a specific conversational form template including topics, extraction schema, and persona configuration.',
21935
24270
  {
21936
24271
  templateId: external_exports.enum(["it-helpdesk", "customer-feedback", "lead-qualification", "patient-intake"]).describe("The template ID")
21937
24272
  },
@@ -21978,7 +24313,7 @@ server.tool(
21978
24313
  })).describe("Topics to cover in conversation"),
21979
24314
  extractionSchema: external_exports.array(external_exports.object({
21980
24315
  field: external_exports.string().describe("Field name"),
21981
- type: external_exports.enum(["string", "number", "boolean", "enum", "array", "object"]).describe("Field type"),
24316
+ type: external_exports.enum(["string", "number", "boolean", "enum", "array", "object", "file"]).describe("Field type"),
21982
24317
  required: external_exports.boolean().describe("Whether required"),
21983
24318
  description: external_exports.string().describe("Field description"),
21984
24319
  options: external_exports.array(external_exports.string()).optional().describe("Options for enum type"),
@@ -22246,7 +24581,7 @@ server.resource(
22246
24581
  );
22247
24582
  server.tool(
22248
24583
  "list_template_categories",
22249
- "List all available form template categories with descriptions and template counts.",
24584
+ '[DEPRECATED - use browse_templates with templateType="form" and action="categories"] List all available form template categories with descriptions and template counts.',
22250
24585
  {},
22251
24586
  async () => {
22252
24587
  return {
@@ -22263,7 +24598,7 @@ server.tool(
22263
24598
  );
22264
24599
  server.tool(
22265
24600
  "list_form_templates",
22266
- "List all available form templates (25+) across multiple categories. Returns template summaries with field counts.",
24601
+ '[DEPRECATED - use browse_templates with templateType="form"] List all available form templates (25+) across multiple categories. Returns template summaries with field counts.',
22267
24602
  {
22268
24603
  category: external_exports.string().optional().describe('Filter by category (business, events, feedback, support, ecommerce, healthcare, hr, finance, education, real-estate, or "all")'),
22269
24604
  search: external_exports.string().optional().describe("Search templates by name, description, or tags")
@@ -22302,7 +24637,7 @@ server.tool(
22302
24637
  );
22303
24638
  server.tool(
22304
24639
  "get_form_template",
22305
- "Get detailed information about a specific form template including all fields, validation rules, and configuration.",
24640
+ '[DEPRECATED - use browse_templates with templateType="form" and action="get"] Get detailed information about a specific form template including all fields, validation rules, and configuration.',
22306
24641
  {
22307
24642
  templateId: external_exports.string().describe('Template ID (e.g., "contact-form", "lead-capture", "patient-intake")')
22308
24643
  },
@@ -22485,7 +24820,7 @@ ${code}
22485
24820
  );
22486
24821
  server.tool(
22487
24822
  "list_query_templates",
22488
- "List all available MongoDB query templates for common operations.",
24823
+ '[DEPRECATED - use browse_templates with templateType="query"] List all available MongoDB query templates for common operations.',
22489
24824
  {},
22490
24825
  async () => {
22491
24826
  const templates = listQueryTemplates();
@@ -22502,7 +24837,7 @@ server.tool(
22502
24837
  );
22503
24838
  server.tool(
22504
24839
  "get_query_template",
22505
- "Get a specific query template with example code.",
24840
+ '[DEPRECATED - use browse_templates with templateType="query" and action="get"] Get a specific query template with example code.',
22506
24841
  {
22507
24842
  templateId: external_exports.string().describe('Template ID (e.g., "find-all", "aggregate-group-count")')
22508
24843
  },