@swarmclawai/swarmclaw 0.2.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 (319) hide show
  1. package/README.md +577 -0
  2. package/bin/server-cmd.js +359 -0
  3. package/bin/swarmclaw.js +29 -0
  4. package/bin/swarmclaw.mjs +1504 -0
  5. package/next.config.ts +33 -0
  6. package/package.json +112 -0
  7. package/postcss.config.mjs +7 -0
  8. package/public/branding/swarmclaw-org-avatar.png +0 -0
  9. package/public/branding/swarmclaw-org-avatar.svg +58 -0
  10. package/public/file.svg +1 -0
  11. package/public/globe.svg +1 -0
  12. package/public/next.svg +1 -0
  13. package/public/screenshots/agents.png +0 -0
  14. package/public/screenshots/connectors.png +0 -0
  15. package/public/screenshots/dashboard.png +0 -0
  16. package/public/screenshots/new-session-openclaw.png +0 -0
  17. package/public/screenshots/providers.png +0 -0
  18. package/public/screenshots/schedules.png +0 -0
  19. package/public/screenshots/tasks.png +0 -0
  20. package/public/vercel.svg +1 -0
  21. package/public/window.svg +1 -0
  22. package/src/app/api/agents/[id]/route.ts +30 -0
  23. package/src/app/api/agents/[id]/thread/route.ts +66 -0
  24. package/src/app/api/agents/generate/route.ts +42 -0
  25. package/src/app/api/agents/route.ts +33 -0
  26. package/src/app/api/auth/route.ts +25 -0
  27. package/src/app/api/claude-skills/route.ts +42 -0
  28. package/src/app/api/clawhub/install/route.ts +39 -0
  29. package/src/app/api/clawhub/search/route.ts +11 -0
  30. package/src/app/api/connectors/[id]/route.ts +79 -0
  31. package/src/app/api/connectors/route.ts +60 -0
  32. package/src/app/api/credentials/[id]/route.ts +14 -0
  33. package/src/app/api/credentials/route.ts +31 -0
  34. package/src/app/api/daemon/health-check/route.ts +11 -0
  35. package/src/app/api/daemon/route.ts +22 -0
  36. package/src/app/api/dirs/pick/route.ts +60 -0
  37. package/src/app/api/dirs/route.ts +29 -0
  38. package/src/app/api/documents/[id]/route.ts +47 -0
  39. package/src/app/api/documents/route.ts +93 -0
  40. package/src/app/api/files/serve/route.ts +69 -0
  41. package/src/app/api/generate/info/route.ts +12 -0
  42. package/src/app/api/generate/route.ts +106 -0
  43. package/src/app/api/ip/route.ts +6 -0
  44. package/src/app/api/knowledge/[id]/route.ts +61 -0
  45. package/src/app/api/knowledge/route.ts +48 -0
  46. package/src/app/api/knowledge/upload/route.ts +86 -0
  47. package/src/app/api/logs/route.ts +65 -0
  48. package/src/app/api/mcp-servers/[id]/route.ts +32 -0
  49. package/src/app/api/mcp-servers/[id]/test/route.ts +23 -0
  50. package/src/app/api/mcp-servers/[id]/tools/route.ts +32 -0
  51. package/src/app/api/mcp-servers/route.ts +27 -0
  52. package/src/app/api/memory/[id]/route.ts +126 -0
  53. package/src/app/api/memory/maintenance/route.ts +63 -0
  54. package/src/app/api/memory/route.ts +111 -0
  55. package/src/app/api/memory-images/[filename]/route.ts +36 -0
  56. package/src/app/api/orchestrator/run/route.ts +43 -0
  57. package/src/app/api/plugins/install/route.ts +58 -0
  58. package/src/app/api/plugins/marketplace/route.ts +33 -0
  59. package/src/app/api/plugins/route.ts +21 -0
  60. package/src/app/api/preview-server/route.ts +339 -0
  61. package/src/app/api/providers/[id]/models/route.ts +29 -0
  62. package/src/app/api/providers/[id]/route.ts +34 -0
  63. package/src/app/api/providers/configs/route.ts +7 -0
  64. package/src/app/api/providers/ollama/route.ts +30 -0
  65. package/src/app/api/providers/openclaw/health/route.ts +23 -0
  66. package/src/app/api/providers/route.ts +28 -0
  67. package/src/app/api/runs/[id]/route.ts +9 -0
  68. package/src/app/api/runs/route.ts +13 -0
  69. package/src/app/api/schedules/[id]/route.ts +28 -0
  70. package/src/app/api/schedules/[id]/run/route.ts +104 -0
  71. package/src/app/api/schedules/route.ts +78 -0
  72. package/src/app/api/secrets/[id]/route.ts +29 -0
  73. package/src/app/api/secrets/route.ts +42 -0
  74. package/src/app/api/sessions/[id]/browser/route.ts +13 -0
  75. package/src/app/api/sessions/[id]/chat/route.ts +96 -0
  76. package/src/app/api/sessions/[id]/clear/route.ts +19 -0
  77. package/src/app/api/sessions/[id]/deploy/route.ts +34 -0
  78. package/src/app/api/sessions/[id]/devserver/route.ts +69 -0
  79. package/src/app/api/sessions/[id]/mailbox/route.ts +70 -0
  80. package/src/app/api/sessions/[id]/main-loop/route.ts +94 -0
  81. package/src/app/api/sessions/[id]/messages/route.ts +9 -0
  82. package/src/app/api/sessions/[id]/retry/route.ts +28 -0
  83. package/src/app/api/sessions/[id]/route.ts +103 -0
  84. package/src/app/api/sessions/[id]/stop/route.ts +13 -0
  85. package/src/app/api/sessions/heartbeat/route.ts +26 -0
  86. package/src/app/api/sessions/route.ts +85 -0
  87. package/src/app/api/settings/route.ts +58 -0
  88. package/src/app/api/setup/check-provider/route.ts +326 -0
  89. package/src/app/api/setup/doctor/route.ts +250 -0
  90. package/src/app/api/skills/[id]/route.ts +40 -0
  91. package/src/app/api/skills/import/route.ts +69 -0
  92. package/src/app/api/skills/route.ts +28 -0
  93. package/src/app/api/tasks/[id]/route.ts +102 -0
  94. package/src/app/api/tasks/route.ts +115 -0
  95. package/src/app/api/tts/route.ts +40 -0
  96. package/src/app/api/upload/route.ts +18 -0
  97. package/src/app/api/uploads/[filename]/route.ts +59 -0
  98. package/src/app/api/usage/route.ts +35 -0
  99. package/src/app/api/version/route.ts +81 -0
  100. package/src/app/api/version/update/route.ts +95 -0
  101. package/src/app/api/webhooks/[id]/history/route.ts +13 -0
  102. package/src/app/api/webhooks/[id]/route.ts +204 -0
  103. package/src/app/api/webhooks/route.ts +37 -0
  104. package/src/app/favicon.ico +0 -0
  105. package/src/app/globals.css +370 -0
  106. package/src/app/layout.tsx +52 -0
  107. package/src/app/page.tsx +172 -0
  108. package/src/cli/index.js +1232 -0
  109. package/src/cli/index.test.js +281 -0
  110. package/src/cli/index.ts +1158 -0
  111. package/src/cli/spec.js +284 -0
  112. package/src/components/agents/agent-card.tsx +219 -0
  113. package/src/components/agents/agent-chat-list.tsx +165 -0
  114. package/src/components/agents/agent-list.tsx +110 -0
  115. package/src/components/agents/agent-sheet.tsx +1220 -0
  116. package/src/components/auth/access-key-gate.tsx +248 -0
  117. package/src/components/auth/setup-wizard.tsx +940 -0
  118. package/src/components/auth/user-picker.tsx +88 -0
  119. package/src/components/chat/chat-area.tsx +406 -0
  120. package/src/components/chat/chat-header.tsx +491 -0
  121. package/src/components/chat/chat-tool-toggles.tsx +161 -0
  122. package/src/components/chat/code-block.tsx +146 -0
  123. package/src/components/chat/dev-server-bar.tsx +39 -0
  124. package/src/components/chat/message-bubble.tsx +486 -0
  125. package/src/components/chat/message-list.tsx +299 -0
  126. package/src/components/chat/session-debug-panel.tsx +196 -0
  127. package/src/components/chat/streaming-bubble.tsx +85 -0
  128. package/src/components/chat/thinking-indicator.tsx +26 -0
  129. package/src/components/chat/tool-call-bubble.tsx +438 -0
  130. package/src/components/chat/tool-request-banner.tsx +103 -0
  131. package/src/components/connectors/connector-list.tsx +196 -0
  132. package/src/components/connectors/connector-sheet.tsx +804 -0
  133. package/src/components/input/chat-input.tsx +235 -0
  134. package/src/components/knowledge/knowledge-list.tsx +206 -0
  135. package/src/components/knowledge/knowledge-sheet.tsx +316 -0
  136. package/src/components/layout/app-layout.tsx +1016 -0
  137. package/src/components/layout/daemon-indicator.tsx +56 -0
  138. package/src/components/layout/mobile-header.tsx +31 -0
  139. package/src/components/layout/network-banner.tsx +17 -0
  140. package/src/components/layout/update-banner.tsx +130 -0
  141. package/src/components/logs/log-list.tsx +358 -0
  142. package/src/components/mcp-servers/mcp-server-list.tsx +122 -0
  143. package/src/components/mcp-servers/mcp-server-sheet.tsx +243 -0
  144. package/src/components/memory/memory-card.tsx +63 -0
  145. package/src/components/memory/memory-detail.tsx +339 -0
  146. package/src/components/memory/memory-list.tsx +198 -0
  147. package/src/components/memory/memory-sheet.tsx +70 -0
  148. package/src/components/plugins/plugin-list.tsx +60 -0
  149. package/src/components/plugins/plugin-sheet.tsx +311 -0
  150. package/src/components/providers/provider-list.tsx +96 -0
  151. package/src/components/providers/provider-sheet.tsx +542 -0
  152. package/src/components/runs/run-list.tsx +231 -0
  153. package/src/components/schedules/schedule-card.tsx +63 -0
  154. package/src/components/schedules/schedule-list.tsx +76 -0
  155. package/src/components/schedules/schedule-sheet.tsx +336 -0
  156. package/src/components/secrets/secret-sheet.tsx +180 -0
  157. package/src/components/secrets/secrets-list.tsx +91 -0
  158. package/src/components/sessions/new-session-sheet.tsx +478 -0
  159. package/src/components/sessions/session-card.tsx +144 -0
  160. package/src/components/sessions/session-list.tsx +202 -0
  161. package/src/components/shared/ai-gen-block.tsx +77 -0
  162. package/src/components/shared/avatar.tsx +48 -0
  163. package/src/components/shared/bottom-sheet.tsx +30 -0
  164. package/src/components/shared/confirm-dialog.tsx +47 -0
  165. package/src/components/shared/connector-platform-icon.tsx +113 -0
  166. package/src/components/shared/dir-browser.tsx +285 -0
  167. package/src/components/shared/dropdown.tsx +55 -0
  168. package/src/components/shared/icon-button.tsx +25 -0
  169. package/src/components/shared/settings/plugin-manager.tsx +207 -0
  170. package/src/components/shared/settings/section-capability-policy.tsx +93 -0
  171. package/src/components/shared/settings/section-embedding.tsx +99 -0
  172. package/src/components/shared/settings/section-heartbeat.tsx +168 -0
  173. package/src/components/shared/settings/section-memory.tsx +77 -0
  174. package/src/components/shared/settings/section-orchestrator.tsx +108 -0
  175. package/src/components/shared/settings/section-providers.tsx +181 -0
  176. package/src/components/shared/settings/section-runtime-loop.tsx +183 -0
  177. package/src/components/shared/settings/section-secrets.tsx +132 -0
  178. package/src/components/shared/settings/section-user-preferences.tsx +24 -0
  179. package/src/components/shared/settings/section-voice.tsx +53 -0
  180. package/src/components/shared/settings/settings-sheet.tsx +88 -0
  181. package/src/components/shared/settings/types.ts +7 -0
  182. package/src/components/shared/settings/utils.ts +13 -0
  183. package/src/components/shared/settings-sheet.tsx +1 -0
  184. package/src/components/shared/skeleton.tsx +19 -0
  185. package/src/components/shared/usage-badge.tsx +28 -0
  186. package/src/components/skills/clawhub-browser.tsx +225 -0
  187. package/src/components/skills/skill-list.tsx +70 -0
  188. package/src/components/skills/skill-sheet.tsx +254 -0
  189. package/src/components/tasks/task-board.tsx +96 -0
  190. package/src/components/tasks/task-card.tsx +179 -0
  191. package/src/components/tasks/task-column.tsx +73 -0
  192. package/src/components/tasks/task-list.tsx +118 -0
  193. package/src/components/tasks/task-sheet.tsx +415 -0
  194. package/src/components/ui/avatar.tsx +109 -0
  195. package/src/components/ui/badge.tsx +48 -0
  196. package/src/components/ui/button.tsx +64 -0
  197. package/src/components/ui/card.tsx +92 -0
  198. package/src/components/ui/dialog.tsx +158 -0
  199. package/src/components/ui/dropdown-menu.tsx +257 -0
  200. package/src/components/ui/input.tsx +21 -0
  201. package/src/components/ui/scroll-area.tsx +58 -0
  202. package/src/components/ui/select.tsx +190 -0
  203. package/src/components/ui/separator.tsx +28 -0
  204. package/src/components/ui/sheet.tsx +143 -0
  205. package/src/components/ui/sonner.tsx +22 -0
  206. package/src/components/ui/textarea.tsx +18 -0
  207. package/src/components/ui/tooltip.tsx +56 -0
  208. package/src/components/usage/usage-list.tsx +105 -0
  209. package/src/components/webhooks/webhook-list.tsx +166 -0
  210. package/src/components/webhooks/webhook-sheet.tsx +402 -0
  211. package/src/hooks/use-auto-resize.ts +20 -0
  212. package/src/hooks/use-media-query.ts +21 -0
  213. package/src/hooks/use-speech-recognition.ts +83 -0
  214. package/src/instrumentation.ts +8 -0
  215. package/src/lib/agents.ts +13 -0
  216. package/src/lib/api-client.ts +100 -0
  217. package/src/lib/chat.ts +60 -0
  218. package/src/lib/memory.ts +42 -0
  219. package/src/lib/openclaw-endpoint.test.ts +48 -0
  220. package/src/lib/openclaw-endpoint.ts +67 -0
  221. package/src/lib/provider-config.ts +13 -0
  222. package/src/lib/providers/anthropic.ts +135 -0
  223. package/src/lib/providers/claude-cli.ts +202 -0
  224. package/src/lib/providers/codex-cli.ts +260 -0
  225. package/src/lib/providers/index.ts +351 -0
  226. package/src/lib/providers/ollama.ts +131 -0
  227. package/src/lib/providers/openai.ts +164 -0
  228. package/src/lib/providers/openclaw.ts +330 -0
  229. package/src/lib/providers/opencode-cli.ts +164 -0
  230. package/src/lib/runtime-loop.ts +15 -0
  231. package/src/lib/schedule-dedupe.test.ts +84 -0
  232. package/src/lib/schedule-dedupe.ts +174 -0
  233. package/src/lib/schedule-name.ts +62 -0
  234. package/src/lib/schedules.ts +16 -0
  235. package/src/lib/server/agent-registry.ts +70 -0
  236. package/src/lib/server/api-routes.test.ts +362 -0
  237. package/src/lib/server/autonomy-contract.ts +200 -0
  238. package/src/lib/server/build-llm.ts +155 -0
  239. package/src/lib/server/capability-router.test.ts +21 -0
  240. package/src/lib/server/capability-router.ts +172 -0
  241. package/src/lib/server/chat-execution.ts +894 -0
  242. package/src/lib/server/clawhub-client.test.ts +161 -0
  243. package/src/lib/server/clawhub-client.ts +26 -0
  244. package/src/lib/server/connectors/connector-routing.test.ts +243 -0
  245. package/src/lib/server/connectors/discord.ts +116 -0
  246. package/src/lib/server/connectors/googlechat.ts +66 -0
  247. package/src/lib/server/connectors/manager.ts +559 -0
  248. package/src/lib/server/connectors/matrix.ts +78 -0
  249. package/src/lib/server/connectors/media.ts +149 -0
  250. package/src/lib/server/connectors/openclaw.test.ts +375 -0
  251. package/src/lib/server/connectors/openclaw.ts +1132 -0
  252. package/src/lib/server/connectors/signal.ts +183 -0
  253. package/src/lib/server/connectors/slack.ts +258 -0
  254. package/src/lib/server/connectors/teams.ts +94 -0
  255. package/src/lib/server/connectors/telegram.ts +221 -0
  256. package/src/lib/server/connectors/types.ts +62 -0
  257. package/src/lib/server/connectors/whatsapp.ts +349 -0
  258. package/src/lib/server/context-manager.ts +232 -0
  259. package/src/lib/server/cost.ts +31 -0
  260. package/src/lib/server/daemon-state.ts +354 -0
  261. package/src/lib/server/data-dir.ts +3 -0
  262. package/src/lib/server/embeddings.ts +111 -0
  263. package/src/lib/server/execution-log.ts +257 -0
  264. package/src/lib/server/gateway/protocol.test.ts +54 -0
  265. package/src/lib/server/gateway/protocol.ts +114 -0
  266. package/src/lib/server/heartbeat-service.ts +366 -0
  267. package/src/lib/server/knowledge-db.test.ts +441 -0
  268. package/src/lib/server/logger.ts +47 -0
  269. package/src/lib/server/main-agent-loop.ts +1017 -0
  270. package/src/lib/server/mcp-client.test.ts +342 -0
  271. package/src/lib/server/mcp-client.ts +130 -0
  272. package/src/lib/server/memory-db.ts +1078 -0
  273. package/src/lib/server/memory-graph.test.ts +153 -0
  274. package/src/lib/server/memory-graph.ts +138 -0
  275. package/src/lib/server/openclaw-health.ts +245 -0
  276. package/src/lib/server/orchestrator-lg.ts +431 -0
  277. package/src/lib/server/orchestrator.ts +364 -0
  278. package/src/lib/server/playwright-proxy.mjs +70 -0
  279. package/src/lib/server/plugins.ts +229 -0
  280. package/src/lib/server/process-manager.ts +327 -0
  281. package/src/lib/server/provider-health.ts +113 -0
  282. package/src/lib/server/queue.ts +859 -0
  283. package/src/lib/server/runtime-settings.ts +119 -0
  284. package/src/lib/server/scheduler.ts +196 -0
  285. package/src/lib/server/session-mailbox.ts +129 -0
  286. package/src/lib/server/session-run-manager.ts +512 -0
  287. package/src/lib/server/session-tools/connector.ts +124 -0
  288. package/src/lib/server/session-tools/context-mgmt.ts +103 -0
  289. package/src/lib/server/session-tools/context.ts +114 -0
  290. package/src/lib/server/session-tools/crud.ts +673 -0
  291. package/src/lib/server/session-tools/delegate.ts +708 -0
  292. package/src/lib/server/session-tools/file.ts +264 -0
  293. package/src/lib/server/session-tools/index.ts +164 -0
  294. package/src/lib/server/session-tools/memory.ts +230 -0
  295. package/src/lib/server/session-tools/session-info.ts +422 -0
  296. package/src/lib/server/session-tools/session-tools-wiring.test.ts +166 -0
  297. package/src/lib/server/session-tools/shell.ts +171 -0
  298. package/src/lib/server/session-tools/web.ts +408 -0
  299. package/src/lib/server/session-tools.ts +9 -0
  300. package/src/lib/server/skills-normalize.ts +130 -0
  301. package/src/lib/server/storage-mcp.test.ts +161 -0
  302. package/src/lib/server/storage.ts +670 -0
  303. package/src/lib/server/stream-agent-chat.ts +571 -0
  304. package/src/lib/server/task-reports.ts +122 -0
  305. package/src/lib/server/task-result.ts +161 -0
  306. package/src/lib/server/task-validation.test.ts +27 -0
  307. package/src/lib/server/task-validation.ts +90 -0
  308. package/src/lib/server/tool-capability-policy.test.ts +58 -0
  309. package/src/lib/server/tool-capability-policy.ts +262 -0
  310. package/src/lib/sessions.ts +68 -0
  311. package/src/lib/tasks.ts +20 -0
  312. package/src/lib/tts.ts +42 -0
  313. package/src/lib/upload.ts +10 -0
  314. package/src/lib/utils.ts +6 -0
  315. package/src/proxy.ts +43 -0
  316. package/src/stores/use-app-store.ts +468 -0
  317. package/src/stores/use-chat-store.ts +323 -0
  318. package/src/types/index.ts +621 -0
  319. package/tsconfig.json +34 -0
@@ -0,0 +1,342 @@
1
+ import { describe, it } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import { z } from 'zod'
4
+ import {
5
+ jsonSchemaToZod,
6
+ sanitizeName,
7
+ mcpToolsToLangChain,
8
+ disconnectMcpServer,
9
+ connectMcpServer,
10
+ } from './mcp-client.ts'
11
+
12
+ /* ============================================================
13
+ * 1. sanitizeName
14
+ * ============================================================ */
15
+
16
+ describe('sanitizeName', () => {
17
+ it('strips spaces, hyphens, dots → underscores', () => {
18
+ assert.equal(sanitizeName('my-server.name here'), 'my_server_name_here')
19
+ })
20
+
21
+ it('preserves alphanumeric and underscores', () => {
22
+ assert.equal(sanitizeName('abc_123_XYZ'), 'abc_123_XYZ')
23
+ })
24
+
25
+ it('empty string stays empty', () => {
26
+ assert.equal(sanitizeName(''), '')
27
+ })
28
+
29
+ it('replaces all special characters', () => {
30
+ assert.equal(sanitizeName('a@b#c$d%e'), 'a_b_c_d_e')
31
+ })
32
+ })
33
+
34
+ /* ============================================================
35
+ * 2. jsonSchemaToZod
36
+ * ============================================================ */
37
+
38
+ describe('jsonSchemaToZod', () => {
39
+ it('empty/null schema returns empty z.object', () => {
40
+ const s1 = jsonSchemaToZod(null)
41
+ assert.deepEqual(s1.parse({}), {})
42
+
43
+ const s2 = jsonSchemaToZod(undefined)
44
+ assert.deepEqual(s2.parse({}), {})
45
+
46
+ const s3 = jsonSchemaToZod({})
47
+ assert.deepEqual(s3.parse({}), {})
48
+ })
49
+
50
+ it('simple string property', () => {
51
+ const schema = jsonSchemaToZod({
52
+ type: 'object',
53
+ properties: { name: { type: 'string' } },
54
+ required: ['name'],
55
+ })
56
+ assert.deepEqual(schema.parse({ name: 'hello' }), { name: 'hello' })
57
+
58
+ const bad = schema.safeParse({ name: 123 })
59
+ assert.equal(bad.success, false)
60
+ })
61
+
62
+ it('simple number property', () => {
63
+ const schema = jsonSchemaToZod({
64
+ type: 'object',
65
+ properties: { count: { type: 'number' } },
66
+ required: ['count'],
67
+ })
68
+ assert.deepEqual(schema.parse({ count: 42 }), { count: 42 })
69
+ })
70
+
71
+ it('integer maps to z.number()', () => {
72
+ const schema = jsonSchemaToZod({
73
+ type: 'object',
74
+ properties: { age: { type: 'integer' } },
75
+ required: ['age'],
76
+ })
77
+ assert.deepEqual(schema.parse({ age: 25 }), { age: 25 })
78
+ })
79
+
80
+ it('boolean property', () => {
81
+ const schema = jsonSchemaToZod({
82
+ type: 'object',
83
+ properties: { active: { type: 'boolean' } },
84
+ required: ['active'],
85
+ })
86
+ assert.deepEqual(schema.parse({ active: true }), { active: true })
87
+ })
88
+
89
+ it('required vs optional properties', () => {
90
+ const schema = jsonSchemaToZod({
91
+ type: 'object',
92
+ properties: {
93
+ name: { type: 'string' },
94
+ age: { type: 'number' },
95
+ },
96
+ required: ['name'],
97
+ })
98
+
99
+ // name required, age optional
100
+ assert.deepEqual(schema.parse({ name: 'Alice' }), { name: 'Alice' })
101
+
102
+ const bad = schema.safeParse({})
103
+ assert.equal(bad.success, false)
104
+
105
+ // with both fields
106
+ assert.deepEqual(schema.parse({ name: 'Alice', age: 30 }), { name: 'Alice', age: 30 })
107
+ })
108
+
109
+ it('nested object schema', () => {
110
+ const schema = jsonSchemaToZod({
111
+ type: 'object',
112
+ properties: {
113
+ address: {
114
+ type: 'object',
115
+ properties: {
116
+ city: { type: 'string' },
117
+ zip: { type: 'string' },
118
+ },
119
+ required: ['city'],
120
+ },
121
+ },
122
+ required: ['address'],
123
+ })
124
+ const result = schema.parse({ address: { city: 'NYC' } })
125
+ assert.deepEqual(result, { address: { city: 'NYC' } })
126
+ })
127
+
128
+ it('array properties', () => {
129
+ const schema = jsonSchemaToZod({
130
+ type: 'object',
131
+ properties: {
132
+ tags: { type: 'array' },
133
+ },
134
+ required: ['tags'],
135
+ })
136
+ assert.deepEqual(schema.parse({ tags: ['a', 'b'] }), { tags: ['a', 'b'] })
137
+ })
138
+
139
+ it('unknown type falls through to z.any()', () => {
140
+ const schema = jsonSchemaToZod({
141
+ type: 'object',
142
+ properties: {
143
+ data: { type: 'fancyType' },
144
+ },
145
+ required: ['data'],
146
+ })
147
+ // z.any() accepts anything
148
+ assert.deepEqual(schema.parse({ data: 'whatever' }), { data: 'whatever' })
149
+ assert.deepEqual(schema.parse({ data: 42 }), { data: 42 })
150
+ assert.deepEqual(schema.parse({ data: null }), { data: null })
151
+ })
152
+
153
+ it('schema with required array properly marks fields', () => {
154
+ const schema = jsonSchemaToZod({
155
+ type: 'object',
156
+ properties: {
157
+ a: { type: 'string' },
158
+ b: { type: 'string' },
159
+ c: { type: 'string' },
160
+ },
161
+ required: ['a', 'c'],
162
+ })
163
+ // a and c required, b optional
164
+ const result = schema.parse({ a: 'x', c: 'z' })
165
+ assert.deepEqual(result, { a: 'x', c: 'z' })
166
+
167
+ const bad = schema.safeParse({ a: 'x' }) // missing c
168
+ assert.equal(bad.success, false)
169
+ })
170
+
171
+ it('no properties key returns empty object schema', () => {
172
+ const schema = jsonSchemaToZod({ type: 'object' })
173
+ assert.deepEqual(schema.parse({}), {})
174
+ })
175
+
176
+ it('prop with no type returns z.any()', () => {
177
+ const schema = jsonSchemaToZod({
178
+ type: 'object',
179
+ properties: {
180
+ mystery: {},
181
+ },
182
+ required: ['mystery'],
183
+ })
184
+ assert.deepEqual(schema.parse({ mystery: 'anything' }), { mystery: 'anything' })
185
+ })
186
+ })
187
+
188
+ /* ============================================================
189
+ * 3. mcpToolsToLangChain
190
+ * ============================================================ */
191
+
192
+ describe('mcpToolsToLangChain', () => {
193
+ function makeMockClient(tools: any[], callToolResult?: any) {
194
+ return {
195
+ listTools: async () => ({ tools }),
196
+ callTool: async (args: any) => callToolResult ?? { content: [] },
197
+ }
198
+ }
199
+
200
+ it('prefixes tool names with mcp_{sanitized_server}_{tool_name}', async () => {
201
+ const client = makeMockClient([
202
+ { name: 'read_file', description: 'Reads a file', inputSchema: { type: 'object', properties: {} } },
203
+ ])
204
+ const tools = await mcpToolsToLangChain(client, 'my-server')
205
+ assert.equal(tools.length, 1)
206
+ assert.equal(tools[0].name, 'mcp_my_server_read_file')
207
+ })
208
+
209
+ it('passes tool descriptions through', async () => {
210
+ const client = makeMockClient([
211
+ { name: 'tool1', description: 'My cool tool', inputSchema: { type: 'object', properties: {} } },
212
+ ])
213
+ const tools = await mcpToolsToLangChain(client, 'srv')
214
+ assert.equal(tools[0].description, 'My cool tool')
215
+ })
216
+
217
+ it('uses default description when none provided', async () => {
218
+ const client = makeMockClient([
219
+ { name: 'tool1', inputSchema: { type: 'object', properties: {} } },
220
+ ])
221
+ const tools = await mcpToolsToLangChain(client, 'srv')
222
+ assert.equal(tools[0].description, 'MCP tool: tool1')
223
+ })
224
+
225
+ it('tool execution calls client.callTool with correct args', async () => {
226
+ let captured: any = null
227
+ const client = {
228
+ listTools: async () => ({
229
+ tools: [
230
+ { name: 'greet', description: 'Greets', inputSchema: { type: 'object', properties: { name: { type: 'string' } }, required: ['name'] } },
231
+ ],
232
+ }),
233
+ callTool: async (args: any) => {
234
+ captured = args
235
+ return { content: [{ type: 'text', text: 'Hello!' }] }
236
+ },
237
+ }
238
+ const tools = await mcpToolsToLangChain(client, 'test')
239
+ const result = await tools[0].invoke({ name: 'World' })
240
+ assert.deepEqual(captured, { name: 'greet', arguments: { name: 'World' } })
241
+ })
242
+
243
+ it('joins text content parts', async () => {
244
+ const client = makeMockClient(
245
+ [{ name: 't', description: 'd', inputSchema: { type: 'object', properties: {} } }],
246
+ { content: [{ type: 'text', text: 'line1' }, { type: 'text', text: 'line2' }, { type: 'image', data: 'x' }] }
247
+ )
248
+ const tools = await mcpToolsToLangChain(client, 'srv')
249
+ const result = await tools[0].invoke({})
250
+ assert.equal(result, 'line1\nline2')
251
+ })
252
+
253
+ it('returns (no output) when no text content', async () => {
254
+ const client = makeMockClient(
255
+ [{ name: 't', description: 'd', inputSchema: { type: 'object', properties: {} } }],
256
+ { content: [{ type: 'image', data: 'x' }] }
257
+ )
258
+ const tools = await mcpToolsToLangChain(client, 'srv')
259
+ const result = await tools[0].invoke({})
260
+ assert.equal(result, '(no output)')
261
+ })
262
+
263
+ it('returns (no output) when content is empty', async () => {
264
+ const client = makeMockClient(
265
+ [{ name: 't', description: 'd', inputSchema: { type: 'object', properties: {} } }],
266
+ { content: [] }
267
+ )
268
+ const tools = await mcpToolsToLangChain(client, 'srv')
269
+ const result = await tools[0].invoke({})
270
+ assert.equal(result, '(no output)')
271
+ })
272
+
273
+ it('returns (no output) when content is undefined', async () => {
274
+ const client = makeMockClient(
275
+ [{ name: 't', description: 'd', inputSchema: { type: 'object', properties: {} } }],
276
+ {}
277
+ )
278
+ const tools = await mcpToolsToLangChain(client, 'srv')
279
+ const result = await tools[0].invoke({})
280
+ assert.equal(result, '(no output)')
281
+ })
282
+
283
+ it('handles multiple tools', async () => {
284
+ const client = makeMockClient([
285
+ { name: 'a', description: 'Tool A', inputSchema: { type: 'object', properties: {} } },
286
+ { name: 'b', description: 'Tool B', inputSchema: { type: 'object', properties: {} } },
287
+ { name: 'c', description: 'Tool C', inputSchema: { type: 'object', properties: {} } },
288
+ ])
289
+ const tools = await mcpToolsToLangChain(client, 'x')
290
+ assert.equal(tools.length, 3)
291
+ assert.equal(tools[0].name, 'mcp_x_a')
292
+ assert.equal(tools[1].name, 'mcp_x_b')
293
+ assert.equal(tools[2].name, 'mcp_x_c')
294
+ })
295
+ })
296
+
297
+ /* ============================================================
298
+ * 4. disconnectMcpServer
299
+ * ============================================================ */
300
+
301
+ describe('disconnectMcpServer', () => {
302
+ it('calls close on both client and transport', async () => {
303
+ let clientClosed = false
304
+ let transportClosed = false
305
+ const client = { close: async () => { clientClosed = true } }
306
+ const transport = { close: async () => { transportClosed = true } }
307
+ await disconnectMcpServer(client, transport)
308
+ assert.equal(clientClosed, true)
309
+ assert.equal(transportClosed, true)
310
+ })
311
+
312
+ it('does not throw if client.close fails', async () => {
313
+ const client = { close: async () => { throw new Error('boom') } }
314
+ const transport = { close: async () => {} }
315
+ await assert.doesNotReject(() => disconnectMcpServer(client, transport))
316
+ })
317
+
318
+ it('does not throw if transport.close fails', async () => {
319
+ const client = { close: async () => {} }
320
+ const transport = { close: async () => { throw new Error('boom') } }
321
+ await assert.doesNotReject(() => disconnectMcpServer(client, transport))
322
+ })
323
+
324
+ it('does not throw if both close fail', async () => {
325
+ const client = { close: async () => { throw new Error('a') } }
326
+ const transport = { close: async () => { throw new Error('b') } }
327
+ await assert.doesNotReject(() => disconnectMcpServer(client, transport))
328
+ })
329
+ })
330
+
331
+ /* ============================================================
332
+ * 5. connectMcpServer
333
+ * ============================================================ */
334
+
335
+ describe('connectMcpServer', () => {
336
+ it('throws on unsupported transport type', async () => {
337
+ await assert.rejects(
338
+ () => connectMcpServer({ transport: 'websocket' as any, name: 'test', id: 'x', createdAt: 0, updatedAt: 0 }),
339
+ { message: 'Unsupported MCP transport: websocket' }
340
+ )
341
+ })
342
+ })
@@ -0,0 +1,130 @@
1
+ import { z } from 'zod'
2
+ import { tool, type StructuredToolInterface } from '@langchain/core/tools'
3
+ import type { McpServerConfig } from '@/types'
4
+
5
+ /* ---------- JSON Schema → Zod helpers ---------- */
6
+
7
+ function jsonSchemaTypeToZod(prop: any): z.ZodTypeAny {
8
+ if (!prop || !prop.type) return z.any()
9
+ switch (prop.type) {
10
+ case 'string': return z.string()
11
+ case 'number':
12
+ case 'integer': return z.number()
13
+ case 'boolean': return z.boolean()
14
+ case 'array': return z.array(z.any())
15
+ case 'object': return jsonSchemaToZod(prop)
16
+ default: return z.any()
17
+ }
18
+ }
19
+
20
+ function jsonSchemaToZod(schema: any): z.ZodObject<any> {
21
+ if (!schema || schema.type !== 'object' || !schema.properties) {
22
+ return z.object({})
23
+ }
24
+ const shape: Record<string, z.ZodTypeAny> = {}
25
+ const required = new Set<string>(schema.required ?? [])
26
+ for (const [key, prop] of Object.entries(schema.properties)) {
27
+ const base = jsonSchemaTypeToZod(prop as any)
28
+ shape[key] = required.has(key) ? base : base.optional()
29
+ }
30
+ return z.object(shape)
31
+ }
32
+
33
+ /* ---------- Sanitize server name for tool naming ---------- */
34
+
35
+ function sanitizeName(name: string): string {
36
+ return name.replace(/[^a-zA-Z0-9_]/g, '_')
37
+ }
38
+
39
+ /* ---------- Connect to an MCP server ---------- */
40
+
41
+ export async function connectMcpServer(
42
+ config: McpServerConfig
43
+ ): Promise<{ client: any; transport: any }> {
44
+ const { Client } = await import('@modelcontextprotocol/sdk/client/index.js')
45
+
46
+ let transport: any
47
+
48
+ if (config.transport === 'stdio') {
49
+ const { StdioClientTransport } = await import(
50
+ '@modelcontextprotocol/sdk/client/stdio.js'
51
+ )
52
+ transport = new StdioClientTransport({
53
+ command: config.command!,
54
+ args: config.args ?? [],
55
+ env: { ...process.env, ...config.env } as Record<string, string>,
56
+ })
57
+ } else if (config.transport === 'sse') {
58
+ const { SSEClientTransport } = await import(
59
+ '@modelcontextprotocol/sdk/client/sse.js'
60
+ )
61
+ transport = new SSEClientTransport(new URL(config.url!), {
62
+ requestInit: config.headers
63
+ ? { headers: config.headers }
64
+ : undefined,
65
+ })
66
+ } else if (config.transport === 'streamable-http') {
67
+ const { StreamableHTTPClientTransport } = await import(
68
+ '@modelcontextprotocol/sdk/client/streamableHttp.js'
69
+ )
70
+ transport = new StreamableHTTPClientTransport(new URL(config.url!), {
71
+ requestInit: config.headers
72
+ ? { headers: config.headers }
73
+ : undefined,
74
+ })
75
+ } else {
76
+ throw new Error(`Unsupported MCP transport: ${config.transport}`)
77
+ }
78
+
79
+ const client = new Client(
80
+ { name: 'swarmclaw', version: '1.0' },
81
+ { capabilities: {} }
82
+ )
83
+ await client.connect(transport)
84
+
85
+ return { client, transport }
86
+ }
87
+
88
+ /* ---------- Convert MCP tools to LangChain tools ---------- */
89
+
90
+ export async function mcpToolsToLangChain(
91
+ client: any,
92
+ serverName: string
93
+ ): Promise<StructuredToolInterface[]> {
94
+ const { tools: mcpTools } = await client.listTools()
95
+ const safeName = sanitizeName(serverName)
96
+
97
+ return mcpTools.map((mcpTool: any) => {
98
+ const schema = jsonSchemaToZod(mcpTool.inputSchema ?? { type: 'object', properties: {} })
99
+
100
+ return tool(
101
+ async (args: Record<string, any>) => {
102
+ const result = await client.callTool({
103
+ name: mcpTool.name,
104
+ arguments: args,
105
+ })
106
+ const parts = (result.content ?? [])
107
+ .filter((c: any) => c.type === 'text')
108
+ .map((c: any) => c.text)
109
+ return parts.join('\n') || '(no output)'
110
+ },
111
+ {
112
+ name: `mcp_${safeName}_${mcpTool.name}`,
113
+ description: mcpTool.description ?? `MCP tool: ${mcpTool.name}`,
114
+ schema,
115
+ }
116
+ )
117
+ })
118
+ }
119
+
120
+ /* ---------- Disconnect ---------- */
121
+
122
+ export async function disconnectMcpServer(
123
+ client: any,
124
+ transport: any
125
+ ): Promise<void> {
126
+ try { await client.close() } catch { /* ignore */ }
127
+ try { await transport.close() } catch { /* ignore */ }
128
+ }
129
+
130
+ export { jsonSchemaToZod, sanitizeName } // test exports