flowengine-mcp-app 3.0.0 → 3.0.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.
Files changed (119) hide show
  1. package/README.md +19 -270
  2. package/bin.js +12 -0
  3. package/package.json +8 -41
  4. package/LICENSE +0 -21
  5. package/build/cli/api.d.ts +0 -19
  6. package/build/cli/api.d.ts.map +0 -1
  7. package/build/cli/api.js +0 -62
  8. package/build/cli/api.js.map +0 -1
  9. package/build/cli/commands/delete.d.ts +0 -4
  10. package/build/cli/commands/delete.d.ts.map +0 -1
  11. package/build/cli/commands/delete.js +0 -16
  12. package/build/cli/commands/delete.js.map +0 -1
  13. package/build/cli/commands/deploy.d.ts +0 -24
  14. package/build/cli/commands/deploy.d.ts.map +0 -1
  15. package/build/cli/commands/deploy.js +0 -103
  16. package/build/cli/commands/deploy.js.map +0 -1
  17. package/build/cli/commands/domain.d.ts +0 -9
  18. package/build/cli/commands/domain.d.ts.map +0 -1
  19. package/build/cli/commands/domain.js +0 -27
  20. package/build/cli/commands/domain.js.map +0 -1
  21. package/build/cli/commands/env.d.ts +0 -13
  22. package/build/cli/commands/env.d.ts.map +0 -1
  23. package/build/cli/commands/env.js +0 -53
  24. package/build/cli/commands/env.js.map +0 -1
  25. package/build/cli/commands/list.d.ts +0 -4
  26. package/build/cli/commands/list.d.ts.map +0 -1
  27. package/build/cli/commands/list.js +0 -25
  28. package/build/cli/commands/list.js.map +0 -1
  29. package/build/cli/commands/login.d.ts +0 -4
  30. package/build/cli/commands/login.d.ts.map +0 -1
  31. package/build/cli/commands/login.js +0 -37
  32. package/build/cli/commands/login.js.map +0 -1
  33. package/build/cli/commands/logout.d.ts +0 -2
  34. package/build/cli/commands/logout.d.ts.map +0 -1
  35. package/build/cli/commands/logout.js +0 -8
  36. package/build/cli/commands/logout.js.map +0 -1
  37. package/build/cli/commands/logs.d.ts +0 -4
  38. package/build/cli/commands/logs.d.ts.map +0 -1
  39. package/build/cli/commands/logs.js +0 -40
  40. package/build/cli/commands/logs.js.map +0 -1
  41. package/build/cli/commands/show.d.ts +0 -4
  42. package/build/cli/commands/show.d.ts.map +0 -1
  43. package/build/cli/commands/show.js +0 -13
  44. package/build/cli/commands/show.js.map +0 -1
  45. package/build/cli/commands/usage.d.ts +0 -7
  46. package/build/cli/commands/usage.d.ts.map +0 -1
  47. package/build/cli/commands/usage.js +0 -16
  48. package/build/cli/commands/usage.js.map +0 -1
  49. package/build/cli/config.d.ts +0 -17
  50. package/build/cli/config.d.ts.map +0 -1
  51. package/build/cli/config.js +0 -42
  52. package/build/cli/config.js.map +0 -1
  53. package/build/cli/pack.d.ts +0 -25
  54. package/build/cli/pack.d.ts.map +0 -1
  55. package/build/cli/pack.js +0 -111
  56. package/build/cli/pack.js.map +0 -1
  57. package/build/cli/ui.d.ts +0 -23
  58. package/build/cli/ui.d.ts.map +0 -1
  59. package/build/cli/ui.js +0 -105
  60. package/build/cli/ui.js.map +0 -1
  61. package/build/cli.d.ts +0 -10
  62. package/build/cli.d.ts.map +0 -1
  63. package/build/cli.js +0 -251
  64. package/build/cli.js.map +0 -1
  65. package/build/client.d.ts +0 -94
  66. package/build/client.d.ts.map +0 -1
  67. package/build/client.js +0 -311
  68. package/build/client.js.map +0 -1
  69. package/build/index.d.ts +0 -12
  70. package/build/index.d.ts.map +0 -1
  71. package/build/index.js +0 -970
  72. package/build/index.js.map +0 -1
  73. package/build/tools/functions.d.ts +0 -14
  74. package/build/tools/functions.d.ts.map +0 -1
  75. package/build/tools/functions.js +0 -241
  76. package/build/tools/functions.js.map +0 -1
  77. package/build/types.d.ts +0 -155
  78. package/build/types.d.ts.map +0 -1
  79. package/build/types.js +0 -5
  80. package/build/types.js.map +0 -1
  81. package/build/ui/base.d.ts +0 -8
  82. package/build/ui/base.d.ts.map +0 -1
  83. package/build/ui/base.js +0 -425
  84. package/build/ui/base.js.map +0 -1
  85. package/build/ui/component-viewer.d.ts +0 -14
  86. package/build/ui/component-viewer.d.ts.map +0 -1
  87. package/build/ui/component-viewer.js +0 -678
  88. package/build/ui/component-viewer.js.map +0 -1
  89. package/build/ui/dashboard.d.ts +0 -21
  90. package/build/ui/dashboard.d.ts.map +0 -1
  91. package/build/ui/dashboard.js +0 -252
  92. package/build/ui/dashboard.js.map +0 -1
  93. package/build/ui/demo.d.ts +0 -14
  94. package/build/ui/demo.d.ts.map +0 -1
  95. package/build/ui/demo.js +0 -222
  96. package/build/ui/demo.js.map +0 -1
  97. package/build/ui/instances.d.ts +0 -17
  98. package/build/ui/instances.d.ts.map +0 -1
  99. package/build/ui/instances.js +0 -233
  100. package/build/ui/instances.js.map +0 -1
  101. package/build/ui/n8n-viewer.d.ts +0 -12
  102. package/build/ui/n8n-viewer.d.ts.map +0 -1
  103. package/build/ui/n8n-viewer.js +0 -371
  104. package/build/ui/n8n-viewer.js.map +0 -1
  105. package/build/ui/portals.d.ts +0 -14
  106. package/build/ui/portals.d.ts.map +0 -1
  107. package/build/ui/portals.js +0 -184
  108. package/build/ui/portals.js.map +0 -1
  109. package/build/ui/ui/components.html +0 -312
  110. package/build/ui/ui/n8n.html +0 -124
  111. package/build/ui/ui/portals.html +0 -211
  112. package/build/ui/widgets.d.ts +0 -17
  113. package/build/ui/widgets.d.ts.map +0 -1
  114. package/build/ui/widgets.js +0 -200
  115. package/build/ui/widgets.js.map +0 -1
  116. package/build/ui/workflows.d.ts +0 -17
  117. package/build/ui/workflows.d.ts.map +0 -1
  118. package/build/ui/workflows.js +0 -217
  119. package/build/ui/workflows.js.map +0 -1
package/build/index.js DELETED
@@ -1,970 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * FlowEngine MCP Server - Modern MCP Apps Implementation
4
- * Manage your white-label automation platform from Claude
5
- *
6
- * Core Features:
7
- * 1. Instance Management - Provision and manage FlowEngine instances
8
- * 2. Client Portals - Monitor and access client portals
9
- * 3. AI FlowBuilder - Create forms, chatbots, and UI embeds
10
- */
11
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
12
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
13
- import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
14
- import { registerAppTool, registerAppResource, RESOURCE_MIME_TYPE } from '@modelcontextprotocol/ext-apps/server';
15
- import { z } from 'zod';
16
- import { readFile } from 'node:fs/promises';
17
- import { join } from 'node:path';
18
- import { fileURLToPath } from 'node:url';
19
- import { dirname } from 'node:path';
20
- import { createServer } from 'node:http';
21
- import { randomUUID } from 'node:crypto';
22
- import { AsyncLocalStorage } from 'node:async_hooks';
23
- import { FlowEngineClient } from './client.js';
24
- import { registerFunctionsTools } from './tools/functions.js';
25
- const __filename = fileURLToPath(import.meta.url);
26
- const __dirname = dirname(__filename);
27
- const UI_DIR = join(__dirname, 'ui', 'ui'); // Vite outputs to build/ui/ui/
28
- // Environment configuration
29
- const API_KEY = process.env.FLOWENGINE_API_KEY;
30
- const BASE_URL = process.env.FLOWENGINE_BASE_URL || 'https://flowengine.cloud';
31
- const MCP_MODE = (process.env.FLOWENGINE_MCP_MODE || 'all');
32
- // Per-request API key context (HTTP mode - each user's key is isolated)
33
- const requestApiKey = new AsyncLocalStorage();
34
- function ensureClient() {
35
- const apiKey = requestApiKey.getStore() || API_KEY;
36
- if (!apiKey) {
37
- throw new Error('FlowEngine API key not configured. Set FLOWENGINE_API_KEY in your MCP config.');
38
- }
39
- return new FlowEngineClient({ apiKey, baseUrl: BASE_URL });
40
- }
41
- function parseBody(req) {
42
- return new Promise((resolve, reject) => {
43
- let body = '';
44
- req.on('data', (chunk) => { body += chunk; });
45
- req.on('end', () => {
46
- try {
47
- resolve(body ? JSON.parse(body) : undefined);
48
- }
49
- catch {
50
- resolve(undefined);
51
- }
52
- });
53
- req.on('error', reject);
54
- });
55
- }
56
- // Store latest tool results for UI to retrieve
57
- const toolResults = new Map();
58
- function storeToolResult(type, data) {
59
- const key = `${type}-${Date.now()}`;
60
- toolResults.set(key, data);
61
- // Keep only last 10 results to prevent memory leaks
62
- if (toolResults.size > 10) {
63
- const firstKey = toolResults.keys().next().value;
64
- if (firstKey) {
65
- toolResults.delete(firstKey);
66
- }
67
- }
68
- return key;
69
- }
70
- function getLatestToolResult(type) {
71
- // Get most recent result of given type (or any type if not specified)
72
- let latest = null;
73
- let latestKey = '';
74
- for (const [key, value] of toolResults) {
75
- if (!type || value.type?.includes(type)) {
76
- if (!latestKey || key > latestKey) {
77
- latest = value;
78
- latestKey = key;
79
- }
80
- }
81
- }
82
- return latest;
83
- }
84
- // Initialize MCP Server with modern API
85
- const server = new McpServer({
86
- name: 'flowengine-mcp',
87
- version: '2.0.9',
88
- }, {
89
- capabilities: {
90
- resources: {},
91
- tools: {},
92
- },
93
- });
94
- /**
95
- * ===========================
96
- * APP RESOURCES (3 Focused UIs)
97
- * ===========================
98
- */
99
- if (MCP_MODE !== 'functions') {
100
- // 1. n8n Workflow Preview - ONLY workflow builder
101
- registerAppResource(server, 'n8n Workflow Preview', 'ui://flowengine/n8n', {
102
- description: 'n8n workflow builder preview. Simple, focused UI showing only the n8n workflow editor.',
103
- mimeType: RESOURCE_MIME_TYPE,
104
- }, async () => {
105
- try {
106
- let html = await readFile(join(UI_DIR, 'n8n.html'), 'utf-8');
107
- // Inject latest tool result into HTML as a global variable
108
- const latestResult = getLatestToolResult('workflow');
109
- if (latestResult) {
110
- const dataScript = `<script>window.__flowengineToolResult = ${JSON.stringify(latestResult)};</script>`;
111
- html = html.replace('</head>', `${dataScript}</head>`);
112
- }
113
- return {
114
- contents: [{
115
- uri: 'ui://flowengine/n8n',
116
- mimeType: RESOURCE_MIME_TYPE,
117
- text: html,
118
- }],
119
- };
120
- }
121
- catch (error) {
122
- return {
123
- contents: [{
124
- uri: 'ui://flowengine/n8n',
125
- mimeType: RESOURCE_MIME_TYPE,
126
- text: `<!DOCTYPE html><html><body><h1>Error loading n8n preview</h1><p>${error.message}</p></body></html>`,
127
- }],
128
- };
129
- }
130
- });
131
- // 2. UI Embed Builder - ONLY component management
132
- registerAppResource(server, 'UI Embed Builder', 'ui://flowengine/components', {
133
- description: 'UI embed builder and manager. Simple, focused UI for creating and managing forms, chatbots, and widgets.',
134
- mimeType: RESOURCE_MIME_TYPE,
135
- }, async () => {
136
- try {
137
- let html = await readFile(join(UI_DIR, 'components.html'), 'utf-8');
138
- // Inject latest tool result into HTML as a global variable
139
- const latestResult = getLatestToolResult('component');
140
- if (latestResult) {
141
- const dataScript = `<script>window.__flowengineToolResult = ${JSON.stringify(latestResult)};</script>`;
142
- html = html.replace('</head>', `${dataScript}</head>`);
143
- }
144
- return {
145
- contents: [{
146
- uri: 'ui://flowengine/components',
147
- mimeType: RESOURCE_MIME_TYPE,
148
- text: html,
149
- }],
150
- };
151
- }
152
- catch (error) {
153
- return {
154
- contents: [{
155
- uri: 'ui://flowengine/components',
156
- mimeType: RESOURCE_MIME_TYPE,
157
- text: `<!DOCTYPE html><html><body><h1>Error loading component builder</h1><p>${error.message}</p></body></html>`,
158
- }],
159
- };
160
- }
161
- });
162
- // 3. Portals Dashboard - ONLY portal management
163
- registerAppResource(server, 'Portals Dashboard', 'ui://flowengine/portals', {
164
- description: 'Portal management dashboard. Simple, focused UI for managing client portals, instances, and storage.',
165
- mimeType: RESOURCE_MIME_TYPE,
166
- }, async () => {
167
- try {
168
- const html = await readFile(join(UI_DIR, 'portals.html'), 'utf-8');
169
- return {
170
- contents: [{
171
- uri: 'ui://flowengine/portals',
172
- mimeType: RESOURCE_MIME_TYPE,
173
- text: html,
174
- }],
175
- };
176
- }
177
- catch (error) {
178
- return {
179
- contents: [{
180
- uri: 'ui://flowengine/portals',
181
- mimeType: RESOURCE_MIME_TYPE,
182
- text: `<!DOCTYPE html><html><body><h1>Error loading portals dashboard</h1><p>${error.message}</p></body></html>`,
183
- }],
184
- };
185
- }
186
- });
187
- /**
188
- * ===========================
189
- * SIMPLE TOOLS (3 Tools Only)
190
- * ===========================
191
- * 1. flowengine_create_workflow - Create n8n workflow, return preview
192
- * 2. flowengine_create_component - Create UI embed (form/chatbot/widget), return preview(s)
193
- * 3. flowengine_get_instances - List portals/instances (read-only, links to website)
194
- */
195
- // 1. Create Workflow - Creates n8n workflow and returns preview
196
- registerAppTool(server, 'flowengine_create_workflow', {
197
- description: 'Create and show a sample n8n workflow instantly. Returns interactive workflow preview with a pre-made workflow template. Use when user asks to create/show workflows, automations, or n8n examples.',
198
- inputSchema: {
199
- name: z.string().describe('Workflow name or description'),
200
- },
201
- _meta: {
202
- ui: {
203
- resourceUri: 'ui://flowengine/n8n',
204
- },
205
- },
206
- }, async (args) => {
207
- const client = ensureClient();
208
- const name = args.name;
209
- // Create sample workflow object
210
- const sampleWorkflow = {
211
- "name": name || "Sample Workflow",
212
- "nodes": [
213
- {
214
- "parameters": {
215
- "path": "webhook",
216
- "responseMode": "onReceived",
217
- "options": {}
218
- },
219
- "id": "webhook-1",
220
- "name": "Webhook",
221
- "type": "n8n-nodes-base.webhook",
222
- "typeVersion": 1,
223
- "position": [250, 300],
224
- "webhookId": "sample-webhook"
225
- },
226
- {
227
- "parameters": {
228
- "values": {
229
- "string": [
230
- {
231
- "name": "message",
232
- "value": "Received data from webhook"
233
- }
234
- ]
235
- },
236
- "options": {}
237
- },
238
- "id": "set-1",
239
- "name": "Process Data",
240
- "type": "n8n-nodes-base.set",
241
- "typeVersion": 1,
242
- "position": [450, 300]
243
- },
244
- {
245
- "parameters": {
246
- "conditions": {
247
- "string": [
248
- {
249
- "value1": "={{$json.email}}",
250
- "operation": "isNotEmpty"
251
- }
252
- ]
253
- }
254
- },
255
- "id": "if-1",
256
- "name": "Check Email",
257
- "type": "n8n-nodes-base.if",
258
- "typeVersion": 1,
259
- "position": [650, 300]
260
- },
261
- {
262
- "parameters": {
263
- "fromEmail": "noreply@example.com",
264
- "toEmail": "={{$json.email}}",
265
- "subject": "Notification",
266
- "text": "={{$json.message}}"
267
- },
268
- "id": "email-1",
269
- "name": "Send Email",
270
- "type": "n8n-nodes-base.emailSend",
271
- "typeVersion": 1,
272
- "position": [850, 250]
273
- }
274
- ],
275
- "connections": {
276
- "Webhook": {
277
- "main": [[{ "node": "Process Data", "type": "main", "index": 0 }]]
278
- },
279
- "Process Data": {
280
- "main": [[{ "node": "Check Email", "type": "main", "index": 0 }]]
281
- },
282
- "Check Email": {
283
- "main": [[{ "node": "Send Email", "type": "main", "index": 0 }]]
284
- }
285
- },
286
- "settings": {
287
- "executionOrder": "v1"
288
- }
289
- };
290
- try {
291
- // Try to save workflow to n8n instance
292
- const createResult = await client.createWorkflow(sampleWorkflow);
293
- const workflowPreview = {
294
- type: 'workflow_preview',
295
- workflow: sampleWorkflow,
296
- workflowId: createResult?.workflowId,
297
- workflowUrl: createResult?.workflowUrl,
298
- message: `✓ Created workflow: ${sampleWorkflow.name}`,
299
- nodes: sampleWorkflow.nodes.length,
300
- description: `Workflow created and saved to ${createResult?.instanceName || 'n8n instance'}. Ready to activate and deploy.`,
301
- instanceName: createResult?.instanceName,
302
- };
303
- // Store for UI to retrieve
304
- storeToolResult('workflow', workflowPreview);
305
- return {
306
- content: [{
307
- type: 'text',
308
- text: JSON.stringify(workflowPreview, null, 2),
309
- }],
310
- };
311
- }
312
- catch (error) {
313
- // Fallback: return sample workflow for preview if creation fails
314
- console.warn('Failed to create workflow in n8n:', error.message);
315
- const fallbackWorkflow = {
316
- type: 'workflow_preview',
317
- workflow: sampleWorkflow,
318
- message: `✓ Workflow preview created: ${sampleWorkflow.name}`,
319
- nodes: sampleWorkflow.nodes.length,
320
- description: `Preview ready. Note: Workflow could not be saved to n8n (${error.message}). Configure API key to enable persistence.`,
321
- warning: error.message,
322
- };
323
- storeToolResult('workflow', fallbackWorkflow);
324
- return {
325
- content: [{
326
- type: 'text',
327
- text: JSON.stringify(fallbackWorkflow, null, 2),
328
- }],
329
- };
330
- }
331
- });
332
- // 2. Create Component - Creates UI embed and returns preview(s)
333
- registerAppTool(server, 'flowengine_create_component', {
334
- description: 'Create a UI embed (form, chatbot, widget) in FlowEngine and show preview. Use when user asks to create forms, chatbots, contact forms, etc. Returns component preview and workflow preview if component needs automation (like chatbot).',
335
- inputSchema: {
336
- type: z.enum(['form', 'chatbot', 'widget']).describe('Component type'),
337
- name: z.string().describe('Component name or description'),
338
- config: z.any().optional().describe('Optional configuration'),
339
- },
340
- _meta: {
341
- ui: {
342
- resourceUri: 'ui://flowengine/components',
343
- },
344
- },
345
- }, async (args) => {
346
- const client = ensureClient();
347
- const type = args.type;
348
- const name = args.name;
349
- const config = args.config;
350
- // Create component data
351
- const componentData = {
352
- type: type,
353
- name: name || `Sample ${type}`,
354
- config: config || {},
355
- };
356
- try {
357
- // Create component in database
358
- const createResult = await client.createComponent(componentData);
359
- const componentPreview = {
360
- type: 'component_preview',
361
- component: {
362
- id: createResult?.component?.id,
363
- ...componentData,
364
- slug: createResult?.component?.slug,
365
- },
366
- componentId: createResult?.component?.id,
367
- componentUrl: createResult?.componentUrl,
368
- message: `✓ Created ${type}: ${componentData.name}`,
369
- componentType: type,
370
- description: `${type.charAt(0).toUpperCase() + type.slice(1)} component created successfully. Ready to customize and embed.`,
371
- };
372
- // Store for UI to retrieve
373
- storeToolResult('component', componentPreview);
374
- return {
375
- content: [{
376
- type: 'text',
377
- text: JSON.stringify(componentPreview, null, 2),
378
- }],
379
- };
380
- }
381
- catch (error) {
382
- // Fallback: return component preview if creation fails
383
- console.warn('Failed to create component:', error.message);
384
- const fallbackComponent = {
385
- type: 'component_preview',
386
- component: {
387
- id: `component-${Date.now()}`,
388
- ...componentData,
389
- },
390
- message: `✓ Component preview created: ${componentData.name}`,
391
- componentType: type,
392
- description: `Preview ready. Note: Component could not be saved (${error.message}). Configure API key to enable persistence.`,
393
- warning: error.message,
394
- };
395
- storeToolResult('component', fallbackComponent);
396
- return {
397
- content: [{
398
- type: 'text',
399
- text: JSON.stringify(fallbackComponent, null, 2),
400
- }],
401
- };
402
- }
403
- });
404
- // 3. Get Instances - Lists portals/instances (read-only, no create/delete)
405
- registerAppTool(server, 'flowengine_get_instances', {
406
- description: 'Show list of FlowEngine portals and hosting instances. Use when user asks to see their instances, portals, or hosting. Returns simple list with basic info and links to website. Read-only - all actions (create/delete) link to website.',
407
- inputSchema: {},
408
- _meta: {
409
- ui: {
410
- resourceUri: 'ui://flowengine/portals',
411
- },
412
- },
413
- }, async () => {
414
- try {
415
- const instances = await ensureClient().getInstances();
416
- return {
417
- content: [{
418
- type: 'text',
419
- text: JSON.stringify({
420
- type: 'instances_list',
421
- instances: instances,
422
- message: `Found ${instances.length} instance(s). Click links to manage on flowengine.cloud`,
423
- }, null, 2),
424
- }],
425
- };
426
- }
427
- catch (error) {
428
- return {
429
- content: [{
430
- type: 'text',
431
- text: JSON.stringify({
432
- type: 'instances_list',
433
- instances: [],
434
- error: error.message,
435
- apiKey: API_KEY ? `${API_KEY.substring(0, 10)}...` : 'NOT SET',
436
- baseUrl: BASE_URL,
437
- }, null, 2),
438
- }],
439
- };
440
- }
441
- });
442
- // 4. Get Components - Lists all UI embeds (forms, chatbots, widgets)
443
- registerAppTool(server, 'flowengine_get_components', {
444
- description: 'Show list of UI embeds (forms, chatbots, widgets) with share links. Use when user asks to see their components, forms, chatbots, or widgets. Returns embed list with embed codes and share options.',
445
- inputSchema: {},
446
- _meta: {
447
- ui: {
448
- resourceUri: 'ui://flowengine/components',
449
- },
450
- },
451
- }, async () => {
452
- try {
453
- const components = await ensureClient().getWidgets();
454
- return {
455
- content: [{
456
- type: 'text',
457
- text: JSON.stringify(components, null, 2),
458
- }],
459
- };
460
- }
461
- catch (error) {
462
- // Return empty list if not authenticated
463
- return {
464
- content: [{
465
- type: 'text',
466
- text: JSON.stringify([], null, 2),
467
- }],
468
- };
469
- }
470
- });
471
- // 5. Deploy Instance - Deploy a docker image or GitHub repo to a hosting instance
472
- registerAppTool(server, 'flowengine_deploy_instance', {
473
- description: 'Deploy a Docker image or GitHub repo to a FlowEngine hosting instance. Saves config then triggers deployment. Use when user wants to deploy, redeploy, or change what is running on an instance. Provide EITHER dockerImage OR githubRepo (not both). Port defaults to 3000.',
474
- inputSchema: {
475
- instanceId: z.string().describe('Instance ID to deploy to'),
476
- dockerImage: z.string().optional().describe('Docker image to deploy (e.g. "nginx:latest", "ghcr.io/owner/repo:latest"). Use this OR githubRepo.'),
477
- githubRepo: z.string().optional().describe('GitHub repo to build and deploy (e.g. "owner/repo"). Requires GitHub App installed. Use this OR dockerImage.'),
478
- port: z.number().optional().describe('Container port your app listens on (default: 3000)'),
479
- envVars: z.record(z.string()).optional().describe('Environment variables as key-value pairs (e.g. {"NODE_ENV": "production"})'),
480
- },
481
- _meta: { ui: { resourceUri: 'ui://flowengine/portals' } },
482
- }, async (args) => {
483
- const client = ensureClient();
484
- const { instanceId, dockerImage, githubRepo, port, envVars } = args;
485
- if (!dockerImage && !githubRepo) {
486
- return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: 'Provide either dockerImage or githubRepo.' }, null, 2) }] };
487
- }
488
- if (dockerImage && githubRepo) {
489
- return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: 'Provide either dockerImage or githubRepo, not both.' }, null, 2) }] };
490
- }
491
- try {
492
- await client.updateInstanceConfig(instanceId, {
493
- ...(dockerImage ? { dockerImage } : { githubRepo }),
494
- ...(port ? { port } : {}),
495
- ...(envVars ? { envVars } : {}),
496
- });
497
- const result = await client.manageInstance(instanceId, 'redeploy');
498
- return {
499
- content: [{
500
- type: 'text',
501
- text: JSON.stringify({
502
- success: true,
503
- instanceId,
504
- source: githubRepo ? 'github' : 'docker',
505
- ...(githubRepo ? { githubRepo } : { dockerImage }),
506
- port: port || 3000,
507
- message: result.message || 'Deployment started. Check status with flowengine_get_instance_status.',
508
- }, null, 2),
509
- }],
510
- };
511
- }
512
- catch (error) {
513
- return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: error.message }, null, 2) }] };
514
- }
515
- });
516
- // 5b. Provision n8n - Provision an n8n automation instance on an empty slot
517
- registerAppTool(server, 'flowengine_provision_n8n', {
518
- description: 'Provision an n8n automation instance on an empty FlowEngine hosting slot. Use when user wants to deploy n8n to an existing empty instance. This is a full n8n provisioning - separate from docker deployments.',
519
- inputSchema: {
520
- instanceId: z.string().describe('Instance ID of the empty slot to provision n8n on'),
521
- },
522
- _meta: { ui: { resourceUri: 'ui://flowengine/portals' } },
523
- }, async (args) => {
524
- const client = ensureClient();
525
- const { instanceId } = args;
526
- try {
527
- const result = await client.deployService(instanceId, 'n8n');
528
- return {
529
- content: [{
530
- type: 'text',
531
- text: JSON.stringify({
532
- success: true,
533
- instanceId,
534
- serviceType: 'n8n',
535
- message: result.message || 'n8n provisioning started. Check status with flowengine_get_instance_status.',
536
- }, null, 2),
537
- }],
538
- };
539
- }
540
- catch (error) {
541
- return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: error.message }, null, 2) }] };
542
- }
543
- });
544
- // 5c. Provision OpenClaw - Provision an OpenClaw AI agent on an empty slot
545
- registerAppTool(server, 'flowengine_provision_openclaw', {
546
- description: 'Provision an OpenClaw AI agent on an empty FlowEngine hosting slot. Requires instance with 30GB+ storage. Use when user wants to deploy OpenClaw to an existing empty instance. This is a full OpenClaw provisioning with browser and gateway components - separate from docker deployments.',
547
- inputSchema: {
548
- instanceId: z.string().describe('Instance ID of the empty slot to provision OpenClaw on (must have 30GB+ storage)'),
549
- },
550
- _meta: { ui: { resourceUri: 'ui://flowengine/portals' } },
551
- }, async (args) => {
552
- const client = ensureClient();
553
- const { instanceId } = args;
554
- try {
555
- const result = await client.deployService(instanceId, 'openclaw');
556
- return {
557
- content: [{
558
- type: 'text',
559
- text: JSON.stringify({
560
- success: true,
561
- instanceId,
562
- serviceType: 'openclaw',
563
- message: result.message || 'OpenClaw provisioning started. Check status with flowengine_get_instance_status.',
564
- }, null, 2),
565
- }],
566
- };
567
- }
568
- catch (error) {
569
- return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: error.message }, null, 2) }] };
570
- }
571
- });
572
- // 5b. List GitHub Repos - List repos available for deployment
573
- registerAppTool(server, 'flowengine_list_github_repos', {
574
- description: 'List GitHub repositories accessible for deployment via the connected GitHub App. Use when user wants to deploy from GitHub and needs to choose a repo. Requires GitHub App to be installed on flowengine.cloud.',
575
- inputSchema: {},
576
- _meta: { ui: { resourceUri: 'ui://flowengine/portals' } },
577
- }, async () => {
578
- try {
579
- const result = await ensureClient().listGithubRepos();
580
- if (!result.success) {
581
- return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: result.error, installUrl: result.installUrl }, null, 2) }] };
582
- }
583
- const repos = (result.repos || []).map((r) => ({
584
- full_name: r.full_name,
585
- name: r.name,
586
- private: r.private,
587
- default_branch: r.default_branch,
588
- html_url: r.html_url,
589
- }));
590
- return {
591
- content: [{
592
- type: 'text',
593
- text: JSON.stringify({ success: true, repos, count: repos.length, tip: 'Use full_name (e.g. "owner/repo") with flowengine_deploy_instance.' }, null, 2),
594
- }],
595
- };
596
- }
597
- catch (error) {
598
- return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: error.message }, null, 2) }] };
599
- }
600
- });
601
- // 5c. Manage Instance - Start/stop/restart a hosting instance
602
- registerAppTool(server, 'flowengine_manage_instance', {
603
- description: 'Start, stop, or restart a FlowEngine hosting instance. Use when user asks to start/stop/restart a container. For deploying a new image or repo, use flowengine_deploy_instance instead.',
604
- inputSchema: {
605
- instanceId: z.string().describe('Instance ID'),
606
- action: z.enum(['start', 'stop', 'restart', 'redeploy']).describe('Action: start (boot stopped container), stop (halt container), restart (stop + start), redeploy (rebuild + restart with current config)'),
607
- },
608
- _meta: {
609
- ui: {
610
- resourceUri: 'ui://flowengine/portals',
611
- },
612
- },
613
- }, async (args) => {
614
- const client = ensureClient();
615
- const { instanceId, action } = args;
616
- try {
617
- const result = await client.manageInstance(instanceId, action);
618
- return {
619
- content: [{
620
- type: 'text',
621
- text: JSON.stringify({ success: true, action, instanceId, message: result.message || `Instance ${action} triggered`, ...result }, null, 2),
622
- }],
623
- };
624
- }
625
- catch (error) {
626
- return {
627
- content: [{
628
- type: 'text',
629
- text: JSON.stringify({ success: false, error: error.message }, null, 2),
630
- }],
631
- };
632
- }
633
- });
634
- // 6. Get Instance Logs - Fetch container logs
635
- registerAppTool(server, 'flowengine_get_instance_logs', {
636
- description: 'Get container logs for a FlowEngine hosting instance (docker, OpenClaw, n8n). Returns recent log output. Use when user asks to see logs, debug issues, or check what a container is doing.',
637
- inputSchema: {
638
- instanceId: z.string().describe('Instance ID'),
639
- lines: z.number().optional().describe('Number of log lines to return (default 200, max 1000)'),
640
- },
641
- _meta: {
642
- ui: {
643
- resourceUri: 'ui://flowengine/portals',
644
- },
645
- },
646
- }, async (args) => {
647
- const client = ensureClient();
648
- const { instanceId, lines = 200 } = args;
649
- try {
650
- const result = await client.getInstanceLogs(instanceId, lines);
651
- return {
652
- content: [{
653
- type: 'text',
654
- text: result.logs || '(no logs available)',
655
- }],
656
- };
657
- }
658
- catch (error) {
659
- return {
660
- content: [{
661
- type: 'text',
662
- text: `Error fetching logs: ${error.message}`,
663
- }],
664
- };
665
- }
666
- });
667
- // 7. Get Instance Deployments - Deployment history
668
- registerAppTool(server, 'flowengine_get_instance_deployments', {
669
- description: 'Get deployment history for a FlowEngine hosting instance. Shows past deploys with timestamps and status. Use when user asks about deployment history, when something was last deployed, or deployment status.',
670
- inputSchema: {
671
- instanceId: z.string().describe('Instance ID'),
672
- },
673
- _meta: {
674
- ui: {
675
- resourceUri: 'ui://flowengine/portals',
676
- },
677
- },
678
- }, async (args) => {
679
- const client = ensureClient();
680
- const { instanceId } = args;
681
- try {
682
- const result = await client.getInstanceDeployments(instanceId);
683
- return {
684
- content: [{
685
- type: 'text',
686
- text: JSON.stringify({ instanceId, deployments: result.deployments || [], message: result.message }, null, 2),
687
- }],
688
- };
689
- }
690
- catch (error) {
691
- return {
692
- content: [{
693
- type: 'text',
694
- text: JSON.stringify({ success: false, error: error.message }, null, 2),
695
- }],
696
- };
697
- }
698
- });
699
- // 8. Get Instance Status - Live status check
700
- registerAppTool(server, 'flowengine_get_instance_status', {
701
- description: 'Get live status of a specific FlowEngine instance. Returns running/stopped/provisioning state and instance details. Use when user asks if an instance is running or wants to check its current state.',
702
- inputSchema: {
703
- instanceId: z.string().describe('Instance ID'),
704
- },
705
- _meta: {
706
- ui: {
707
- resourceUri: 'ui://flowengine/portals',
708
- },
709
- },
710
- }, async (args) => {
711
- const client = ensureClient();
712
- const { instanceId } = args;
713
- try {
714
- const result = await client.getInstanceStatus(instanceId);
715
- return {
716
- content: [{
717
- type: 'text',
718
- text: JSON.stringify({
719
- instanceId,
720
- status: result.containerStatus || result.status,
721
- coolifyStatus: result.coolifyStatus,
722
- instance: result.instance,
723
- }, null, 2),
724
- }],
725
- };
726
- }
727
- catch (error) {
728
- return {
729
- content: [{
730
- type: 'text',
731
- text: JSON.stringify({ success: false, error: error.message }, null, 2),
732
- }],
733
- };
734
- }
735
- });
736
- // 9. List Instance Backups
737
- registerAppTool(server, 'flowengine_list_instance_backups', {
738
- description: 'List backups for an OpenClaw instance. Returns backup history with status, file size, and timestamps.',
739
- inputSchema: {
740
- instanceId: z.string().describe('Instance ID'),
741
- },
742
- _meta: { ui: { resourceUri: 'ui://flowengine/portals' } },
743
- }, async (args) => {
744
- const client = ensureClient();
745
- const { instanceId } = args;
746
- try {
747
- const result = await client.listInstanceBackups(instanceId);
748
- return {
749
- content: [{
750
- type: 'text',
751
- text: JSON.stringify({ instanceId, backups: result.backups || [] }, null, 2),
752
- }],
753
- };
754
- }
755
- catch (error) {
756
- return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: error.message }, null, 2) }] };
757
- }
758
- });
759
- // 10. Create Instance Backup
760
- registerAppTool(server, 'flowengine_create_instance_backup', {
761
- description: 'Create a manual backup of an OpenClaw instance. Backs up Docker volumes to the server. Takes up to a few minutes.',
762
- inputSchema: {
763
- instanceId: z.string().describe('Instance ID'),
764
- },
765
- _meta: { ui: { resourceUri: 'ui://flowengine/portals' } },
766
- }, async (args) => {
767
- const client = ensureClient();
768
- const { instanceId } = args;
769
- try {
770
- const result = await client.createInstanceBackup(instanceId);
771
- return {
772
- content: [{
773
- type: 'text',
774
- text: JSON.stringify(result, null, 2),
775
- }],
776
- };
777
- }
778
- catch (error) {
779
- return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: error.message }, null, 2) }] };
780
- }
781
- });
782
- // 11. Update Instance Config
783
- registerAppTool(server, 'flowengine_update_instance_config', {
784
- description: 'Update the docker image, port, or environment variables for a docker/website instance. Does not redeploy automatically - call flowengine_manage_instance with action=redeploy after to apply changes.',
785
- inputSchema: {
786
- instanceId: z.string().describe('Instance ID'),
787
- dockerImage: z.string().optional().describe('New Docker image (e.g. nginx:latest)'),
788
- githubRepo: z.string().optional().describe('GitHub repo to build from (e.g. "owner/repo"). Requires GitHub App to be installed.'),
789
- port: z.number().optional().describe('Container port to expose'),
790
- envVars: z.record(z.string()).optional().describe('Environment variables as key-value object'),
791
- },
792
- _meta: { ui: { resourceUri: 'ui://flowengine/portals' } },
793
- }, async (args) => {
794
- const client = ensureClient();
795
- const { instanceId, dockerImage, githubRepo, port, envVars } = args;
796
- try {
797
- const result = await client.updateInstanceConfig(instanceId, { dockerImage, githubRepo, port, envVars });
798
- return {
799
- content: [{
800
- type: 'text',
801
- text: JSON.stringify(result, null, 2),
802
- }],
803
- };
804
- }
805
- catch (error) {
806
- return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: error.message }, null, 2) }] };
807
- }
808
- });
809
- // 12. Update Instance Type - Reclassify between docker and website
810
- registerAppTool(server, 'flowengine_update_instance_type', {
811
- description: 'Change the type of a FlowEngine instance between "docker" (full management) and "website" (URL + notes tracking). Use when user wants to reclassify an instance.',
812
- inputSchema: {
813
- instanceId: z.string().describe('Instance ID'),
814
- serviceType: z.enum(['docker', 'website']).describe('New type: "docker" for full management, "website" for URL/notes tracking'),
815
- },
816
- _meta: { ui: { resourceUri: 'ui://flowengine/portals' } },
817
- }, async (args) => {
818
- const client = ensureClient();
819
- const { instanceId, serviceType } = args;
820
- try {
821
- await client.updateInstanceConfig(instanceId, { serviceType });
822
- return {
823
- content: [{ type: 'text', text: JSON.stringify({ success: true, instanceId, serviceType, message: `Instance type updated to "${serviceType}"` }, null, 2) }],
824
- };
825
- }
826
- catch (error) {
827
- return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: error.message }, null, 2) }] };
828
- }
829
- });
830
- // 13. Update Instance Domain - Update tracked URL for a website/docker instance
831
- registerAppTool(server, 'flowengine_update_instance_domain', {
832
- description: 'Update the tracked URL/domain for a website or docker instance. Updates the instance URL in FlowEngine (does not configure Coolify/DNS - do that separately). Use after setting up a custom domain.',
833
- inputSchema: {
834
- instanceId: z.string().describe('Instance ID'),
835
- domain: z.string().describe('New domain (e.g. "app.yourdomain.com" or "https://app.yourdomain.com")'),
836
- },
837
- _meta: { ui: { resourceUri: 'ui://flowengine/portals' } },
838
- }, async (args) => {
839
- const client = ensureClient();
840
- const { instanceId, domain } = args;
841
- try {
842
- const result = await client.updateInstanceDomain(instanceId, domain);
843
- return {
844
- content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
845
- };
846
- }
847
- catch (error) {
848
- return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: error.message }, null, 2) }] };
849
- }
850
- });
851
- // 15. List Enrichment Providers - returns available providers with config schemas
852
- registerAppTool(server, 'flowengine_list_enrichment_providers', {
853
- description: 'List available enrichment providers (FlowEngine, Apollo, Bright Data) with their config schemas and connection status. Use this to see which providers the user can configure for visitor intelligence.',
854
- inputSchema: {},
855
- _meta: { ui: { resourceUri: 'ui://flowengine/n8n' } },
856
- }, async () => {
857
- const client = ensureClient();
858
- try {
859
- const result = await client.listEnrichmentProviders();
860
- storeToolResult('enrichment', result);
861
- return {
862
- content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
863
- };
864
- }
865
- catch (error) {
866
- return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: error.message }, null, 2) }] };
867
- }
868
- });
869
- // 16. Configure Enrichment - update provider config on a site (NEVER sets enrichment_provider - user choice only)
870
- registerAppTool(server, 'flowengine_configure_enrichment', {
871
- description: 'Configure enrichment and auto-push settings for a visitor intel site. Can update provider config, enrichment level, and auto-push to Smartlead/Instantly. IMPORTANT: Cannot change enrichment_provider or deanon_provider - those are user-only choices.',
872
- inputSchema: {
873
- siteId: z.string().describe('Visitor site ID'),
874
- providerConfig: z.record(z.unknown()).optional().describe('Provider-specific configuration options (e.g. Apollo seniority filters, Bright Data dataset ID)'),
875
- enrichmentLevel: z.enum(['basic', 'standard', 'full']).optional().describe('Enrichment depth: basic (3 contacts), standard (5), full (10)'),
876
- autoPushTo: z.enum(['smartlead', 'instantly']).optional().describe('Auto-push new leads to this outreach platform. Set to empty string to disable.'),
877
- autoPushCampaignId: z.string().optional().describe('Campaign ID to push leads to (required if autoPushTo is set)'),
878
- },
879
- _meta: { ui: { resourceUri: 'ui://flowengine/n8n' } },
880
- }, async (args) => {
881
- const client = ensureClient();
882
- const { siteId, providerConfig, enrichmentLevel, autoPushTo, autoPushCampaignId } = args;
883
- try {
884
- const result = await client.configureEnrichment(siteId, { providerConfig, enrichmentLevel, autoPushTo, autoPushCampaignId });
885
- return {
886
- content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
887
- };
888
- }
889
- catch (error) {
890
- return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: error.message }, null, 2) }] };
891
- }
892
- });
893
- // 17. Get Latest Tool Result - Used by UI to fetch the result of the last tool call
894
- registerAppTool(server, 'flowengine_get_latest_result', {
895
- description: 'Internal: Get the latest tool result (used by UI to fetch preview data)',
896
- inputSchema: {},
897
- _meta: {
898
- ui: {
899
- resourceUri: 'ui://flowengine/internal',
900
- },
901
- },
902
- }, async () => {
903
- const latest = getLatestToolResult();
904
- if (!latest) {
905
- return {
906
- content: [{
907
- type: 'text',
908
- text: JSON.stringify({ error: 'No result available' }, null, 2),
909
- }],
910
- };
911
- }
912
- return {
913
- content: [{
914
- type: 'text',
915
- text: JSON.stringify(latest, null, 2),
916
- }],
917
- };
918
- });
919
- } // end MCP_MODE !== 'functions'
920
- if (MCP_MODE !== 'hosting') {
921
- registerFunctionsTools(server, {
922
- baseUrl: BASE_URL,
923
- getApiKey: () => {
924
- const apiKey = requestApiKey.getStore() || API_KEY;
925
- if (!apiKey) {
926
- throw new Error('FlowEngine API key not configured. Set FLOWENGINE_API_KEY in your MCP config.');
927
- }
928
- return apiKey;
929
- },
930
- });
931
- }
932
- /**
933
- * ===========================
934
- * SERVER STARTUP
935
- * ===========================
936
- */
937
- async function main() {
938
- const port = process.env.PORT ? parseInt(process.env.PORT, 10) : null;
939
- if (port) {
940
- // HTTP mode - for Claude.ai online "Add custom connector"
941
- // API key is read from Authorization: Bearer <fe_...> header per request
942
- const transport = new StreamableHTTPServerTransport({
943
- sessionIdGenerator: () => randomUUID(),
944
- });
945
- await server.server.connect(transport);
946
- const httpServer = createServer(async (req, res) => {
947
- const authHeader = req.headers['authorization'] ?? '';
948
- const token = authHeader.startsWith('Bearer ') ? authHeader.slice(7).trim() : '';
949
- let body;
950
- if (req.method === 'POST') {
951
- body = await parseBody(req);
952
- }
953
- await requestApiKey.run(token, () => transport.handleRequest(req, res, body));
954
- });
955
- httpServer.listen(port, () => {
956
- console.error(`FlowEngine MCP Server running on HTTP port ${port}`);
957
- });
958
- }
959
- else {
960
- // Stdio mode - for Claude Desktop / Claude Code CLI
961
- const transport = new StdioServerTransport();
962
- await server.server.connect(transport);
963
- console.error('FlowEngine MCP Server running on stdio');
964
- }
965
- }
966
- main().catch((error) => {
967
- console.error('Fatal error in MCP server:', error);
968
- process.exit(1);
969
- });
970
- //# sourceMappingURL=index.js.map