@agenticmail/enterprise 0.5.241 → 0.5.243
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-heartbeat-IZSQY2W4.js +510 -0
- package/dist/agent-heartbeat-W47EK5A7.js +510 -0
- package/dist/chunk-4VZAJQUR.js +4463 -0
- package/dist/chunk-EIORALQZ.js +3778 -0
- package/dist/chunk-H3NKARYD.js +1224 -0
- package/dist/chunk-PAENAIFM.js +4463 -0
- package/dist/chunk-SQ42SAUY.js +3778 -0
- package/dist/chunk-TM3CIRVW.js +1224 -0
- package/dist/cli-agent-O5AL2A3U.js +1721 -0
- package/dist/cli-agent-WUIV3COT.js +1721 -0
- package/dist/cli-serve-HOEKOOUY.js +114 -0
- package/dist/cli-serve-V7YB747O.js +114 -0
- package/dist/cli.js +3 -3
- package/dist/dashboard/app.js +1 -1
- package/dist/dashboard/docs/browser-providers.html +11 -11
- package/dist/dashboard/pages/agent-detail/meeting-browser.js +4 -1
- package/dist/dashboard/pages/skill-connections.js +673 -454
- package/dist/index.js +3 -3
- package/dist/routes-5OVWH3G3.js +13488 -0
- package/dist/routes-Z4PGYK4X.js +13487 -0
- package/dist/runtime-CRWSN52P.js +45 -0
- package/dist/runtime-MEOBOFBE.js +45 -0
- package/dist/server-BCMKVA47.js +15 -0
- package/dist/server-UV2MP24J.js +15 -0
- package/dist/setup-LLZ6NTTG.js +20 -0
- package/dist/setup-SOQGPCGF.js +20 -0
- package/package.json +1 -1
- package/src/dashboard/app.js +1 -1
- package/src/dashboard/docs/browser-providers.html +11 -11
- package/src/dashboard/pages/agent-detail/meeting-browser.js +4 -1
- package/src/dashboard/pages/skill-connections.js +673 -454
- package/src/engine/agent-routes.ts +2 -1
- package/src/engine/routes.ts +213 -0
|
@@ -3,515 +3,734 @@ import { I } from '../components/icons.js';
|
|
|
3
3
|
import { Modal } from '../components/modal.js';
|
|
4
4
|
import { HelpButton } from '../components/help-button.js';
|
|
5
5
|
|
|
6
|
-
//
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
'
|
|
13
|
-
'
|
|
14
|
-
'
|
|
15
|
-
'
|
|
16
|
-
'
|
|
17
|
-
'
|
|
18
|
-
'
|
|
19
|
-
'
|
|
20
|
-
'
|
|
21
|
-
'
|
|
22
|
-
'
|
|
23
|
-
'
|
|
6
|
+
// ═══════════════════════════════════════════════════════════
|
|
7
|
+
// Skill Connections & MCP Hub — Enterprise Integration Center
|
|
8
|
+
// ═══════════════════════════════════════════════════════════
|
|
9
|
+
|
|
10
|
+
// Category display metadata
|
|
11
|
+
var CATEGORY_META = {
|
|
12
|
+
'crm': { label: 'CRM & Sales', icon: 'users' },
|
|
13
|
+
'communication': { label: 'Communication', icon: 'messages' },
|
|
14
|
+
'productivity': { label: 'Productivity', icon: 'dashboard' },
|
|
15
|
+
'devops': { label: 'DevOps & CI/CD', icon: 'code' },
|
|
16
|
+
'finance': { label: 'Finance & Billing', icon: 'activity' },
|
|
17
|
+
'marketing': { label: 'Marketing', icon: 'globe' },
|
|
18
|
+
'hr': { label: 'HR & People', icon: 'users' },
|
|
19
|
+
'ecommerce': { label: 'E-Commerce', icon: 'upload' },
|
|
20
|
+
'infrastructure': { label: 'Infrastructure', icon: 'settings' },
|
|
21
|
+
'design': { label: 'Design & Media', icon: 'journal' },
|
|
22
|
+
'security': { label: 'Security', icon: 'shield' },
|
|
23
|
+
'monitoring': { label: 'Monitoring', icon: 'activity' },
|
|
24
|
+
'social': { label: 'Social Media', icon: 'globe' },
|
|
25
|
+
'data-ai': { label: 'Data & AI', icon: 'code' },
|
|
26
|
+
'enterprise': { label: 'Enterprise', icon: 'settings' },
|
|
27
|
+
'cms': { label: 'CMS', icon: 'journal' },
|
|
28
|
+
'general': { label: 'General', icon: 'settings' },
|
|
24
29
|
};
|
|
25
30
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
var AUTH_TYPE_LABELS = {
|
|
32
|
+
'oauth2': 'OAuth 2.0',
|
|
33
|
+
'api_key': 'API Key',
|
|
34
|
+
'token': 'Bearer Token',
|
|
35
|
+
'credentials': 'Credentials',
|
|
36
|
+
};
|
|
31
37
|
|
|
32
|
-
|
|
33
|
-
const { toast } = useApp();
|
|
34
|
-
const [installed, setInstalled] = useState([]);
|
|
35
|
-
const [statuses, setStatuses] = useState({});
|
|
36
|
-
const [providers, setProviders] = useState([]);
|
|
37
|
-
const [loading, setLoading] = useState(true);
|
|
38
|
-
const [error, setError] = useState(null);
|
|
39
|
-
|
|
40
|
-
// Config modal state
|
|
41
|
-
const [configSkill, setConfigSkill] = useState(null);
|
|
42
|
-
const [configSchema, setConfigSchema] = useState(null);
|
|
43
|
-
const [configValues, setConfigValues] = useState({});
|
|
44
|
-
const [configLoading, setConfigLoading] = useState(false);
|
|
45
|
-
const [configSaving, setConfigSaving] = useState(false);
|
|
46
|
-
|
|
47
|
-
// OAuth popup ref
|
|
48
|
-
const [connectingSkillId, setConnectingSkillId] = useState(null);
|
|
49
|
-
|
|
50
|
-
const loadStatuses = useCallback(function(skills) {
|
|
51
|
-
var promises = skills.map(function(skill) {
|
|
52
|
-
return engineCall('/oauth/status/' + skill.skillId)
|
|
53
|
-
.then(function(d) { return { skillId: skill.skillId, status: d }; })
|
|
54
|
-
.catch(function() { return { skillId: skill.skillId, status: { connected: false, provider: null, expiresAt: null } }; });
|
|
55
|
-
});
|
|
56
|
-
Promise.all(promises).then(function(results) {
|
|
57
|
-
var map = {};
|
|
58
|
-
results.forEach(function(r) { map[r.skillId] = r.status; });
|
|
59
|
-
setStatuses(map);
|
|
60
|
-
});
|
|
61
|
-
}, []);
|
|
38
|
+
// ── Section 1: MCP Servers ──────────────────────────────
|
|
62
39
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
40
|
+
function McpServersSection() {
|
|
41
|
+
var app = useApp(); var toast = app.toast;
|
|
42
|
+
var _servers = useState([]); var servers = _servers[0]; var setServers = _servers[1];
|
|
43
|
+
var _loading = useState(true); var loading = _loading[0]; var setLoading = _loading[1];
|
|
44
|
+
var _showAdd = useState(false); var showAdd = _showAdd[0]; var setShowAdd = _showAdd[1];
|
|
45
|
+
var _editServer = useState(null); var editServer = _editServer[0]; var setEditServer = _editServer[1];
|
|
46
|
+
var _testing = useState(null); var testing = _testing[0]; var setTesting = _testing[1];
|
|
66
47
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
setInstalled(skills);
|
|
71
|
-
loadStatuses(skills);
|
|
72
|
-
})
|
|
73
|
-
.catch(function(e) { setError(e.message || 'Failed to load installed skills'); })
|
|
74
|
-
.finally(function() { setLoading(false); });
|
|
48
|
+
// Add/edit form
|
|
49
|
+
var _form = useState({ name: '', type: 'stdio', command: '', args: '', url: '', apiKey: '', headers: '{}', env: '{}', enabled: true, description: '', autoRestart: true, timeout: 30 });
|
|
50
|
+
var form = _form[0]; var setForm = _form[1];
|
|
75
51
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
52
|
+
var load = useCallback(function() {
|
|
53
|
+
setLoading(true);
|
|
54
|
+
engineCall('/mcp-servers')
|
|
55
|
+
.then(function(d) { setServers(d.servers || []); })
|
|
56
|
+
.catch(function() { setServers([]); })
|
|
57
|
+
.finally(function() { setLoading(false); });
|
|
58
|
+
}, []);
|
|
80
59
|
|
|
81
60
|
useEffect(function() { load(); }, [load]);
|
|
82
61
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
function handleMessage(event) {
|
|
86
|
-
if (event.data && event.data.type === 'oauth-result') {
|
|
87
|
-
if (event.data.status === 'success') {
|
|
88
|
-
toast('OAuth connected successfully', 'success');
|
|
89
|
-
// Refresh status for the skill that was being connected
|
|
90
|
-
if (connectingSkillId) {
|
|
91
|
-
engineCall('/oauth/status/' + connectingSkillId)
|
|
92
|
-
.then(function(d) {
|
|
93
|
-
setStatuses(function(prev) {
|
|
94
|
-
var updated = Object.assign({}, prev);
|
|
95
|
-
updated[connectingSkillId] = d;
|
|
96
|
-
return updated;
|
|
97
|
-
});
|
|
98
|
-
})
|
|
99
|
-
.catch(function() {});
|
|
100
|
-
}
|
|
101
|
-
setConnectingSkillId(null);
|
|
102
|
-
} else {
|
|
103
|
-
toast(event.data.message || 'OAuth connection failed', 'error');
|
|
104
|
-
setConnectingSkillId(null);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
window.addEventListener('message', handleMessage);
|
|
109
|
-
return function() { window.removeEventListener('message', handleMessage); };
|
|
110
|
-
}, [connectingSkillId, toast]);
|
|
111
|
-
|
|
112
|
-
// Connect OAuth
|
|
113
|
-
var connectOAuth = async function(skillId) {
|
|
114
|
-
setConnectingSkillId(skillId);
|
|
115
|
-
try {
|
|
116
|
-
var result = await engineCall('/oauth/authorize/' + skillId);
|
|
117
|
-
if (result.authUrl) {
|
|
118
|
-
var w = 600;
|
|
119
|
-
var ht = 700;
|
|
120
|
-
var left = (window.screen.width - w) / 2;
|
|
121
|
-
var top = (window.screen.height - ht) / 2;
|
|
122
|
-
window.open(
|
|
123
|
-
result.authUrl,
|
|
124
|
-
'oauth_popup',
|
|
125
|
-
'width=' + w + ',height=' + ht + ',left=' + left + ',top=' + top + ',scrollbars=yes,resizable=yes'
|
|
126
|
-
);
|
|
127
|
-
} else {
|
|
128
|
-
toast('No authorization URL returned', 'error');
|
|
129
|
-
setConnectingSkillId(null);
|
|
130
|
-
}
|
|
131
|
-
} catch (e) {
|
|
132
|
-
toast(e.message || 'Failed to start OAuth flow', 'error');
|
|
133
|
-
setConnectingSkillId(null);
|
|
134
|
-
}
|
|
62
|
+
var resetForm = function() {
|
|
63
|
+
setForm({ name: '', type: 'stdio', command: '', args: '', url: '', apiKey: '', headers: '{}', env: '{}', enabled: true, description: '', autoRestart: true, timeout: 30 });
|
|
135
64
|
};
|
|
136
65
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
66
|
+
var openAdd = function() { resetForm(); setEditServer(null); setShowAdd(true); };
|
|
67
|
+
|
|
68
|
+
var openEdit = function(server) {
|
|
69
|
+
setForm({
|
|
70
|
+
name: server.name || '',
|
|
71
|
+
type: server.type || 'stdio',
|
|
72
|
+
command: server.command || '',
|
|
73
|
+
args: (server.args || []).join(' '),
|
|
74
|
+
url: server.url || '',
|
|
75
|
+
apiKey: server.apiKey || '',
|
|
76
|
+
headers: JSON.stringify(server.headers || {}, null, 2),
|
|
77
|
+
env: JSON.stringify(server.env || {}, null, 2),
|
|
78
|
+
enabled: server.enabled !== false,
|
|
79
|
+
description: server.description || '',
|
|
80
|
+
autoRestart: server.autoRestart !== false,
|
|
81
|
+
timeout: server.timeout || 30,
|
|
82
|
+
});
|
|
83
|
+
setEditServer(server);
|
|
84
|
+
setShowAdd(true);
|
|
150
85
|
};
|
|
151
86
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
87
|
+
var saveServer = function() {
|
|
88
|
+
var payload = {
|
|
89
|
+
name: form.name.trim(),
|
|
90
|
+
type: form.type,
|
|
91
|
+
enabled: form.enabled,
|
|
92
|
+
description: form.description.trim(),
|
|
93
|
+
autoRestart: form.autoRestart,
|
|
94
|
+
timeout: parseInt(form.timeout) || 30,
|
|
95
|
+
};
|
|
96
|
+
if (form.type === 'stdio') {
|
|
97
|
+
payload.command = form.command.trim();
|
|
98
|
+
payload.args = form.args.trim().split(/\s+/).filter(Boolean);
|
|
99
|
+
try { payload.env = JSON.parse(form.env || '{}'); } catch { payload.env = {}; }
|
|
100
|
+
} else {
|
|
101
|
+
payload.url = form.url.trim();
|
|
102
|
+
if (form.apiKey) payload.apiKey = form.apiKey;
|
|
103
|
+
try { payload.headers = JSON.parse(form.headers || '{}'); } catch { payload.headers = {}; }
|
|
165
104
|
}
|
|
166
|
-
setConfigLoading(false);
|
|
167
|
-
};
|
|
168
105
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
if (!
|
|
172
|
-
setConfigSaving(true);
|
|
173
|
-
try {
|
|
174
|
-
await engineCall('/community/skills/' + configSkill.skillId + '/config', {
|
|
175
|
-
method: 'PUT',
|
|
176
|
-
body: JSON.stringify(configValues)
|
|
177
|
-
});
|
|
178
|
-
toast('Configuration saved', 'success');
|
|
179
|
-
// Update local installed list with new config
|
|
180
|
-
setInstalled(function(prev) {
|
|
181
|
-
return prev.map(function(s) {
|
|
182
|
-
if (s.skillId === configSkill.skillId) {
|
|
183
|
-
return Object.assign({}, s, { config: Object.assign({}, configValues) });
|
|
184
|
-
}
|
|
185
|
-
return s;
|
|
186
|
-
});
|
|
187
|
-
});
|
|
188
|
-
setConfigSkill(null);
|
|
189
|
-
} catch (e) {
|
|
190
|
-
toast(e.message || 'Failed to save configuration', 'error');
|
|
191
|
-
}
|
|
192
|
-
setConfigSaving(false);
|
|
193
|
-
};
|
|
106
|
+
if (!payload.name) { toast('Server name is required', 'error'); return; }
|
|
107
|
+
if (form.type === 'stdio' && !payload.command) { toast('Command is required for stdio servers', 'error'); return; }
|
|
108
|
+
if (form.type !== 'stdio' && !payload.url) { toast('URL is required for HTTP/SSE servers', 'error'); return; }
|
|
194
109
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
110
|
+
var method = editServer ? 'PUT' : 'POST';
|
|
111
|
+
var url = editServer ? '/mcp-servers/' + editServer.id : '/mcp-servers';
|
|
112
|
+
engineCall(url, { method: method, body: JSON.stringify(payload) })
|
|
113
|
+
.then(function(d) {
|
|
114
|
+
if (d.error) { toast(d.error, 'error'); return; }
|
|
115
|
+
toast(editServer ? 'Server updated' : 'Server added', 'success');
|
|
116
|
+
setShowAdd(false); load();
|
|
117
|
+
})
|
|
118
|
+
.catch(function(e) { toast(e.message, 'error'); });
|
|
119
|
+
};
|
|
198
120
|
|
|
199
|
-
var
|
|
200
|
-
|
|
121
|
+
var deleteServer = function(id) {
|
|
122
|
+
if (!confirm('Remove this MCP server? Agents will lose access to its tools.')) return;
|
|
123
|
+
engineCall('/mcp-servers/' + id, { method: 'DELETE' })
|
|
124
|
+
.then(function() { toast('Server removed', 'success'); load(); })
|
|
125
|
+
.catch(function(e) { toast(e.message, 'error'); });
|
|
201
126
|
};
|
|
202
127
|
|
|
203
|
-
var
|
|
204
|
-
|
|
205
|
-
|
|
128
|
+
var toggleServer = function(server) {
|
|
129
|
+
engineCall('/mcp-servers/' + server.id, { method: 'PUT', body: JSON.stringify({ enabled: !server.enabled }) })
|
|
130
|
+
.then(function() { toast(server.enabled ? 'Server disabled' : 'Server enabled', 'success'); load(); })
|
|
131
|
+
.catch(function(e) { toast(e.message, 'error'); });
|
|
206
132
|
};
|
|
207
133
|
|
|
208
|
-
var
|
|
209
|
-
|
|
134
|
+
var testServer = function(server) {
|
|
135
|
+
setTesting(server.id);
|
|
136
|
+
engineCall('/mcp-servers/' + server.id + '/test', { method: 'POST' })
|
|
137
|
+
.then(function(d) {
|
|
138
|
+
if (d.error) toast('Connection failed: ' + d.error, 'error');
|
|
139
|
+
else toast('Connected! ' + (d.tools || 0) + ' tools discovered', 'success');
|
|
140
|
+
load();
|
|
141
|
+
})
|
|
142
|
+
.catch(function(e) { toast(e.message, 'error'); })
|
|
143
|
+
.finally(function() { setTesting(null); });
|
|
210
144
|
};
|
|
211
145
|
|
|
212
|
-
var
|
|
213
|
-
|
|
214
|
-
return
|
|
146
|
+
var typeLabel = function(t) {
|
|
147
|
+
if (t === 'stdio') return 'Local Process (stdio)';
|
|
148
|
+
if (t === 'sse') return 'Server-Sent Events (SSE)';
|
|
149
|
+
return 'HTTP (Streamable)';
|
|
215
150
|
};
|
|
216
151
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
var needsConfigCount = installed.filter(function(s) { return needsConfig(s); }).length;
|
|
221
|
-
|
|
222
|
-
// Status badge
|
|
223
|
-
var statusBadge = function(skill) {
|
|
224
|
-
var status = getSkillStatus(skill.skillId);
|
|
225
|
-
if (status.connected) {
|
|
226
|
-
return h('span', {
|
|
227
|
-
className: 'badge',
|
|
228
|
-
style: { background: 'var(--success)', color: '#fff', fontSize: 11 }
|
|
229
|
-
}, 'Connected');
|
|
230
|
-
}
|
|
231
|
-
if (needsConfig(skill)) {
|
|
232
|
-
return h('span', {
|
|
233
|
-
className: 'badge',
|
|
234
|
-
style: { background: 'var(--warning)', color: '#fff', fontSize: 11 }
|
|
235
|
-
}, 'Needs Config');
|
|
236
|
-
}
|
|
237
|
-
return h('span', {
|
|
238
|
-
className: 'badge',
|
|
239
|
-
style: { background: 'var(--text-muted)', color: '#fff', fontSize: 11 }
|
|
240
|
-
}, 'Not Connected');
|
|
152
|
+
var statusDot = function(server) {
|
|
153
|
+
var color = server.status === 'connected' ? 'var(--success)' : server.status === 'error' ? 'var(--danger)' : server.enabled ? 'var(--warning)' : 'var(--text-muted)';
|
|
154
|
+
return h('span', { style: { width: 8, height: 8, borderRadius: '50%', background: color, display: 'inline-block', flexShrink: 0 } });
|
|
241
155
|
};
|
|
242
156
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
h('
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
157
|
+
return h('div', null,
|
|
158
|
+
// Header
|
|
159
|
+
h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 } },
|
|
160
|
+
h('div', { style: { display: 'flex', alignItems: 'center', gap: 8 } },
|
|
161
|
+
h('h2', { style: { fontSize: 16, fontWeight: 700, margin: 0 } }, 'MCP Servers'),
|
|
162
|
+
h(HelpButton, { label: 'MCP Servers' },
|
|
163
|
+
h('p', null, 'Connect external Model Context Protocol (MCP) servers to give your agents access to additional tools and capabilities.'),
|
|
164
|
+
h('h4', { style: { marginTop: 12, marginBottom: 6, fontSize: 14 } }, 'Connection Types'),
|
|
165
|
+
h('ul', { style: { paddingLeft: 20, margin: '4px 0 8px' } },
|
|
166
|
+
h('li', null, h('strong', null, 'Local Process (stdio)'), ' — Runs a command on your server. The MCP server communicates via stdin/stdout. Best for locally installed tools.'),
|
|
167
|
+
h('li', null, h('strong', null, 'SSE (Server-Sent Events)'), ' — Connects to a remote MCP server via HTTP with SSE for streaming. Best for remote/cloud MCP servers.'),
|
|
168
|
+
h('li', null, h('strong', null, 'HTTP (Streamable)'), ' — Standard HTTP transport. Stateless request/response pattern.')
|
|
169
|
+
),
|
|
170
|
+
h('h4', { style: { marginTop: 12, marginBottom: 6, fontSize: 14 } }, 'How it works'),
|
|
171
|
+
h('p', null, 'When you add an MCP server, we automatically discover all available tools it provides. These tools become available to your agents alongside the built-in integrations.'),
|
|
172
|
+
h('div', { style: { marginTop: 12, padding: 12, background: 'var(--bg-secondary)', borderRadius: 8, fontSize: 13 } },
|
|
173
|
+
h('strong', null, 'Examples: '),
|
|
174
|
+
'npx @modelcontextprotocol/server-filesystem /path/to/dir, ',
|
|
175
|
+
'npx @modelcontextprotocol/server-github, ',
|
|
176
|
+
'docker run -i mcp/postgres, ',
|
|
177
|
+
'Any MCP-compatible server'
|
|
178
|
+
)
|
|
179
|
+
)
|
|
180
|
+
),
|
|
181
|
+
h('button', { className: 'btn btn-primary btn-sm', onClick: openAdd }, I.plus(), ' Add MCP Server')
|
|
182
|
+
),
|
|
267
183
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
184
|
+
// Server list
|
|
185
|
+
loading ? h('div', { style: { padding: 32, textAlign: 'center', color: 'var(--text-muted)', fontSize: 13 } }, 'Loading MCP servers...')
|
|
186
|
+
: servers.length === 0 ? h('div', { style: { padding: 32, textAlign: 'center', border: '2px dashed var(--border)', borderRadius: 'var(--radius-lg)', color: 'var(--text-muted)' } },
|
|
187
|
+
h('div', { style: { marginBottom: 8 } }, I.code()),
|
|
188
|
+
h('p', { style: { fontSize: 14, fontWeight: 500, marginBottom: 4 } }, 'No MCP servers connected'),
|
|
189
|
+
h('p', { style: { fontSize: 12 } }, 'Add an MCP server to extend your agents with external tools'),
|
|
190
|
+
h('button', { className: 'btn btn-secondary btn-sm', style: { marginTop: 12 }, onClick: openAdd }, I.plus(), ' Add Your First Server')
|
|
191
|
+
)
|
|
192
|
+
: h('div', { style: { display: 'flex', flexDirection: 'column', gap: 8 } },
|
|
193
|
+
servers.map(function(server) {
|
|
194
|
+
var isTesting = testing === server.id;
|
|
195
|
+
return h('div', { key: server.id, style: {
|
|
196
|
+
padding: '14px 16px', background: 'var(--bg-card)', border: '1px solid var(--border)', borderRadius: 'var(--radius)',
|
|
197
|
+
opacity: server.enabled === false ? 0.6 : 1,
|
|
198
|
+
} },
|
|
199
|
+
h('div', { style: { display: 'flex', alignItems: 'center', gap: 10 } },
|
|
200
|
+
statusDot(server),
|
|
201
|
+
h('div', { style: { flex: 1, minWidth: 0 } },
|
|
202
|
+
h('div', { style: { display: 'flex', alignItems: 'center', gap: 8 } },
|
|
203
|
+
h('span', { style: { fontWeight: 600, fontSize: 14 } }, server.name),
|
|
204
|
+
h('span', { className: 'badge badge-neutral', style: { fontSize: 10 } }, server.type === 'stdio' ? 'stdio' : server.type === 'sse' ? 'SSE' : 'HTTP'),
|
|
205
|
+
server.toolCount > 0 && h('span', { className: 'badge', style: { fontSize: 10, background: 'var(--accent-soft)', color: 'var(--accent)' } }, server.toolCount + ' tools'),
|
|
206
|
+
!server.enabled && h('span', { className: 'badge', style: { fontSize: 10, background: 'var(--bg-tertiary)', color: 'var(--text-muted)' } }, 'Disabled')
|
|
207
|
+
),
|
|
208
|
+
h('div', { style: { fontSize: 11, color: 'var(--text-muted)', marginTop: 2, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } },
|
|
209
|
+
server.type === 'stdio'
|
|
210
|
+
? (server.command + ' ' + (server.args || []).join(' ')).trim()
|
|
211
|
+
: server.url || ''
|
|
212
|
+
),
|
|
213
|
+
server.description && h('div', { style: { fontSize: 11, color: 'var(--text-secondary)', marginTop: 2 } }, server.description)
|
|
214
|
+
),
|
|
215
|
+
h('div', { style: { display: 'flex', gap: 4, flexShrink: 0 } },
|
|
216
|
+
h('button', { className: 'btn btn-ghost btn-sm', title: 'Test connection', disabled: isTesting, onClick: function() { testServer(server); } },
|
|
217
|
+
isTesting ? '...' : I.refresh()),
|
|
218
|
+
h('button', { className: 'btn btn-ghost btn-sm', title: server.enabled ? 'Disable' : 'Enable', onClick: function() { toggleServer(server); } },
|
|
219
|
+
server.enabled ? I.pause() : I.play()),
|
|
220
|
+
h('button', { className: 'btn btn-ghost btn-sm', title: 'Edit', onClick: function() { openEdit(server); } }, I.settings()),
|
|
221
|
+
h('button', { className: 'btn btn-ghost btn-sm', title: 'Remove', style: { color: 'var(--danger)' }, onClick: function() { deleteServer(server.id); } }, I.x())
|
|
222
|
+
)
|
|
223
|
+
),
|
|
224
|
+
// Show tools if expanded (server has discovered tools)
|
|
225
|
+
server.tools && server.tools.length > 0 && h('div', { style: { marginTop: 10, paddingTop: 10, borderTop: '1px solid var(--border)', display: 'flex', flexWrap: 'wrap', gap: 4 } },
|
|
226
|
+
server.tools.slice(0, 20).map(function(tool) {
|
|
227
|
+
return h('span', { key: tool.name, style: {
|
|
228
|
+
fontSize: 10, padding: '2px 6px', borderRadius: 4,
|
|
229
|
+
background: 'var(--bg-tertiary)', color: 'var(--text-secondary)',
|
|
230
|
+
whiteSpace: 'nowrap',
|
|
231
|
+
} }, tool.name);
|
|
232
|
+
}),
|
|
233
|
+
server.tools.length > 20 && h('span', { style: { fontSize: 10, color: 'var(--text-muted)', padding: '2px 4px' } }, '+' + (server.tools.length - 20) + ' more')
|
|
234
|
+
)
|
|
235
|
+
);
|
|
236
|
+
})
|
|
237
|
+
),
|
|
238
|
+
|
|
239
|
+
// Add/Edit modal
|
|
240
|
+
showAdd && h(Modal, {
|
|
241
|
+
title: editServer ? 'Edit MCP Server' : 'Add MCP Server',
|
|
242
|
+
onClose: function() { setShowAdd(false); },
|
|
243
|
+
footer: h(Fragment, null,
|
|
244
|
+
h('button', { className: 'btn btn-secondary', onClick: function() { setShowAdd(false); } }, 'Cancel'),
|
|
245
|
+
h('button', { className: 'btn btn-primary', onClick: saveServer }, editServer ? 'Save Changes' : 'Add Server')
|
|
246
|
+
)
|
|
247
|
+
},
|
|
248
|
+
h('div', { style: { display: 'flex', flexDirection: 'column', gap: 14 } },
|
|
249
|
+
// Name
|
|
250
|
+
h('div', { className: 'form-group' },
|
|
251
|
+
h('label', { className: 'form-label' }, 'Server Name *'),
|
|
252
|
+
h('input', { className: 'input', placeholder: 'e.g., GitHub MCP, Filesystem, Database', value: form.name,
|
|
253
|
+
onChange: function(e) { setForm(Object.assign({}, form, { name: e.target.value })); } })
|
|
273
254
|
),
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
var updated = Object.assign({}, prev);
|
|
280
|
-
updated[fieldName] = e.target.value;
|
|
281
|
-
return updated;
|
|
282
|
-
});
|
|
283
|
-
}
|
|
284
|
-
},
|
|
285
|
-
h('option', { value: '' }, '-- Select --'),
|
|
286
|
-
(schema.options || []).map(function(opt) {
|
|
287
|
-
var optValue = typeof opt === 'string' ? opt : opt.value;
|
|
288
|
-
var optLabel = typeof opt === 'string' ? opt : opt.label;
|
|
289
|
-
return h('option', { key: optValue, value: optValue }, optLabel);
|
|
290
|
-
})
|
|
255
|
+
// Description
|
|
256
|
+
h('div', { className: 'form-group' },
|
|
257
|
+
h('label', { className: 'form-label' }, 'Description'),
|
|
258
|
+
h('input', { className: 'input', placeholder: 'What does this server provide?', value: form.description,
|
|
259
|
+
onChange: function(e) { setForm(Object.assign({}, form, { description: e.target.value })); } })
|
|
291
260
|
),
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
261
|
+
// Type selector
|
|
262
|
+
h('div', { className: 'form-group' },
|
|
263
|
+
h('label', { className: 'form-label' }, 'Connection Type *'),
|
|
264
|
+
h('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 8 } },
|
|
265
|
+
['stdio', 'sse', 'http'].map(function(t) {
|
|
266
|
+
var selected = form.type === t;
|
|
267
|
+
return h('div', {
|
|
268
|
+
key: t,
|
|
269
|
+
onClick: function() { setForm(Object.assign({}, form, { type: t })); },
|
|
270
|
+
style: {
|
|
271
|
+
padding: '10px 12px', borderRadius: 'var(--radius)', cursor: 'pointer', textAlign: 'center',
|
|
272
|
+
border: '2px solid ' + (selected ? 'var(--accent)' : 'var(--border)'),
|
|
273
|
+
background: selected ? 'var(--accent-soft)' : 'var(--bg-secondary)',
|
|
274
|
+
}
|
|
275
|
+
},
|
|
276
|
+
h('div', { style: { fontWeight: 600, fontSize: 12 } }, t === 'stdio' ? 'Local Process' : t === 'sse' ? 'SSE' : 'HTTP'),
|
|
277
|
+
h('div', { style: { fontSize: 10, color: 'var(--text-muted)', marginTop: 2 } },
|
|
278
|
+
t === 'stdio' ? 'stdin/stdout' : t === 'sse' ? 'Server-Sent Events' : 'Streamable HTTP')
|
|
279
|
+
);
|
|
280
|
+
})
|
|
281
|
+
)
|
|
282
|
+
),
|
|
283
|
+
// stdio fields
|
|
284
|
+
form.type === 'stdio' && h(Fragment, null,
|
|
285
|
+
h('div', { className: 'form-group' },
|
|
286
|
+
h('label', { className: 'form-label' }, 'Command *'),
|
|
287
|
+
h('input', { className: 'input', placeholder: 'npx, node, python, docker...', value: form.command,
|
|
288
|
+
onChange: function(e) { setForm(Object.assign({}, form, { command: e.target.value })); } }),
|
|
289
|
+
h('div', { style: { fontSize: 11, color: 'var(--text-muted)', marginTop: 4 } }, 'The executable to run. Must be installed on this machine.')
|
|
290
|
+
),
|
|
291
|
+
h('div', { className: 'form-group' },
|
|
292
|
+
h('label', { className: 'form-label' }, 'Arguments'),
|
|
293
|
+
h('input', { className: 'input', placeholder: '@modelcontextprotocol/server-filesystem /home/user/docs', value: form.args,
|
|
294
|
+
onChange: function(e) { setForm(Object.assign({}, form, { args: e.target.value })); } }),
|
|
295
|
+
h('div', { style: { fontSize: 11, color: 'var(--text-muted)', marginTop: 4 } }, 'Space-separated arguments passed to the command.')
|
|
296
|
+
),
|
|
297
|
+
h('div', { className: 'form-group' },
|
|
298
|
+
h('label', { className: 'form-label' }, 'Environment Variables'),
|
|
299
|
+
h('textarea', { className: 'input', rows: 3, placeholder: '{\n "GITHUB_TOKEN": "ghp_...",\n "DATABASE_URL": "postgres://..."\n}', value: form.env,
|
|
300
|
+
style: { fontFamily: 'var(--font-mono, monospace)', fontSize: 12 },
|
|
301
|
+
onChange: function(e) { setForm(Object.assign({}, form, { env: e.target.value })); } }),
|
|
302
|
+
h('div', { style: { fontSize: 11, color: 'var(--text-muted)', marginTop: 4 } }, 'JSON object of environment variables. Secrets are encrypted at rest.')
|
|
303
|
+
)
|
|
304
|
+
),
|
|
305
|
+
// HTTP/SSE fields
|
|
306
|
+
form.type !== 'stdio' && h(Fragment, null,
|
|
307
|
+
h('div', { className: 'form-group' },
|
|
308
|
+
h('label', { className: 'form-label' }, 'Server URL *'),
|
|
309
|
+
h('input', { className: 'input', placeholder: form.type === 'sse' ? 'https://mcp.example.com/sse' : 'https://mcp.example.com/mcp', value: form.url,
|
|
310
|
+
onChange: function(e) { setForm(Object.assign({}, form, { url: e.target.value })); } })
|
|
311
|
+
),
|
|
312
|
+
h('div', { className: 'form-group' },
|
|
313
|
+
h('label', { className: 'form-label' }, 'API Key / Bearer Token'),
|
|
314
|
+
h('input', { className: 'input', type: 'password', placeholder: 'Optional — for authenticated endpoints', value: form.apiKey,
|
|
315
|
+
onChange: function(e) { setForm(Object.assign({}, form, { apiKey: e.target.value })); } })
|
|
316
|
+
),
|
|
317
|
+
h('div', { className: 'form-group' },
|
|
318
|
+
h('label', { className: 'form-label' }, 'Custom Headers'),
|
|
319
|
+
h('textarea', { className: 'input', rows: 2, placeholder: '{\n "X-Custom-Header": "value"\n}', value: form.headers,
|
|
320
|
+
style: { fontFamily: 'var(--font-mono, monospace)', fontSize: 12 },
|
|
321
|
+
onChange: function(e) { setForm(Object.assign({}, form, { headers: e.target.value })); } })
|
|
322
|
+
)
|
|
323
|
+
),
|
|
324
|
+
// Common settings
|
|
325
|
+
h('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 } },
|
|
326
|
+
h('div', { className: 'form-group' },
|
|
327
|
+
h('label', { className: 'form-label' }, 'Connection Timeout (s)'),
|
|
328
|
+
h('input', { className: 'input', type: 'number', min: 5, max: 300, value: form.timeout,
|
|
329
|
+
onChange: function(e) { setForm(Object.assign({}, form, { timeout: e.target.value })); } })
|
|
330
|
+
),
|
|
331
|
+
h('div', { className: 'form-group' },
|
|
332
|
+
h('label', { style: { display: 'flex', alignItems: 'center', gap: 8, cursor: 'pointer', fontSize: 13, marginTop: 24 } },
|
|
333
|
+
h('input', { type: 'checkbox', checked: form.autoRestart,
|
|
334
|
+
onChange: function(e) { setForm(Object.assign({}, form, { autoRestart: e.target.checked })); } }),
|
|
335
|
+
'Auto-restart on failure'
|
|
336
|
+
)
|
|
337
|
+
)
|
|
338
|
+
),
|
|
339
|
+
// Preset templates
|
|
340
|
+
!editServer && h('div', { style: { paddingTop: 12, borderTop: '1px solid var(--border)' } },
|
|
341
|
+
h('div', { style: { fontSize: 12, fontWeight: 600, color: 'var(--text-muted)', marginBottom: 8 } }, 'Quick Start Templates'),
|
|
342
|
+
h('div', { style: { display: 'flex', flexWrap: 'wrap', gap: 6 } },
|
|
343
|
+
[
|
|
344
|
+
{ label: 'Filesystem', name: 'Filesystem', cmd: 'npx', args: '-y @modelcontextprotocol/server-filesystem /home' },
|
|
345
|
+
{ label: 'GitHub', name: 'GitHub', cmd: 'npx', args: '-y @modelcontextprotocol/server-github', envs: '{"GITHUB_PERSONAL_ACCESS_TOKEN": ""}' },
|
|
346
|
+
{ label: 'PostgreSQL', name: 'PostgreSQL', cmd: 'npx', args: '-y @modelcontextprotocol/server-postgres', envs: '{"DATABASE_URL": ""}' },
|
|
347
|
+
{ label: 'Brave Search', name: 'Brave Search', cmd: 'npx', args: '-y @modelcontextprotocol/server-brave-search', envs: '{"BRAVE_API_KEY": ""}' },
|
|
348
|
+
{ label: 'Puppeteer', name: 'Puppeteer', cmd: 'npx', args: '-y @modelcontextprotocol/server-puppeteer' },
|
|
349
|
+
{ label: 'Slack', name: 'Slack', cmd: 'npx', args: '-y @modelcontextprotocol/server-slack', envs: '{"SLACK_BOT_TOKEN": "", "SLACK_TEAM_ID": ""}' },
|
|
350
|
+
{ label: 'Google Drive', name: 'Google Drive', cmd: 'npx', args: '-y @modelcontextprotocol/server-gdrive' },
|
|
351
|
+
{ label: 'Memory', name: 'Memory', cmd: 'npx', args: '-y @modelcontextprotocol/server-memory' },
|
|
352
|
+
{ label: 'Sentry', name: 'Sentry', cmd: 'npx', args: '-y @modelcontextprotocol/server-sentry', envs: '{"SENTRY_AUTH_TOKEN": ""}' },
|
|
353
|
+
{ label: 'Fetch', name: 'Fetch', cmd: 'npx', args: '-y @modelcontextprotocol/server-fetch' },
|
|
354
|
+
].map(function(tpl) {
|
|
355
|
+
return h('button', {
|
|
356
|
+
key: tpl.label,
|
|
357
|
+
className: 'btn btn-ghost btn-sm',
|
|
358
|
+
style: { fontSize: 11, padding: '4px 8px' },
|
|
359
|
+
onClick: function() { setForm(Object.assign({}, form, { name: tpl.name, type: 'stdio', command: tpl.cmd, args: tpl.args, env: tpl.envs || '{}' })); }
|
|
360
|
+
}, tpl.label);
|
|
361
|
+
})
|
|
362
|
+
)
|
|
363
|
+
)
|
|
364
|
+
)
|
|
365
|
+
)
|
|
366
|
+
);
|
|
367
|
+
}
|
|
295
368
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
})
|
|
315
|
-
|
|
316
|
-
|
|
369
|
+
// ── Section 2: Built-in Integrations ─────────────────────
|
|
370
|
+
|
|
371
|
+
function IntegrationsSection() {
|
|
372
|
+
var app = useApp(); var toast = app.toast;
|
|
373
|
+
var _catalog = useState([]); var catalog = _catalog[0]; var setCatalog = _catalog[1];
|
|
374
|
+
var _categories = useState({}); var categories = _categories[0]; var setCategories = _categories[1];
|
|
375
|
+
var _loading = useState(true); var loading = _loading[0]; var setLoading = _loading[1];
|
|
376
|
+
var _search = useState(''); var search = _search[0]; var setSearch = _search[1];
|
|
377
|
+
var _catFilter = useState('all'); var catFilter = _catFilter[0]; var setCatFilter = _catFilter[1];
|
|
378
|
+
var _configModal = useState(null); var configModal = _configModal[0]; var setConfigModal = _configModal[1];
|
|
379
|
+
var _configValues = useState({}); var configValues = _configValues[0]; var setConfigValues = _configValues[1];
|
|
380
|
+
var _saving = useState(false); var saving = _saving[0]; var setSaving = _saving[1];
|
|
381
|
+
var _showAll = useState(false); var showAll = _showAll[0]; var setShowAll = _showAll[1];
|
|
382
|
+
|
|
383
|
+
var load = useCallback(function() {
|
|
384
|
+
setLoading(true);
|
|
385
|
+
engineCall('/integrations/catalog')
|
|
386
|
+
.then(function(d) { setCatalog(d.catalog || []); setCategories(d.categories || {}); })
|
|
387
|
+
.catch(function() {})
|
|
388
|
+
.finally(function() { setLoading(false); });
|
|
389
|
+
}, []);
|
|
390
|
+
|
|
391
|
+
useEffect(function() { load(); }, [load]);
|
|
392
|
+
|
|
393
|
+
// Filtering
|
|
394
|
+
var filtered = catalog.filter(function(item) {
|
|
395
|
+
if (catFilter !== 'all' && item.category !== catFilter) return false;
|
|
396
|
+
if (search) {
|
|
397
|
+
var q = search.toLowerCase();
|
|
398
|
+
return item.name.toLowerCase().includes(q) || item.skillId.toLowerCase().includes(q) || (item.category || '').toLowerCase().includes(q);
|
|
399
|
+
}
|
|
400
|
+
return true;
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
var connectedCount = catalog.filter(function(i) { return i.connected; }).length;
|
|
404
|
+
var cats = {};
|
|
405
|
+
catalog.forEach(function(i) { cats[i.category] = (cats[i.category] || 0) + 1; });
|
|
406
|
+
|
|
407
|
+
// Save credentials
|
|
408
|
+
var saveCredentials = function() {
|
|
409
|
+
if (!configModal) return;
|
|
410
|
+
setSaving(true);
|
|
411
|
+
engineCall('/integrations/' + configModal.skillId + '/credentials', {
|
|
412
|
+
method: 'PUT',
|
|
413
|
+
body: JSON.stringify(configValues)
|
|
414
|
+
})
|
|
415
|
+
.then(function(d) {
|
|
416
|
+
if (d.error) { toast(d.error, 'error'); }
|
|
417
|
+
else { toast(configModal.name + ' connected!', 'success'); setConfigModal(null); load(); }
|
|
418
|
+
})
|
|
419
|
+
.catch(function(e) { toast(e.message, 'error'); })
|
|
420
|
+
.finally(function() { setSaving(false); });
|
|
317
421
|
};
|
|
318
422
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
// Error state
|
|
325
|
-
if (error) {
|
|
326
|
-
return h(Fragment, null,
|
|
327
|
-
h('div', { style: { marginBottom: 20 } },
|
|
328
|
-
h('h1', { style: { fontSize: 20, fontWeight: 700 } }, 'Skill Connections'),
|
|
329
|
-
h('p', { style: { color: 'var(--text-muted)', fontSize: 13 } }, 'Connect external services and configure skill settings')
|
|
330
|
-
),
|
|
331
|
-
h('div', { style: { textAlign: 'center', padding: 60 } },
|
|
332
|
-
h('p', { style: { color: 'var(--danger)', marginBottom: 12 } }, error),
|
|
333
|
-
h('button', { className: 'btn btn-primary', onClick: load }, I.refresh(), ' Retry')
|
|
334
|
-
)
|
|
335
|
-
);
|
|
336
|
-
}
|
|
423
|
+
var disconnectIntegration = function(skillId) {
|
|
424
|
+
engineCall('/integrations/' + skillId + '/credentials', { method: 'DELETE' })
|
|
425
|
+
.then(function() { toast('Disconnected', 'success'); load(); })
|
|
426
|
+
.catch(function(e) { toast(e.message, 'error'); });
|
|
427
|
+
};
|
|
337
428
|
|
|
338
|
-
var
|
|
339
|
-
|
|
340
|
-
|
|
429
|
+
var openConnect = function(item) {
|
|
430
|
+
if (item.authType === 'oauth2') {
|
|
431
|
+
// Start OAuth flow
|
|
432
|
+
engineCall('/oauth/authorize/' + item.skillId)
|
|
433
|
+
.then(function(d) {
|
|
434
|
+
if (d.authUrl) {
|
|
435
|
+
var w = 600, ht = 700;
|
|
436
|
+
window.open(d.authUrl, 'oauth_popup', 'width=' + w + ',height=' + ht + ',left=' + (screen.width - w) / 2 + ',top=' + (screen.height - ht) / 2);
|
|
437
|
+
}
|
|
438
|
+
})
|
|
439
|
+
.catch(function(e) { toast(e.message, 'error'); });
|
|
440
|
+
} else {
|
|
441
|
+
// Open credentials modal
|
|
442
|
+
var vals = {};
|
|
443
|
+
if (item.fields) item.fields.forEach(function(f) { vals[f] = ''; });
|
|
444
|
+
else vals.token = '';
|
|
445
|
+
setConfigValues(vals);
|
|
446
|
+
setConfigModal(item);
|
|
447
|
+
}
|
|
448
|
+
};
|
|
341
449
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
450
|
+
var displayItems = showAll ? filtered : filtered.slice(0, 24);
|
|
451
|
+
|
|
452
|
+
if (loading) return h('div', { style: { padding: 32, textAlign: 'center', color: 'var(--text-muted)', fontSize: 13 } }, 'Loading integrations...');
|
|
453
|
+
|
|
454
|
+
return h('div', null,
|
|
455
|
+
// Header
|
|
456
|
+
h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16, flexWrap: 'wrap', gap: 8 } },
|
|
457
|
+
h('div', { style: { display: 'flex', alignItems: 'center', gap: 8 } },
|
|
458
|
+
h('h2', { style: { fontSize: 16, fontWeight: 700, margin: 0 } }, 'Built-in Integrations'),
|
|
459
|
+
h('span', { className: 'badge badge-neutral', style: { fontSize: 10 } }, catalog.length),
|
|
460
|
+
h('span', { className: 'badge', style: { fontSize: 10, background: 'var(--success)', color: '#fff' } }, connectedCount + ' connected'),
|
|
461
|
+
h(HelpButton, { label: 'Built-in Integrations' },
|
|
462
|
+
h('p', null, 'AgenticMail includes ' + catalog.length + ' pre-built integrations powered by MCP adapters. Each integration provides tools that agents can use to interact with external services.'),
|
|
463
|
+
h('h4', { style: { marginTop: 12, marginBottom: 6, fontSize: 14 } }, 'Authentication Types'),
|
|
464
|
+
h('ul', { style: { paddingLeft: 20, margin: '4px 0 8px' } },
|
|
465
|
+
h('li', null, h('strong', null, 'OAuth 2.0'), ' — Click "Connect" to authorize via the service\'s login page. Tokens auto-refresh.'),
|
|
466
|
+
h('li', null, h('strong', null, 'API Key'), ' — Paste a key from the service\'s developer settings.'),
|
|
467
|
+
h('li', null, h('strong', null, 'Credentials'), ' — Multiple fields (key + domain, key + project ID, etc.).')
|
|
353
468
|
),
|
|
354
|
-
h('div', { style:
|
|
355
|
-
|
|
356
|
-
|
|
469
|
+
h('div', { style: { marginTop: 12, padding: 12, background: 'var(--bg-secondary)', borderRadius: 8, fontSize: 13 } },
|
|
470
|
+
h('strong', null, 'Security: '), 'All credentials are encrypted in the vault. OAuth tokens auto-refresh before expiry.')
|
|
471
|
+
)
|
|
357
472
|
),
|
|
358
|
-
h('
|
|
473
|
+
h('div', { style: { display: 'flex', gap: 8, alignItems: 'center' } },
|
|
474
|
+
h('input', { className: 'input', placeholder: 'Search integrations...', value: search,
|
|
475
|
+
style: { width: 220, fontSize: 12 },
|
|
476
|
+
onChange: function(e) { setSearch(e.target.value); } }),
|
|
477
|
+
h('select', { className: 'input', value: catFilter, style: { width: 160, fontSize: 12 },
|
|
478
|
+
onChange: function(e) { setCatFilter(e.target.value); } },
|
|
479
|
+
h('option', { value: 'all' }, 'All categories (' + catalog.length + ')'),
|
|
480
|
+
Object.keys(cats).sort().map(function(cat) {
|
|
481
|
+
var meta = CATEGORY_META[cat] || { label: cat };
|
|
482
|
+
return h('option', { key: cat, value: cat }, meta.label + ' (' + cats[cat] + ')');
|
|
483
|
+
})
|
|
484
|
+
)
|
|
485
|
+
)
|
|
359
486
|
),
|
|
360
487
|
|
|
361
|
-
//
|
|
362
|
-
h('div', {
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
h('div', {
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
488
|
+
// Grid
|
|
489
|
+
h('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(280px, 1fr))', gap: 8 } },
|
|
490
|
+
displayItems.map(function(item) {
|
|
491
|
+
var meta = CATEGORY_META[item.category] || {};
|
|
492
|
+
return h('div', { key: item.skillId, style: {
|
|
493
|
+
padding: '12px 14px', background: 'var(--bg-card)', border: '1px solid ' + (item.connected ? 'rgba(21,128,61,0.3)' : 'var(--border)'),
|
|
494
|
+
borderRadius: 'var(--radius)', display: 'flex', alignItems: 'center', gap: 10,
|
|
495
|
+
} },
|
|
496
|
+
h('div', { style: { flex: 1, minWidth: 0 } },
|
|
497
|
+
h('div', { style: { display: 'flex', alignItems: 'center', gap: 6 } },
|
|
498
|
+
h('span', { style: { fontWeight: 600, fontSize: 13 } }, item.name),
|
|
499
|
+
item.connected && h('span', { style: { width: 6, height: 6, borderRadius: '50%', background: 'var(--success)', flexShrink: 0 } })
|
|
500
|
+
),
|
|
501
|
+
h('div', { style: { fontSize: 10, color: 'var(--text-muted)', marginTop: 2, display: 'flex', gap: 6 } },
|
|
502
|
+
h('span', null, (meta.label || item.category)),
|
|
503
|
+
h('span', null, '\u00B7'),
|
|
504
|
+
h('span', null, AUTH_TYPE_LABELS[item.authType] || item.authType),
|
|
505
|
+
h('span', null, '\u00B7'),
|
|
506
|
+
h('span', null, item.toolCount + ' tools')
|
|
507
|
+
)
|
|
508
|
+
),
|
|
509
|
+
item.connected
|
|
510
|
+
? h('button', { className: 'btn btn-ghost btn-sm', style: { fontSize: 11, color: 'var(--danger)', flexShrink: 0 },
|
|
511
|
+
onClick: function() { disconnectIntegration(item.skillId); } }, 'Disconnect')
|
|
512
|
+
: h('button', { className: 'btn btn-primary btn-sm', style: { fontSize: 11, flexShrink: 0 },
|
|
513
|
+
onClick: function() { openConnect(item); } }, 'Connect')
|
|
514
|
+
);
|
|
515
|
+
})
|
|
375
516
|
),
|
|
376
517
|
|
|
377
|
-
//
|
|
378
|
-
|
|
379
|
-
h('
|
|
380
|
-
|
|
381
|
-
h('p', { style: { fontSize: 13 } }, 'Install skills from the Community Marketplace to manage their connections here.')
|
|
518
|
+
// Show more
|
|
519
|
+
filtered.length > 24 && !showAll && h('div', { style: { textAlign: 'center', marginTop: 12 } },
|
|
520
|
+
h('button', { className: 'btn btn-secondary btn-sm', onClick: function() { setShowAll(true); } },
|
|
521
|
+
'Show all ' + filtered.length + ' integrations')
|
|
382
522
|
),
|
|
383
523
|
|
|
384
|
-
//
|
|
385
|
-
|
|
386
|
-
|
|
524
|
+
// Credentials modal
|
|
525
|
+
configModal && h(Modal, {
|
|
526
|
+
title: 'Connect ' + configModal.name,
|
|
527
|
+
onClose: function() { setConfigModal(null); },
|
|
528
|
+
footer: h(Fragment, null,
|
|
529
|
+
h('button', { className: 'btn btn-secondary', onClick: function() { setConfigModal(null); } }, 'Cancel'),
|
|
530
|
+
h('button', { className: 'btn btn-primary', disabled: saving, onClick: saveCredentials }, saving ? 'Saving...' : 'Connect')
|
|
531
|
+
)
|
|
387
532
|
},
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
533
|
+
h('div', { style: { display: 'flex', flexDirection: 'column', gap: 14 } },
|
|
534
|
+
h('p', { style: { fontSize: 13, color: 'var(--text-secondary)' } },
|
|
535
|
+
'Enter the credentials for ', h('strong', null, configModal.name), '. All values are encrypted in the vault.'
|
|
536
|
+
),
|
|
537
|
+
configModal.fields ? configModal.fields.map(function(field) {
|
|
538
|
+
var label = (configModal.fieldLabels || {})[field] || field;
|
|
539
|
+
var isSecret = /key|token|secret|password/i.test(field);
|
|
540
|
+
return h('div', { className: 'form-group', key: field },
|
|
541
|
+
h('label', { className: 'form-label' }, label),
|
|
542
|
+
h('input', {
|
|
543
|
+
className: 'input',
|
|
544
|
+
type: isSecret ? 'password' : 'text',
|
|
545
|
+
value: configValues[field] || '',
|
|
546
|
+
placeholder: label,
|
|
547
|
+
onChange: function(e) {
|
|
548
|
+
setConfigValues(function(prev) { var u = Object.assign({}, prev); u[field] = e.target.value; return u; });
|
|
549
|
+
}
|
|
550
|
+
})
|
|
551
|
+
);
|
|
552
|
+
})
|
|
553
|
+
: h('div', { className: 'form-group' },
|
|
554
|
+
h('label', { className: 'form-label' }, configModal.authType === 'api_key' ? 'API Key' : 'Access Token'),
|
|
555
|
+
h('input', {
|
|
556
|
+
className: 'input', type: 'password',
|
|
557
|
+
value: configValues.token || '',
|
|
558
|
+
placeholder: 'Paste your ' + (configModal.authType === 'api_key' ? 'API key' : 'access token'),
|
|
559
|
+
onChange: function(e) { setConfigValues({ token: e.target.value }); }
|
|
560
|
+
})
|
|
561
|
+
)
|
|
562
|
+
)
|
|
563
|
+
)
|
|
564
|
+
);
|
|
565
|
+
}
|
|
404
566
|
|
|
405
|
-
|
|
406
|
-
status.connected && status.provider && h('div', {
|
|
407
|
-
style: { fontSize: 12, color: 'var(--text-secondary)', marginBottom: 12, padding: '8px 10px', background: 'var(--bg-tertiary)', borderRadius: 6 }
|
|
408
|
-
},
|
|
409
|
-
h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center' } },
|
|
410
|
-
h('span', null, 'Provider: ', h('strong', null, status.provider)),
|
|
411
|
-
status.expiresAt && h('span', { style: { color: 'var(--text-muted)', fontSize: 11 } },
|
|
412
|
-
'Expires: ' + new Date(status.expiresAt).toLocaleDateString()
|
|
413
|
-
)
|
|
414
|
-
)
|
|
415
|
-
),
|
|
567
|
+
// ── Section 3: Community Skills ──────────────────────────
|
|
416
568
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
// Configure button (always available if skill has config potential)
|
|
445
|
-
h('button', {
|
|
446
|
-
className: 'btn btn-secondary btn-sm',
|
|
447
|
-
onClick: function() { openConfig(skill); }
|
|
448
|
-
}, I.settings(), ' Configure')
|
|
449
|
-
)
|
|
450
|
-
);
|
|
569
|
+
function CommunitySkillsSection() {
|
|
570
|
+
var app = useApp(); var toast = app.toast;
|
|
571
|
+
var _installed = useState([]); var installed = _installed[0]; var setInstalled = _installed[1];
|
|
572
|
+
var _statuses = useState({}); var statuses = _statuses[0]; var setStatuses = _statuses[1];
|
|
573
|
+
var _loading = useState(true); var loading = _loading[0]; var setLoading = _loading[1];
|
|
574
|
+
var _configSkill = useState(null); var configSkill = _configSkill[0]; var setConfigSkill = _configSkill[1];
|
|
575
|
+
var _configSchema = useState(null); var configSchema = _configSchema[0]; var setConfigSchema = _configSchema[1];
|
|
576
|
+
var _configValues = useState({}); var configValues = _configValues[0]; var setConfigValues = _configValues[1];
|
|
577
|
+
var _configSaving = useState(false); var configSaving = _configSaving[0]; var setConfigSaving = _configSaving[1];
|
|
578
|
+
|
|
579
|
+
var load = useCallback(function() {
|
|
580
|
+
setLoading(true);
|
|
581
|
+
engineCall('/community/installed')
|
|
582
|
+
.then(function(d) {
|
|
583
|
+
var skills = d.installed || [];
|
|
584
|
+
setInstalled(skills);
|
|
585
|
+
// Load statuses
|
|
586
|
+
var promises = skills.map(function(skill) {
|
|
587
|
+
return engineCall('/oauth/status/' + skill.skillId)
|
|
588
|
+
.then(function(d) { return { skillId: skill.skillId, status: d }; })
|
|
589
|
+
.catch(function() { return { skillId: skill.skillId, status: { connected: false } }; });
|
|
590
|
+
});
|
|
591
|
+
Promise.all(promises).then(function(results) {
|
|
592
|
+
var map = {};
|
|
593
|
+
results.forEach(function(r) { map[r.skillId] = r.status; });
|
|
594
|
+
setStatuses(map);
|
|
595
|
+
});
|
|
451
596
|
})
|
|
597
|
+
.catch(function() {})
|
|
598
|
+
.finally(function() { setLoading(false); });
|
|
599
|
+
}, []);
|
|
600
|
+
|
|
601
|
+
useEffect(function() { load(); }, [load]);
|
|
602
|
+
|
|
603
|
+
var openConfig = function(skill) {
|
|
604
|
+
setConfigSkill(skill);
|
|
605
|
+
setConfigValues(skill.config || {});
|
|
606
|
+
engineCall('/community/skills/' + skill.skillId + '/config-schema')
|
|
607
|
+
.then(function(d) { setConfigSchema(d.configSchema || {}); })
|
|
608
|
+
.catch(function() { setConfigSchema({}); });
|
|
609
|
+
};
|
|
610
|
+
|
|
611
|
+
var saveConfig = function() {
|
|
612
|
+
if (!configSkill) return;
|
|
613
|
+
setConfigSaving(true);
|
|
614
|
+
engineCall('/community/skills/' + configSkill.skillId + '/config', { method: 'PUT', body: JSON.stringify(configValues) })
|
|
615
|
+
.then(function() { toast('Saved', 'success'); setConfigSkill(null); load(); })
|
|
616
|
+
.catch(function(e) { toast(e.message, 'error'); })
|
|
617
|
+
.finally(function() { setConfigSaving(false); });
|
|
618
|
+
};
|
|
619
|
+
|
|
620
|
+
if (loading) return h('div', { style: { padding: 32, textAlign: 'center', color: 'var(--text-muted)', fontSize: 13 } }, 'Loading community skills...');
|
|
621
|
+
|
|
622
|
+
return h('div', null,
|
|
623
|
+
h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 } },
|
|
624
|
+
h('div', { style: { display: 'flex', alignItems: 'center', gap: 8 } },
|
|
625
|
+
h('h2', { style: { fontSize: 16, fontWeight: 700, margin: 0 } }, 'Community Skills'),
|
|
626
|
+
h('span', { className: 'badge badge-neutral', style: { fontSize: 10 } }, installed.length),
|
|
627
|
+
h(HelpButton, { label: 'Community Skills' },
|
|
628
|
+
h('p', null, 'Skills installed from the Community Marketplace. These are custom skill packages that add specialized capabilities to your agents.'),
|
|
629
|
+
h('div', { style: { marginTop: 12, padding: 12, background: 'var(--bg-secondary)', borderRadius: 8, fontSize: 13 } },
|
|
630
|
+
h('strong', null, 'Tip: '), 'Install more skills from the Community Skills page in the sidebar.')
|
|
631
|
+
)
|
|
632
|
+
),
|
|
633
|
+
h('button', { className: 'btn btn-secondary btn-sm', onClick: load }, I.refresh())
|
|
452
634
|
),
|
|
453
635
|
|
|
454
|
-
|
|
636
|
+
installed.length === 0
|
|
637
|
+
? h('div', { style: { padding: 32, textAlign: 'center', border: '2px dashed var(--border)', borderRadius: 'var(--radius-lg)', color: 'var(--text-muted)' } },
|
|
638
|
+
h('p', { style: { fontSize: 13 } }, 'No community skills installed. Visit the marketplace to add some.')
|
|
639
|
+
)
|
|
640
|
+
: h('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))', gap: 8 } },
|
|
641
|
+
installed.map(function(skill) {
|
|
642
|
+
var status = statuses[skill.skillId] || {};
|
|
643
|
+
var meta = skill.skill || skill.manifest || skill;
|
|
644
|
+
return h('div', { key: skill.skillId, style: {
|
|
645
|
+
padding: '12px 14px', background: 'var(--bg-card)', border: '1px solid var(--border)', borderRadius: 'var(--radius)',
|
|
646
|
+
display: 'flex', alignItems: 'center', gap: 10,
|
|
647
|
+
} },
|
|
648
|
+
h('div', { style: { flex: 1, minWidth: 0 } },
|
|
649
|
+
h('div', { style: { fontWeight: 600, fontSize: 13 } }, meta.name || skill.skillId),
|
|
650
|
+
meta.description && h('div', { style: { fontSize: 11, color: 'var(--text-muted)', marginTop: 2 } }, meta.description)
|
|
651
|
+
),
|
|
652
|
+
h('div', { style: { display: 'flex', gap: 4, flexShrink: 0 } },
|
|
653
|
+
status.connected
|
|
654
|
+
? h('span', { className: 'badge', style: { background: 'var(--success)', color: '#fff', fontSize: 10 } }, 'Connected')
|
|
655
|
+
: h('span', { className: 'badge badge-neutral', style: { fontSize: 10 } }, 'Not connected'),
|
|
656
|
+
h('button', { className: 'btn btn-ghost btn-sm', onClick: function() { openConfig(skill); } }, I.settings())
|
|
657
|
+
)
|
|
658
|
+
);
|
|
659
|
+
})
|
|
660
|
+
),
|
|
661
|
+
|
|
662
|
+
// Config modal
|
|
455
663
|
configSkill && h(Modal, {
|
|
456
|
-
title: 'Configure ' + (configSkill.skill?.name || configSkill.
|
|
664
|
+
title: 'Configure ' + (configSkill.skill?.name || configSkill.skillId),
|
|
457
665
|
onClose: function() { setConfigSkill(null); },
|
|
458
666
|
footer: h(Fragment, null,
|
|
459
667
|
h('button', { className: 'btn btn-secondary', onClick: function() { setConfigSkill(null); } }, 'Cancel'),
|
|
460
|
-
h('button', {
|
|
461
|
-
className: 'btn btn-primary',
|
|
462
|
-
onClick: saveConfig,
|
|
463
|
-
disabled: configSaving || configLoading
|
|
464
|
-
}, configSaving ? 'Saving...' : 'Save Configuration')
|
|
668
|
+
h('button', { className: 'btn btn-primary', disabled: configSaving, onClick: saveConfig }, configSaving ? 'Saving...' : 'Save')
|
|
465
669
|
)
|
|
466
670
|
},
|
|
467
|
-
|
|
468
|
-
? h('div', { style: {
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
(
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
)
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
Object.entries(configSchema).map(function(entry) {
|
|
487
|
-
return renderConfigField(entry[0], entry[1]);
|
|
488
|
-
})
|
|
489
|
-
)
|
|
490
|
-
: h('div', null,
|
|
491
|
-
h('p', { style: { fontSize: 13, color: 'var(--text-secondary)', marginBottom: 16 } },
|
|
492
|
-
'This skill accepts custom configuration. Enter key-value pairs below.'
|
|
493
|
-
),
|
|
494
|
-
// Fallback: show existing config as editable key-value pairs
|
|
495
|
-
Object.entries(configValues).map(function(entry) {
|
|
496
|
-
return h('div', { className: 'form-group', key: entry[0] },
|
|
497
|
-
h('label', { className: 'form-label' }, entry[0]),
|
|
498
|
-
h('input', {
|
|
499
|
-
className: 'input',
|
|
500
|
-
value: entry[1] || '',
|
|
501
|
-
onChange: function(e) {
|
|
502
|
-
setConfigValues(function(prev) {
|
|
503
|
-
var updated = Object.assign({}, prev);
|
|
504
|
-
updated[entry[0]] = e.target.value;
|
|
505
|
-
return updated;
|
|
506
|
-
});
|
|
507
|
-
}
|
|
508
|
-
})
|
|
509
|
-
);
|
|
510
|
-
}),
|
|
511
|
-
Object.keys(configValues).length === 0 && h('div', {
|
|
512
|
-
style: { textAlign: 'center', padding: 20, color: 'var(--text-muted)' }
|
|
513
|
-
}, 'No configuration schema available for this skill.')
|
|
514
|
-
)
|
|
671
|
+
configSchema && Object.keys(configSchema).length > 0
|
|
672
|
+
? h('div', { style: { display: 'flex', flexDirection: 'column', gap: 12 } },
|
|
673
|
+
Object.entries(configSchema).map(function(entry) {
|
|
674
|
+
var fieldName = entry[0]; var schema = entry[1];
|
|
675
|
+
var isSecret = schema.type === 'secret' || /key|token|secret/i.test(fieldName);
|
|
676
|
+
return h('div', { className: 'form-group', key: fieldName },
|
|
677
|
+
h('label', { className: 'form-label' }, schema.label || fieldName),
|
|
678
|
+
h('input', {
|
|
679
|
+
className: 'input',
|
|
680
|
+
type: isSecret ? 'password' : 'text',
|
|
681
|
+
value: configValues[fieldName] || '',
|
|
682
|
+
placeholder: schema.placeholder || schema.default || '',
|
|
683
|
+
onChange: function(e) { setConfigValues(function(p) { var u = Object.assign({}, p); u[fieldName] = e.target.value; return u; }); }
|
|
684
|
+
}),
|
|
685
|
+
schema.description && h('div', { style: { fontSize: 11, color: 'var(--text-muted)', marginTop: 4 } }, schema.description)
|
|
686
|
+
);
|
|
687
|
+
})
|
|
688
|
+
)
|
|
689
|
+
: h('div', { style: { padding: 20, textAlign: 'center', color: 'var(--text-muted)' } }, 'No configuration options available.')
|
|
515
690
|
)
|
|
516
691
|
);
|
|
517
692
|
}
|
|
693
|
+
|
|
694
|
+
// ═══════════════════════════════════════════════════════════
|
|
695
|
+
// Main Page
|
|
696
|
+
// ═══════════════════════════════════════════════════════════
|
|
697
|
+
|
|
698
|
+
export function SkillConnectionsPage() {
|
|
699
|
+
var _tab = useState('mcp'); var tab = _tab[0]; var setTab = _tab[1];
|
|
700
|
+
|
|
701
|
+
var _h4 = { marginTop: 16, marginBottom: 8, fontSize: 14 };
|
|
702
|
+
var _tip = { marginTop: 12, padding: 12, background: 'var(--bg-secondary)', borderRadius: 8, fontSize: 13 };
|
|
703
|
+
|
|
704
|
+
return h(Fragment, null,
|
|
705
|
+
// Page Header
|
|
706
|
+
h('div', { style: { marginBottom: 20 } },
|
|
707
|
+
h('h1', { style: { fontSize: 20, fontWeight: 700, display: 'flex', alignItems: 'center', gap: 8 } },
|
|
708
|
+
'Integrations & MCP Hub',
|
|
709
|
+
h(HelpButton, { label: 'Integrations & MCP Hub' },
|
|
710
|
+
h('p', null, 'The central hub for connecting your AI agents to external tools, services, and MCP servers. Everything your agents need to interact with the outside world is managed here.'),
|
|
711
|
+
h('h4', { style: _h4 }, 'Three connection types'),
|
|
712
|
+
h('ul', { style: { paddingLeft: 20, margin: '4px 0 8px' } },
|
|
713
|
+
h('li', null, h('strong', null, 'MCP Servers'), ' — Connect external Model Context Protocol servers (like Claude Code\'s MCP system). Agents get access to all tools the server provides.'),
|
|
714
|
+
h('li', null, h('strong', null, 'Built-in Integrations'), ' — 145+ pre-built adapters for popular services (Slack, GitHub, Salesforce, etc.). Just add credentials.'),
|
|
715
|
+
h('li', null, h('strong', null, 'Community Skills'), ' — Custom skill packages from the marketplace. Install and configure them here.')
|
|
716
|
+
),
|
|
717
|
+
h('div', { style: _tip }, h('strong', null, 'MCP Protocol: '), 'MCP (Model Context Protocol) is an open standard by Anthropic for connecting AI models to external tools. Any MCP-compatible server works here — same format as Claude Code, Cursor, and other AI tools.')
|
|
718
|
+
)
|
|
719
|
+
),
|
|
720
|
+
h('p', { style: { color: 'var(--text-muted)', fontSize: 13, marginTop: 4 } },
|
|
721
|
+
'Connect MCP servers, built-in integrations, and community skills to extend your agents')
|
|
722
|
+
),
|
|
723
|
+
|
|
724
|
+
// Tab bar
|
|
725
|
+
h('div', { className: 'tabs', style: { marginBottom: 20 } },
|
|
726
|
+
h('div', { className: 'tab' + (tab === 'mcp' ? ' active' : ''), onClick: function() { setTab('mcp'); } }, I.code(), ' MCP Servers'),
|
|
727
|
+
h('div', { className: 'tab' + (tab === 'integrations' ? ' active' : ''), onClick: function() { setTab('integrations'); } }, I.globe(), ' Built-in Integrations'),
|
|
728
|
+
h('div', { className: 'tab' + (tab === 'community' ? ' active' : ''), onClick: function() { setTab('community'); } }, I.users(), ' Community Skills')
|
|
729
|
+
),
|
|
730
|
+
|
|
731
|
+
// Tab content
|
|
732
|
+
tab === 'mcp' && h(McpServersSection),
|
|
733
|
+
tab === 'integrations' && h(IntegrationsSection),
|
|
734
|
+
tab === 'community' && h(CommunitySkillsSection)
|
|
735
|
+
);
|
|
736
|
+
}
|