@harbinger-ai/harbinger 0.1.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/LICENSE +21 -0
- package/README.md +406 -0
- package/agents/README.md +76 -0
- package/agents/_template/CONFIG.yaml +7 -0
- package/agents/_template/HEARTBEAT.md +59 -0
- package/agents/_template/IDENTITY.md +4 -0
- package/agents/_template/SKILLS.md +1 -0
- package/agents/_template/SOUL.md +25 -0
- package/agents/_template/TOOLS.md +3 -0
- package/agents/binary-reverser/CONFIG.yaml +21 -0
- package/agents/binary-reverser/HEARTBEAT.md +65 -0
- package/agents/binary-reverser/IDENTITY.md +1 -0
- package/agents/binary-reverser/SKILLS.md +1 -0
- package/agents/binary-reverser/SOUL.md +23 -0
- package/agents/binary-reverser/TOOLS.md +99 -0
- package/agents/browser-agent/CONFIG.yaml +20 -0
- package/agents/browser-agent/HEARTBEAT.md +79 -0
- package/agents/browser-agent/IDENTITY.md +5 -0
- package/agents/browser-agent/SKILLS.md +86 -0
- package/agents/browser-agent/SOUL.md +23 -0
- package/agents/browser-agent/TOOLS.md +186 -0
- package/agents/cloud-infiltrator/CONFIG.yaml +22 -0
- package/agents/cloud-infiltrator/HEARTBEAT.md +78 -0
- package/agents/cloud-infiltrator/IDENTITY.md +1 -0
- package/agents/cloud-infiltrator/SKILLS.md +1 -0
- package/agents/cloud-infiltrator/SOUL.md +23 -0
- package/agents/cloud-infiltrator/TOOLS.md +68 -0
- package/agents/coding-assistant/CONFIG.yaml +22 -0
- package/agents/coding-assistant/HEARTBEAT.md +57 -0
- package/agents/coding-assistant/IDENTITY.md +5 -0
- package/agents/coding-assistant/SKILLS.md +69 -0
- package/agents/coding-assistant/SOUL.md +60 -0
- package/agents/coding-assistant/TOOLS.md +168 -0
- package/agents/learning-agent/CONFIG.yaml +21 -0
- package/agents/learning-agent/HEARTBEAT.md +63 -0
- package/agents/learning-agent/IDENTITY.md +5 -0
- package/agents/learning-agent/SKILLS.md +86 -0
- package/agents/learning-agent/SOUL.md +77 -0
- package/agents/learning-agent/TOOLS.md +145 -0
- package/agents/maintainer/CONFIG.yaml +31 -0
- package/agents/maintainer/HEARTBEAT.md +28 -0
- package/agents/maintainer/IDENTITY.md +33 -0
- package/agents/maintainer/SKILLS.md +24 -0
- package/agents/maintainer/SOUL.md +61 -0
- package/agents/maintainer/TOOLS.md +29 -0
- package/agents/maintainer/lib/engine.js +279 -0
- package/agents/maintainer/lib/safe-fixer.js +183 -0
- package/agents/morning-brief/CONFIG.yaml +22 -0
- package/agents/morning-brief/HEARTBEAT.md +60 -0
- package/agents/morning-brief/IDENTITY.md +5 -0
- package/agents/morning-brief/SKILLS.md +56 -0
- package/agents/morning-brief/SOUL.md +64 -0
- package/agents/morning-brief/TOOLS.md +112 -0
- package/agents/osint-detective/CONFIG.yaml +24 -0
- package/agents/osint-detective/HEARTBEAT.md +66 -0
- package/agents/osint-detective/IDENTITY.md +1 -0
- package/agents/osint-detective/SKILLS.md +1 -0
- package/agents/osint-detective/SOUL.md +23 -0
- package/agents/osint-detective/TOOLS.md +81 -0
- package/agents/recon-scout/CONFIG.yaml +22 -0
- package/agents/recon-scout/HEARTBEAT.md +79 -0
- package/agents/recon-scout/IDENTITY.md +1 -0
- package/agents/recon-scout/SKILLS.md +1 -0
- package/agents/recon-scout/SOUL.md +23 -0
- package/agents/recon-scout/TOOLS.md +93 -0
- package/agents/report-writer/CONFIG.yaml +21 -0
- package/agents/report-writer/HEARTBEAT.md +63 -0
- package/agents/report-writer/IDENTITY.md +1 -0
- package/agents/report-writer/SKILLS.md +1 -0
- package/agents/report-writer/SOUL.md +23 -0
- package/agents/report-writer/TOOLS.md +69 -0
- package/agents/shared/README.md +13 -0
- package/agents/web-hacker/CONFIG.yaml +24 -0
- package/agents/web-hacker/HEARTBEAT.md +78 -0
- package/agents/web-hacker/IDENTITY.md +1 -0
- package/agents/web-hacker/SKILLS.md +1 -0
- package/agents/web-hacker/SOUL.md +23 -0
- package/agents/web-hacker/TOOLS.md +86 -0
- package/api/CLAUDE.md +19 -0
- package/api/index.js +274 -0
- package/bin/cli.js +620 -0
- package/bin/local.sh +31 -0
- package/bin/postinstall.js +63 -0
- package/config/index.js +24 -0
- package/config/instrumentation.js +93 -0
- package/drizzle/0000_initial.sql +52 -0
- package/drizzle/0001_bounty_and_registry.sql +82 -0
- package/drizzle/0002_sync_columns.sql +7 -0
- package/drizzle/0003_graceful_bloodscream.sql +86 -0
- package/drizzle/meta/0000_snapshot.json +321 -0
- package/drizzle/meta/0003_snapshot.json +878 -0
- package/drizzle/meta/_journal.json +34 -0
- package/drizzle/relations.ts +3 -0
- package/drizzle/schema.ts +145 -0
- package/lib/actions.js +47 -0
- package/lib/agents.js +166 -0
- package/lib/ai/agent.js +96 -0
- package/lib/ai/autonomous-engine.js +261 -0
- package/lib/ai/index.js +359 -0
- package/lib/ai/model-router.js +254 -0
- package/lib/ai/model.js +73 -0
- package/lib/ai/tools.js +84 -0
- package/lib/auth/actions.js +28 -0
- package/lib/auth/config.js +27 -0
- package/lib/auth/edge-config.js +27 -0
- package/lib/auth/index.js +27 -0
- package/lib/auth/middleware.js +53 -0
- package/lib/bounty/actions.js +119 -0
- package/lib/bounty/findings.js +64 -0
- package/lib/bounty/programs.js +34 -0
- package/lib/bounty/sync-targets.js +267 -0
- package/lib/bounty/targets.js +33 -0
- package/lib/channels/base.js +56 -0
- package/lib/channels/index.js +15 -0
- package/lib/channels/telegram.js +148 -0
- package/lib/chat/actions.js +288 -0
- package/lib/chat/api.js +135 -0
- package/lib/chat/components/app-sidebar.js +237 -0
- package/lib/chat/components/app-sidebar.jsx +289 -0
- package/lib/chat/components/chat-header.js +27 -0
- package/lib/chat/components/chat-header.jsx +37 -0
- package/lib/chat/components/chat-input.js +230 -0
- package/lib/chat/components/chat-input.jsx +228 -0
- package/lib/chat/components/chat-nav-context.js +11 -0
- package/lib/chat/components/chat-nav-context.jsx +11 -0
- package/lib/chat/components/chat-page.js +81 -0
- package/lib/chat/components/chat-page.jsx +100 -0
- package/lib/chat/components/chat.js +150 -0
- package/lib/chat/components/chat.jsx +182 -0
- package/lib/chat/components/chats-page.js +302 -0
- package/lib/chat/components/chats-page.jsx +330 -0
- package/lib/chat/components/crons-page.js +172 -0
- package/lib/chat/components/crons-page.jsx +244 -0
- package/lib/chat/components/enhanced-tool-call.js +103 -0
- package/lib/chat/components/enhanced-tool-call.jsx +139 -0
- package/lib/chat/components/findings-page.js +175 -0
- package/lib/chat/components/findings-page.jsx +214 -0
- package/lib/chat/components/greeting.js +22 -0
- package/lib/chat/components/greeting.jsx +26 -0
- package/lib/chat/components/icons.js +777 -0
- package/lib/chat/components/icons.jsx +741 -0
- package/lib/chat/components/index.js +26 -0
- package/lib/chat/components/mcp-page.js +260 -0
- package/lib/chat/components/mcp-page.jsx +355 -0
- package/lib/chat/components/message.js +289 -0
- package/lib/chat/components/message.jsx +315 -0
- package/lib/chat/components/messages.js +66 -0
- package/lib/chat/components/messages.jsx +77 -0
- package/lib/chat/components/notifications-page.js +56 -0
- package/lib/chat/components/notifications-page.jsx +87 -0
- package/lib/chat/components/page-layout.js +21 -0
- package/lib/chat/components/page-layout.jsx +28 -0
- package/lib/chat/components/registry-page.js +222 -0
- package/lib/chat/components/registry-page.jsx +255 -0
- package/lib/chat/components/settings-layout.js +40 -0
- package/lib/chat/components/settings-layout.jsx +54 -0
- package/lib/chat/components/settings-secrets-page.js +216 -0
- package/lib/chat/components/settings-secrets-page.jsx +264 -0
- package/lib/chat/components/sidebar-history-item.js +132 -0
- package/lib/chat/components/sidebar-history-item.jsx +113 -0
- package/lib/chat/components/sidebar-history.js +115 -0
- package/lib/chat/components/sidebar-history.jsx +157 -0
- package/lib/chat/components/sidebar-user-nav.js +63 -0
- package/lib/chat/components/sidebar-user-nav.jsx +73 -0
- package/lib/chat/components/status-bar.js +39 -0
- package/lib/chat/components/status-bar.jsx +51 -0
- package/lib/chat/components/swarm-page.js +157 -0
- package/lib/chat/components/swarm-page.jsx +210 -0
- package/lib/chat/components/targets-page.js +376 -0
- package/lib/chat/components/targets-page.jsx +389 -0
- package/lib/chat/components/tool-call.js +86 -0
- package/lib/chat/components/tool-call.jsx +104 -0
- package/lib/chat/components/tool-panel.js +107 -0
- package/lib/chat/components/tool-panel.jsx +145 -0
- package/lib/chat/components/triggers-page.js +153 -0
- package/lib/chat/components/triggers-page.jsx +221 -0
- package/lib/chat/components/ui/confirm-dialog.js +53 -0
- package/lib/chat/components/ui/confirm-dialog.jsx +57 -0
- package/lib/chat/components/ui/dropdown-menu.js +98 -0
- package/lib/chat/components/ui/dropdown-menu.jsx +116 -0
- package/lib/chat/components/ui/rename-dialog.js +74 -0
- package/lib/chat/components/ui/rename-dialog.jsx +72 -0
- package/lib/chat/components/ui/scroll-area.js +13 -0
- package/lib/chat/components/ui/scroll-area.jsx +17 -0
- package/lib/chat/components/ui/separator.js +21 -0
- package/lib/chat/components/ui/separator.jsx +18 -0
- package/lib/chat/components/ui/sheet.js +75 -0
- package/lib/chat/components/ui/sheet.jsx +95 -0
- package/lib/chat/components/ui/sidebar.js +227 -0
- package/lib/chat/components/ui/sidebar.jsx +245 -0
- package/lib/chat/components/ui/tooltip.js +56 -0
- package/lib/chat/components/ui/tooltip.jsx +66 -0
- package/lib/chat/components/upgrade-dialog.js +151 -0
- package/lib/chat/components/upgrade-dialog.jsx +170 -0
- package/lib/chat/utils.js +11 -0
- package/lib/cron.js +246 -0
- package/lib/db/api-keys.js +163 -0
- package/lib/db/chats.js +145 -0
- package/lib/db/index.js +52 -0
- package/lib/db/notifications.js +99 -0
- package/lib/db/schema.js +145 -0
- package/lib/db/update-check.js +96 -0
- package/lib/db/users.js +89 -0
- package/lib/mcp/actions.js +104 -0
- package/lib/mcp/client.js +79 -0
- package/lib/mcp/handler.js +57 -0
- package/lib/mcp/server.js +165 -0
- package/lib/paths.js +46 -0
- package/lib/registry/actions.js +164 -0
- package/lib/registry/catalog.js +137 -0
- package/lib/registry/tools.js +71 -0
- package/lib/tools/create-job.js +99 -0
- package/lib/tools/github.js +217 -0
- package/lib/tools/openai.js +35 -0
- package/lib/tools/telegram.js +292 -0
- package/lib/triggers.js +118 -0
- package/lib/utils/render-md.js +102 -0
- package/package.json +103 -0
- package/setup/lib/auth.mjs +81 -0
- package/setup/lib/env.mjs +21 -0
- package/setup/lib/fs-utils.mjs +20 -0
- package/setup/lib/github.mjs +149 -0
- package/setup/lib/prerequisites.mjs +155 -0
- package/setup/lib/prompts.mjs +267 -0
- package/setup/lib/providers.mjs +48 -0
- package/setup/lib/sync.mjs +125 -0
- package/setup/lib/targets.mjs +45 -0
- package/setup/lib/telegram-verify.mjs +63 -0
- package/setup/lib/telegram.mjs +76 -0
- package/setup/setup-telegram.mjs +264 -0
- package/setup/setup.mjs +842 -0
- package/templates/.dockerignore +5 -0
- package/templates/.env.example +63 -0
- package/templates/.github/workflows/auto-merge.yml +117 -0
- package/templates/.github/workflows/build-image.yml +36 -0
- package/templates/.github/workflows/notify-job-failed.yml +64 -0
- package/templates/.github/workflows/notify-pr-complete.yml +119 -0
- package/templates/.github/workflows/rebuild-event-handler.yml +121 -0
- package/templates/.github/workflows/run-job.yml +89 -0
- package/templates/.github/workflows/upgrade-event-handler.yml +62 -0
- package/templates/.gitignore.template +45 -0
- package/templates/.pi/extensions/env-sanitizer/index.ts +48 -0
- package/templates/.pi/extensions/env-sanitizer/package.json +5 -0
- package/templates/CLAUDE.md +29 -0
- package/templates/CLAUDE.md.template +307 -0
- package/templates/app/api/[...thepopebot]/route.js +1 -0
- package/templates/app/api/auth/[...nextauth]/route.js +1 -0
- package/templates/app/chat/[chatId]/page.js +8 -0
- package/templates/app/chats/page.js +7 -0
- package/templates/app/components/ascii-logo.jsx +10 -0
- package/templates/app/components/login-form.jsx +92 -0
- package/templates/app/components/setup-form.jsx +82 -0
- package/templates/app/components/theme-provider.jsx +11 -0
- package/templates/app/components/theme-toggle.jsx +38 -0
- package/templates/app/components/ui/button.jsx +21 -0
- package/templates/app/components/ui/card.jsx +23 -0
- package/templates/app/components/ui/input.jsx +10 -0
- package/templates/app/components/ui/label.jsx +10 -0
- package/templates/app/crons/page.js +5 -0
- package/templates/app/findings/page.js +7 -0
- package/templates/app/globals.css +90 -0
- package/templates/app/layout.js +19 -0
- package/templates/app/login/page.js +15 -0
- package/templates/app/notifications/page.js +7 -0
- package/templates/app/page.js +7 -0
- package/templates/app/settings/crons/page.js +5 -0
- package/templates/app/settings/layout.js +7 -0
- package/templates/app/settings/mcp/page.js +5 -0
- package/templates/app/settings/page.js +5 -0
- package/templates/app/settings/secrets/page.js +5 -0
- package/templates/app/settings/triggers/page.js +5 -0
- package/templates/app/stream/chat/route.js +1 -0
- package/templates/app/swarm/page.js +7 -0
- package/templates/app/targets/page.js +7 -0
- package/templates/app/toolbox/page.js +7 -0
- package/templates/app/triggers/page.js +5 -0
- package/templates/config/AGENT.md +34 -0
- package/templates/config/CRONS.json +56 -0
- package/templates/config/EVENT_HANDLER.md +224 -0
- package/templates/config/HEARTBEAT.md +3 -0
- package/templates/config/JOB_SUMMARY.md +130 -0
- package/templates/config/MCP_SERVERS.json +1 -0
- package/templates/config/SKILL_BUILDING_GUIDE.md +90 -0
- package/templates/config/SOUL.md +17 -0
- package/templates/config/TRIGGERS.json +58 -0
- package/templates/docker/event-handler/Dockerfile +20 -0
- package/templates/docker/event-handler/ecosystem.config.cjs +8 -0
- package/templates/docker/job-claude-code/Dockerfile +34 -0
- package/templates/docker/job-claude-code/entrypoint.sh +139 -0
- package/templates/docker/job-pi-coding-agent/Dockerfile +44 -0
- package/templates/docker/job-pi-coding-agent/entrypoint.sh +163 -0
- package/templates/docker-compose.yml +63 -0
- package/templates/instrumentation.js +6 -0
- package/templates/middleware.js +1 -0
- package/templates/next.config.mjs +3 -0
- package/templates/postcss.config.mjs +5 -0
- package/templates/skills/LICENSE +21 -0
- package/templates/skills/README.md +119 -0
- package/templates/skills/brave-search/SKILL.md +79 -0
- package/templates/skills/brave-search/content.js +86 -0
- package/templates/skills/brave-search/package-lock.json +621 -0
- package/templates/skills/brave-search/package.json +14 -0
- package/templates/skills/brave-search/search.js +199 -0
- package/templates/skills/browser-tools/SKILL.md +196 -0
- package/templates/skills/browser-tools/browser-content.js +103 -0
- package/templates/skills/browser-tools/browser-cookies.js +35 -0
- package/templates/skills/browser-tools/browser-eval.js +53 -0
- package/templates/skills/browser-tools/browser-hn-scraper.js +108 -0
- package/templates/skills/browser-tools/browser-nav.js +44 -0
- package/templates/skills/browser-tools/browser-pick.js +162 -0
- package/templates/skills/browser-tools/browser-screenshot.js +34 -0
- package/templates/skills/browser-tools/browser-start.js +87 -0
- package/templates/skills/browser-tools/package-lock.json +2556 -0
- package/templates/skills/browser-tools/package.json +19 -0
- package/templates/skills/llm-secrets/SKILL.md +34 -0
- package/templates/skills/llm-secrets/llm-secrets.js +33 -0
- package/templates/skills/modify-self/SKILL.md +12 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export { ChatPage } from './chat-page.js';
|
|
2
|
+
export { ChatsPage } from './chats-page.js';
|
|
3
|
+
export { NotificationsPage } from './notifications-page.js';
|
|
4
|
+
export { SwarmPage } from './swarm-page.js';
|
|
5
|
+
export { CronsPage } from './crons-page.js';
|
|
6
|
+
export { TriggersPage } from './triggers-page.js';
|
|
7
|
+
export { PageLayout } from './page-layout.js';
|
|
8
|
+
export { SettingsLayout } from './settings-layout.js';
|
|
9
|
+
export { SettingsSecretsPage } from './settings-secrets-page.js';
|
|
10
|
+
export { McpPage } from './mcp-page.js';
|
|
11
|
+
export { TargetsPage } from './targets-page.js';
|
|
12
|
+
export { FindingsPage } from './findings-page.js';
|
|
13
|
+
export { RegistryPage } from './registry-page.js';
|
|
14
|
+
export { AppSidebar } from './app-sidebar.js';
|
|
15
|
+
export { SidebarHistory } from './sidebar-history.js';
|
|
16
|
+
export { SidebarHistoryItem } from './sidebar-history-item.js';
|
|
17
|
+
export { SidebarUserNav } from './sidebar-user-nav.js';
|
|
18
|
+
export { Chat } from './chat.js';
|
|
19
|
+
export { Messages } from './messages.js';
|
|
20
|
+
export { PreviewMessage, ThinkingMessage } from './message.js';
|
|
21
|
+
export { ChatInput } from './chat-input.js';
|
|
22
|
+
export { ChatHeader } from './chat-header.js';
|
|
23
|
+
export { Greeting } from './greeting.js';
|
|
24
|
+
export { EnhancedToolCall } from './enhanced-tool-call.js';
|
|
25
|
+
export { StatusBar } from './status-bar.js';
|
|
26
|
+
export { ToolPanel } from './tool-panel.js';
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
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";
|
|
7
|
+
const cardVariants = {
|
|
8
|
+
hidden: { opacity: 0, y: 12 },
|
|
9
|
+
visible: (i) => ({
|
|
10
|
+
opacity: 1,
|
|
11
|
+
y: 0,
|
|
12
|
+
transition: { delay: i * 0.05, duration: 0.3, ease: "easeOut" }
|
|
13
|
+
})
|
|
14
|
+
};
|
|
15
|
+
function SectionHeader({ label, count }) {
|
|
16
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 pt-6 pb-2", children: [
|
|
17
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono text-[10px] font-medium text-[--cyan] uppercase tracking-wider", children: label }),
|
|
18
|
+
count !== void 0 && /* @__PURE__ */ jsx("span", { className: "inline-flex items-center rounded-full bg-[--cyan]/10 px-2 py-0.5 text-[10px] font-mono font-medium text-[--cyan]", children: count })
|
|
19
|
+
] });
|
|
20
|
+
}
|
|
21
|
+
function ItemCard({ name, description, badge, index = 0 }) {
|
|
22
|
+
return /* @__PURE__ */ jsxs(
|
|
23
|
+
motion.div,
|
|
24
|
+
{
|
|
25
|
+
custom: index,
|
|
26
|
+
variants: cardVariants,
|
|
27
|
+
initial: "hidden",
|
|
28
|
+
animate: "visible",
|
|
29
|
+
className: "flex items-center gap-3 p-3 rounded-lg border border-white/[0.06] bg-[--card]",
|
|
30
|
+
children: [
|
|
31
|
+
/* @__PURE__ */ jsx("div", { className: "shrink-0 rounded-md bg-[--cyan]/10 p-2", children: /* @__PURE__ */ jsx(WrenchIcon, { size: 14, className: "text-[--cyan]" }) }),
|
|
32
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
33
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium font-mono truncate text-foreground", children: name }),
|
|
34
|
+
description && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-0.5 truncate", children: description })
|
|
35
|
+
] }),
|
|
36
|
+
badge && /* @__PURE__ */ jsx("span", { className: "inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-mono font-medium bg-white/[0.04] text-muted-foreground shrink-0 border border-white/[0.06]", children: badge }),
|
|
37
|
+
/* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full bg-[--success] shrink-0" })
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
function ServerCard({ server, index = 0 }) {
|
|
43
|
+
const [expanded, setExpanded] = useState(false);
|
|
44
|
+
const disabled = server.enabled === false;
|
|
45
|
+
return /* @__PURE__ */ jsxs(
|
|
46
|
+
motion.div,
|
|
47
|
+
{
|
|
48
|
+
custom: index,
|
|
49
|
+
variants: cardVariants,
|
|
50
|
+
initial: "hidden",
|
|
51
|
+
animate: "visible",
|
|
52
|
+
className: `rounded-lg border border-white/[0.06] bg-[--card] transition-opacity ${disabled ? "opacity-50" : ""}`,
|
|
53
|
+
children: [
|
|
54
|
+
/* @__PURE__ */ jsxs(
|
|
55
|
+
"button",
|
|
56
|
+
{
|
|
57
|
+
onClick: () => setExpanded(!expanded),
|
|
58
|
+
className: "flex items-center gap-3 w-full text-left p-4 hover:bg-white/[0.02] rounded-lg transition-colors",
|
|
59
|
+
children: [
|
|
60
|
+
/* @__PURE__ */ jsx("div", { className: "shrink-0 rounded-md bg-[--cyan]/10 p-2", children: /* @__PURE__ */ jsx(PlugIcon, { size: 16, className: "text-[--cyan]" }) }),
|
|
61
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
62
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium truncate text-foreground", children: server.name }),
|
|
63
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-0.5 font-mono truncate", children: server.url })
|
|
64
|
+
] }),
|
|
65
|
+
/* @__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" }),
|
|
67
|
+
/* @__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
|
+
/* @__PURE__ */ jsx("span", { className: `transition-transform ${expanded ? "rotate-180" : ""}`, children: /* @__PURE__ */ jsx(ChevronDownIcon, { size: 14 }) })
|
|
69
|
+
] })
|
|
70
|
+
]
|
|
71
|
+
}
|
|
72
|
+
),
|
|
73
|
+
expanded && /* @__PURE__ */ jsx("div", { className: "border-t border-white/[0.06] px-4 py-3", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
|
|
74
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
75
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono text-[10px] font-medium text-muted-foreground uppercase tracking-wider", children: "URL" }),
|
|
76
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-mono text-foreground/80", children: server.url })
|
|
77
|
+
] }),
|
|
78
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
79
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono text-[10px] font-medium text-muted-foreground uppercase tracking-wider", children: "Transport" }),
|
|
80
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-mono text-foreground/80", children: server.transport || "http" })
|
|
81
|
+
] }),
|
|
82
|
+
server.headers && Object.keys(server.headers).length > 0 && /* @__PURE__ */ jsxs("div", { children: [
|
|
83
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono text-[10px] font-medium text-muted-foreground uppercase tracking-wider", children: "Headers" }),
|
|
84
|
+
/* @__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) })
|
|
85
|
+
] })
|
|
86
|
+
] }) })
|
|
87
|
+
]
|
|
88
|
+
}
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
function ToolCard({ tool, index = 0 }) {
|
|
92
|
+
const [testing, setTesting] = useState(false);
|
|
93
|
+
const [result, setResult] = useState(null);
|
|
94
|
+
async function handleTest() {
|
|
95
|
+
setTesting(true);
|
|
96
|
+
setResult(null);
|
|
97
|
+
try {
|
|
98
|
+
const res = await testMcpTool(tool.name, {});
|
|
99
|
+
setResult(res);
|
|
100
|
+
} catch (err) {
|
|
101
|
+
setResult({ error: err.message });
|
|
102
|
+
} finally {
|
|
103
|
+
setTesting(false);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const hasResult = result !== null;
|
|
107
|
+
const isError = result?.error;
|
|
108
|
+
return /* @__PURE__ */ jsxs(
|
|
109
|
+
motion.div,
|
|
110
|
+
{
|
|
111
|
+
custom: index,
|
|
112
|
+
variants: cardVariants,
|
|
113
|
+
initial: "hidden",
|
|
114
|
+
animate: "visible",
|
|
115
|
+
className: "flex items-start gap-3 p-3 rounded-lg border border-white/[0.06] bg-[--card]",
|
|
116
|
+
children: [
|
|
117
|
+
/* @__PURE__ */ jsx("div", { className: "shrink-0 rounded-md bg-[--cyan]/10 p-2 mt-0.5", children: /* @__PURE__ */ jsx(WrenchIcon, { size: 14, className: "text-[--cyan]" }) }),
|
|
118
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
119
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium font-mono truncate text-foreground", children: tool.name }),
|
|
120
|
+
tool.description && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-0.5", children: tool.description }),
|
|
121
|
+
hasResult && /* @__PURE__ */ jsxs("div", { className: "mt-2 rounded-md border border-white/[0.04] bg-black/30 overflow-hidden", children: [
|
|
122
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 px-2.5 py-1.5 border-b border-white/[0.04]", children: [
|
|
123
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
|
|
124
|
+
/* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full bg-[#ff5f57]" }),
|
|
125
|
+
/* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full bg-[#febc2e]" }),
|
|
126
|
+
/* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full bg-[#28c840]" })
|
|
127
|
+
] }),
|
|
128
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono text-[9px] text-muted-foreground ml-1", children: "output" }),
|
|
129
|
+
isError ? /* @__PURE__ */ jsx(XIcon, { size: 10, className: "text-[--destructive] ml-auto" }) : /* @__PURE__ */ jsx(CheckIcon, { size: 10, className: "text-[--success] ml-auto" })
|
|
130
|
+
] }),
|
|
131
|
+
/* @__PURE__ */ jsx("pre", { className: "text-[11px] p-2.5 font-mono overflow-auto max-h-32 whitespace-pre-wrap break-words text-foreground/80", children: isError ? `Error: ${result.error}` : JSON.stringify(result.result, null, 2) })
|
|
132
|
+
] })
|
|
133
|
+
] }),
|
|
134
|
+
/* @__PURE__ */ jsx(
|
|
135
|
+
"button",
|
|
136
|
+
{
|
|
137
|
+
onClick: handleTest,
|
|
138
|
+
disabled: testing,
|
|
139
|
+
className: "shrink-0 inline-flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-xs font-mono font-medium border border-white/[0.06] hover:bg-white/[0.04] hover:border-[--cyan]/30 hover:text-[--cyan] transition-colors disabled:opacity-50",
|
|
140
|
+
children: testing ? /* @__PURE__ */ jsx(SpinnerIcon, { size: 12 }) : "Test"
|
|
141
|
+
}
|
|
142
|
+
)
|
|
143
|
+
]
|
|
144
|
+
}
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
function StatsCard({ label, value }) {
|
|
148
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center p-4 rounded-lg border border-white/[0.06] bg-[--card]", children: [
|
|
149
|
+
/* @__PURE__ */ jsx("span", { className: "text-2xl font-semibold text-[--cyan] font-mono", children: value }),
|
|
150
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono text-[10px] text-muted-foreground uppercase tracking-wider mt-1", children: label })
|
|
151
|
+
] });
|
|
152
|
+
}
|
|
153
|
+
function McpPage() {
|
|
154
|
+
const [loading, setLoading] = useState(true);
|
|
155
|
+
const [reloading, setReloading] = useState(false);
|
|
156
|
+
const [servers, setServers] = useState([]);
|
|
157
|
+
const [status, setStatus] = useState(null);
|
|
158
|
+
const [serverInfo, setServerInfo] = useState(null);
|
|
159
|
+
async function load() {
|
|
160
|
+
try {
|
|
161
|
+
const [s, st, info] = await Promise.all([
|
|
162
|
+
getMcpServers(),
|
|
163
|
+
getMcpStatus(),
|
|
164
|
+
getOwnMcpServerInfo()
|
|
165
|
+
]);
|
|
166
|
+
setServers(s);
|
|
167
|
+
setStatus(st);
|
|
168
|
+
setServerInfo(info);
|
|
169
|
+
} catch {
|
|
170
|
+
}
|
|
171
|
+
setLoading(false);
|
|
172
|
+
}
|
|
173
|
+
useEffect(() => {
|
|
174
|
+
load();
|
|
175
|
+
}, []);
|
|
176
|
+
async function handleReload() {
|
|
177
|
+
setReloading(true);
|
|
178
|
+
try {
|
|
179
|
+
await reloadMcpClient();
|
|
180
|
+
await load();
|
|
181
|
+
} catch {
|
|
182
|
+
}
|
|
183
|
+
setReloading(false);
|
|
184
|
+
}
|
|
185
|
+
if (loading) {
|
|
186
|
+
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
|
+
}
|
|
188
|
+
const enabledServers = servers.filter((s) => s.enabled !== false);
|
|
189
|
+
const disabledServers = servers.filter((s) => s.enabled === false);
|
|
190
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
191
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-3 gap-3 mb-6", children: [
|
|
192
|
+
/* @__PURE__ */ jsx(StatsCard, { label: "External Tools", value: status?.toolCount || 0 }),
|
|
193
|
+
/* @__PURE__ */ jsx(StatsCard, { label: "Servers", value: enabledServers.length }),
|
|
194
|
+
/* @__PURE__ */ jsx(StatsCard, { label: "Server Tools", value: serverInfo?.tools?.length || 0 })
|
|
195
|
+
] }),
|
|
196
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-4", children: [
|
|
197
|
+
/* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground font-mono", children: [
|
|
198
|
+
status?.toolCount || 0,
|
|
199
|
+
" tool",
|
|
200
|
+
status?.toolCount !== 1 ? "s" : "",
|
|
201
|
+
" loaded",
|
|
202
|
+
servers.length > 0 && ` from ${enabledServers.length} server${enabledServers.length !== 1 ? "s" : ""}`
|
|
203
|
+
] }),
|
|
204
|
+
/* @__PURE__ */ jsxs(
|
|
205
|
+
"button",
|
|
206
|
+
{
|
|
207
|
+
onClick: handleReload,
|
|
208
|
+
disabled: reloading,
|
|
209
|
+
className: "inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-mono font-medium border border-white/[0.06] hover:bg-white/[0.04] hover:border-[--cyan]/30 hover:text-[--cyan] transition-colors disabled:opacity-50",
|
|
210
|
+
children: [
|
|
211
|
+
reloading ? /* @__PURE__ */ jsx(SpinnerIcon, { size: 12 }) : /* @__PURE__ */ jsx(RefreshIcon, { size: 12 }),
|
|
212
|
+
"Reload"
|
|
213
|
+
]
|
|
214
|
+
}
|
|
215
|
+
)
|
|
216
|
+
] }),
|
|
217
|
+
serverInfo && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
218
|
+
/* @__PURE__ */ jsx(SectionHeader, { label: "Server \u2014 Exposed to External Clients", count: serverInfo.tools.length + serverInfo.resources.length }),
|
|
219
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mb-3", children: [
|
|
220
|
+
/* @__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" }),
|
|
221
|
+
/* @__PURE__ */ jsx("div", { className: "w-1.5 h-1.5 rounded-full bg-[--success]" })
|
|
222
|
+
] }),
|
|
223
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 mb-2", children: [
|
|
224
|
+
serverInfo.tools.map((t, i) => /* @__PURE__ */ jsx(ItemCard, { name: t.name, description: t.description, badge: "tool", index: i }, t.name)),
|
|
225
|
+
serverInfo.resources.map((r, i) => /* @__PURE__ */ jsx(ItemCard, { name: r.uri, description: r.description, badge: "resource", index: serverInfo.tools.length + i }, r.uri)),
|
|
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: [
|
|
244
|
+
"(",
|
|
245
|
+
disabledServers.length,
|
|
246
|
+
")"
|
|
247
|
+
] })
|
|
248
|
+
] }),
|
|
249
|
+
disabledServers.map((s, i) => /* @__PURE__ */ jsx(ServerCard, { server: s, index: i }, `disabled-${i}`))
|
|
250
|
+
] })
|
|
251
|
+
] }),
|
|
252
|
+
status?.tools?.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
253
|
+
/* @__PURE__ */ jsx(SectionHeader, { label: "Loaded External Tools", count: status.tools.length }),
|
|
254
|
+
/* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2", children: status.tools.map((t, i) => /* @__PURE__ */ jsx(ToolCard, { tool: t, index: i }, t.name)) })
|
|
255
|
+
] })
|
|
256
|
+
] });
|
|
257
|
+
}
|
|
258
|
+
export {
|
|
259
|
+
McpPage
|
|
260
|
+
};
|
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
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';
|
|
7
|
+
|
|
8
|
+
// ─── Animation variants ─────────────────────────────────────────────────────
|
|
9
|
+
|
|
10
|
+
const cardVariants = {
|
|
11
|
+
hidden: { opacity: 0, y: 12 },
|
|
12
|
+
visible: (i) => ({
|
|
13
|
+
opacity: 1,
|
|
14
|
+
y: 0,
|
|
15
|
+
transition: { delay: i * 0.05, duration: 0.3, ease: 'easeOut' },
|
|
16
|
+
}),
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// ─── Section Header ──────────────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
function SectionHeader({ label, count }) {
|
|
22
|
+
return (
|
|
23
|
+
<div className="flex items-center gap-2 pt-6 pb-2">
|
|
24
|
+
<span className="font-mono text-[10px] font-medium text-[--cyan] uppercase tracking-wider">{label}</span>
|
|
25
|
+
{count !== undefined && (
|
|
26
|
+
<span className="inline-flex items-center rounded-full bg-[--cyan]/10 px-2 py-0.5 text-[10px] font-mono font-medium text-[--cyan]">
|
|
27
|
+
{count}
|
|
28
|
+
</span>
|
|
29
|
+
)}
|
|
30
|
+
</div>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// ─── Item Card (server tool/resource/prompt) ─────────────────────────────────
|
|
35
|
+
|
|
36
|
+
function ItemCard({ name, description, badge, index = 0 }) {
|
|
37
|
+
return (
|
|
38
|
+
<motion.div
|
|
39
|
+
custom={index}
|
|
40
|
+
variants={cardVariants}
|
|
41
|
+
initial="hidden"
|
|
42
|
+
animate="visible"
|
|
43
|
+
className="flex items-center gap-3 p-3 rounded-lg border border-white/[0.06] bg-[--card]"
|
|
44
|
+
>
|
|
45
|
+
<div className="shrink-0 rounded-md bg-[--cyan]/10 p-2">
|
|
46
|
+
<WrenchIcon size={14} className="text-[--cyan]" />
|
|
47
|
+
</div>
|
|
48
|
+
<div className="flex-1 min-w-0">
|
|
49
|
+
<p className="text-sm font-medium font-mono truncate text-foreground">{name}</p>
|
|
50
|
+
{description && <p className="text-xs text-muted-foreground mt-0.5 truncate">{description}</p>}
|
|
51
|
+
</div>
|
|
52
|
+
{badge && (
|
|
53
|
+
<span className="inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-mono font-medium bg-white/[0.04] text-muted-foreground shrink-0 border border-white/[0.06]">
|
|
54
|
+
{badge}
|
|
55
|
+
</span>
|
|
56
|
+
)}
|
|
57
|
+
<div className="w-2 h-2 rounded-full bg-[--success] shrink-0" />
|
|
58
|
+
</motion.div>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ─── External Server Card ────────────────────────────────────────────────────
|
|
63
|
+
|
|
64
|
+
function ServerCard({ server, index = 0 }) {
|
|
65
|
+
const [expanded, setExpanded] = useState(false);
|
|
66
|
+
const disabled = server.enabled === false;
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<motion.div
|
|
70
|
+
custom={index}
|
|
71
|
+
variants={cardVariants}
|
|
72
|
+
initial="hidden"
|
|
73
|
+
animate="visible"
|
|
74
|
+
className={`rounded-lg border border-white/[0.06] bg-[--card] transition-opacity ${disabled ? 'opacity-50' : ''}`}
|
|
75
|
+
>
|
|
76
|
+
<button
|
|
77
|
+
onClick={() => setExpanded(!expanded)}
|
|
78
|
+
className="flex items-center gap-3 w-full text-left p-4 hover:bg-white/[0.02] rounded-lg transition-colors"
|
|
79
|
+
>
|
|
80
|
+
<div className="shrink-0 rounded-md bg-[--cyan]/10 p-2">
|
|
81
|
+
<PlugIcon size={16} className="text-[--cyan]" />
|
|
82
|
+
</div>
|
|
83
|
+
<div className="flex-1 min-w-0">
|
|
84
|
+
<p className="text-sm font-medium truncate text-foreground">{server.name}</p>
|
|
85
|
+
<p className="text-xs text-muted-foreground mt-0.5 font-mono truncate">{server.url}</p>
|
|
86
|
+
</div>
|
|
87
|
+
<div className="flex items-center gap-2 shrink-0">
|
|
88
|
+
<span className={`inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-mono font-medium ${
|
|
89
|
+
server.transport === 'http'
|
|
90
|
+
? 'bg-blue-500/10 text-blue-400 border border-blue-500/20'
|
|
91
|
+
: 'bg-orange-500/10 text-orange-400 border border-orange-500/20'
|
|
92
|
+
}`}>
|
|
93
|
+
{server.transport || 'http'}
|
|
94
|
+
</span>
|
|
95
|
+
<span className={`inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-mono font-medium ${
|
|
96
|
+
disabled
|
|
97
|
+
? 'bg-white/[0.04] text-muted-foreground border border-white/[0.06]'
|
|
98
|
+
: 'bg-[--success]/10 text-[--success] border border-[--success]/20'
|
|
99
|
+
}`}>
|
|
100
|
+
{disabled ? 'disabled' : 'enabled'}
|
|
101
|
+
</span>
|
|
102
|
+
<span className={`transition-transform ${expanded ? 'rotate-180' : ''}`}>
|
|
103
|
+
<ChevronDownIcon size={14} />
|
|
104
|
+
</span>
|
|
105
|
+
</div>
|
|
106
|
+
</button>
|
|
107
|
+
|
|
108
|
+
{expanded && (
|
|
109
|
+
<div className="border-t border-white/[0.06] px-4 py-3">
|
|
110
|
+
<div className="flex flex-col gap-1.5">
|
|
111
|
+
<div className="flex items-center gap-2">
|
|
112
|
+
<span className="font-mono text-[10px] font-medium text-muted-foreground uppercase tracking-wider">URL</span>
|
|
113
|
+
<span className="text-xs font-mono text-foreground/80">{server.url}</span>
|
|
114
|
+
</div>
|
|
115
|
+
<div className="flex items-center gap-2">
|
|
116
|
+
<span className="font-mono text-[10px] font-medium text-muted-foreground uppercase tracking-wider">Transport</span>
|
|
117
|
+
<span className="text-xs font-mono text-foreground/80">{server.transport || 'http'}</span>
|
|
118
|
+
</div>
|
|
119
|
+
{server.headers && Object.keys(server.headers).length > 0 && (
|
|
120
|
+
<div>
|
|
121
|
+
<span className="font-mono text-[10px] font-medium text-muted-foreground uppercase tracking-wider">Headers</span>
|
|
122
|
+
<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]">
|
|
123
|
+
{JSON.stringify(server.headers, null, 2)}
|
|
124
|
+
</pre>
|
|
125
|
+
</div>
|
|
126
|
+
)}
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
)}
|
|
130
|
+
</motion.div>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ─── Tool Card with Test ─────────────────────────────────────────────────────
|
|
135
|
+
|
|
136
|
+
function ToolCard({ tool, index = 0 }) {
|
|
137
|
+
const [testing, setTesting] = useState(false);
|
|
138
|
+
const [result, setResult] = useState(null);
|
|
139
|
+
|
|
140
|
+
async function handleTest() {
|
|
141
|
+
setTesting(true);
|
|
142
|
+
setResult(null);
|
|
143
|
+
try {
|
|
144
|
+
const res = await testMcpTool(tool.name, {});
|
|
145
|
+
setResult(res);
|
|
146
|
+
} catch (err) {
|
|
147
|
+
setResult({ error: err.message });
|
|
148
|
+
} finally {
|
|
149
|
+
setTesting(false);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const hasResult = result !== null;
|
|
154
|
+
const isError = result?.error;
|
|
155
|
+
|
|
156
|
+
return (
|
|
157
|
+
<motion.div
|
|
158
|
+
custom={index}
|
|
159
|
+
variants={cardVariants}
|
|
160
|
+
initial="hidden"
|
|
161
|
+
animate="visible"
|
|
162
|
+
className="flex items-start gap-3 p-3 rounded-lg border border-white/[0.06] bg-[--card]"
|
|
163
|
+
>
|
|
164
|
+
<div className="shrink-0 rounded-md bg-[--cyan]/10 p-2 mt-0.5">
|
|
165
|
+
<WrenchIcon size={14} className="text-[--cyan]" />
|
|
166
|
+
</div>
|
|
167
|
+
<div className="flex-1 min-w-0">
|
|
168
|
+
<p className="text-sm font-medium font-mono truncate text-foreground">{tool.name}</p>
|
|
169
|
+
{tool.description && <p className="text-xs text-muted-foreground mt-0.5">{tool.description}</p>}
|
|
170
|
+
|
|
171
|
+
{/* Terminal-style test output */}
|
|
172
|
+
{hasResult && (
|
|
173
|
+
<div className="mt-2 rounded-md border border-white/[0.04] bg-black/30 overflow-hidden">
|
|
174
|
+
<div className="flex items-center gap-1.5 px-2.5 py-1.5 border-b border-white/[0.04]">
|
|
175
|
+
<div className="flex items-center gap-1">
|
|
176
|
+
<div className="w-2 h-2 rounded-full bg-[#ff5f57]" />
|
|
177
|
+
<div className="w-2 h-2 rounded-full bg-[#febc2e]" />
|
|
178
|
+
<div className="w-2 h-2 rounded-full bg-[#28c840]" />
|
|
179
|
+
</div>
|
|
180
|
+
<span className="font-mono text-[9px] text-muted-foreground ml-1">output</span>
|
|
181
|
+
{isError ? (
|
|
182
|
+
<XIcon size={10} className="text-[--destructive] ml-auto" />
|
|
183
|
+
) : (
|
|
184
|
+
<CheckIcon size={10} className="text-[--success] ml-auto" />
|
|
185
|
+
)}
|
|
186
|
+
</div>
|
|
187
|
+
<pre className="text-[11px] p-2.5 font-mono overflow-auto max-h-32 whitespace-pre-wrap break-words text-foreground/80">
|
|
188
|
+
{isError ? `Error: ${result.error}` : JSON.stringify(result.result, null, 2)}
|
|
189
|
+
</pre>
|
|
190
|
+
</div>
|
|
191
|
+
)}
|
|
192
|
+
</div>
|
|
193
|
+
<button
|
|
194
|
+
onClick={handleTest}
|
|
195
|
+
disabled={testing}
|
|
196
|
+
className="shrink-0 inline-flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-xs font-mono font-medium border border-white/[0.06] hover:bg-white/[0.04] hover:border-[--cyan]/30 hover:text-[--cyan] transition-colors disabled:opacity-50"
|
|
197
|
+
>
|
|
198
|
+
{testing ? <SpinnerIcon size={12} /> : 'Test'}
|
|
199
|
+
</button>
|
|
200
|
+
</motion.div>
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// ─── Stats Card ──────────────────────────────────────────────────────────────
|
|
205
|
+
|
|
206
|
+
function StatsCard({ label, value }) {
|
|
207
|
+
return (
|
|
208
|
+
<div className="flex flex-col items-center justify-center p-4 rounded-lg border border-white/[0.06] bg-[--card]">
|
|
209
|
+
<span className="text-2xl font-semibold text-[--cyan] font-mono">{value}</span>
|
|
210
|
+
<span className="font-mono text-[10px] text-muted-foreground uppercase tracking-wider mt-1">{label}</span>
|
|
211
|
+
</div>
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// ─── Main Page ───────────────────────────────────────────────────────────────
|
|
216
|
+
|
|
217
|
+
export function McpPage() {
|
|
218
|
+
const [loading, setLoading] = useState(true);
|
|
219
|
+
const [reloading, setReloading] = useState(false);
|
|
220
|
+
const [servers, setServers] = useState([]);
|
|
221
|
+
const [status, setStatus] = useState(null);
|
|
222
|
+
const [serverInfo, setServerInfo] = useState(null);
|
|
223
|
+
|
|
224
|
+
async function load() {
|
|
225
|
+
try {
|
|
226
|
+
const [s, st, info] = await Promise.all([
|
|
227
|
+
getMcpServers(),
|
|
228
|
+
getMcpStatus(),
|
|
229
|
+
getOwnMcpServerInfo(),
|
|
230
|
+
]);
|
|
231
|
+
setServers(s);
|
|
232
|
+
setStatus(st);
|
|
233
|
+
setServerInfo(info);
|
|
234
|
+
} catch {}
|
|
235
|
+
setLoading(false);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
useEffect(() => { load(); }, []);
|
|
239
|
+
|
|
240
|
+
async function handleReload() {
|
|
241
|
+
setReloading(true);
|
|
242
|
+
try {
|
|
243
|
+
await reloadMcpClient();
|
|
244
|
+
await load();
|
|
245
|
+
} catch {}
|
|
246
|
+
setReloading(false);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (loading) {
|
|
250
|
+
return (
|
|
251
|
+
<div className="flex flex-col gap-3">
|
|
252
|
+
{[...Array(3)].map((_, i) => (
|
|
253
|
+
<div key={i} className="h-20 animate-shimmer rounded-lg border border-white/[0.06] bg-[--card]" />
|
|
254
|
+
))}
|
|
255
|
+
</div>
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const enabledServers = servers.filter(s => s.enabled !== false);
|
|
260
|
+
const disabledServers = servers.filter(s => s.enabled === false);
|
|
261
|
+
|
|
262
|
+
return (
|
|
263
|
+
<>
|
|
264
|
+
{/* Stats row */}
|
|
265
|
+
<div className="grid grid-cols-3 gap-3 mb-6">
|
|
266
|
+
<StatsCard label="External Tools" value={status?.toolCount || 0} />
|
|
267
|
+
<StatsCard label="Servers" value={enabledServers.length} />
|
|
268
|
+
<StatsCard label="Server Tools" value={serverInfo?.tools?.length || 0} />
|
|
269
|
+
</div>
|
|
270
|
+
|
|
271
|
+
{/* Reload bar */}
|
|
272
|
+
<div className="flex items-center justify-between mb-4">
|
|
273
|
+
<p className="text-xs text-muted-foreground font-mono">
|
|
274
|
+
{status?.toolCount || 0} tool{status?.toolCount !== 1 ? 's' : ''} loaded
|
|
275
|
+
{servers.length > 0 && ` from ${enabledServers.length} server${enabledServers.length !== 1 ? 's' : ''}`}
|
|
276
|
+
</p>
|
|
277
|
+
<button
|
|
278
|
+
onClick={handleReload}
|
|
279
|
+
disabled={reloading}
|
|
280
|
+
className="inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-mono font-medium border border-white/[0.06] hover:bg-white/[0.04] hover:border-[--cyan]/30 hover:text-[--cyan] transition-colors disabled:opacity-50"
|
|
281
|
+
>
|
|
282
|
+
{reloading ? <SpinnerIcon size={12} /> : <RefreshIcon size={12} />}
|
|
283
|
+
Reload
|
|
284
|
+
</button>
|
|
285
|
+
</div>
|
|
286
|
+
|
|
287
|
+
{/* ── Server — Exposed MCP ── */}
|
|
288
|
+
{serverInfo && (
|
|
289
|
+
<>
|
|
290
|
+
<SectionHeader label="Server — Exposed to External Clients" count={serverInfo.tools.length + serverInfo.resources.length} />
|
|
291
|
+
<div className="flex items-center gap-2 mb-3">
|
|
292
|
+
<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">
|
|
293
|
+
/api/mcp
|
|
294
|
+
</span>
|
|
295
|
+
<div className="w-1.5 h-1.5 rounded-full bg-[--success]" />
|
|
296
|
+
</div>
|
|
297
|
+
<div className="flex flex-col gap-2 mb-2">
|
|
298
|
+
{serverInfo.tools.map((t, i) => (
|
|
299
|
+
<ItemCard key={t.name} name={t.name} description={t.description} badge="tool" index={i} />
|
|
300
|
+
))}
|
|
301
|
+
{serverInfo.resources.map((r, i) => (
|
|
302
|
+
<ItemCard key={r.uri} name={r.uri} description={r.description} badge="resource" index={serverInfo.tools.length + i} />
|
|
303
|
+
))}
|
|
304
|
+
{serverInfo.prompts.map((p, i) => (
|
|
305
|
+
<ItemCard key={p.name} name={p.name} description={p.description} badge="prompt" index={serverInfo.tools.length + serverInfo.resources.length + i} />
|
|
306
|
+
))}
|
|
307
|
+
</div>
|
|
308
|
+
</>
|
|
309
|
+
)}
|
|
310
|
+
|
|
311
|
+
{/* ── Client — External MCP Servers ── */}
|
|
312
|
+
<SectionHeader label="Client — External MCP Servers" count={servers.length} />
|
|
313
|
+
{servers.length === 0 ? (
|
|
314
|
+
<div className="flex flex-col items-center justify-center py-12 text-center rounded-lg border border-white/[0.06] bg-[--card]">
|
|
315
|
+
<div className="rounded-full bg-[--cyan]/10 p-4 mb-4">
|
|
316
|
+
<PlugIcon size={24} className="text-[--cyan]" />
|
|
317
|
+
</div>
|
|
318
|
+
<p className="text-sm font-medium mb-1 text-foreground">No external MCP servers configured</p>
|
|
319
|
+
<p className="text-xs text-muted-foreground max-w-sm">
|
|
320
|
+
Add servers by editing <span className="font-mono text-[--cyan]">config/MCP_SERVERS.json</span> in your project.
|
|
321
|
+
</p>
|
|
322
|
+
</div>
|
|
323
|
+
) : (
|
|
324
|
+
<div className="flex flex-col gap-3">
|
|
325
|
+
{enabledServers.length > 0 && enabledServers.map((s, i) => (
|
|
326
|
+
<ServerCard key={`enabled-${i}`} server={s} index={i} />
|
|
327
|
+
))}
|
|
328
|
+
{disabledServers.length > 0 && (
|
|
329
|
+
<>
|
|
330
|
+
<div className="flex items-center gap-2 pt-2 pb-1">
|
|
331
|
+
<span className="font-mono text-[10px] font-medium text-muted-foreground uppercase tracking-wider">Disabled</span>
|
|
332
|
+
<span className="text-[10px] font-mono text-muted-foreground">({disabledServers.length})</span>
|
|
333
|
+
</div>
|
|
334
|
+
{disabledServers.map((s, i) => (
|
|
335
|
+
<ServerCard key={`disabled-${i}`} server={s} index={i} />
|
|
336
|
+
))}
|
|
337
|
+
</>
|
|
338
|
+
)}
|
|
339
|
+
</div>
|
|
340
|
+
)}
|
|
341
|
+
|
|
342
|
+
{/* ── Loaded External Tools ── */}
|
|
343
|
+
{status?.tools?.length > 0 && (
|
|
344
|
+
<>
|
|
345
|
+
<SectionHeader label="Loaded External Tools" count={status.tools.length} />
|
|
346
|
+
<div className="flex flex-col gap-2">
|
|
347
|
+
{status.tools.map((t, i) => (
|
|
348
|
+
<ToolCard key={t.name} tool={t} index={i} />
|
|
349
|
+
))}
|
|
350
|
+
</div>
|
|
351
|
+
</>
|
|
352
|
+
)}
|
|
353
|
+
</>
|
|
354
|
+
);
|
|
355
|
+
}
|