@swarmclawai/swarmclaw 0.6.7 → 0.7.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/README.md +82 -39
- package/next.config.ts +31 -6
- package/package.json +3 -2
- package/src/app/api/agents/[id]/thread/route.ts +1 -0
- package/src/app/api/agents/route.ts +19 -5
- package/src/app/api/approvals/route.ts +22 -0
- package/src/app/api/chatrooms/[id]/chat/route.ts +4 -0
- package/src/app/api/clawhub/install/route.ts +2 -2
- package/src/app/api/eval/run/route.ts +37 -0
- package/src/app/api/eval/scenarios/route.ts +24 -0
- package/src/app/api/eval/suite/route.ts +29 -0
- package/src/app/api/mcp-servers/[id]/conformance/route.ts +26 -0
- package/src/app/api/mcp-servers/[id]/invoke/route.ts +81 -0
- package/src/app/api/memory/graph/route.ts +46 -0
- package/src/app/api/memory/route.ts +36 -5
- package/src/app/api/notifications/route.ts +3 -0
- package/src/app/api/plugins/install/route.ts +57 -5
- package/src/app/api/plugins/marketplace/route.ts +73 -22
- package/src/app/api/plugins/route.ts +61 -1
- package/src/app/api/plugins/ui/route.ts +34 -0
- package/src/app/api/sessions/[id]/checkpoints/route.ts +31 -0
- package/src/app/api/sessions/[id]/restore/route.ts +36 -0
- package/src/app/api/settings/route.ts +62 -0
- package/src/app/api/setup/doctor/route.ts +22 -5
- package/src/app/api/souls/[id]/route.ts +65 -0
- package/src/app/api/souls/route.ts +70 -0
- package/src/app/api/tasks/[id]/approve/route.ts +4 -3
- package/src/app/api/tasks/[id]/route.ts +16 -3
- package/src/app/api/tasks/route.ts +10 -2
- package/src/app/api/usage/route.ts +9 -2
- package/src/app/globals.css +27 -0
- package/src/app/page.tsx +10 -5
- package/src/cli/index.js +37 -0
- package/src/components/activity/activity-feed.tsx +9 -2
- package/src/components/agents/agent-avatar.tsx +5 -1
- package/src/components/agents/agent-card.tsx +55 -9
- package/src/components/agents/agent-sheet.tsx +112 -34
- package/src/components/agents/inspector-panel.tsx +1 -1
- package/src/components/agents/soul-library-picker.tsx +84 -13
- package/src/components/auth/access-key-gate.tsx +63 -54
- package/src/components/auth/user-picker.tsx +37 -32
- package/src/components/chat/activity-moment.tsx +2 -0
- package/src/components/chat/chat-area.tsx +11 -0
- package/src/components/chat/chat-header.tsx +69 -25
- package/src/components/chat/chat-tool-toggles.tsx +2 -2
- package/src/components/chat/checkpoint-timeline.tsx +112 -0
- package/src/components/chat/code-block.tsx +3 -1
- package/src/components/chat/exec-approval-card.tsx +8 -1
- package/src/components/chat/message-bubble.tsx +164 -4
- package/src/components/chat/message-list.tsx +46 -4
- package/src/components/chat/session-approval-card.tsx +80 -0
- package/src/components/chat/session-debug-panel.tsx +106 -84
- package/src/components/chat/streaming-bubble.tsx +6 -5
- package/src/components/chat/task-approval-card.tsx +78 -0
- package/src/components/chat/thinking-indicator.tsx +48 -12
- package/src/components/chat/tool-call-bubble.tsx +3 -0
- package/src/components/chat/tool-request-banner.tsx +39 -20
- package/src/components/chatrooms/chatroom-list.tsx +11 -4
- package/src/components/chatrooms/chatroom-sheet.tsx +7 -2
- package/src/components/connectors/connector-list.tsx +33 -11
- package/src/components/connectors/connector-sheet.tsx +37 -7
- package/src/components/home/home-view.tsx +54 -24
- package/src/components/input/chat-input.tsx +22 -1
- package/src/components/knowledge/knowledge-list.tsx +17 -18
- package/src/components/knowledge/knowledge-sheet.tsx +9 -5
- package/src/components/layout/app-layout.tsx +87 -19
- package/src/components/mcp-servers/mcp-server-list.tsx +352 -50
- package/src/components/mcp-servers/mcp-server-sheet.tsx +25 -9
- package/src/components/memory/memory-browser.tsx +73 -45
- package/src/components/memory/memory-graph-view.tsx +203 -0
- package/src/components/memory/memory-list.tsx +20 -13
- package/src/components/plugins/plugin-list.tsx +214 -60
- package/src/components/plugins/plugin-sheet.tsx +119 -24
- package/src/components/projects/project-list.tsx +17 -9
- package/src/components/providers/provider-list.tsx +21 -6
- package/src/components/providers/provider-sheet.tsx +42 -25
- package/src/components/runs/run-list.tsx +17 -13
- package/src/components/schedules/schedule-card.tsx +10 -3
- package/src/components/schedules/schedule-list.tsx +2 -2
- package/src/components/schedules/schedule-sheet.tsx +28 -9
- package/src/components/secrets/secret-sheet.tsx +7 -2
- package/src/components/secrets/secrets-list.tsx +18 -5
- package/src/components/sessions/new-session-sheet.tsx +183 -376
- package/src/components/sessions/session-card.tsx +10 -2
- package/src/components/settings/gateway-connection-panel.tsx +9 -8
- package/src/components/shared/command-palette.tsx +13 -5
- package/src/components/shared/empty-state.tsx +20 -8
- package/src/components/shared/hint-tip.tsx +31 -0
- package/src/components/shared/notification-center.tsx +134 -86
- package/src/components/shared/profile-sheet.tsx +4 -0
- package/src/components/shared/settings/plugin-manager.tsx +360 -135
- package/src/components/shared/settings/section-capability-policy.tsx +3 -3
- package/src/components/shared/settings/section-runtime-loop.tsx +149 -4
- package/src/components/skills/clawhub-browser.tsx +1 -0
- package/src/components/skills/skill-list.tsx +31 -12
- package/src/components/skills/skill-sheet.tsx +20 -7
- package/src/components/tasks/approvals-panel.tsx +224 -0
- package/src/components/tasks/task-board.tsx +20 -12
- package/src/components/tasks/task-card.tsx +21 -7
- package/src/components/tasks/task-column.tsx +4 -3
- package/src/components/tasks/task-list.tsx +1 -1
- package/src/components/tasks/task-sheet.tsx +130 -1
- package/src/components/ui/dialog.tsx +1 -0
- package/src/components/ui/sheet.tsx +1 -0
- package/src/components/usage/metrics-dashboard.tsx +72 -48
- package/src/components/wallets/wallet-panel.tsx +65 -41
- package/src/components/wallets/wallet-section.tsx +9 -3
- package/src/components/webhooks/webhook-list.tsx +21 -12
- package/src/components/webhooks/webhook-sheet.tsx +13 -3
- package/src/lib/approval-display.test.ts +45 -0
- package/src/lib/approval-display.ts +62 -0
- package/src/lib/clipboard.ts +38 -0
- package/src/lib/memory.ts +8 -0
- package/src/lib/providers/claude-cli.ts +5 -3
- package/src/lib/providers/index.ts +67 -21
- package/src/lib/runtime-loop.ts +3 -2
- package/src/lib/server/approvals.ts +150 -0
- package/src/lib/server/chat-execution.ts +319 -74
- package/src/lib/server/chatroom-helpers.ts +63 -5
- package/src/lib/server/chatroom-orchestration.ts +74 -0
- package/src/lib/server/clawhub-client.ts +82 -6
- package/src/lib/server/connectors/manager.ts +27 -1
- package/src/lib/server/context-manager.ts +132 -50
- package/src/lib/server/cost.test.ts +73 -0
- package/src/lib/server/cost.ts +165 -34
- package/src/lib/server/daemon-state.ts +112 -1
- package/src/lib/server/data-dir.ts +18 -1
- package/src/lib/server/eval/runner.ts +126 -0
- package/src/lib/server/eval/scenarios.ts +218 -0
- package/src/lib/server/eval/scorer.ts +96 -0
- package/src/lib/server/eval/store.ts +37 -0
- package/src/lib/server/eval/types.ts +48 -0
- package/src/lib/server/execution-log.ts +12 -8
- package/src/lib/server/guardian.ts +34 -0
- package/src/lib/server/heartbeat-service.ts +53 -1
- package/src/lib/server/integrity-monitor.ts +208 -0
- package/src/lib/server/langgraph-checkpoint.ts +10 -0
- package/src/lib/server/link-understanding.ts +55 -0
- package/src/lib/server/llm-response-cache.test.ts +102 -0
- package/src/lib/server/llm-response-cache.ts +227 -0
- package/src/lib/server/main-agent-loop.ts +115 -16
- package/src/lib/server/main-session.ts +6 -3
- package/src/lib/server/mcp-conformance.test.ts +18 -0
- package/src/lib/server/mcp-conformance.ts +233 -0
- package/src/lib/server/memory-db.ts +193 -19
- package/src/lib/server/memory-retrieval.test.ts +56 -0
- package/src/lib/server/mmr.ts +73 -0
- package/src/lib/server/orchestrator-lg.ts +7 -1
- package/src/lib/server/orchestrator.ts +4 -3
- package/src/lib/server/plugins.ts +662 -132
- package/src/lib/server/process-manager.ts +18 -0
- package/src/lib/server/query-expansion.ts +57 -0
- package/src/lib/server/queue.ts +280 -11
- package/src/lib/server/runtime-settings.ts +9 -0
- package/src/lib/server/session-run-manager.test.ts +23 -0
- package/src/lib/server/session-run-manager.ts +32 -2
- package/src/lib/server/session-tools/canvas.ts +85 -50
- package/src/lib/server/session-tools/chatroom.ts +130 -127
- package/src/lib/server/session-tools/connector.ts +233 -454
- package/src/lib/server/session-tools/context-mgmt.ts +87 -105
- package/src/lib/server/session-tools/crud.ts +84 -7
- package/src/lib/server/session-tools/delegate.ts +351 -752
- package/src/lib/server/session-tools/discovery.ts +198 -0
- package/src/lib/server/session-tools/edit_file.ts +82 -0
- package/src/lib/server/session-tools/file-send.test.ts +39 -0
- package/src/lib/server/session-tools/file.ts +257 -425
- package/src/lib/server/session-tools/git.ts +87 -47
- package/src/lib/server/session-tools/http.ts +95 -33
- package/src/lib/server/session-tools/index.ts +217 -138
- package/src/lib/server/session-tools/memory.ts +154 -239
- package/src/lib/server/session-tools/monitor.ts +126 -0
- package/src/lib/server/session-tools/normalize-tool-args.test.ts +61 -0
- package/src/lib/server/session-tools/normalize-tool-args.ts +48 -0
- package/src/lib/server/session-tools/openclaw-nodes.ts +82 -99
- package/src/lib/server/session-tools/openclaw-workspace.ts +103 -93
- package/src/lib/server/session-tools/platform.ts +86 -0
- package/src/lib/server/session-tools/plugin-creator.ts +239 -0
- package/src/lib/server/session-tools/sample-ui.ts +97 -0
- package/src/lib/server/session-tools/sandbox.ts +175 -148
- package/src/lib/server/session-tools/schedule.ts +78 -0
- package/src/lib/server/session-tools/session-info.ts +104 -410
- package/src/lib/server/session-tools/shell-normalize.test.ts +43 -0
- package/src/lib/server/session-tools/shell.ts +171 -143
- package/src/lib/server/session-tools/subagent.ts +77 -77
- package/src/lib/server/session-tools/wallet.ts +182 -106
- package/src/lib/server/session-tools/web.ts +181 -327
- package/src/lib/server/storage.ts +36 -0
- package/src/lib/server/stream-agent-chat.ts +348 -242
- package/src/lib/server/task-quality-gate.test.ts +44 -0
- package/src/lib/server/task-quality-gate.ts +67 -0
- package/src/lib/server/task-validation.test.ts +78 -0
- package/src/lib/server/task-validation.ts +67 -2
- package/src/lib/server/tool-aliases.ts +68 -0
- package/src/lib/server/tool-capability-policy.ts +24 -5
- package/src/lib/server/tool-retry.ts +62 -0
- package/src/lib/server/transcript-repair.ts +72 -0
- package/src/lib/setup-defaults.ts +1 -0
- package/src/lib/tasks.ts +7 -1
- package/src/lib/tool-definitions.ts +24 -23
- package/src/lib/validation/schemas.ts +13 -0
- package/src/lib/view-routes.ts +2 -23
- package/src/stores/use-app-store.ts +23 -1
- package/src/types/index.ts +155 -10
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { useEffect, useState, useRef } from 'react'
|
|
4
4
|
import { useAppStore } from '@/stores/use-app-store'
|
|
5
5
|
import { api } from '@/lib/api-client'
|
|
6
|
+
import { toast } from 'sonner'
|
|
6
7
|
|
|
7
8
|
const transportColors: Record<string, string> = {
|
|
8
9
|
stdio: 'bg-emerald-500/15 text-emerald-400',
|
|
@@ -11,6 +12,39 @@ const transportColors: Record<string, string> = {
|
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
type McpStatus = { ok: boolean; tools?: string[]; error?: string; loading: boolean }
|
|
15
|
+
type McpToolMeta = { name: string; description?: string; inputSchema?: Record<string, unknown> }
|
|
16
|
+
type McpInvokeResult = { ok: boolean; text?: string; error?: string; isError?: boolean; result?: unknown }
|
|
17
|
+
type McpConformanceIssue = { level: 'error' | 'warning'; code: string; message: string; toolName?: string }
|
|
18
|
+
type McpConformanceResult = {
|
|
19
|
+
ok: boolean
|
|
20
|
+
toolsCount: number
|
|
21
|
+
smokeToolName: string | null
|
|
22
|
+
issues: McpConformanceIssue[]
|
|
23
|
+
timings: { connectMs: number; listToolsMs: number; smokeInvokeMs: number | null }
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function buildArgsTemplate(inputSchema: Record<string, unknown> | undefined): string {
|
|
27
|
+
const schema = inputSchema || {}
|
|
28
|
+
const required = Array.isArray(schema.required) ? schema.required.filter((k): k is string => typeof k === 'string') : []
|
|
29
|
+
const properties = (schema.properties && typeof schema.properties === 'object')
|
|
30
|
+
? schema.properties as Record<string, Record<string, unknown>>
|
|
31
|
+
: {}
|
|
32
|
+
const template: Record<string, unknown> = {}
|
|
33
|
+
for (const key of required.slice(0, 8)) {
|
|
34
|
+
const prop = properties[key] || {}
|
|
35
|
+
const type = typeof prop.type === 'string' ? prop.type : 'string'
|
|
36
|
+
template[key] = type === 'number' || type === 'integer'
|
|
37
|
+
? 0
|
|
38
|
+
: type === 'boolean'
|
|
39
|
+
? false
|
|
40
|
+
: type === 'array'
|
|
41
|
+
? []
|
|
42
|
+
: type === 'object'
|
|
43
|
+
? {}
|
|
44
|
+
: ''
|
|
45
|
+
}
|
|
46
|
+
return JSON.stringify(template, null, 2) || '{}'
|
|
47
|
+
}
|
|
14
48
|
|
|
15
49
|
export function McpServerList({ inSidebar }: { inSidebar?: boolean }) {
|
|
16
50
|
const mcpServers = useAppStore((s) => s.mcpServers)
|
|
@@ -18,13 +52,35 @@ export function McpServerList({ inSidebar }: { inSidebar?: boolean }) {
|
|
|
18
52
|
const setMcpServerSheetOpen = useAppStore((s) => s.setMcpServerSheetOpen)
|
|
19
53
|
const setEditingMcpServerId = useAppStore((s) => s.setEditingMcpServerId)
|
|
20
54
|
const [statuses, setStatuses] = useState<Record<string, McpStatus>>({})
|
|
55
|
+
const [inspectorServerId, setInspectorServerId] = useState<string | null>(null)
|
|
56
|
+
const [toolsByServer, setToolsByServer] = useState<Record<string, McpToolMeta[]>>({})
|
|
57
|
+
const [inspectorLoading, setInspectorLoading] = useState(false)
|
|
58
|
+
const [inspectorError, setInspectorError] = useState<string | null>(null)
|
|
59
|
+
const [selectedTool, setSelectedTool] = useState('')
|
|
60
|
+
const [argsJson, setArgsJson] = useState('{}')
|
|
61
|
+
const [invokeLoading, setInvokeLoading] = useState(false)
|
|
62
|
+
const [invokeResult, setInvokeResult] = useState<McpInvokeResult | null>(null)
|
|
63
|
+
const [conformanceByServer, setConformanceByServer] = useState<Record<string, McpConformanceResult>>({})
|
|
64
|
+
const [conformanceLoading, setConformanceLoading] = useState<Record<string, boolean>>({})
|
|
21
65
|
const timersRef = useRef<ReturnType<typeof setTimeout>[]>([])
|
|
22
66
|
|
|
23
67
|
useEffect(() => {
|
|
24
68
|
loadMcpServers()
|
|
25
69
|
}, [loadMcpServers])
|
|
26
70
|
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
if (inspectorServerId && !mcpServers[inspectorServerId]) {
|
|
73
|
+
setInspectorServerId(null)
|
|
74
|
+
setInspectorError(null)
|
|
75
|
+
setInvokeResult(null)
|
|
76
|
+
}
|
|
77
|
+
}, [inspectorServerId, mcpServers])
|
|
78
|
+
|
|
27
79
|
const serverList = Object.values(mcpServers)
|
|
80
|
+
const activeInspectorServer = inspectorServerId ? mcpServers[inspectorServerId] : null
|
|
81
|
+
const activeTools = inspectorServerId ? (toolsByServer[inspectorServerId] || []) : []
|
|
82
|
+
const activeToolMeta = activeTools.find((tool) => tool.name === selectedTool) || null
|
|
83
|
+
const activeConformance = inspectorServerId ? conformanceByServer[inspectorServerId] : null
|
|
28
84
|
|
|
29
85
|
// Staggered status tests on mount
|
|
30
86
|
useEffect(() => {
|
|
@@ -55,8 +111,16 @@ export function McpServerList({ inSidebar }: { inSidebar?: boolean }) {
|
|
|
55
111
|
|
|
56
112
|
const handleDelete = async (e: React.MouseEvent, id: string) => {
|
|
57
113
|
e.stopPropagation()
|
|
58
|
-
|
|
59
|
-
|
|
114
|
+
const server = mcpServers[id]
|
|
115
|
+
if (!confirm(`Delete MCP server "${server?.name || id}"?`)) return
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
await api('DELETE', `/mcp-servers/${id}`)
|
|
119
|
+
toast.success('MCP server deleted')
|
|
120
|
+
await loadMcpServers()
|
|
121
|
+
} catch (err: unknown) {
|
|
122
|
+
toast.error(err instanceof Error ? err.message : 'Failed to delete server')
|
|
123
|
+
}
|
|
60
124
|
}
|
|
61
125
|
|
|
62
126
|
const handleRetest = async (e: React.MouseEvent, id: string) => {
|
|
@@ -65,8 +129,117 @@ export function McpServerList({ inSidebar }: { inSidebar?: boolean }) {
|
|
|
65
129
|
try {
|
|
66
130
|
const res = await api<{ ok: boolean; tools?: string[]; error?: string }>('POST', `/mcp-servers/${id}/test`)
|
|
67
131
|
setStatuses((prev) => ({ ...prev, [id]: { ok: res.ok, tools: res.tools, error: res.error, loading: false } }))
|
|
68
|
-
|
|
132
|
+
if (res.ok) toast.success('Connection test passed')
|
|
133
|
+
else toast.error(res.error || 'Connection test failed')
|
|
134
|
+
} catch (err: unknown) {
|
|
69
135
|
setStatuses((prev) => ({ ...prev, [id]: { ok: false, error: 'Test failed', loading: false } }))
|
|
136
|
+
toast.error(err instanceof Error ? err.message : 'Test failed')
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const handleConformance = async (e: React.MouseEvent, id: string) => {
|
|
141
|
+
e.stopPropagation()
|
|
142
|
+
setConformanceLoading((prev) => ({ ...prev, [id]: true }))
|
|
143
|
+
try {
|
|
144
|
+
const res = await api<McpConformanceResult>('POST', `/mcp-servers/${id}/conformance`, {
|
|
145
|
+
timeoutMs: 12000,
|
|
146
|
+
})
|
|
147
|
+
setConformanceByServer((prev) => ({ ...prev, [id]: res }))
|
|
148
|
+
if (res.ok) toast.success('Conformance check passed')
|
|
149
|
+
else toast.error(`Conformance issues found (${res.issues.length})`)
|
|
150
|
+
} catch (err) {
|
|
151
|
+
const msg = err instanceof Error ? err.message : 'Conformance failed'
|
|
152
|
+
setConformanceByServer((prev) => ({
|
|
153
|
+
...prev,
|
|
154
|
+
[id]: {
|
|
155
|
+
ok: false,
|
|
156
|
+
toolsCount: 0,
|
|
157
|
+
smokeToolName: null,
|
|
158
|
+
issues: [{ level: 'error', code: 'request_failed', message: msg }],
|
|
159
|
+
timings: { connectMs: 0, listToolsMs: 0, smokeInvokeMs: null },
|
|
160
|
+
},
|
|
161
|
+
}))
|
|
162
|
+
toast.error(msg)
|
|
163
|
+
} finally {
|
|
164
|
+
setConformanceLoading((prev) => ({ ...prev, [id]: false }))
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const openInspector = async (e: React.MouseEvent, id: string) => {
|
|
169
|
+
e.stopPropagation()
|
|
170
|
+
if (inspectorServerId === id) {
|
|
171
|
+
setInspectorServerId(null)
|
|
172
|
+
setInspectorError(null)
|
|
173
|
+
return
|
|
174
|
+
}
|
|
175
|
+
setInspectorServerId(id)
|
|
176
|
+
setInspectorError(null)
|
|
177
|
+
setInvokeResult(null)
|
|
178
|
+
|
|
179
|
+
if (toolsByServer[id]?.length) {
|
|
180
|
+
const first = toolsByServer[id][0]
|
|
181
|
+
setSelectedTool(first.name)
|
|
182
|
+
setArgsJson(buildArgsTemplate(first.inputSchema))
|
|
183
|
+
return
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
setInspectorLoading(true)
|
|
187
|
+
try {
|
|
188
|
+
const tools = await api<McpToolMeta[]>('GET', `/mcp-servers/${id}/tools`)
|
|
189
|
+
setToolsByServer((prev) => ({ ...prev, [id]: Array.isArray(tools) ? tools : [] }))
|
|
190
|
+
const first = Array.isArray(tools) && tools.length > 0 ? tools[0] : null
|
|
191
|
+
setSelectedTool(first?.name || '')
|
|
192
|
+
setArgsJson(first ? buildArgsTemplate(first.inputSchema) : '{}')
|
|
193
|
+
} catch (err) {
|
|
194
|
+
setInspectorError(err instanceof Error ? err.message : 'Failed to load tools')
|
|
195
|
+
setSelectedTool('')
|
|
196
|
+
setArgsJson('{}')
|
|
197
|
+
} finally {
|
|
198
|
+
setInspectorLoading(false)
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const handleToolChange = (toolName: string) => {
|
|
203
|
+
setSelectedTool(toolName)
|
|
204
|
+
setInvokeResult(null)
|
|
205
|
+
const tool = activeTools.find((t) => t.name === toolName)
|
|
206
|
+
setArgsJson(buildArgsTemplate(tool?.inputSchema))
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const handleInvoke = async () => {
|
|
210
|
+
if (!inspectorServerId || !selectedTool) return
|
|
211
|
+
let parsedArgs: Record<string, unknown> = {}
|
|
212
|
+
try {
|
|
213
|
+
parsedArgs = argsJson.trim() ? JSON.parse(argsJson) : {}
|
|
214
|
+
if (!parsedArgs || typeof parsedArgs !== 'object' || Array.isArray(parsedArgs)) {
|
|
215
|
+
setInvokeResult({ ok: false, error: 'Args must be a JSON object.' })
|
|
216
|
+
return
|
|
217
|
+
}
|
|
218
|
+
} catch {
|
|
219
|
+
setInvokeResult({ ok: false, error: 'Args must be valid JSON.' })
|
|
220
|
+
return
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
setInvokeLoading(true)
|
|
224
|
+
setInvokeResult(null)
|
|
225
|
+
try {
|
|
226
|
+
const result = await api<McpInvokeResult>('POST', `/mcp-servers/${inspectorServerId}/invoke`, {
|
|
227
|
+
toolName: selectedTool,
|
|
228
|
+
args: parsedArgs,
|
|
229
|
+
})
|
|
230
|
+
setInvokeResult(result)
|
|
231
|
+
if (result.ok) {
|
|
232
|
+
if (result.isError) toast.error('Tool returned an error')
|
|
233
|
+
else toast.success('Tool invoked successfully')
|
|
234
|
+
} else {
|
|
235
|
+
toast.error(result.error || 'Invocation failed')
|
|
236
|
+
}
|
|
237
|
+
} catch (err) {
|
|
238
|
+
const msg = err instanceof Error ? err.message : 'Invocation failed'
|
|
239
|
+
setInvokeResult({ ok: false, error: msg })
|
|
240
|
+
toast.error(msg)
|
|
241
|
+
} finally {
|
|
242
|
+
setInvokeLoading(false)
|
|
70
243
|
}
|
|
71
244
|
}
|
|
72
245
|
|
|
@@ -84,60 +257,189 @@ export function McpServerList({ inSidebar }: { inSidebar?: boolean }) {
|
|
|
84
257
|
</button>
|
|
85
258
|
</div>
|
|
86
259
|
) : (
|
|
87
|
-
|
|
88
|
-
{
|
|
89
|
-
<
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
260
|
+
<>
|
|
261
|
+
{!inSidebar && inspectorServerId && (
|
|
262
|
+
<div className="mb-4 p-4 rounded-[14px] border border-white/[0.08] bg-surface-2">
|
|
263
|
+
<div className="flex items-center justify-between gap-3 mb-3">
|
|
264
|
+
<div className="min-w-0">
|
|
265
|
+
<h3 className="font-display text-[14px] font-600 text-text truncate">
|
|
266
|
+
MCP Inspector: {activeInspectorServer?.name || inspectorServerId}
|
|
267
|
+
</h3>
|
|
268
|
+
<p className="text-[12px] text-text-3/70">List tools and invoke them with structured JSON args.</p>
|
|
269
|
+
</div>
|
|
270
|
+
<button
|
|
271
|
+
onClick={() => setInspectorServerId(null)}
|
|
272
|
+
className="text-[11px] text-text-3/70 hover:text-text-2 transition-colors"
|
|
273
|
+
>
|
|
274
|
+
Close
|
|
275
|
+
</button>
|
|
276
|
+
</div>
|
|
277
|
+
|
|
278
|
+
{inspectorLoading ? (
|
|
279
|
+
<p className="text-[12px] text-text-3/70">Loading tools...</p>
|
|
280
|
+
) : inspectorError ? (
|
|
281
|
+
<p className="text-[12px] text-red-300">{inspectorError}</p>
|
|
282
|
+
) : (
|
|
283
|
+
<div className="space-y-3">
|
|
284
|
+
{activeConformance && (
|
|
285
|
+
<div className={`rounded-[10px] border p-3 ${activeConformance.ok ? 'border-emerald-400/20 bg-emerald-500/[0.06]' : 'border-amber-400/20 bg-amber-500/[0.06]'}`}>
|
|
286
|
+
<p className={`text-[12px] font-600 mb-1 ${activeConformance.ok ? 'text-emerald-300' : 'text-amber-300'}`}>
|
|
287
|
+
Conformance {activeConformance.ok ? 'passed' : 'issues found'}
|
|
288
|
+
</p>
|
|
289
|
+
<p className="text-[11px] text-text-2/80">
|
|
290
|
+
tools={activeConformance.toolsCount}, smoke={activeConformance.smokeToolName || 'none'}, issues={activeConformance.issues.length}
|
|
291
|
+
</p>
|
|
292
|
+
</div>
|
|
293
|
+
)}
|
|
294
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
|
|
295
|
+
<label className="text-[11px] text-text-3/70 uppercase tracking-[0.08em]">Tool</label>
|
|
296
|
+
<label className="text-[11px] text-text-3/70 uppercase tracking-[0.08em]">Args (JSON)</label>
|
|
297
|
+
<select
|
|
298
|
+
value={selectedTool}
|
|
299
|
+
onChange={(e) => handleToolChange(e.target.value)}
|
|
300
|
+
className="px-3 py-2 rounded-[10px] border border-white/[0.08] bg-bg text-text text-[12px]"
|
|
301
|
+
style={{ fontFamily: 'inherit' }}
|
|
302
|
+
>
|
|
303
|
+
{activeTools.length === 0 && <option value="">No tools available</option>}
|
|
304
|
+
{activeTools.map((tool) => (
|
|
305
|
+
<option key={tool.name} value={tool.name}>{tool.name}</option>
|
|
306
|
+
))}
|
|
307
|
+
</select>
|
|
308
|
+
<textarea
|
|
309
|
+
value={argsJson}
|
|
310
|
+
onChange={(e) => setArgsJson(e.target.value)}
|
|
311
|
+
className="min-h-[96px] px-3 py-2 rounded-[10px] border border-white/[0.08] bg-bg text-text text-[12px] font-mono"
|
|
312
|
+
/>
|
|
313
|
+
</div>
|
|
314
|
+
|
|
315
|
+
{activeToolMeta?.description && (
|
|
316
|
+
<p className="text-[12px] text-text-3/80">{activeToolMeta.description}</p>
|
|
317
|
+
)}
|
|
318
|
+
|
|
319
|
+
<div className="flex items-center gap-2">
|
|
320
|
+
<button
|
|
321
|
+
onClick={handleInvoke}
|
|
322
|
+
disabled={!selectedTool || invokeLoading}
|
|
323
|
+
className="px-3 py-1.5 rounded-[9px] bg-accent-soft text-accent-bright text-[12px] font-600 disabled:opacity-60 disabled:cursor-not-allowed"
|
|
324
|
+
style={{ fontFamily: 'inherit' }}
|
|
325
|
+
>
|
|
326
|
+
{invokeLoading ? 'Running...' : 'Invoke Plugin'} </button>
|
|
327
|
+
<span className="text-[11px] text-text-3/60">Result is captured below with raw payload.</span>
|
|
328
|
+
</div>
|
|
329
|
+
|
|
330
|
+
{invokeResult && (
|
|
331
|
+
<div className={`rounded-[10px] border p-3 ${invokeResult.ok ? 'border-emerald-400/20 bg-emerald-500/[0.06]' : 'border-red-400/20 bg-red-500/[0.06]'}`}>
|
|
332
|
+
<p className={`text-[12px] font-600 mb-2 ${invokeResult.ok ? 'text-emerald-300' : 'text-red-300'}`}>
|
|
333
|
+
{invokeResult.ok ? (invokeResult.isError ? 'Invocation returned MCP error' : 'Invocation succeeded') : 'Invocation failed'}
|
|
334
|
+
</p>
|
|
335
|
+
<pre className="text-[11px] text-text-2/90 font-mono whitespace-pre-wrap break-words">
|
|
336
|
+
{invokeResult.ok
|
|
337
|
+
? JSON.stringify({ text: invokeResult.text, isError: invokeResult.isError, result: invokeResult.result }, null, 2)
|
|
338
|
+
: (invokeResult.error || 'Unknown error')}
|
|
339
|
+
</pre>
|
|
340
|
+
</div>
|
|
341
|
+
)}
|
|
108
342
|
</div>
|
|
109
|
-
|
|
110
|
-
|
|
343
|
+
)}
|
|
344
|
+
</div>
|
|
345
|
+
)}
|
|
346
|
+
|
|
347
|
+
<div className={inSidebar ? 'space-y-2' : 'grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-3'}>
|
|
348
|
+
{serverList.map((server) => (
|
|
349
|
+
<div
|
|
350
|
+
key={server.id}
|
|
351
|
+
role="button"
|
|
352
|
+
tabIndex={0}
|
|
353
|
+
onClick={() => handleEdit(server.id)}
|
|
354
|
+
onKeyDown={(e) => {
|
|
355
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
356
|
+
e.preventDefault()
|
|
357
|
+
handleEdit(server.id)
|
|
358
|
+
}
|
|
359
|
+
}}
|
|
360
|
+
className="w-full text-left p-4 rounded-[14px] border border-white/[0.06] bg-surface hover:bg-surface-2 transition-all cursor-pointer"
|
|
361
|
+
>
|
|
362
|
+
<div className="flex items-center justify-between mb-1">
|
|
363
|
+
<div className="flex items-center gap-2 min-w-0">
|
|
364
|
+
{(() => {
|
|
365
|
+
const s = statuses[server.id]
|
|
366
|
+
if (!s || s.loading) return <span className="w-2 h-2 rounded-full bg-yellow-400 animate-pulse shrink-0" title="Testing..." />
|
|
367
|
+
if (s.ok) return (
|
|
368
|
+
<span className="flex items-center gap-1 shrink-0">
|
|
369
|
+
<span className="w-2 h-2 rounded-full bg-emerald-400 shrink-0" />
|
|
370
|
+
{s.tools && <span className="text-[10px] text-emerald-400/80 font-mono">{s.tools.length} tools</span>}
|
|
371
|
+
</span>
|
|
372
|
+
)
|
|
373
|
+
return <span className="w-2 h-2 rounded-full bg-red-400 shrink-0" title={s.error || 'Failed'} />
|
|
374
|
+
})()}
|
|
375
|
+
<span className="font-display text-[14px] font-600 text-text truncate">{server.name}</span>
|
|
376
|
+
</div>
|
|
377
|
+
<div className="flex items-center gap-2 shrink-0 ml-2">
|
|
378
|
+
{!inSidebar && (
|
|
379
|
+
<>
|
|
380
|
+
<button
|
|
381
|
+
onClick={(e) => openInspector(e, server.id)}
|
|
382
|
+
className={`text-[10px] font-600 px-2 py-0.5 rounded-[7px] transition-colors ${
|
|
383
|
+
inspectorServerId === server.id
|
|
384
|
+
? 'bg-accent-soft text-accent-bright'
|
|
385
|
+
: 'bg-white/[0.06] text-text-3 hover:text-text-2'
|
|
386
|
+
}`}
|
|
387
|
+
title="Open MCP inspector"
|
|
388
|
+
>
|
|
389
|
+
Inspect
|
|
390
|
+
</button>
|
|
391
|
+
<button
|
|
392
|
+
onClick={(e) => handleConformance(e, server.id)}
|
|
393
|
+
className={`text-[10px] font-600 px-2 py-0.5 rounded-[7px] transition-colors ${
|
|
394
|
+
conformanceByServer[server.id]?.ok
|
|
395
|
+
? 'bg-emerald-500/10 text-emerald-300'
|
|
396
|
+
: conformanceByServer[server.id]
|
|
397
|
+
? 'bg-amber-500/10 text-amber-300'
|
|
398
|
+
: 'bg-white/[0.06] text-text-3 hover:text-text-2'
|
|
399
|
+
}`}
|
|
400
|
+
title="Run MCP conformance checks"
|
|
401
|
+
>
|
|
402
|
+
{conformanceLoading[server.id] ? 'Checking...' : 'Conformance'}
|
|
403
|
+
</button>
|
|
404
|
+
<button
|
|
405
|
+
onClick={(e) => handleRetest(e, server.id)}
|
|
406
|
+
className="text-text-3/40 hover:text-text-2 transition-colors p-0.5"
|
|
407
|
+
title="Re-test connection"
|
|
408
|
+
>
|
|
409
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
410
|
+
<path d="M21 2v6h-6M3 12a9 9 0 0 1 15-6.7L21 8M3 22v-6h6M21 12a9 9 0 0 1-15 6.7L3 16" />
|
|
411
|
+
</svg>
|
|
412
|
+
</button>
|
|
413
|
+
</>
|
|
414
|
+
)}
|
|
415
|
+
<span className={`text-[10px] font-mono px-2 py-0.5 rounded-full ${transportColors[server.transport] || 'bg-white/10 text-text-3'}`}>
|
|
416
|
+
{server.transport}
|
|
417
|
+
</span>
|
|
111
418
|
<button
|
|
112
|
-
onClick={(e) =>
|
|
113
|
-
className="text-text-3/40 hover:text-
|
|
114
|
-
title="
|
|
419
|
+
onClick={(e) => handleDelete(e, server.id)}
|
|
420
|
+
className="text-text-3/40 hover:text-red-400 transition-colors p-0.5"
|
|
421
|
+
title="Delete server"
|
|
115
422
|
>
|
|
116
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round"
|
|
117
|
-
<path d="
|
|
423
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
|
424
|
+
<path d="M3 6h18M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" />
|
|
118
425
|
</svg>
|
|
119
426
|
</button>
|
|
120
|
-
|
|
121
|
-
<span className={`text-[10px] font-mono px-2 py-0.5 rounded-full ${transportColors[server.transport] || 'bg-white/10 text-text-3'}`}>
|
|
122
|
-
{server.transport}
|
|
123
|
-
</span>
|
|
124
|
-
<button
|
|
125
|
-
onClick={(e) => handleDelete(e, server.id)}
|
|
126
|
-
className="text-text-3/40 hover:text-red-400 transition-colors p-0.5"
|
|
127
|
-
title="Delete server"
|
|
128
|
-
>
|
|
129
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
|
130
|
-
<path d="M3 6h18M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" />
|
|
131
|
-
</svg>
|
|
132
|
-
</button>
|
|
427
|
+
</div>
|
|
133
428
|
</div>
|
|
429
|
+
<p className="text-[12px] text-text-3/60 font-mono truncate">
|
|
430
|
+
{server.transport === 'stdio' ? server.command : server.url}
|
|
431
|
+
</p>
|
|
432
|
+
{conformanceByServer[server.id] && (
|
|
433
|
+
<p className={`mt-1 text-[11px] ${conformanceByServer[server.id].ok ? 'text-emerald-300/80' : 'text-amber-300/80'}`}>
|
|
434
|
+
{conformanceByServer[server.id].ok
|
|
435
|
+
? `Conformance passed (${conformanceByServer[server.id].toolsCount} tools)`
|
|
436
|
+
: `Conformance issues: ${conformanceByServer[server.id].issues.length}`}
|
|
437
|
+
</p>
|
|
438
|
+
)}
|
|
134
439
|
</div>
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
</button>
|
|
139
|
-
))}
|
|
140
|
-
</div>
|
|
440
|
+
))}
|
|
441
|
+
</div>
|
|
442
|
+
</>
|
|
141
443
|
)}
|
|
142
444
|
</div>
|
|
143
445
|
)
|
|
@@ -4,6 +4,7 @@ import { useState } from 'react'
|
|
|
4
4
|
import { useAppStore } from '@/stores/use-app-store'
|
|
5
5
|
import { BottomSheet } from '@/components/shared/bottom-sheet'
|
|
6
6
|
import { api } from '@/lib/api-client'
|
|
7
|
+
import { toast } from 'sonner'
|
|
7
8
|
import type { McpServerConfig, McpTransport } from '@/types'
|
|
8
9
|
|
|
9
10
|
function McpServerForm({ editing, onClose, loadMcpServers }: {
|
|
@@ -58,20 +59,31 @@ function McpServerForm({ editing, onClose, loadMcpServers }: {
|
|
|
58
59
|
} else {
|
|
59
60
|
data.url = url.trim()
|
|
60
61
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
try {
|
|
63
|
+
if (editing) {
|
|
64
|
+
await api('PUT', `/mcp-servers/${editing.id}`, data)
|
|
65
|
+
toast.success('MCP server updated')
|
|
66
|
+
} else {
|
|
67
|
+
await api('POST', '/mcp-servers', data)
|
|
68
|
+
toast.success('MCP server created')
|
|
69
|
+
}
|
|
70
|
+
await loadMcpServers()
|
|
71
|
+
onClose()
|
|
72
|
+
} catch (err: unknown) {
|
|
73
|
+
toast.error(err instanceof Error ? err.message : 'Failed to save server')
|
|
65
74
|
}
|
|
66
|
-
await loadMcpServers()
|
|
67
|
-
onClose()
|
|
68
75
|
}
|
|
69
76
|
|
|
70
77
|
const handleDelete = async () => {
|
|
71
|
-
if (editing)
|
|
78
|
+
if (!editing) return
|
|
79
|
+
if (!confirm('Delete this MCP server?')) return
|
|
80
|
+
try {
|
|
72
81
|
await api('DELETE', `/mcp-servers/${editing.id}`)
|
|
82
|
+
toast.success('MCP server deleted')
|
|
73
83
|
await loadMcpServers()
|
|
74
84
|
onClose()
|
|
85
|
+
} catch (err: unknown) {
|
|
86
|
+
toast.error(err instanceof Error ? err.message : 'Failed to delete server')
|
|
75
87
|
}
|
|
76
88
|
}
|
|
77
89
|
|
|
@@ -82,8 +94,12 @@ function McpServerForm({ editing, onClose, loadMcpServers }: {
|
|
|
82
94
|
try {
|
|
83
95
|
const result = await api<{ ok: boolean; tools?: string[]; error?: string }>('POST', `/mcp-servers/${editing.id}/test`)
|
|
84
96
|
setTestResult(result)
|
|
97
|
+
if (result.ok) toast.success('Connection test passed')
|
|
98
|
+
else toast.error(result.error || 'Connection test failed')
|
|
85
99
|
} catch (err: unknown) {
|
|
86
|
-
|
|
100
|
+
const msg = err instanceof Error ? err.message : 'Test failed'
|
|
101
|
+
setTestResult({ ok: false, error: msg })
|
|
102
|
+
toast.error(msg)
|
|
87
103
|
}
|
|
88
104
|
setTesting(false)
|
|
89
105
|
}
|
|
@@ -99,7 +115,7 @@ function McpServerForm({ editing, onClose, loadMcpServers }: {
|
|
|
99
115
|
<h2 className="font-display text-[28px] font-700 tracking-[-0.03em] mb-2">
|
|
100
116
|
{editing ? 'Edit MCP Server' : 'New MCP Server'}
|
|
101
117
|
</h2>
|
|
102
|
-
<p className="text-[14px] text-text-3">Configure an MCP server to provide
|
|
118
|
+
<p className="text-[14px] text-text-3">Configure an MCP server to provide plugins to agents</p>
|
|
103
119
|
</div>
|
|
104
120
|
|
|
105
121
|
<div className="mb-8">
|