flowengine-mcp-app 2.0.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/cli/api.d.ts +19 -0
- package/build/cli/api.d.ts.map +1 -0
- package/build/cli/api.js +62 -0
- package/build/cli/api.js.map +1 -0
- package/build/cli/commands/delete.d.ts +4 -0
- package/build/cli/commands/delete.d.ts.map +1 -0
- package/build/cli/commands/delete.js +16 -0
- package/build/cli/commands/delete.js.map +1 -0
- package/build/cli/commands/deploy.d.ts +24 -0
- package/build/cli/commands/deploy.d.ts.map +1 -0
- package/build/cli/commands/deploy.js +103 -0
- package/build/cli/commands/deploy.js.map +1 -0
- package/build/cli/commands/domain.d.ts +9 -0
- package/build/cli/commands/domain.d.ts.map +1 -0
- package/build/cli/commands/domain.js +27 -0
- package/build/cli/commands/domain.js.map +1 -0
- package/build/cli/commands/env.d.ts +13 -0
- package/build/cli/commands/env.d.ts.map +1 -0
- package/build/cli/commands/env.js +53 -0
- package/build/cli/commands/env.js.map +1 -0
- package/build/cli/commands/list.d.ts +4 -0
- package/build/cli/commands/list.d.ts.map +1 -0
- package/build/cli/commands/list.js +25 -0
- package/build/cli/commands/list.js.map +1 -0
- package/build/cli/commands/login.d.ts +4 -0
- package/build/cli/commands/login.d.ts.map +1 -0
- package/build/cli/commands/login.js +37 -0
- package/build/cli/commands/login.js.map +1 -0
- package/build/cli/commands/logout.d.ts +2 -0
- package/build/cli/commands/logout.d.ts.map +1 -0
- package/build/cli/commands/logout.js +8 -0
- package/build/cli/commands/logout.js.map +1 -0
- package/build/cli/commands/logs.d.ts +4 -0
- package/build/cli/commands/logs.d.ts.map +1 -0
- package/build/cli/commands/logs.js +40 -0
- package/build/cli/commands/logs.js.map +1 -0
- package/build/cli/commands/show.d.ts +4 -0
- package/build/cli/commands/show.d.ts.map +1 -0
- package/build/cli/commands/show.js +13 -0
- package/build/cli/commands/show.js.map +1 -0
- package/build/cli/commands/usage.d.ts +7 -0
- package/build/cli/commands/usage.d.ts.map +1 -0
- package/build/cli/commands/usage.js +16 -0
- package/build/cli/commands/usage.js.map +1 -0
- package/build/cli/config.d.ts +17 -0
- package/build/cli/config.d.ts.map +1 -0
- package/build/cli/config.js +42 -0
- package/build/cli/config.js.map +1 -0
- package/build/cli/pack.d.ts +25 -0
- package/build/cli/pack.d.ts.map +1 -0
- package/build/cli/pack.js +111 -0
- package/build/cli/pack.js.map +1 -0
- package/build/cli/ui.d.ts +23 -0
- package/build/cli/ui.d.ts.map +1 -0
- package/build/cli/ui.js +105 -0
- package/build/cli/ui.js.map +1 -0
- package/build/cli.d.ts +10 -0
- package/build/cli.d.ts.map +1 -0
- package/build/cli.js +251 -0
- package/build/cli.js.map +1 -0
- package/build/client.d.ts +38 -0
- package/build/client.d.ts.map +1 -1
- package/build/client.js +124 -1
- package/build/client.js.map +1 -1
- package/build/index.d.ts +1 -1
- package/build/index.js +905 -344
- package/build/index.js.map +1 -1
- package/build/tools/functions.d.ts +14 -0
- package/build/tools/functions.d.ts.map +1 -0
- package/build/tools/functions.js +241 -0
- package/build/tools/functions.js.map +1 -0
- package/build/types.d.ts +155 -0
- package/build/types.d.ts.map +1 -0
- package/build/types.js +5 -0
- package/build/types.js.map +1 -0
- package/build/ui/component-viewer.d.ts +2 -2
- package/build/ui/component-viewer.js +4 -4
- package/build/ui/component-viewer.js.map +1 -1
- package/build/ui/dashboard.js +1 -1
- package/build/ui/ui/components.html +312 -0
- package/build/ui/ui/n8n.html +124 -0
- package/build/ui/ui/portals.html +211 -0
- package/build/ui/widgets.js +1 -1
- package/build/ui/widgets.js.map +1 -1
- package/package.json +23 -11
package/build/index.js
CHANGED
|
@@ -6,35 +6,85 @@
|
|
|
6
6
|
* Core Features:
|
|
7
7
|
* 1. Instance Management - Provision and manage FlowEngine instances
|
|
8
8
|
* 2. Client Portals - Monitor and access client portals
|
|
9
|
-
* 3. AI FlowBuilder - Create forms, chatbots, and UI
|
|
9
|
+
* 3. AI FlowBuilder - Create forms, chatbots, and UI embeds
|
|
10
10
|
*/
|
|
11
11
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
12
12
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
13
|
+
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
13
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';
|
|
14
23
|
import { FlowEngineClient } from './client.js';
|
|
15
|
-
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
import { renderError } from './ui/base.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/
|
|
20
28
|
// Environment configuration
|
|
21
29
|
const API_KEY = process.env.FLOWENGINE_API_KEY;
|
|
22
30
|
const BASE_URL = process.env.FLOWENGINE_BASE_URL || 'https://flowengine.cloud';
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
baseUrl: BASE_URL,
|
|
27
|
-
}) : null;
|
|
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();
|
|
28
34
|
function ensureClient() {
|
|
29
|
-
|
|
35
|
+
const apiKey = requestApiKey.getStore() || API_KEY;
|
|
36
|
+
if (!apiKey) {
|
|
30
37
|
throw new Error('FlowEngine API key not configured. Set FLOWENGINE_API_KEY in your MCP config.');
|
|
31
38
|
}
|
|
32
|
-
return
|
|
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;
|
|
33
83
|
}
|
|
34
84
|
// Initialize MCP Server with modern API
|
|
35
85
|
const server = new McpServer({
|
|
36
86
|
name: 'flowengine-mcp',
|
|
37
|
-
version: '2.0.
|
|
87
|
+
version: '2.0.9',
|
|
38
88
|
}, {
|
|
39
89
|
capabilities: {
|
|
40
90
|
resources: {},
|
|
@@ -43,364 +93,875 @@ const server = new McpServer({
|
|
|
43
93
|
});
|
|
44
94
|
/**
|
|
45
95
|
* ===========================
|
|
46
|
-
* APP RESOURCES (
|
|
47
|
-
* ===========================
|
|
48
|
-
*/
|
|
49
|
-
// Dashboard Resource (Portals + Instances)
|
|
50
|
-
registerAppResource(server, 'FlowEngine Dashboard', 'ui://flowengine/dashboard', {
|
|
51
|
-
description: 'INTERACTIVE UI APP: Shows live dashboard with all portals, instances, storage stats, and management controls. USE THIS UI when user asks for: dashboard, portals, instances, client accounts, portal management, or "show me FlowEngine". This provides a rich visual interface instead of JSON data.',
|
|
52
|
-
}, async () => {
|
|
53
|
-
try {
|
|
54
|
-
const portals = await ensureClient().getClientInstances();
|
|
55
|
-
const html = renderUnifiedDashboard(portals);
|
|
56
|
-
return {
|
|
57
|
-
contents: [{
|
|
58
|
-
uri: 'ui://flowengine/dashboard',
|
|
59
|
-
mimeType: RESOURCE_MIME_TYPE,
|
|
60
|
-
text: html,
|
|
61
|
-
}],
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
catch (error) {
|
|
65
|
-
const html = renderError(error.message || 'Failed to load dashboard');
|
|
66
|
-
return {
|
|
67
|
-
contents: [{
|
|
68
|
-
uri: 'ui://flowengine/dashboard',
|
|
69
|
-
mimeType: RESOURCE_MIME_TYPE,
|
|
70
|
-
text: html,
|
|
71
|
-
}],
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
});
|
|
75
|
-
// n8n Workflow Viewer Resource
|
|
76
|
-
registerAppResource(server, 'n8n Workflow Viewer', 'ui://flowengine/workflow-viewer', {
|
|
77
|
-
description: 'INTERACTIVE UI APP: Full-screen n8n workflow visualization with interactive demo. USE THIS UI when user asks to: view workflow, show n8n workflow, visualize automation, or preview workflow. Shows live workflow editor with share/export capabilities.',
|
|
78
|
-
}, async () => {
|
|
79
|
-
const html = renderN8nViewer();
|
|
80
|
-
return {
|
|
81
|
-
contents: [{
|
|
82
|
-
uri: 'ui://flowengine/workflow-viewer',
|
|
83
|
-
mimeType: RESOURCE_MIME_TYPE,
|
|
84
|
-
text: html,
|
|
85
|
-
}],
|
|
86
|
-
};
|
|
87
|
-
});
|
|
88
|
-
// UI Component Viewer Resource
|
|
89
|
-
registerAppResource(server, 'UI Component Viewer', 'ui://flowengine/component-viewer', {
|
|
90
|
-
description: 'INTERACTIVE UI APP: Live preview of forms, chatbots, widgets and UI components. USE THIS UI when user asks to: preview component, show chatbot, view form, see widget, or display UI component. Interactive preview with embed codes.',
|
|
91
|
-
}, async () => {
|
|
92
|
-
const html = renderComponentViewer();
|
|
93
|
-
return {
|
|
94
|
-
contents: [{
|
|
95
|
-
uri: 'ui://flowengine/component-viewer',
|
|
96
|
-
mimeType: RESOURCE_MIME_TYPE,
|
|
97
|
-
text: html,
|
|
98
|
-
}],
|
|
99
|
-
};
|
|
100
|
-
});
|
|
101
|
-
// AI FlowBuilder Dashboard Resource
|
|
102
|
-
registerAppResource(server, 'AI FlowBuilder Dashboard', 'ui://flowengine/ui-builder', {
|
|
103
|
-
description: 'INTERACTIVE UI APP: Dashboard for building and managing all UI components (forms, chatbots, widgets). USE THIS UI when user asks for: UI builder, component builder, create form, build chatbot, or manage components. Shows component gallery with creation tools.',
|
|
104
|
-
}, async () => {
|
|
105
|
-
try {
|
|
106
|
-
const widgets = await ensureClient().getWidgets();
|
|
107
|
-
const html = renderWidgetBuilder(widgets);
|
|
108
|
-
return {
|
|
109
|
-
contents: [{
|
|
110
|
-
uri: 'ui://flowengine/ui-builder',
|
|
111
|
-
mimeType: RESOURCE_MIME_TYPE,
|
|
112
|
-
text: html,
|
|
113
|
-
}],
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
catch (error) {
|
|
117
|
-
const html = renderError(error.message || 'Failed to load UI builder');
|
|
118
|
-
return {
|
|
119
|
-
contents: [{
|
|
120
|
-
uri: 'ui://flowengine/ui-builder',
|
|
121
|
-
mimeType: RESOURCE_MIME_TYPE,
|
|
122
|
-
text: html,
|
|
123
|
-
}],
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
});
|
|
127
|
-
/**
|
|
128
|
-
* ===========================
|
|
129
|
-
* TOOLS WITH UI (MCP Apps)
|
|
96
|
+
* APP RESOURCES (3 Focused UIs)
|
|
130
97
|
* ===========================
|
|
131
98
|
*/
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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'),
|
|
142
200
|
},
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
return {
|
|
147
|
-
content: [{
|
|
148
|
-
type: 'text',
|
|
149
|
-
text: JSON.stringify(instances, null, 2),
|
|
150
|
-
}],
|
|
151
|
-
};
|
|
152
|
-
});
|
|
153
|
-
// Portal Management - List with UI
|
|
154
|
-
registerAppTool(server, 'flowengine_list_portals', {
|
|
155
|
-
description: 'Show the FlowEngine Dashboard with interactive UI displaying all client portals, instances, and management controls. Use this when user wants to see portals, view client accounts, check portal status, or manage portals. Renders a visual dashboard interface instead of JSON.',
|
|
156
|
-
inputSchema: {
|
|
157
|
-
type: 'object',
|
|
158
|
-
properties: {},
|
|
159
|
-
},
|
|
160
|
-
_meta: {
|
|
161
|
-
ui: {
|
|
162
|
-
resourceUri: 'ui://flowengine/dashboard',
|
|
163
|
-
},
|
|
164
|
-
},
|
|
165
|
-
}, async () => {
|
|
166
|
-
const portals = await ensureClient().getClientInstances();
|
|
167
|
-
return {
|
|
168
|
-
content: [{
|
|
169
|
-
type: 'text',
|
|
170
|
-
text: JSON.stringify(portals, null, 2),
|
|
171
|
-
}],
|
|
172
|
-
};
|
|
173
|
-
});
|
|
174
|
-
// UI Components - List with UI
|
|
175
|
-
registerAppTool(server, 'flowengine_list_components', {
|
|
176
|
-
description: 'Show the AI FlowBuilder Dashboard with interactive UI displaying all forms, chatbots, widgets, and UI components. Use this when user wants to see the UI builder, view components, manage forms/chatbots, or build UI elements. Renders a component gallery interface instead of JSON. Optionally filter by instanceId.',
|
|
177
|
-
inputSchema: {
|
|
178
|
-
type: 'object',
|
|
179
|
-
properties: {
|
|
180
|
-
instanceId: {
|
|
181
|
-
type: 'string',
|
|
182
|
-
description: 'Optional: Filter by instance ID',
|
|
201
|
+
_meta: {
|
|
202
|
+
ui: {
|
|
203
|
+
resourceUri: 'ui://flowengine/n8n',
|
|
183
204
|
},
|
|
184
205
|
},
|
|
185
|
-
},
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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
|
+
}
|
|
215
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'),
|
|
216
339
|
},
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
const instanceId = args.instanceId;
|
|
221
|
-
const status = await ensureClient().getInstanceStatus(instanceId);
|
|
222
|
-
return {
|
|
223
|
-
content: [{
|
|
224
|
-
type: 'text',
|
|
225
|
-
text: JSON.stringify(status, null, 2),
|
|
226
|
-
}],
|
|
227
|
-
};
|
|
228
|
-
});
|
|
229
|
-
// Create Instance
|
|
230
|
-
server.registerTool('flowengine_create_instance', {
|
|
231
|
-
description: 'Provision a new FlowEngine instance for a client',
|
|
232
|
-
inputSchema: {
|
|
233
|
-
type: 'object',
|
|
234
|
-
properties: {
|
|
235
|
-
data: {
|
|
236
|
-
type: 'object',
|
|
237
|
-
description: 'Instance configuration with client info and settings',
|
|
340
|
+
_meta: {
|
|
341
|
+
ui: {
|
|
342
|
+
resourceUri: 'ui://flowengine/components',
|
|
238
343
|
},
|
|
239
344
|
},
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
//
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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',
|
|
261
411
|
},
|
|
262
412
|
},
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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',
|
|
284
449
|
},
|
|
285
450
|
},
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
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',
|
|
307
611
|
},
|
|
308
612
|
},
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
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',
|
|
330
644
|
},
|
|
331
645
|
},
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
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',
|
|
353
676
|
},
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
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',
|
|
357
708
|
},
|
|
358
709
|
},
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
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',
|
|
381
900
|
},
|
|
382
901
|
},
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
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
|
+
}
|
|
395
932
|
/**
|
|
396
933
|
* ===========================
|
|
397
934
|
* SERVER STARTUP
|
|
398
935
|
* ===========================
|
|
399
936
|
*/
|
|
400
937
|
async function main() {
|
|
401
|
-
const
|
|
402
|
-
|
|
403
|
-
|
|
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
|
+
}
|
|
404
965
|
}
|
|
405
966
|
main().catch((error) => {
|
|
406
967
|
console.error('Fatal error in MCP server:', error);
|