@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.
Files changed (203) hide show
  1. package/README.md +82 -39
  2. package/next.config.ts +31 -6
  3. package/package.json +3 -2
  4. package/src/app/api/agents/[id]/thread/route.ts +1 -0
  5. package/src/app/api/agents/route.ts +19 -5
  6. package/src/app/api/approvals/route.ts +22 -0
  7. package/src/app/api/chatrooms/[id]/chat/route.ts +4 -0
  8. package/src/app/api/clawhub/install/route.ts +2 -2
  9. package/src/app/api/eval/run/route.ts +37 -0
  10. package/src/app/api/eval/scenarios/route.ts +24 -0
  11. package/src/app/api/eval/suite/route.ts +29 -0
  12. package/src/app/api/mcp-servers/[id]/conformance/route.ts +26 -0
  13. package/src/app/api/mcp-servers/[id]/invoke/route.ts +81 -0
  14. package/src/app/api/memory/graph/route.ts +46 -0
  15. package/src/app/api/memory/route.ts +36 -5
  16. package/src/app/api/notifications/route.ts +3 -0
  17. package/src/app/api/plugins/install/route.ts +57 -5
  18. package/src/app/api/plugins/marketplace/route.ts +73 -22
  19. package/src/app/api/plugins/route.ts +61 -1
  20. package/src/app/api/plugins/ui/route.ts +34 -0
  21. package/src/app/api/sessions/[id]/checkpoints/route.ts +31 -0
  22. package/src/app/api/sessions/[id]/restore/route.ts +36 -0
  23. package/src/app/api/settings/route.ts +62 -0
  24. package/src/app/api/setup/doctor/route.ts +22 -5
  25. package/src/app/api/souls/[id]/route.ts +65 -0
  26. package/src/app/api/souls/route.ts +70 -0
  27. package/src/app/api/tasks/[id]/approve/route.ts +4 -3
  28. package/src/app/api/tasks/[id]/route.ts +16 -3
  29. package/src/app/api/tasks/route.ts +10 -2
  30. package/src/app/api/usage/route.ts +9 -2
  31. package/src/app/globals.css +27 -0
  32. package/src/app/page.tsx +10 -5
  33. package/src/cli/index.js +37 -0
  34. package/src/components/activity/activity-feed.tsx +9 -2
  35. package/src/components/agents/agent-avatar.tsx +5 -1
  36. package/src/components/agents/agent-card.tsx +55 -9
  37. package/src/components/agents/agent-sheet.tsx +112 -34
  38. package/src/components/agents/inspector-panel.tsx +1 -1
  39. package/src/components/agents/soul-library-picker.tsx +84 -13
  40. package/src/components/auth/access-key-gate.tsx +63 -54
  41. package/src/components/auth/user-picker.tsx +37 -32
  42. package/src/components/chat/activity-moment.tsx +2 -0
  43. package/src/components/chat/chat-area.tsx +11 -0
  44. package/src/components/chat/chat-header.tsx +69 -25
  45. package/src/components/chat/chat-tool-toggles.tsx +2 -2
  46. package/src/components/chat/checkpoint-timeline.tsx +112 -0
  47. package/src/components/chat/code-block.tsx +3 -1
  48. package/src/components/chat/exec-approval-card.tsx +8 -1
  49. package/src/components/chat/message-bubble.tsx +164 -4
  50. package/src/components/chat/message-list.tsx +46 -4
  51. package/src/components/chat/session-approval-card.tsx +80 -0
  52. package/src/components/chat/session-debug-panel.tsx +106 -84
  53. package/src/components/chat/streaming-bubble.tsx +6 -5
  54. package/src/components/chat/task-approval-card.tsx +78 -0
  55. package/src/components/chat/thinking-indicator.tsx +48 -12
  56. package/src/components/chat/tool-call-bubble.tsx +3 -0
  57. package/src/components/chat/tool-request-banner.tsx +39 -20
  58. package/src/components/chatrooms/chatroom-list.tsx +11 -4
  59. package/src/components/chatrooms/chatroom-sheet.tsx +7 -2
  60. package/src/components/connectors/connector-list.tsx +33 -11
  61. package/src/components/connectors/connector-sheet.tsx +37 -7
  62. package/src/components/home/home-view.tsx +54 -24
  63. package/src/components/input/chat-input.tsx +22 -1
  64. package/src/components/knowledge/knowledge-list.tsx +17 -18
  65. package/src/components/knowledge/knowledge-sheet.tsx +9 -5
  66. package/src/components/layout/app-layout.tsx +87 -19
  67. package/src/components/mcp-servers/mcp-server-list.tsx +352 -50
  68. package/src/components/mcp-servers/mcp-server-sheet.tsx +25 -9
  69. package/src/components/memory/memory-browser.tsx +73 -45
  70. package/src/components/memory/memory-graph-view.tsx +203 -0
  71. package/src/components/memory/memory-list.tsx +20 -13
  72. package/src/components/plugins/plugin-list.tsx +214 -60
  73. package/src/components/plugins/plugin-sheet.tsx +119 -24
  74. package/src/components/projects/project-list.tsx +17 -9
  75. package/src/components/providers/provider-list.tsx +21 -6
  76. package/src/components/providers/provider-sheet.tsx +42 -25
  77. package/src/components/runs/run-list.tsx +17 -13
  78. package/src/components/schedules/schedule-card.tsx +10 -3
  79. package/src/components/schedules/schedule-list.tsx +2 -2
  80. package/src/components/schedules/schedule-sheet.tsx +28 -9
  81. package/src/components/secrets/secret-sheet.tsx +7 -2
  82. package/src/components/secrets/secrets-list.tsx +18 -5
  83. package/src/components/sessions/new-session-sheet.tsx +183 -376
  84. package/src/components/sessions/session-card.tsx +10 -2
  85. package/src/components/settings/gateway-connection-panel.tsx +9 -8
  86. package/src/components/shared/command-palette.tsx +13 -5
  87. package/src/components/shared/empty-state.tsx +20 -8
  88. package/src/components/shared/hint-tip.tsx +31 -0
  89. package/src/components/shared/notification-center.tsx +134 -86
  90. package/src/components/shared/profile-sheet.tsx +4 -0
  91. package/src/components/shared/settings/plugin-manager.tsx +360 -135
  92. package/src/components/shared/settings/section-capability-policy.tsx +3 -3
  93. package/src/components/shared/settings/section-runtime-loop.tsx +149 -4
  94. package/src/components/skills/clawhub-browser.tsx +1 -0
  95. package/src/components/skills/skill-list.tsx +31 -12
  96. package/src/components/skills/skill-sheet.tsx +20 -7
  97. package/src/components/tasks/approvals-panel.tsx +224 -0
  98. package/src/components/tasks/task-board.tsx +20 -12
  99. package/src/components/tasks/task-card.tsx +21 -7
  100. package/src/components/tasks/task-column.tsx +4 -3
  101. package/src/components/tasks/task-list.tsx +1 -1
  102. package/src/components/tasks/task-sheet.tsx +130 -1
  103. package/src/components/ui/dialog.tsx +1 -0
  104. package/src/components/ui/sheet.tsx +1 -0
  105. package/src/components/usage/metrics-dashboard.tsx +72 -48
  106. package/src/components/wallets/wallet-panel.tsx +65 -41
  107. package/src/components/wallets/wallet-section.tsx +9 -3
  108. package/src/components/webhooks/webhook-list.tsx +21 -12
  109. package/src/components/webhooks/webhook-sheet.tsx +13 -3
  110. package/src/lib/approval-display.test.ts +45 -0
  111. package/src/lib/approval-display.ts +62 -0
  112. package/src/lib/clipboard.ts +38 -0
  113. package/src/lib/memory.ts +8 -0
  114. package/src/lib/providers/claude-cli.ts +5 -3
  115. package/src/lib/providers/index.ts +67 -21
  116. package/src/lib/runtime-loop.ts +3 -2
  117. package/src/lib/server/approvals.ts +150 -0
  118. package/src/lib/server/chat-execution.ts +319 -74
  119. package/src/lib/server/chatroom-helpers.ts +63 -5
  120. package/src/lib/server/chatroom-orchestration.ts +74 -0
  121. package/src/lib/server/clawhub-client.ts +82 -6
  122. package/src/lib/server/connectors/manager.ts +27 -1
  123. package/src/lib/server/context-manager.ts +132 -50
  124. package/src/lib/server/cost.test.ts +73 -0
  125. package/src/lib/server/cost.ts +165 -34
  126. package/src/lib/server/daemon-state.ts +112 -1
  127. package/src/lib/server/data-dir.ts +18 -1
  128. package/src/lib/server/eval/runner.ts +126 -0
  129. package/src/lib/server/eval/scenarios.ts +218 -0
  130. package/src/lib/server/eval/scorer.ts +96 -0
  131. package/src/lib/server/eval/store.ts +37 -0
  132. package/src/lib/server/eval/types.ts +48 -0
  133. package/src/lib/server/execution-log.ts +12 -8
  134. package/src/lib/server/guardian.ts +34 -0
  135. package/src/lib/server/heartbeat-service.ts +53 -1
  136. package/src/lib/server/integrity-monitor.ts +208 -0
  137. package/src/lib/server/langgraph-checkpoint.ts +10 -0
  138. package/src/lib/server/link-understanding.ts +55 -0
  139. package/src/lib/server/llm-response-cache.test.ts +102 -0
  140. package/src/lib/server/llm-response-cache.ts +227 -0
  141. package/src/lib/server/main-agent-loop.ts +115 -16
  142. package/src/lib/server/main-session.ts +6 -3
  143. package/src/lib/server/mcp-conformance.test.ts +18 -0
  144. package/src/lib/server/mcp-conformance.ts +233 -0
  145. package/src/lib/server/memory-db.ts +193 -19
  146. package/src/lib/server/memory-retrieval.test.ts +56 -0
  147. package/src/lib/server/mmr.ts +73 -0
  148. package/src/lib/server/orchestrator-lg.ts +7 -1
  149. package/src/lib/server/orchestrator.ts +4 -3
  150. package/src/lib/server/plugins.ts +662 -132
  151. package/src/lib/server/process-manager.ts +18 -0
  152. package/src/lib/server/query-expansion.ts +57 -0
  153. package/src/lib/server/queue.ts +280 -11
  154. package/src/lib/server/runtime-settings.ts +9 -0
  155. package/src/lib/server/session-run-manager.test.ts +23 -0
  156. package/src/lib/server/session-run-manager.ts +32 -2
  157. package/src/lib/server/session-tools/canvas.ts +85 -50
  158. package/src/lib/server/session-tools/chatroom.ts +130 -127
  159. package/src/lib/server/session-tools/connector.ts +233 -454
  160. package/src/lib/server/session-tools/context-mgmt.ts +87 -105
  161. package/src/lib/server/session-tools/crud.ts +84 -7
  162. package/src/lib/server/session-tools/delegate.ts +351 -752
  163. package/src/lib/server/session-tools/discovery.ts +198 -0
  164. package/src/lib/server/session-tools/edit_file.ts +82 -0
  165. package/src/lib/server/session-tools/file-send.test.ts +39 -0
  166. package/src/lib/server/session-tools/file.ts +257 -425
  167. package/src/lib/server/session-tools/git.ts +87 -47
  168. package/src/lib/server/session-tools/http.ts +95 -33
  169. package/src/lib/server/session-tools/index.ts +217 -138
  170. package/src/lib/server/session-tools/memory.ts +154 -239
  171. package/src/lib/server/session-tools/monitor.ts +126 -0
  172. package/src/lib/server/session-tools/normalize-tool-args.test.ts +61 -0
  173. package/src/lib/server/session-tools/normalize-tool-args.ts +48 -0
  174. package/src/lib/server/session-tools/openclaw-nodes.ts +82 -99
  175. package/src/lib/server/session-tools/openclaw-workspace.ts +103 -93
  176. package/src/lib/server/session-tools/platform.ts +86 -0
  177. package/src/lib/server/session-tools/plugin-creator.ts +239 -0
  178. package/src/lib/server/session-tools/sample-ui.ts +97 -0
  179. package/src/lib/server/session-tools/sandbox.ts +175 -148
  180. package/src/lib/server/session-tools/schedule.ts +78 -0
  181. package/src/lib/server/session-tools/session-info.ts +104 -410
  182. package/src/lib/server/session-tools/shell-normalize.test.ts +43 -0
  183. package/src/lib/server/session-tools/shell.ts +171 -143
  184. package/src/lib/server/session-tools/subagent.ts +77 -77
  185. package/src/lib/server/session-tools/wallet.ts +182 -106
  186. package/src/lib/server/session-tools/web.ts +181 -327
  187. package/src/lib/server/storage.ts +36 -0
  188. package/src/lib/server/stream-agent-chat.ts +348 -242
  189. package/src/lib/server/task-quality-gate.test.ts +44 -0
  190. package/src/lib/server/task-quality-gate.ts +67 -0
  191. package/src/lib/server/task-validation.test.ts +78 -0
  192. package/src/lib/server/task-validation.ts +67 -2
  193. package/src/lib/server/tool-aliases.ts +68 -0
  194. package/src/lib/server/tool-capability-policy.ts +24 -5
  195. package/src/lib/server/tool-retry.ts +62 -0
  196. package/src/lib/server/transcript-repair.ts +72 -0
  197. package/src/lib/setup-defaults.ts +1 -0
  198. package/src/lib/tasks.ts +7 -1
  199. package/src/lib/tool-definitions.ts +24 -23
  200. package/src/lib/validation/schemas.ts +13 -0
  201. package/src/lib/view-routes.ts +2 -23
  202. package/src/stores/use-app-store.ts +23 -1
  203. 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
- await api('DELETE', `/mcp-servers/${id}`)
59
- await loadMcpServers()
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
- } catch {
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
- <div className={inSidebar ? 'space-y-2' : 'grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-3'}>
88
- {serverList.map((server) => (
89
- <button
90
- key={server.id}
91
- onClick={() => handleEdit(server.id)}
92
- className="w-full text-left p-4 rounded-[14px] border border-white/[0.06] bg-surface hover:bg-surface-2 transition-all cursor-pointer"
93
- >
94
- <div className="flex items-center justify-between mb-1">
95
- <div className="flex items-center gap-2 min-w-0">
96
- {(() => {
97
- const s = statuses[server.id]
98
- if (!s || s.loading) return <span className="w-2 h-2 rounded-full bg-yellow-400 animate-pulse shrink-0" title="Testing..." />
99
- if (s.ok) return (
100
- <span className="flex items-center gap-1 shrink-0">
101
- <span className="w-2 h-2 rounded-full bg-emerald-400 shrink-0" />
102
- {s.tools && <span className="text-[10px] text-emerald-400/80 font-mono">{s.tools.length} tools</span>}
103
- </span>
104
- )
105
- return <span className="w-2 h-2 rounded-full bg-red-400 shrink-0" title={s.error || 'Failed'} />
106
- })()}
107
- <span className="font-display text-[14px] font-600 text-text truncate">{server.name}</span>
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
- <div className="flex items-center gap-2 shrink-0 ml-2">
110
- {!inSidebar && (
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) => handleRetest(e, server.id)}
113
- className="text-text-3/40 hover:text-text-2 transition-colors p-0.5"
114
- title="Re-test connection"
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" strokeLinejoin="round">
117
- <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" />
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
- <p className="text-[12px] text-text-3/60 font-mono truncate">
136
- {server.transport === 'stdio' ? server.command : server.url}
137
- </p>
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
- if (editing) {
62
- await api('PUT', `/mcp-servers/${editing.id}`, data)
63
- } else {
64
- await api('POST', '/mcp-servers', data)
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
- setTestResult({ ok: false, error: err instanceof Error ? err.message : 'Test failed' })
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 tools to agents</p>
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">