@harbinger-ai/harbinger 0.1.2 → 0.1.4
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/lib/bounty/actions.js +45 -0
- package/lib/chat/actions.js +715 -0
- package/lib/chat/components/agents-page.js +918 -0
- package/lib/chat/components/agents-page.jsx +869 -0
- package/lib/chat/components/app-sidebar.js +17 -1
- package/lib/chat/components/app-sidebar.jsx +19 -1
- package/lib/chat/components/icons.js +145 -0
- package/lib/chat/components/icons.jsx +171 -0
- package/lib/chat/components/index.js +2 -0
- package/lib/chat/components/mcp-page.js +383 -55
- package/lib/chat/components/mcp-page.jsx +404 -101
- package/lib/chat/components/page-layout.js +41 -2
- package/lib/chat/components/page-layout.jsx +40 -2
- package/lib/chat/components/settings-layout.js +3 -2
- package/lib/chat/components/settings-layout.jsx +2 -1
- package/lib/chat/components/settings-providers-page.js +872 -0
- package/lib/chat/components/settings-providers-page.jsx +917 -0
- package/lib/chat/components/settings-secrets-page.js +91 -66
- package/lib/chat/components/settings-secrets-page.jsx +83 -72
- package/lib/chat/components/targets-page.js +554 -96
- package/lib/chat/components/targets-page.jsx +464 -114
- package/lib/mcp/actions.js +120 -0
- package/lib/mcp/registry.js +164 -0
- package/package.json +1 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useState, useEffect } from "react";
|
|
4
|
-
import { motion } from "framer-motion";
|
|
5
|
-
import { PlugIcon, RefreshIcon, SpinnerIcon, ChevronDownIcon, WrenchIcon, CheckIcon, XIcon } from "./icons.js";
|
|
6
|
-
import { getMcpServers, getMcpStatus, getOwnMcpServerInfo, testMcpTool, reloadMcpClient } from "../../mcp/actions.js";
|
|
4
|
+
import { motion, AnimatePresence } from "framer-motion";
|
|
5
|
+
import { PlugIcon, RefreshIcon, SpinnerIcon, ChevronDownIcon, WrenchIcon, CheckIcon, XIcon, SearchIcon, PlusIcon, TrashIcon, DownloadIcon } from "./icons.js";
|
|
6
|
+
import { getMcpServers, getMcpStatus, getOwnMcpServerInfo, testMcpTool, reloadMcpClient, getMcpRegistry, addMcpServer, removeMcpServer, toggleMcpServer } from "../../mcp/actions.js";
|
|
7
7
|
const cardVariants = {
|
|
8
8
|
hidden: { opacity: 0, y: 12 },
|
|
9
9
|
visible: (i) => ({
|
|
@@ -12,6 +12,19 @@ const cardVariants = {
|
|
|
12
12
|
transition: { delay: i * 0.05, duration: 0.3, ease: "easeOut" }
|
|
13
13
|
})
|
|
14
14
|
};
|
|
15
|
+
const CATEGORY_COLORS = {
|
|
16
|
+
search: { bg: "bg-blue-500/10", text: "text-blue-500", border: "border-blue-500/20" },
|
|
17
|
+
web: { bg: "bg-orange-500/10", text: "text-orange-500", border: "border-orange-500/20" },
|
|
18
|
+
dev: { bg: "bg-purple-500/10", text: "text-purple-500", border: "border-purple-500/20" },
|
|
19
|
+
communication: { bg: "bg-green-500/10", text: "text-green-500", border: "border-green-500/20" },
|
|
20
|
+
system: { bg: "bg-gray-500/10", text: "text-gray-500", border: "border-gray-500/20" },
|
|
21
|
+
database: { bg: "bg-cyan-500/10", text: "text-cyan-500", border: "border-cyan-500/20" },
|
|
22
|
+
monitoring: { bg: "bg-yellow-500/10", text: "text-yellow-500", border: "border-yellow-500/20" },
|
|
23
|
+
cloud: { bg: "bg-indigo-500/10", text: "text-indigo-500", border: "border-indigo-500/20" }
|
|
24
|
+
};
|
|
25
|
+
function getCatStyle(cat) {
|
|
26
|
+
return CATEGORY_COLORS[cat] || { bg: "bg-white/5", text: "text-muted-foreground", border: "border-white/10" };
|
|
27
|
+
}
|
|
15
28
|
function SectionHeader({ label, count }) {
|
|
16
29
|
return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 pt-6 pb-2", children: [
|
|
17
30
|
/* @__PURE__ */ jsx("span", { className: "font-mono text-[10px] font-medium text-[--cyan] uppercase tracking-wider", children: label }),
|
|
@@ -39,9 +52,104 @@ function ItemCard({ name, description, badge, index = 0 }) {
|
|
|
39
52
|
}
|
|
40
53
|
);
|
|
41
54
|
}
|
|
42
|
-
function
|
|
55
|
+
function RegistryCard({ server, installed, onInstall, installing, index }) {
|
|
56
|
+
const [apiKey, setApiKey] = useState("");
|
|
57
|
+
const [showKeyInput, setShowKeyInput] = useState(false);
|
|
58
|
+
const cat = getCatStyle(server.category);
|
|
59
|
+
function handleAdd() {
|
|
60
|
+
if (server.requiresKey && !apiKey && !showKeyInput) {
|
|
61
|
+
setShowKeyInput(true);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const config = {
|
|
65
|
+
name: server.name,
|
|
66
|
+
url: server.url || "",
|
|
67
|
+
transport: server.transport || "http",
|
|
68
|
+
enabled: true
|
|
69
|
+
};
|
|
70
|
+
if (apiKey && server.keyEnvVar) {
|
|
71
|
+
config.headers = { Authorization: `Bearer ${apiKey}` };
|
|
72
|
+
}
|
|
73
|
+
if (server.npmPackage) {
|
|
74
|
+
config.command = "npx";
|
|
75
|
+
config.args = ["-y", server.npmPackage];
|
|
76
|
+
config.transport = "stdio";
|
|
77
|
+
}
|
|
78
|
+
onInstall(config);
|
|
79
|
+
}
|
|
80
|
+
return /* @__PURE__ */ jsxs(
|
|
81
|
+
motion.div,
|
|
82
|
+
{
|
|
83
|
+
initial: { opacity: 0, y: 8 },
|
|
84
|
+
animate: { opacity: 1, y: 0 },
|
|
85
|
+
transition: { duration: 0.25, delay: index * 0.02 },
|
|
86
|
+
className: "group rounded-lg border border-white/[0.06] bg-[--card] hover:border-[--cyan]/20 transition-colors overflow-hidden",
|
|
87
|
+
children: [
|
|
88
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 p-3", children: [
|
|
89
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 shrink-0", children: [
|
|
90
|
+
/* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full bg-[#ff5f57]" }),
|
|
91
|
+
/* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full bg-[#febc2e]" }),
|
|
92
|
+
/* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full bg-[#28c840]" })
|
|
93
|
+
] }),
|
|
94
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
95
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-mono font-medium truncate", children: server.name }),
|
|
96
|
+
/* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground mt-0.5 line-clamp-1 font-mono", children: server.description })
|
|
97
|
+
] }),
|
|
98
|
+
/* @__PURE__ */ jsx("span", { className: `shrink-0 inline-flex rounded-full px-2 py-0.5 text-[10px] font-mono font-medium border ${cat.bg} ${cat.text} ${cat.border}`, children: server.category }),
|
|
99
|
+
installed ? /* @__PURE__ */ jsxs("span", { className: "shrink-0 inline-flex items-center gap-1 rounded-full bg-green-500/10 text-green-500 border border-green-500/20 px-2 py-0.5 text-[10px] font-mono font-medium", children: [
|
|
100
|
+
/* @__PURE__ */ jsx(CheckIcon, { size: 10 }),
|
|
101
|
+
" added"
|
|
102
|
+
] }) : /* @__PURE__ */ jsxs(
|
|
103
|
+
"button",
|
|
104
|
+
{
|
|
105
|
+
onClick: handleAdd,
|
|
106
|
+
disabled: installing,
|
|
107
|
+
className: "shrink-0 inline-flex items-center gap-1 rounded-md px-2.5 py-1 text-xs font-mono font-medium bg-[--cyan]/10 text-[--cyan] border border-[--cyan]/20 hover:bg-[--cyan] hover:text-[--primary-foreground] transition-colors disabled:opacity-50",
|
|
108
|
+
children: [
|
|
109
|
+
installing ? /* @__PURE__ */ jsx(SpinnerIcon, { size: 12 }) : /* @__PURE__ */ jsx(DownloadIcon, { size: 12 }),
|
|
110
|
+
"Add"
|
|
111
|
+
]
|
|
112
|
+
}
|
|
113
|
+
)
|
|
114
|
+
] }),
|
|
115
|
+
showKeyInput && !installed && /* @__PURE__ */ jsxs("div", { className: "border-t border-white/[0.06] px-3 py-2.5 flex gap-2", children: [
|
|
116
|
+
/* @__PURE__ */ jsx(
|
|
117
|
+
"input",
|
|
118
|
+
{
|
|
119
|
+
type: "password",
|
|
120
|
+
value: apiKey,
|
|
121
|
+
onChange: (e) => setApiKey(e.target.value),
|
|
122
|
+
placeholder: `${server.keyEnvVar || "API_KEY"}...`,
|
|
123
|
+
className: "flex-1 text-xs border border-white/[0.06] rounded-md px-2.5 py-1.5 bg-black/20 font-mono text-foreground/80 placeholder:text-muted-foreground/50 focus:outline-none focus:border-[--cyan]/40 focus:ring-1 focus:ring-[--cyan]/20 transition-colors",
|
|
124
|
+
autoFocus: true
|
|
125
|
+
}
|
|
126
|
+
),
|
|
127
|
+
/* @__PURE__ */ jsx(
|
|
128
|
+
"button",
|
|
129
|
+
{
|
|
130
|
+
onClick: handleAdd,
|
|
131
|
+
disabled: !apiKey,
|
|
132
|
+
className: "inline-flex items-center gap-1 rounded-md px-2.5 py-1.5 text-xs font-mono font-medium bg-[--cyan]/10 text-[--cyan] border border-[--cyan]/20 hover:bg-[--cyan] hover:text-[--primary-foreground] transition-colors disabled:opacity-50",
|
|
133
|
+
children: "Add"
|
|
134
|
+
}
|
|
135
|
+
)
|
|
136
|
+
] })
|
|
137
|
+
]
|
|
138
|
+
}
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
function ServerCard({ server, onToggle, onRemove, index = 0 }) {
|
|
43
142
|
const [expanded, setExpanded] = useState(false);
|
|
143
|
+
const [confirming, setConfirming] = useState(false);
|
|
44
144
|
const disabled = server.enabled === false;
|
|
145
|
+
function handleRemove() {
|
|
146
|
+
if (!confirming) {
|
|
147
|
+
setConfirming(true);
|
|
148
|
+
setTimeout(() => setConfirming(false), 3e3);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
onRemove(server.name);
|
|
152
|
+
}
|
|
45
153
|
return /* @__PURE__ */ jsxs(
|
|
46
154
|
motion.div,
|
|
47
155
|
{
|
|
@@ -63,31 +171,161 @@ function ServerCard({ server, index = 0 }) {
|
|
|
63
171
|
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-0.5 font-mono truncate", children: server.url })
|
|
64
172
|
] }),
|
|
65
173
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 shrink-0", children: [
|
|
66
|
-
/* @__PURE__ */ jsx("span", { className: `inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-mono font-medium ${server.transport === "http" ? "bg-blue-500/10 text-blue-400 border border-blue-500/20" : "bg-orange-500/10 text-orange-400 border border-orange-500/20"}`, children: server.transport || "http" }),
|
|
174
|
+
/* @__PURE__ */ jsx("span", { className: `inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-mono font-medium ${server.transport === "http" ? "bg-blue-500/10 text-blue-400 border border-blue-500/20" : server.transport === "sse" ? "bg-orange-500/10 text-orange-400 border border-orange-500/20" : "bg-purple-500/10 text-purple-400 border border-purple-500/20"}`, children: server.transport || "http" }),
|
|
67
175
|
/* @__PURE__ */ jsx("span", { className: `inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-mono font-medium ${disabled ? "bg-white/[0.04] text-muted-foreground border border-white/[0.06]" : "bg-[--success]/10 text-[--success] border border-[--success]/20"}`, children: disabled ? "disabled" : "enabled" }),
|
|
68
176
|
/* @__PURE__ */ jsx("span", { className: `transition-transform ${expanded ? "rotate-180" : ""}`, children: /* @__PURE__ */ jsx(ChevronDownIcon, { size: 14 }) })
|
|
69
177
|
] })
|
|
70
178
|
]
|
|
71
179
|
}
|
|
72
180
|
),
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
181
|
+
/* @__PURE__ */ jsx(AnimatePresence, { children: expanded && /* @__PURE__ */ jsx(
|
|
182
|
+
motion.div,
|
|
183
|
+
{
|
|
184
|
+
initial: { height: 0, opacity: 0 },
|
|
185
|
+
animate: { height: "auto", opacity: 1 },
|
|
186
|
+
exit: { height: 0, opacity: 0 },
|
|
187
|
+
transition: { duration: 0.2 },
|
|
188
|
+
className: "overflow-hidden",
|
|
189
|
+
children: /* @__PURE__ */ jsxs("div", { className: "border-t border-white/[0.06] px-4 py-3", children: [
|
|
190
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
|
|
191
|
+
server.url && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
192
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono text-[10px] font-medium text-muted-foreground uppercase tracking-wider w-16 shrink-0", children: "URL" }),
|
|
193
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-mono text-foreground/80", children: server.url })
|
|
194
|
+
] }),
|
|
195
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
196
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono text-[10px] font-medium text-muted-foreground uppercase tracking-wider w-16 shrink-0", children: "Transport" }),
|
|
197
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-mono text-foreground/80", children: server.transport || "http" })
|
|
198
|
+
] }),
|
|
199
|
+
server.headers && Object.keys(server.headers).length > 0 && /* @__PURE__ */ jsxs("div", { children: [
|
|
200
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono text-[10px] font-medium text-muted-foreground uppercase tracking-wider", children: "Headers" }),
|
|
201
|
+
/* @__PURE__ */ jsx("pre", { className: "text-[11px] bg-black/30 rounded-md p-2.5 mt-1 font-mono overflow-auto max-h-24 text-foreground/80 border border-white/[0.04]", children: JSON.stringify(server.headers, null, 2) })
|
|
202
|
+
] })
|
|
203
|
+
] }),
|
|
204
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mt-3", children: [
|
|
205
|
+
/* @__PURE__ */ jsx(
|
|
206
|
+
"button",
|
|
207
|
+
{
|
|
208
|
+
onClick: (e) => {
|
|
209
|
+
e.stopPropagation();
|
|
210
|
+
onToggle(server.name);
|
|
211
|
+
},
|
|
212
|
+
className: "inline-flex items-center gap-1 rounded-md px-2.5 py-1 text-xs font-mono font-medium border border-white/[0.06] hover:bg-white/[0.04] transition-colors",
|
|
213
|
+
children: disabled ? "Enable" : "Disable"
|
|
214
|
+
}
|
|
215
|
+
),
|
|
216
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1" }),
|
|
217
|
+
/* @__PURE__ */ jsx(
|
|
218
|
+
"button",
|
|
219
|
+
{
|
|
220
|
+
onClick: (e) => {
|
|
221
|
+
e.stopPropagation();
|
|
222
|
+
handleRemove();
|
|
223
|
+
},
|
|
224
|
+
className: `text-xs font-mono transition-colors ${confirming ? "text-[--destructive]" : "text-muted-foreground hover:text-[--destructive]"}`,
|
|
225
|
+
children: confirming ? "Confirm remove" : "Remove"
|
|
226
|
+
}
|
|
227
|
+
)
|
|
228
|
+
] })
|
|
229
|
+
] })
|
|
230
|
+
}
|
|
231
|
+
) })
|
|
87
232
|
]
|
|
88
233
|
}
|
|
89
234
|
);
|
|
90
235
|
}
|
|
236
|
+
function AddServerForm({ onAdd }) {
|
|
237
|
+
const [name, setName] = useState("");
|
|
238
|
+
const [url, setUrl] = useState("");
|
|
239
|
+
const [transport, setTransport] = useState("http");
|
|
240
|
+
const [headers, setHeaders] = useState("");
|
|
241
|
+
const [loading, setLoading] = useState(false);
|
|
242
|
+
const [result, setResult] = useState(null);
|
|
243
|
+
async function handleAdd() {
|
|
244
|
+
if (!name || !url) return;
|
|
245
|
+
setLoading(true);
|
|
246
|
+
setResult(null);
|
|
247
|
+
let headerObj = {};
|
|
248
|
+
if (headers) {
|
|
249
|
+
try {
|
|
250
|
+
headerObj = JSON.parse(headers);
|
|
251
|
+
} catch {
|
|
252
|
+
setResult({ error: "Invalid JSON headers" });
|
|
253
|
+
setLoading(false);
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
const res = await onAdd({ name, url, transport, headers: headerObj, enabled: true });
|
|
258
|
+
setResult(res);
|
|
259
|
+
if (!res.error) {
|
|
260
|
+
setName("");
|
|
261
|
+
setUrl("");
|
|
262
|
+
setHeaders("");
|
|
263
|
+
}
|
|
264
|
+
setLoading(false);
|
|
265
|
+
}
|
|
266
|
+
return /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-white/[0.06] bg-[--card] p-4 mb-4", children: [
|
|
267
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mb-3", children: [
|
|
268
|
+
/* @__PURE__ */ jsx(PlusIcon, { size: 14 }),
|
|
269
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-mono font-medium text-[--cyan]", children: "Add Custom Server" })
|
|
270
|
+
] }),
|
|
271
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-3 mb-3", children: [
|
|
272
|
+
/* @__PURE__ */ jsx(
|
|
273
|
+
"input",
|
|
274
|
+
{
|
|
275
|
+
placeholder: "Server name",
|
|
276
|
+
value: name,
|
|
277
|
+
onChange: (e) => setName(e.target.value),
|
|
278
|
+
className: "text-sm border border-white/[0.06] rounded-md px-3 py-2 bg-black/20 font-mono text-foreground/80 placeholder:text-muted-foreground/50 focus:outline-none focus:border-[--cyan]/40 focus:ring-1 focus:ring-[--cyan]/20 transition-colors"
|
|
279
|
+
}
|
|
280
|
+
),
|
|
281
|
+
/* @__PURE__ */ jsx(
|
|
282
|
+
"input",
|
|
283
|
+
{
|
|
284
|
+
placeholder: "https://mcp.example.com/sse",
|
|
285
|
+
value: url,
|
|
286
|
+
onChange: (e) => setUrl(e.target.value),
|
|
287
|
+
className: "text-sm border border-white/[0.06] rounded-md px-3 py-2 bg-black/20 font-mono text-foreground/80 placeholder:text-muted-foreground/50 focus:outline-none focus:border-[--cyan]/40 focus:ring-1 focus:ring-[--cyan]/20 transition-colors"
|
|
288
|
+
}
|
|
289
|
+
),
|
|
290
|
+
/* @__PURE__ */ jsxs(
|
|
291
|
+
"select",
|
|
292
|
+
{
|
|
293
|
+
value: transport,
|
|
294
|
+
onChange: (e) => setTransport(e.target.value),
|
|
295
|
+
className: "text-sm border border-white/[0.06] rounded-md px-3 py-2 bg-black/20 font-mono text-foreground/80 focus:outline-none focus:border-[--cyan]/40 focus:ring-1 focus:ring-[--cyan]/20 transition-colors appearance-none",
|
|
296
|
+
children: [
|
|
297
|
+
/* @__PURE__ */ jsx("option", { value: "http", children: "HTTP" }),
|
|
298
|
+
/* @__PURE__ */ jsx("option", { value: "sse", children: "SSE" })
|
|
299
|
+
]
|
|
300
|
+
}
|
|
301
|
+
),
|
|
302
|
+
/* @__PURE__ */ jsx(
|
|
303
|
+
"input",
|
|
304
|
+
{
|
|
305
|
+
placeholder: '{"Authorization": "Bearer ..."}',
|
|
306
|
+
value: headers,
|
|
307
|
+
onChange: (e) => setHeaders(e.target.value),
|
|
308
|
+
className: "text-sm border border-white/[0.06] rounded-md px-3 py-2 bg-black/20 font-mono text-foreground/80 placeholder:text-muted-foreground/50 focus:outline-none focus:border-[--cyan]/40 focus:ring-1 focus:ring-[--cyan]/20 transition-colors"
|
|
309
|
+
}
|
|
310
|
+
)
|
|
311
|
+
] }),
|
|
312
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
313
|
+
/* @__PURE__ */ jsxs(
|
|
314
|
+
"button",
|
|
315
|
+
{
|
|
316
|
+
onClick: handleAdd,
|
|
317
|
+
disabled: loading || !name || !url,
|
|
318
|
+
className: "inline-flex items-center gap-1.5 rounded-md px-4 py-2 text-xs font-mono font-medium bg-[--cyan]/10 text-[--cyan] border border-[--cyan]/20 hover:bg-[--cyan] hover:text-[--primary-foreground] transition-colors disabled:opacity-50",
|
|
319
|
+
children: [
|
|
320
|
+
loading ? /* @__PURE__ */ jsx(SpinnerIcon, { size: 12 }) : /* @__PURE__ */ jsx(PlusIcon, { size: 12 }),
|
|
321
|
+
" Add Server"
|
|
322
|
+
]
|
|
323
|
+
}
|
|
324
|
+
),
|
|
325
|
+
result && /* @__PURE__ */ jsx("p", { className: `text-xs font-mono ${result.error ? "text-[--destructive]" : "text-green-500"}`, children: result.error || "Added successfully" })
|
|
326
|
+
] })
|
|
327
|
+
] });
|
|
328
|
+
}
|
|
91
329
|
function ToolCard({ tool, index = 0 }) {
|
|
92
330
|
const [testing, setTesting] = useState(false);
|
|
93
331
|
const [result, setResult] = useState(null);
|
|
@@ -156,16 +394,22 @@ function McpPage() {
|
|
|
156
394
|
const [servers, setServers] = useState([]);
|
|
157
395
|
const [status, setStatus] = useState(null);
|
|
158
396
|
const [serverInfo, setServerInfo] = useState(null);
|
|
397
|
+
const [registry, setRegistry] = useState({ servers: [], categories: [] });
|
|
398
|
+
const [search, setSearch] = useState("");
|
|
399
|
+
const [installing, setInstalling] = useState(false);
|
|
400
|
+
const [tab, setTab] = useState("servers");
|
|
159
401
|
async function load() {
|
|
160
402
|
try {
|
|
161
|
-
const [s, st, info] = await Promise.all([
|
|
403
|
+
const [s, st, info, reg] = await Promise.all([
|
|
162
404
|
getMcpServers(),
|
|
163
405
|
getMcpStatus(),
|
|
164
|
-
getOwnMcpServerInfo()
|
|
406
|
+
getOwnMcpServerInfo(),
|
|
407
|
+
getMcpRegistry()
|
|
165
408
|
]);
|
|
166
409
|
setServers(s);
|
|
167
410
|
setStatus(st);
|
|
168
411
|
setServerInfo(info);
|
|
412
|
+
setRegistry(reg);
|
|
169
413
|
} catch {
|
|
170
414
|
}
|
|
171
415
|
setLoading(false);
|
|
@@ -182,11 +426,41 @@ function McpPage() {
|
|
|
182
426
|
}
|
|
183
427
|
setReloading(false);
|
|
184
428
|
}
|
|
429
|
+
async function handleAddFromRegistry(config) {
|
|
430
|
+
setInstalling(true);
|
|
431
|
+
await addMcpServer(config);
|
|
432
|
+
await load();
|
|
433
|
+
setInstalling(false);
|
|
434
|
+
}
|
|
435
|
+
async function handleAddCustom(config) {
|
|
436
|
+
const result = await addMcpServer(config);
|
|
437
|
+
if (!result.error) await load();
|
|
438
|
+
return result;
|
|
439
|
+
}
|
|
440
|
+
async function handleToggle(name) {
|
|
441
|
+
await toggleMcpServer(name);
|
|
442
|
+
await load();
|
|
443
|
+
}
|
|
444
|
+
async function handleRemove(name) {
|
|
445
|
+
await removeMcpServer(name);
|
|
446
|
+
await load();
|
|
447
|
+
}
|
|
185
448
|
if (loading) {
|
|
186
449
|
return /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-3", children: [...Array(3)].map((_, i) => /* @__PURE__ */ jsx("div", { className: "h-20 animate-shimmer rounded-lg border border-white/[0.06] bg-[--card]" }, i)) });
|
|
187
450
|
}
|
|
188
451
|
const enabledServers = servers.filter((s) => s.enabled !== false);
|
|
189
452
|
const disabledServers = servers.filter((s) => s.enabled === false);
|
|
453
|
+
const serverNames = new Set(servers.map((s) => s.name));
|
|
454
|
+
const filteredRegistry = registry.servers.filter((s) => {
|
|
455
|
+
if (!search) return true;
|
|
456
|
+
const q = search.toLowerCase();
|
|
457
|
+
return s.name.toLowerCase().includes(q) || s.description.toLowerCase().includes(q) || s.category.toLowerCase().includes(q);
|
|
458
|
+
});
|
|
459
|
+
const tabs = [
|
|
460
|
+
{ id: "servers", label: "SERVERS", count: servers.length },
|
|
461
|
+
{ id: "hub", label: "MCP HUB", count: registry.servers.length },
|
|
462
|
+
{ id: "tools", label: "TOOLS", count: status?.toolCount || 0 }
|
|
463
|
+
];
|
|
190
464
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
191
465
|
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-3 gap-3 mb-6", children: [
|
|
192
466
|
/* @__PURE__ */ jsx(StatsCard, { label: "External Tools", value: status?.toolCount || 0 }),
|
|
@@ -214,44 +488,98 @@ function McpPage() {
|
|
|
214
488
|
}
|
|
215
489
|
)
|
|
216
490
|
] }),
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
serverInfo.prompts.map((p, i) => /* @__PURE__ */ jsx(ItemCard, { name: p.name, description: p.description, badge: "prompt", index: serverInfo.tools.length + serverInfo.resources.length + i }, p.name))
|
|
227
|
-
] })
|
|
228
|
-
] }),
|
|
229
|
-
/* @__PURE__ */ jsx(SectionHeader, { label: "Client \u2014 External MCP Servers", count: servers.length }),
|
|
230
|
-
servers.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-12 text-center rounded-lg border border-white/[0.06] bg-[--card]", children: [
|
|
231
|
-
/* @__PURE__ */ jsx("div", { className: "rounded-full bg-[--cyan]/10 p-4 mb-4", children: /* @__PURE__ */ jsx(PlugIcon, { size: 24, className: "text-[--cyan]" }) }),
|
|
232
|
-
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium mb-1 text-foreground", children: "No external MCP servers configured" }),
|
|
233
|
-
/* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground max-w-sm", children: [
|
|
234
|
-
"Add servers by editing ",
|
|
235
|
-
/* @__PURE__ */ jsx("span", { className: "font-mono text-[--cyan]", children: "config/MCP_SERVERS.json" }),
|
|
236
|
-
" in your project."
|
|
237
|
-
] })
|
|
238
|
-
] }) : /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3", children: [
|
|
239
|
-
enabledServers.length > 0 && enabledServers.map((s, i) => /* @__PURE__ */ jsx(ServerCard, { server: s, index: i }, `enabled-${i}`)),
|
|
240
|
-
disabledServers.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
241
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 pt-2 pb-1", children: [
|
|
242
|
-
/* @__PURE__ */ jsx("span", { className: "font-mono text-[10px] font-medium text-muted-foreground uppercase tracking-wider", children: "Disabled" }),
|
|
243
|
-
/* @__PURE__ */ jsxs("span", { className: "text-[10px] font-mono text-muted-foreground", children: [
|
|
491
|
+
/* @__PURE__ */ jsx("div", { className: "flex gap-1 border-b border-white/[0.06] mb-4", children: tabs.map((t) => /* @__PURE__ */ jsxs(
|
|
492
|
+
"button",
|
|
493
|
+
{
|
|
494
|
+
onClick: () => setTab(t.id),
|
|
495
|
+
className: `px-4 py-2.5 text-[11px] font-mono font-medium uppercase tracking-wider border-b-2 transition-colors ${tab === t.id ? "border-[--cyan] text-[--cyan]" : "border-transparent text-muted-foreground hover:text-foreground"}`,
|
|
496
|
+
children: [
|
|
497
|
+
t.label,
|
|
498
|
+
" ",
|
|
499
|
+
/* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
|
|
244
500
|
"(",
|
|
245
|
-
|
|
501
|
+
t.count,
|
|
246
502
|
")"
|
|
247
503
|
] })
|
|
504
|
+
]
|
|
505
|
+
},
|
|
506
|
+
t.id
|
|
507
|
+
)) }),
|
|
508
|
+
/* @__PURE__ */ jsxs(AnimatePresence, { mode: "wait", children: [
|
|
509
|
+
tab === "servers" && /* @__PURE__ */ jsxs(motion.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 }, transition: { duration: 0.15 }, children: [
|
|
510
|
+
serverInfo && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
511
|
+
/* @__PURE__ */ jsx(SectionHeader, { label: "Server \u2014 Exposed to External Clients", count: serverInfo.tools.length + serverInfo.resources.length }),
|
|
512
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mb-3", children: [
|
|
513
|
+
/* @__PURE__ */ jsx("span", { className: "inline-flex items-center rounded-md bg-[--card] border border-white/[0.06] px-2.5 py-1 text-[11px] font-mono text-muted-foreground", children: "/api/mcp" }),
|
|
514
|
+
/* @__PURE__ */ jsx("div", { className: "w-1.5 h-1.5 rounded-full bg-[--success]" })
|
|
515
|
+
] }),
|
|
516
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 mb-2", children: [
|
|
517
|
+
serverInfo.tools.map((t, i) => /* @__PURE__ */ jsx(ItemCard, { name: t.name, description: t.description, badge: "tool", index: i }, t.name)),
|
|
518
|
+
serverInfo.resources.map((r, i) => /* @__PURE__ */ jsx(ItemCard, { name: r.uri, description: r.description, badge: "resource", index: serverInfo.tools.length + i }, r.uri)),
|
|
519
|
+
serverInfo.prompts.map((p, i) => /* @__PURE__ */ jsx(ItemCard, { name: p.name, description: p.description, badge: "prompt", index: serverInfo.tools.length + serverInfo.resources.length + i }, p.name))
|
|
520
|
+
] })
|
|
248
521
|
] }),
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
522
|
+
/* @__PURE__ */ jsx(SectionHeader, { label: "Client \u2014 External MCP Servers", count: servers.length }),
|
|
523
|
+
/* @__PURE__ */ jsx(AddServerForm, { onAdd: handleAddCustom }),
|
|
524
|
+
servers.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-12 text-center rounded-lg border border-white/[0.06] bg-[--card]", children: [
|
|
525
|
+
/* @__PURE__ */ jsx("div", { className: "rounded-full bg-[--cyan]/10 p-4 mb-4", children: /* @__PURE__ */ jsx(PlugIcon, { size: 24, className: "text-[--cyan]" }) }),
|
|
526
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium mb-1 text-foreground", children: "No external MCP servers configured" }),
|
|
527
|
+
/* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground max-w-sm", children: [
|
|
528
|
+
"Add servers from the ",
|
|
529
|
+
/* @__PURE__ */ jsx("button", { onClick: () => setTab("hub"), className: "text-[--cyan] hover:underline", children: "MCP Hub" }),
|
|
530
|
+
" or add a custom server above."
|
|
531
|
+
] })
|
|
532
|
+
] }) : /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3", children: [
|
|
533
|
+
enabledServers.map((s, i) => /* @__PURE__ */ jsx(ServerCard, { server: s, onToggle: handleToggle, onRemove: handleRemove, index: i }, `enabled-${s.name}-${i}`)),
|
|
534
|
+
disabledServers.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
535
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 pt-2 pb-1", children: [
|
|
536
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono text-[10px] font-medium text-muted-foreground uppercase tracking-wider", children: "Disabled" }),
|
|
537
|
+
/* @__PURE__ */ jsxs("span", { className: "text-[10px] font-mono text-muted-foreground", children: [
|
|
538
|
+
"(",
|
|
539
|
+
disabledServers.length,
|
|
540
|
+
")"
|
|
541
|
+
] })
|
|
542
|
+
] }),
|
|
543
|
+
disabledServers.map((s, i) => /* @__PURE__ */ jsx(ServerCard, { server: s, onToggle: handleToggle, onRemove: handleRemove, index: i }, `disabled-${s.name}-${i}`))
|
|
544
|
+
] })
|
|
545
|
+
] })
|
|
546
|
+
] }, "servers"),
|
|
547
|
+
tab === "hub" && /* @__PURE__ */ jsxs(motion.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 }, transition: { duration: 0.15 }, children: [
|
|
548
|
+
/* @__PURE__ */ jsxs("div", { className: "relative mb-4", children: [
|
|
549
|
+
/* @__PURE__ */ jsx("div", { className: "absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground", children: /* @__PURE__ */ jsx(SearchIcon, { size: 14 }) }),
|
|
550
|
+
/* @__PURE__ */ jsx(
|
|
551
|
+
"input",
|
|
552
|
+
{
|
|
553
|
+
placeholder: "Search MCP servers...",
|
|
554
|
+
value: search,
|
|
555
|
+
onChange: (e) => setSearch(e.target.value),
|
|
556
|
+
className: "w-full text-sm border border-white/[0.06] rounded-md pl-9 pr-3 py-2 bg-black/20 font-mono placeholder:text-muted-foreground/50 focus:outline-none focus:border-[--cyan]/40 focus:ring-1 focus:ring-[--cyan]/20 transition-colors"
|
|
557
|
+
}
|
|
558
|
+
)
|
|
559
|
+
] }),
|
|
560
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
561
|
+
filteredRegistry.map((s, i) => /* @__PURE__ */ jsx(
|
|
562
|
+
RegistryCard,
|
|
563
|
+
{
|
|
564
|
+
server: s,
|
|
565
|
+
installed: serverNames.has(s.name),
|
|
566
|
+
onInstall: handleAddFromRegistry,
|
|
567
|
+
installing,
|
|
568
|
+
index: i
|
|
569
|
+
},
|
|
570
|
+
s.id
|
|
571
|
+
)),
|
|
572
|
+
filteredRegistry.length === 0 && /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center py-12 text-center", children: [
|
|
573
|
+
/* @__PURE__ */ jsx("div", { className: "rounded-full bg-white/[0.04] border border-white/[0.06] p-4 mb-4", children: /* @__PURE__ */ jsx(SearchIcon, { size: 24 }) }),
|
|
574
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-mono text-muted-foreground", children: "No servers match your search." })
|
|
575
|
+
] })
|
|
576
|
+
] })
|
|
577
|
+
] }, "hub"),
|
|
578
|
+
tab === "tools" && /* @__PURE__ */ jsx(motion.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 }, transition: { duration: 0.15 }, children: status?.tools?.length > 0 ? /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2", children: status.tools.map((t, i) => /* @__PURE__ */ jsx(ToolCard, { tool: t, index: i }, t.name)) }) : /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-16 text-center", children: [
|
|
579
|
+
/* @__PURE__ */ jsx("div", { className: "rounded-full bg-white/[0.04] border border-white/[0.06] p-4 mb-4", children: /* @__PURE__ */ jsx(WrenchIcon, { size: 24 }) }),
|
|
580
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-mono font-medium mb-1", children: "No tools loaded" }),
|
|
581
|
+
/* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground font-mono", children: "Add MCP servers to load external tools." })
|
|
582
|
+
] }) }, "tools")
|
|
255
583
|
] })
|
|
256
584
|
] });
|
|
257
585
|
}
|