shadow-claw 1.28.0 → 1.29.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 (194) hide show
  1. package/.well-known/mcp/server-card.json +1 -1
  2. package/.well-known/mcp.json +1 -1
  3. package/README.md +96 -278
  4. package/bin/cli.mjs +4 -0
  5. package/bin/commands/mcp.mjs +409 -125
  6. package/bin/commands/mcp.test.mjs +151 -18
  7. package/bin/utils/control-client.mjs +338 -103
  8. package/bin/utils/control-client.test.mjs +14 -0
  9. package/dist/electron/main.cjs +179 -179
  10. package/dist/lib/components/index.js +21 -21
  11. package/dist/lib/components/settings/shadow-claw-channel-config.js +1 -1
  12. package/dist/lib/components/settings/shadow-claw-control-plane.js +1 -1
  13. package/dist/lib/components/settings/shadow-claw-git.js +1 -1
  14. package/dist/lib/components/settings/shadow-claw-llm.js +1 -1
  15. package/dist/lib/components/settings/shadow-claw-networking.js +1 -1
  16. package/dist/lib/components/settings/shadow-claw-notifications.js +1 -1
  17. package/dist/lib/components/settings/shadow-claw-peerjs.js +1 -1
  18. package/dist/lib/components/settings/shadow-claw-storage.js +1 -1
  19. package/dist/lib/components/settings/shadow-claw-task-server.js +1 -1
  20. package/dist/lib/components/settings/shadow-claw-webvm.js +1 -1
  21. package/dist/lib/components/shadow-claw-channel-config.js +1 -1
  22. package/dist/lib/components/shadow-claw-channels.js +1 -1
  23. package/dist/lib/components/shadow-claw-chat.js +1 -1
  24. package/dist/lib/components/shadow-claw-control-plane.js +1 -1
  25. package/dist/lib/components/shadow-claw-conversations.js +1 -1
  26. package/dist/lib/components/shadow-claw-file-viewer.js +1 -1
  27. package/dist/lib/components/shadow-claw-files.js +1 -1
  28. package/dist/lib/components/shadow-claw-git.js +1 -1
  29. package/dist/lib/components/shadow-claw-llm.js +1 -1
  30. package/dist/lib/components/shadow-claw-networking.js +1 -1
  31. package/dist/lib/components/shadow-claw-notifications.js +1 -1
  32. package/dist/lib/components/shadow-claw-pages.js +1 -1
  33. package/dist/lib/components/shadow-claw-peerjs.js +1 -1
  34. package/dist/lib/components/shadow-claw-settings.js +1 -1
  35. package/dist/lib/components/shadow-claw-storage.js +1 -1
  36. package/dist/lib/components/shadow-claw-task-server.js +1 -1
  37. package/dist/lib/components/shadow-claw-tasks.js +1 -1
  38. package/dist/lib/components/shadow-claw-terminal.js +1 -1
  39. package/dist/lib/components/shadow-claw-tools.js +1 -1
  40. package/dist/lib/components/shadow-claw-webvm.js +1 -1
  41. package/dist/lib/components/shadow-claw.js +1 -1
  42. package/dist/lib/{configurePeerJs-D8Ph0-A9.js → configurePeerJs-DG4hliKp.js} +2 -2
  43. package/dist/lib/{configurePeerJs-D8Ph0-A9.js.map → configurePeerJs-DG4hliKp.js.map} +1 -1
  44. package/dist/lib/index.js +21 -21
  45. package/dist/lib/{initControlPlane-BIrWmpzc.js → initControlPlane-DnpAzphg.js} +8 -8
  46. package/dist/lib/{initControlPlane-BIrWmpzc.js.map → initControlPlane-DnpAzphg.js.map} +1 -1
  47. package/dist/lib/{markdown-DEUoM1c6.js → markdown-DgdSt2GD.js} +2 -2
  48. package/dist/lib/{markdown-DEUoM1c6.js.map → markdown-DgdSt2GD.js.map} +1 -1
  49. package/dist/lib/{orchestrator-CFCW-l1O.js → orchestrator-CjEau_RG.js} +5 -5
  50. package/dist/lib/{orchestrator-CFCW-l1O.js.map → orchestrator-CjEau_RG.js.map} +1 -1
  51. package/dist/lib/{orchestrator-BtVj9bBB.js → orchestrator-ClXbJ4Mb.js} +2 -2
  52. package/dist/lib/{orchestrator-BtVj9bBB.js.map → orchestrator-ClXbJ4Mb.js.map} +1 -1
  53. package/dist/lib/{peerjs-BoBkx9DJ.js → peerjs-BdfpltIe.js} +3 -3
  54. package/dist/lib/{peerjs-BoBkx9DJ.js.map → peerjs-BdfpltIe.js.map} +1 -1
  55. package/dist/lib/{push-client-D6hbzDNV.js → push-client-DjwKknNg.js} +9 -3
  56. package/dist/lib/push-client-DjwKknNg.js.map +1 -0
  57. package/dist/lib/{shadow-claw-Cuf2O5bZ.js → shadow-claw-DcPGKenn.js} +4 -4
  58. package/dist/lib/{shadow-claw-Cuf2O5bZ.js.map → shadow-claw-DcPGKenn.js.map} +1 -1
  59. package/dist/lib/{shadow-claw-channel-config-BDPKt6Bd.js → shadow-claw-channel-config-DEL-aZ7N.js} +3 -3
  60. package/dist/lib/{shadow-claw-channel-config-BDPKt6Bd.js.map → shadow-claw-channel-config-DEL-aZ7N.js.map} +1 -1
  61. package/dist/lib/{shadow-claw-channels-BsP5JABM.js → shadow-claw-channels-BgaJiPdE.js} +2 -2
  62. package/dist/lib/{shadow-claw-channels-BsP5JABM.js.map → shadow-claw-channels-BgaJiPdE.js.map} +1 -1
  63. package/dist/lib/{shadow-claw-chat-Cam2lX4k.js → shadow-claw-chat-mXPQZbwT.js} +4 -4
  64. package/dist/lib/{shadow-claw-chat-Cam2lX4k.js.map → shadow-claw-chat-mXPQZbwT.js.map} +1 -1
  65. package/dist/lib/{shadow-claw-control-plane-DB2qcCFs.js → shadow-claw-control-plane-DO6foOLV.js} +6 -6
  66. package/dist/lib/{shadow-claw-control-plane-DB2qcCFs.js.map → shadow-claw-control-plane-DO6foOLV.js.map} +1 -1
  67. package/dist/lib/{shadow-claw-conversations-C9Vf4XGx.js → shadow-claw-conversations-BLwHTwdW.js} +2 -2
  68. package/dist/lib/{shadow-claw-conversations-C9Vf4XGx.js.map → shadow-claw-conversations-BLwHTwdW.js.map} +1 -1
  69. package/dist/lib/{shadow-claw-file-viewer-B4HYiI0v.js → shadow-claw-file-viewer-BSbZvSl-.js} +3 -3
  70. package/dist/lib/{shadow-claw-file-viewer-B4HYiI0v.js.map → shadow-claw-file-viewer-BSbZvSl-.js.map} +1 -1
  71. package/dist/lib/{shadow-claw-files-DVROUESO.js → shadow-claw-files-BLVr9Pua.js} +2 -2
  72. package/dist/lib/{shadow-claw-files-DVROUESO.js.map → shadow-claw-files-BLVr9Pua.js.map} +1 -1
  73. package/dist/lib/{shadow-claw-git-BqrO7cHJ.js → shadow-claw-git-CHC1H86J.js} +3 -3
  74. package/dist/lib/{shadow-claw-git-BqrO7cHJ.js.map → shadow-claw-git-CHC1H86J.js.map} +1 -1
  75. package/dist/lib/{shadow-claw-llm-BexUQdYo.js → shadow-claw-llm-CKXfxd3D.js} +2 -2
  76. package/dist/lib/{shadow-claw-llm-BexUQdYo.js.map → shadow-claw-llm-CKXfxd3D.js.map} +1 -1
  77. package/dist/lib/{shadow-claw-networking-CfXRGpUD.js → shadow-claw-networking-dpYvfchh.js} +2 -2
  78. package/dist/lib/{shadow-claw-networking-CfXRGpUD.js.map → shadow-claw-networking-dpYvfchh.js.map} +1 -1
  79. package/dist/lib/{shadow-claw-notifications-C9KPMgx_.js → shadow-claw-notifications-_YeAVPga.js} +2 -2
  80. package/dist/lib/{shadow-claw-notifications-C9KPMgx_.js.map → shadow-claw-notifications-_YeAVPga.js.map} +1 -1
  81. package/dist/lib/{shadow-claw-pages-CGzGXmrI.js → shadow-claw-pages-DVzZriZ7.js} +3 -3
  82. package/dist/lib/{shadow-claw-pages-CGzGXmrI.js.map → shadow-claw-pages-DVzZriZ7.js.map} +1 -1
  83. package/dist/lib/{shadow-claw-peerjs-B0ZB7yhB.js → shadow-claw-peerjs-BvixjeBz.js} +3 -3
  84. package/dist/lib/{shadow-claw-peerjs-B0ZB7yhB.js.map → shadow-claw-peerjs-BvixjeBz.js.map} +1 -1
  85. package/dist/lib/{shadow-claw-settings-DBfMZdDS.js → shadow-claw-settings-DV9-gD4Q.js} +2 -2
  86. package/dist/lib/{shadow-claw-settings-DBfMZdDS.js.map → shadow-claw-settings-DV9-gD4Q.js.map} +1 -1
  87. package/dist/lib/{shadow-claw-storage-CM-w6mgG.js → shadow-claw-storage-Bv__kXG6.js} +2 -2
  88. package/dist/lib/{shadow-claw-storage-CM-w6mgG.js.map → shadow-claw-storage-Bv__kXG6.js.map} +1 -1
  89. package/dist/lib/{shadow-claw-task-server-DV4b1g2-.js → shadow-claw-task-server-DNOe8PWK.js} +2 -2
  90. package/dist/lib/{shadow-claw-task-server-DV4b1g2-.js.map → shadow-claw-task-server-DNOe8PWK.js.map} +1 -1
  91. package/dist/lib/{shadow-claw-tasks-K-foOHSE.js → shadow-claw-tasks-qmcDT4PU.js} +3 -3
  92. package/dist/lib/{shadow-claw-tasks-K-foOHSE.js.map → shadow-claw-tasks-qmcDT4PU.js.map} +1 -1
  93. package/dist/lib/{shadow-claw-terminal-CpAknKyA.js → shadow-claw-terminal-0QUWP8of.js} +2 -2
  94. package/dist/lib/{shadow-claw-terminal-CpAknKyA.js.map → shadow-claw-terminal-0QUWP8of.js.map} +1 -1
  95. package/dist/lib/{shadow-claw-tools-D98XZ0Ep.js → shadow-claw-tools-Bs_bmI6-.js} +3 -3
  96. package/dist/lib/{shadow-claw-tools-D98XZ0Ep.js.map → shadow-claw-tools-Bs_bmI6-.js.map} +1 -1
  97. package/dist/lib/{shadow-claw-webvm-DLOb8dMI.js → shadow-claw-webvm-DYaoL7QY.js} +2 -2
  98. package/dist/lib/{shadow-claw-webvm-DLOb8dMI.js.map → shadow-claw-webvm-DYaoL7QY.js.map} +1 -1
  99. package/dist/lib/{syncWebMcpRegistration-NspKefOI.js → syncWebMcpRegistration-BhbqI6R2.js} +2 -2
  100. package/dist/lib/{syncWebMcpRegistration-NspKefOI.js.map → syncWebMcpRegistration-BhbqI6R2.js.map} +1 -1
  101. package/dist/public/.well-known/mcp/server-card.json +1 -1
  102. package/dist/public/.well-known/mcp.json +1 -1
  103. package/dist/public/AGENTS.md +22 -3
  104. package/dist/public/README.md +96 -278
  105. package/dist/public/agent.worker.js +50 -50
  106. package/dist/public/{base-task-model-CYkpwnvU-DDfHUUIS.js → base-task-model-CYkpwnvU-DrXr4G1R.js} +1 -1
  107. package/dist/public/{configurePeerJs-BhBokQlC.js → configurePeerJs-B470pdeU.js} +1 -1
  108. package/dist/public/docs/README.md +31 -31
  109. package/dist/public/docs/architecture/context-management.md +7 -0
  110. package/dist/public/docs/architecture/storage.md +10 -1
  111. package/dist/public/docs/decisions/bundled-typescript-architecture.md +0 -1
  112. package/dist/public/docs/decisions/native-web-components-and-signals.md +0 -1
  113. package/dist/public/docs/example/article/index.html +1 -1
  114. package/dist/public/docs/guides/publishing-to-github-pages.md +334 -0
  115. package/dist/public/docs/guides/server-development-configuration.md +15 -0
  116. package/dist/public/docs/publishing/index.html +1 -1
  117. package/dist/public/docs/skill-creator/index.html +1 -1
  118. package/dist/public/docs/subsystems/cli.md +10 -2
  119. package/dist/public/docs/subsystems/control-plane.md +9 -8
  120. package/dist/public/docs/subsystems/crypto.md +9 -0
  121. package/dist/public/docs/subsystems/custom-element-security.md +2 -0
  122. package/dist/public/docs/subsystems/mcp-server.md +31 -16
  123. package/dist/public/docs/subsystems/notifications.md +8 -5
  124. package/dist/public/docs/subsystems/providers.md +9 -0
  125. package/dist/public/docs/subsystems/share-target.md +107 -0
  126. package/dist/public/docs/subsystems/webmcp.md +43 -0
  127. package/dist/public/e2e/README.md +4 -2
  128. package/dist/public/{e2e-bridge-DcBoeFvd.js → e2e-bridge-CSW0S5Yz.js} +1 -1
  129. package/dist/public/{file-viewer-CKtVC9Ix.js → file-viewer-Cs-riNvy.js} +1 -1
  130. package/dist/public/{firebase-OSDX3viP.js → firebase-cwAl_ABm.js} +1 -1
  131. package/dist/public/{git-CkhCJonj.js → git-GD09WATp.js} +25 -25
  132. package/dist/public/index.html +1 -1
  133. package/dist/public/index.js +1 -1
  134. package/dist/public/{initControlPlane-Bi8ouFEb.js → initControlPlane-BqCY0NQy.js} +1 -1
  135. package/dist/public/initControlPlane-C-IXaKYX.js +1 -0
  136. package/dist/public/{language-detector-BRKrlra0.js → language-detector-CIeEuQFk.js} +1 -1
  137. package/dist/public/llms.txt +4 -2
  138. package/dist/public/main/index.html +1 -1
  139. package/dist/public/main/memory/index.html +1 -1
  140. package/dist/public/{markdown-C-hCVJtc.js → markdown-Cw7vCtTg.js} +1 -1
  141. package/dist/public/{openai-Bpd71Oja.js → openai-BJIb2c5j.js} +3 -3
  142. package/dist/public/{orchestrator-8qNGfw8r.js → orchestrator-BWkHHO9Y.js} +1 -1
  143. package/dist/public/{orchestrator-BT3Ks5ip.js → orchestrator-DpkkeD6T.js} +1 -1
  144. package/dist/public/{peerjs-DfhwEiiq.js → peerjs-tf2EmWjB.js} +1 -1
  145. package/dist/public/{prompt-api-polyfill-Dl1kbJhR.js → prompt-api-polyfill-CXd75leW.js} +1 -1
  146. package/dist/public/{push-client-BVLrE9pK.js → push-client-CHjSbrWs.js} +1 -1
  147. package/dist/public/{rewriter-j6D9LDER.js → rewriter-BEHn9gll.js} +1 -1
  148. package/dist/public/service-worker.js +1 -1
  149. package/dist/public/{shadow-claw-DCjxNDCX.js → shadow-claw-BBPV_9X8.js} +2 -2
  150. package/dist/public/{shadow-claw-channels-CITDrqMl.js → shadow-claw-channels-BJvrKQjg.js} +1 -1
  151. package/dist/public/{shadow-claw-chat-BQrSgSKF.js → shadow-claw-chat-BG2EaRaw.js} +1 -1
  152. package/dist/public/{shadow-claw-control-plane-Cp001OY0.js → shadow-claw-control-plane-BBa7WbTg.js} +2 -2
  153. package/dist/public/{shadow-claw-conversations-EhX4RvIj.js → shadow-claw-conversations-CFaWFmkm.js} +1 -1
  154. package/dist/public/{shadow-claw-file-viewer-DiQXIjZD.js → shadow-claw-file-viewer-Z-GIKaE9.js} +1 -1
  155. package/dist/public/{shadow-claw-files-lkTnSJsn.js → shadow-claw-files-BBx4iMsk.js} +1 -1
  156. package/dist/public/{shadow-claw-git-BlcA_z8x.js → shadow-claw-git-vTOKatwd.js} +2 -2
  157. package/dist/public/{shadow-claw-llm-CW16X9Rg.js → shadow-claw-llm-Ce1Ax6-x.js} +1 -1
  158. package/dist/public/{shadow-claw-networking-ySsMBY_i.js → shadow-claw-networking-q4XIzdB5.js} +1 -1
  159. package/dist/public/{shadow-claw-notifications-CyV2zCQ8.js → shadow-claw-notifications-D2cz0gmm.js} +1 -1
  160. package/dist/public/{shadow-claw-pages-Cx7cF9ah.js → shadow-claw-pages-3335lmSC.js} +1 -1
  161. package/dist/public/{shadow-claw-settings-zm4PHHGi.js → shadow-claw-settings-4ptYuJwO.js} +2 -2
  162. package/dist/public/{shadow-claw-storage-dy9JAEGd.js → shadow-claw-storage-bIrPKFhF.js} +1 -1
  163. package/dist/public/{shadow-claw-task-server-Bleww48s.js → shadow-claw-task-server-5yqVBxy1.js} +1 -1
  164. package/dist/public/{shadow-claw-tasks-CTI5oaLN.js → shadow-claw-tasks-DcqYHPLZ.js} +1 -1
  165. package/dist/public/{shadow-claw-terminal-mZ3i4HaQ.js → shadow-claw-terminal-fARaDONr.js} +1 -1
  166. package/dist/public/{shadow-claw-tools-B_nuk8Zl.js → shadow-claw-tools-paEAhcJr.js} +1 -1
  167. package/dist/public/{shadow-claw-webvm-Vl_jH5YL.js → shadow-claw-webvm-CbzH_vyX.js} +1 -1
  168. package/dist/public/{summarizer-VJNr-7xG.js → summarizer-BT3AuFed.js} +1 -1
  169. package/dist/public/{syncWebMcpRegistration-n2vAMJ4w.js → syncWebMcpRegistration-CWSYaHex.js} +1 -1
  170. package/dist/public/{transformers-BhQMWQG5.js → transformers-1VTxqKjT.js} +1 -1
  171. package/dist/public/{translator-DEQXb7ZL.js → translator-B50RYlFt.js} +1 -1
  172. package/dist/public/{webllm-CHN5H-Sy.js → webllm-FmyJWWpt.js} +1 -1
  173. package/dist/public/{writer-CD9oN2pq.js → writer-Bhb4YYRE.js} +1 -1
  174. package/dist/server.js +205 -203
  175. package/package.json +1 -1
  176. package/src/components/settings/shadow-claw-llm/shadow-claw-llm.test.ts +2 -0
  177. package/src/core/orchestrator/utils/configurePeerJs.test.ts +2 -0
  178. package/src/server/client-registry.test.ts +9 -0
  179. package/src/server/client-registry.ts +14 -0
  180. package/src/server/mcp/mcp-integration.test.ts +68 -16
  181. package/src/server/mcp/tools/built-in-tools.ts +125 -34
  182. package/src/server/mcp/tools/client-tool-relay.ts +81 -44
  183. package/src/server/mcp/tools/mcp-tools.test.ts +103 -13
  184. package/src/server/mcp/types.ts +3 -0
  185. package/src/server/openapi/paths/push.ts +48 -0
  186. package/src/server/server.ts +30 -0
  187. package/src/subsystems/notifications/push-client.test.ts +17 -0
  188. package/src/subsystems/notifications/push-client.ts +22 -2
  189. package/src/subsystems/notifications/push-routes.test.ts +31 -0
  190. package/src/subsystems/notifications/push-routes.ts +10 -0
  191. package/src/subsystems/notifications/push-store.test.ts +49 -0
  192. package/src/subsystems/notifications/push-store.ts +45 -0
  193. package/src/worker/worker.test.ts +2 -0
  194. package/dist/lib/push-client-D6hbzDNV.js.map +0 -1
@@ -25,9 +25,24 @@ function getCliVersion() {
25
25
  return "1.27.1";
26
26
  }
27
27
 
28
+ export const MCP_CLIENT_TOOL_PREFIX = "shadowclaw_client_";
29
+ export const MCP_SERVER_TOOL_PREFIX = "shadowclaw_server_";
30
+
31
+ function getClientRawToolName(name) {
32
+ return typeof name === "string" && name.startsWith(MCP_CLIENT_TOOL_PREFIX)
33
+ ? name.slice(MCP_CLIENT_TOOL_PREFIX.length)
34
+ : name;
35
+ }
36
+
37
+ function toClientExposedToolName(name) {
38
+ return typeof name === "string" && !name.startsWith(MCP_CLIENT_TOOL_PREFIX)
39
+ ? `${MCP_CLIENT_TOOL_PREFIX}${name}`
40
+ : name;
41
+ }
42
+
28
43
  export const CLI_BUILTIN_TOOLS = [
29
44
  {
30
- name: "shadowclaw_list_clients",
45
+ name: "shadowclaw_server_list_clients",
31
46
  description:
32
47
  "List connected browser and Electron clients, including device type, ID, and active capabilities.",
33
48
  inputSchema: {
@@ -36,7 +51,7 @@ export const CLI_BUILTIN_TOOLS = [
36
51
  },
37
52
  },
38
53
  {
39
- name: "shadowclaw_send_message",
54
+ name: "shadowclaw_server_send_message",
40
55
  description:
41
56
  "Send a message or prompt to a connected ShadowClaw client's active AI orchestrator queue.",
42
57
  inputSchema: {
@@ -61,7 +76,7 @@ export const CLI_BUILTIN_TOOLS = [
61
76
  },
62
77
  },
63
78
  {
64
- name: "shadowclaw_read_state",
79
+ name: "shadowclaw_server_read_state",
65
80
  description:
66
81
  "Read the current orchestrator state, active group ID, and capabilities from a connected client.",
67
82
  inputSchema: {
@@ -75,7 +90,7 @@ export const CLI_BUILTIN_TOOLS = [
75
90
  },
76
91
  },
77
92
  {
78
- name: "shadowclaw_list_tasks",
93
+ name: "shadowclaw_server_list_tasks",
79
94
  description:
80
95
  "List scheduled background tasks configured on a connected client.",
81
96
  inputSchema: {
@@ -87,7 +102,7 @@ export const CLI_BUILTIN_TOOLS = [
87
102
  },
88
103
  },
89
104
  {
90
- name: "shadowclaw_set_active_client",
105
+ name: "shadowclaw_server_set_active_client",
91
106
  description:
92
107
  "Set the active default connected client used when no clientId is explicitly provided in tool calls.",
93
108
  inputSchema: {
@@ -103,7 +118,7 @@ export const CLI_BUILTIN_TOOLS = [
103
118
  },
104
119
  },
105
120
  {
106
- name: "shadowclaw_manage_backup",
121
+ name: "shadowclaw_server_manage_backup",
107
122
  description:
108
123
  "Trigger or manage OPFS workspace backups for a connected client (trigger, list, or delete).",
109
124
  inputSchema: {
@@ -121,7 +136,16 @@ export const CLI_BUILTIN_TOOLS = [
121
136
  },
122
137
  },
123
138
  {
124
- name: "shadowclaw_send_notification",
139
+ name: "shadowclaw_server_status",
140
+ description:
141
+ "Query ShadowClaw Node server status, version, and active client count.",
142
+ inputSchema: {
143
+ type: "object",
144
+ properties: {},
145
+ },
146
+ },
147
+ {
148
+ name: "shadowclaw_server_send_notification",
125
149
  description:
126
150
  "Broadcast an OS-level push notification to subscribed devices via Web Push (VAPID), or send to a specific registered client. Works even when the client browser tab is closed, asleep, or running in the background.",
127
151
  inputSchema: {
@@ -232,17 +256,19 @@ export function createCliMcpEngine(options = {}) {
232
256
  Array.isArray(res.data.tools)
233
257
  ) {
234
258
  for (const t of res.data.tools) {
259
+ const rawName = getClientRawToolName(t.name);
235
260
  if (
236
- t.name === "send_notification" ||
237
- t.name.startsWith("shadowclaw_")
261
+ rawName === "send_notification" ||
262
+ (rawName.startsWith("shadowclaw_") &&
263
+ !rawName.startsWith("shadowclaw_client_"))
238
264
  ) {
239
265
  continue;
240
266
  }
241
- if (!toolDefMap.has(t.name)) {
242
- toolDefMap.set(t.name, t);
243
- toolSupportingClientsMap.set(t.name, []);
267
+ if (!toolDefMap.has(rawName)) {
268
+ toolDefMap.set(rawName, t);
269
+ toolSupportingClientsMap.set(rawName, []);
244
270
  }
245
- toolSupportingClientsMap.get(t.name).push(c);
271
+ toolSupportingClientsMap.get(rawName).push(c);
246
272
  }
247
273
  }
248
274
  } catch (_) {}
@@ -251,8 +277,8 @@ export function createCliMcpEngine(options = {}) {
251
277
  const activeTargetId = await resolveTargetId();
252
278
  const relayedTools = [];
253
279
 
254
- for (const [toolName, t] of toolDefMap.entries()) {
255
- const supportingClients = toolSupportingClientsMap.get(toolName) || [];
280
+ for (const [rawToolName, t] of toolDefMap.entries()) {
281
+ const supportingClients = toolSupportingClientsMap.get(rawToolName) || [];
256
282
  const supportingClientIds = supportingClients
257
283
  .map((cl) => cl.clientId || cl.id)
258
284
  .filter(Boolean);
@@ -286,10 +312,13 @@ export function createCliMcpEngine(options = {}) {
286
312
  : ` [Client: ${toolDefaultLabel} (${toolDefaultId.slice(0, 14)}...)]`
287
313
  : "";
288
314
 
315
+ const exposedName = toClientExposedToolName(rawToolName);
316
+
289
317
  relayedTools.push({
290
- name: t.name,
318
+ name: exposedName,
291
319
  description:
292
- (t.description || `Relayed tool '${t.name}' from connected client.`) +
320
+ (t.description ||
321
+ `Relayed tool '${rawToolName}' from connected client.`) +
293
322
  clientNote,
294
323
  inputSchema: {
295
324
  ...existingSchema,
@@ -355,6 +384,39 @@ export function createCliMcpEngine(options = {}) {
355
384
  }
356
385
 
357
386
  case "tools/list": {
387
+ let pushClients = [];
388
+ try {
389
+ if (typeof client.listPushClients === "function") {
390
+ pushClients = await client.listPushClients();
391
+ } else if (typeof client.listPushSubscriptions === "function") {
392
+ const subs = await client.listPushSubscriptions();
393
+ if (Array.isArray(subs)) {
394
+ const map = new Map();
395
+ for (const sub of subs) {
396
+ const cid =
397
+ (sub.client_id || "").trim() ||
398
+ (sub.id ? String(sub.id) : "");
399
+ if (cid && !map.has(cid)) {
400
+ map.set(cid, {
401
+ clientId: cid,
402
+ deviceLabel: sub.device_label || undefined,
403
+ });
404
+ }
405
+ }
406
+ pushClients = Array.from(map.values());
407
+ }
408
+ }
409
+ } catch (_) {}
410
+
411
+ const sortedPushClients = Array.isArray(pushClients)
412
+ ? [...pushClients].sort((a, b) =>
413
+ (a.clientId || "").localeCompare(b.clientId || ""),
414
+ )
415
+ : [];
416
+ const pushClientIds = sortedPushClients
417
+ .map((c) => c.clientId)
418
+ .filter(Boolean);
419
+
358
420
  const relayed = await getRelayedTools();
359
421
  const combined = new Map();
360
422
  for (const t of CLI_BUILTIN_TOOLS) {
@@ -366,6 +428,32 @@ export function createCliMcpEngine(options = {}) {
366
428
  }
367
429
  }
368
430
 
431
+ if (pushClientIds.length > 0) {
432
+ for (const targetName of [
433
+ "shadowclaw_server_send_notification",
434
+ "shadowclaw_send_notification",
435
+ ]) {
436
+ const existing = combined.get(targetName);
437
+ if (existing) {
438
+ combined.set(targetName, {
439
+ ...existing,
440
+ inputSchema: {
441
+ ...existing.inputSchema,
442
+ type: "object",
443
+ properties: {
444
+ ...(existing.inputSchema?.properties || {}),
445
+ clientId: {
446
+ type: "string",
447
+ description: `Target client ID, prefix, or device label of a specific client registered for push notifications (optional; if omitted, broadcasts to all subscribed devices). Available on: ${sortedPushClients.map((c) => `${c.clientId} (${c.deviceLabel || "Client"})`).join(", ")}`,
448
+ enum: pushClientIds,
449
+ },
450
+ },
451
+ },
452
+ });
453
+ }
454
+ }
455
+ }
456
+
369
457
  const sortedTools = Array.from(combined.values()).sort((a, b) =>
370
458
  a.name.localeCompare(b.name),
371
459
  );
@@ -400,22 +488,46 @@ export function createCliMcpEngine(options = {}) {
400
488
  }
401
489
 
402
490
  // Built-in tools
403
- if (toolName === "shadowclaw_list_clients") {
404
- const clients = await client.listClients();
405
- return {
406
- jsonrpc: "2.0",
407
- id: reqId,
408
- result: {
409
- resultType: "complete",
410
- content: [
411
- { type: "text", text: JSON.stringify({ clients }, null, 2) },
412
- ],
413
- _meta: { "io.modelcontextprotocol/serverInfo": serverInfo },
414
- },
415
- };
491
+ if (
492
+ toolName === "shadowclaw_server_list_clients" ||
493
+ toolName === "shadowclaw_list_clients"
494
+ ) {
495
+ try {
496
+ const clients = await client.listClients();
497
+ return {
498
+ jsonrpc: "2.0",
499
+ id: reqId,
500
+ result: {
501
+ resultType: "complete",
502
+ content: [
503
+ { type: "text", text: JSON.stringify({ clients }, null, 2) },
504
+ ],
505
+ _meta: { "io.modelcontextprotocol/serverInfo": serverInfo },
506
+ },
507
+ };
508
+ } catch (err) {
509
+ return {
510
+ jsonrpc: "2.0",
511
+ id: reqId,
512
+ result: {
513
+ resultType: "complete",
514
+ isError: true,
515
+ content: [
516
+ {
517
+ type: "text",
518
+ text: `Error listing clients: ${err.message}`,
519
+ },
520
+ ],
521
+ _meta: { "io.modelcontextprotocol/serverInfo": serverInfo },
522
+ },
523
+ };
524
+ }
416
525
  }
417
526
 
418
- if (toolName === "shadowclaw_set_active_client") {
527
+ if (
528
+ toolName === "shadowclaw_server_set_active_client" ||
529
+ toolName === "shadowclaw_set_active_client"
530
+ ) {
419
531
  const targetId = await resolveTargetId(args.clientId);
420
532
  if (!targetId) {
421
533
  return {
@@ -457,7 +569,10 @@ export function createCliMcpEngine(options = {}) {
457
569
  };
458
570
  }
459
571
 
460
- if (toolName === "shadowclaw_send_message") {
572
+ if (
573
+ toolName === "shadowclaw_server_send_message" ||
574
+ toolName === "shadowclaw_send_message"
575
+ ) {
461
576
  const targetId = await resolveTargetId(args.clientId);
462
577
  if (!targetId) {
463
578
  return {
@@ -476,119 +591,254 @@ export function createCliMcpEngine(options = {}) {
476
591
  },
477
592
  };
478
593
  }
479
- const res = await client.sendCommand(targetId, "send-message", {
480
- text: args.text,
481
- groupId: args.groupId,
482
- });
483
- return {
484
- jsonrpc: "2.0",
485
- id: reqId,
486
- result: {
487
- resultType: "complete",
488
- isError: !res.success,
489
- content: [
490
- {
491
- type: "text",
492
- text: JSON.stringify(res.data || res, null, 2),
493
- },
494
- ],
495
- _meta: { "io.modelcontextprotocol/serverInfo": serverInfo },
496
- },
497
- };
594
+ try {
595
+ const res = await client.sendCommand(targetId, "send-message", {
596
+ text: args.text,
597
+ groupId: args.groupId,
598
+ });
599
+ return {
600
+ jsonrpc: "2.0",
601
+ id: reqId,
602
+ result: {
603
+ resultType: "complete",
604
+ isError: !res.success,
605
+ content: [
606
+ {
607
+ type: "text",
608
+ text: JSON.stringify(res.data || res, null, 2),
609
+ },
610
+ ],
611
+ _meta: { "io.modelcontextprotocol/serverInfo": serverInfo },
612
+ },
613
+ };
614
+ } catch (err) {
615
+ return {
616
+ jsonrpc: "2.0",
617
+ id: reqId,
618
+ result: {
619
+ resultType: "complete",
620
+ isError: true,
621
+ content: [
622
+ {
623
+ type: "text",
624
+ text: `Error sending message: ${err.message}`,
625
+ },
626
+ ],
627
+ _meta: { "io.modelcontextprotocol/serverInfo": serverInfo },
628
+ },
629
+ };
630
+ }
498
631
  }
499
632
 
500
- if (toolName === "shadowclaw_read_state") {
633
+ if (
634
+ toolName === "shadowclaw_server_read_state" ||
635
+ toolName === "shadowclaw_read_state"
636
+ ) {
501
637
  const targetId = await resolveTargetId(args.clientId);
502
- const res = await client.sendCommand(targetId, "read-state", {});
503
- return {
504
- jsonrpc: "2.0",
505
- id: reqId,
506
- result: {
507
- resultType: "complete",
508
- isError: !res.success,
509
- content: [
510
- {
511
- type: "text",
512
- text: JSON.stringify(res.data || res, null, 2),
513
- },
514
- ],
515
- _meta: { "io.modelcontextprotocol/serverInfo": serverInfo },
516
- },
517
- };
638
+ try {
639
+ const res = await client.sendCommand(targetId, "read-state", {});
640
+ return {
641
+ jsonrpc: "2.0",
642
+ id: reqId,
643
+ result: {
644
+ resultType: "complete",
645
+ isError: !res.success,
646
+ content: [
647
+ {
648
+ type: "text",
649
+ text: JSON.stringify(res.data || res, null, 2),
650
+ },
651
+ ],
652
+ _meta: { "io.modelcontextprotocol/serverInfo": serverInfo },
653
+ },
654
+ };
655
+ } catch (err) {
656
+ return {
657
+ jsonrpc: "2.0",
658
+ id: reqId,
659
+ result: {
660
+ resultType: "complete",
661
+ isError: true,
662
+ content: [
663
+ {
664
+ type: "text",
665
+ text: `Error reading state: ${err.message}`,
666
+ },
667
+ ],
668
+ _meta: { "io.modelcontextprotocol/serverInfo": serverInfo },
669
+ },
670
+ };
671
+ }
518
672
  }
519
673
 
520
- if (toolName === "shadowclaw_list_tasks") {
674
+ if (
675
+ toolName === "shadowclaw_server_list_tasks" ||
676
+ toolName === "shadowclaw_list_tasks"
677
+ ) {
521
678
  const targetId = await resolveTargetId(args.clientId);
522
- const res = await client.sendCommand(targetId, "list-tasks", {
523
- groupId: args.groupId,
524
- });
525
- return {
526
- jsonrpc: "2.0",
527
- id: reqId,
528
- result: {
529
- resultType: "complete",
530
- isError: !res.success,
531
- content: [
532
- {
533
- type: "text",
534
- text: JSON.stringify(res.data || res, null, 2),
535
- },
536
- ],
537
- _meta: { "io.modelcontextprotocol/serverInfo": serverInfo },
538
- },
539
- };
679
+ try {
680
+ const res = await client.sendCommand(targetId, "list-tasks", {
681
+ groupId: args.groupId,
682
+ });
683
+ return {
684
+ jsonrpc: "2.0",
685
+ id: reqId,
686
+ result: {
687
+ resultType: "complete",
688
+ isError: !res.success,
689
+ content: [
690
+ {
691
+ type: "text",
692
+ text: JSON.stringify(res.data || res, null, 2),
693
+ },
694
+ ],
695
+ _meta: { "io.modelcontextprotocol/serverInfo": serverInfo },
696
+ },
697
+ };
698
+ } catch (err) {
699
+ return {
700
+ jsonrpc: "2.0",
701
+ id: reqId,
702
+ result: {
703
+ resultType: "complete",
704
+ isError: true,
705
+ content: [
706
+ {
707
+ type: "text",
708
+ text: `Error listing tasks: ${err.message}`,
709
+ },
710
+ ],
711
+ _meta: { "io.modelcontextprotocol/serverInfo": serverInfo },
712
+ },
713
+ };
714
+ }
540
715
  }
541
716
 
542
- if (toolName === "shadowclaw_manage_backup") {
717
+ if (
718
+ toolName === "shadowclaw_server_manage_backup" ||
719
+ toolName === "shadowclaw_manage_backup"
720
+ ) {
543
721
  const action = args.action || "trigger";
544
722
  const targetId = await resolveTargetId(args.clientId);
545
- if (action === "list") {
546
- const backups = await client.listBackups(targetId);
723
+ try {
724
+ if (action === "list") {
725
+ const backups = await client.listBackups(targetId);
726
+ return {
727
+ jsonrpc: "2.0",
728
+ id: reqId,
729
+ result: {
730
+ resultType: "complete",
731
+ content: [
732
+ {
733
+ type: "text",
734
+ text: JSON.stringify({ backups }, null, 2),
735
+ },
736
+ ],
737
+ _meta: { "io.modelcontextprotocol/serverInfo": serverInfo },
738
+ },
739
+ };
740
+ }
741
+ if (action === "delete") {
742
+ const res = await client.deleteBackup(args.backupId, targetId);
743
+ return {
744
+ jsonrpc: "2.0",
745
+ id: reqId,
746
+ result: {
747
+ resultType: "complete",
748
+ content: [
749
+ { type: "text", text: JSON.stringify(res, null, 2) },
750
+ ],
751
+ _meta: { "io.modelcontextprotocol/serverInfo": serverInfo },
752
+ },
753
+ };
754
+ }
755
+ const res = await client.sendCommand(targetId, "trigger-backup", {
756
+ groupId: args.groupId,
757
+ });
758
+ return {
759
+ jsonrpc: "2.0",
760
+ id: reqId,
761
+ result: {
762
+ resultType: "complete",
763
+ isError: !res.success,
764
+ content: [
765
+ {
766
+ type: "text",
767
+ text: JSON.stringify(res.data || res, null, 2),
768
+ },
769
+ ],
770
+ _meta: { "io.modelcontextprotocol/serverInfo": serverInfo },
771
+ },
772
+ };
773
+ } catch (err) {
547
774
  return {
548
775
  jsonrpc: "2.0",
549
776
  id: reqId,
550
777
  result: {
551
778
  resultType: "complete",
779
+ isError: true,
552
780
  content: [
553
- { type: "text", text: JSON.stringify({ backups }, null, 2) },
781
+ {
782
+ type: "text",
783
+ text: `Error managing backup: ${err.message}`,
784
+ },
554
785
  ],
555
786
  _meta: { "io.modelcontextprotocol/serverInfo": serverInfo },
556
787
  },
557
788
  };
558
789
  }
559
- if (action === "delete") {
560
- const res = await client.deleteBackup(args.backupId, targetId);
790
+ }
791
+
792
+ if (
793
+ toolName === "shadowclaw_server_status" ||
794
+ toolName === "shadowclaw_status"
795
+ ) {
796
+ try {
797
+ const clients = await client.listClients();
798
+ return {
799
+ jsonrpc: "2.0",
800
+ id: reqId,
801
+ result: {
802
+ resultType: "complete",
803
+ content: [
804
+ {
805
+ type: "text",
806
+ text: JSON.stringify(
807
+ {
808
+ server: "ShadowClaw",
809
+ version: serverInfo.version,
810
+ connectedClients: clients.length,
811
+ status: "healthy",
812
+ },
813
+ null,
814
+ 2,
815
+ ),
816
+ },
817
+ ],
818
+ _meta: { "io.modelcontextprotocol/serverInfo": serverInfo },
819
+ },
820
+ };
821
+ } catch (err) {
561
822
  return {
562
823
  jsonrpc: "2.0",
563
824
  id: reqId,
564
825
  result: {
565
826
  resultType: "complete",
566
- content: [{ type: "text", text: JSON.stringify(res, null, 2) }],
827
+ isError: true,
828
+ content: [
829
+ {
830
+ type: "text",
831
+ text: `Error querying server status: ${err.message}`,
832
+ },
833
+ ],
567
834
  _meta: { "io.modelcontextprotocol/serverInfo": serverInfo },
568
835
  },
569
836
  };
570
837
  }
571
- const res = await client.sendCommand(targetId, "trigger-backup", {
572
- groupId: args.groupId,
573
- });
574
- return {
575
- jsonrpc: "2.0",
576
- id: reqId,
577
- result: {
578
- resultType: "complete",
579
- isError: !res.success,
580
- content: [
581
- {
582
- type: "text",
583
- text: JSON.stringify(res.data || res, null, 2),
584
- },
585
- ],
586
- _meta: { "io.modelcontextprotocol/serverInfo": serverInfo },
587
- },
588
- };
589
838
  }
590
839
 
591
840
  if (
841
+ toolName === "shadowclaw_server_send_notification" ||
592
842
  toolName === "shadowclaw_send_notification" ||
593
843
  toolName === "send_notification"
594
844
  ) {
@@ -671,9 +921,11 @@ export function createCliMcpEngine(options = {}) {
671
921
  }
672
922
  }
673
923
 
924
+ const rawToolName = getClientRawToolName(toolName);
925
+
674
926
  // Relayed interactive ask_user tool with MRTR response fulfillment if provided
675
927
  if (
676
- toolName === "ask_user" &&
928
+ (rawToolName === "ask_user" || toolName === "ask_user") &&
677
929
  inputResponses &&
678
930
  inputResponses["response"]
679
931
  ) {
@@ -695,7 +947,10 @@ export function createCliMcpEngine(options = {}) {
695
947
  await getRelayedTools();
696
948
  }
697
949
 
698
- const supportingClients = toolSupportingClientsMap.get(toolName) || [];
950
+ const supportingClients =
951
+ toolSupportingClientsMap.get(rawToolName) ||
952
+ toolSupportingClientsMap.get(toolName) ||
953
+ [];
699
954
  const supportingClientIds = supportingClients
700
955
  .map((cl) => cl.clientId || cl.id)
701
956
  .filter(Boolean);
@@ -755,23 +1010,23 @@ export function createCliMcpEngine(options = {}) {
755
1010
 
756
1011
  try {
757
1012
  console.error(
758
- `[ShadowClaw MCP] Executing '${toolName}' on client: ${targetId}`,
1013
+ `[ShadowClaw MCP] Executing '${rawToolName}' on client: ${targetId}`,
759
1014
  );
760
1015
  const toolArgs = { ...args };
761
1016
  delete toolArgs.clientId;
762
- const isInteractive = toolName === "ask_user";
1017
+ const isInteractive = rawToolName === "ask_user";
763
1018
  const res = isInteractive
764
1019
  ? await client.sendCommand(
765
1020
  targetId,
766
1021
  "invoke-tool",
767
1022
  {
768
- toolName,
1023
+ toolName: rawToolName,
769
1024
  input: toolArgs,
770
1025
  },
771
1026
  300000,
772
1027
  )
773
1028
  : await client.sendCommand(targetId, "invoke-tool", {
774
- toolName,
1029
+ toolName: rawToolName,
775
1030
  input: toolArgs,
776
1031
  });
777
1032
 
@@ -885,8 +1140,22 @@ export async function runMcpCommand(options = {}) {
885
1140
  let data = "";
886
1141
  req.on("data", (chunk) => (data += chunk));
887
1142
  req.on("end", async () => {
1143
+ let body;
1144
+ try {
1145
+ body = JSON.parse(data);
1146
+ } catch {
1147
+ res.writeHead(400, { "Content-Type": "application/json" });
1148
+ res.end(
1149
+ JSON.stringify({
1150
+ jsonrpc: "2.0",
1151
+ id: null,
1152
+ error: { code: -32700, message: "Parse error" },
1153
+ }),
1154
+ );
1155
+ return;
1156
+ }
1157
+
888
1158
  try {
889
- const body = JSON.parse(data);
890
1159
  const headers = {
891
1160
  "mcp-protocol-version": req.headers["mcp-protocol-version"],
892
1161
  "mcp-method": req.headers["mcp-method"],
@@ -906,12 +1175,15 @@ export async function runMcpCommand(options = {}) {
906
1175
  });
907
1176
  res.end(JSON.stringify(reply));
908
1177
  } catch (err) {
909
- res.writeHead(400, { "Content-Type": "application/json" });
1178
+ res.writeHead(500, { "Content-Type": "application/json" });
910
1179
  res.end(
911
1180
  JSON.stringify({
912
1181
  jsonrpc: "2.0",
913
- id: null,
914
- error: { code: -32700, message: "Parse error" },
1182
+ id: body?.id ?? null,
1183
+ error: {
1184
+ code: -32603,
1185
+ message: err?.message || "Internal error",
1186
+ },
915
1187
  }),
916
1188
  );
917
1189
  }
@@ -944,8 +1216,20 @@ export async function runMcpCommand(options = {}) {
944
1216
  const trimmed = line.trim();
945
1217
  if (!trimmed) return;
946
1218
 
1219
+ let request;
1220
+ try {
1221
+ request = JSON.parse(trimmed);
1222
+ } catch {
1223
+ const errorResponse = {
1224
+ jsonrpc: "2.0",
1225
+ id: null,
1226
+ error: { code: -32700, message: "Parse error" },
1227
+ };
1228
+ process.stdout.write(JSON.stringify(errorResponse) + "\n");
1229
+ return;
1230
+ }
1231
+
947
1232
  try {
948
- const request = JSON.parse(trimmed);
949
1233
  const response = await engine.handleMessage(request);
950
1234
  if (response !== null) {
951
1235
  process.stdout.write(JSON.stringify(response) + "\n");
@@ -953,8 +1237,8 @@ export async function runMcpCommand(options = {}) {
953
1237
  } catch (err) {
954
1238
  const errorResponse = {
955
1239
  jsonrpc: "2.0",
956
- id: null,
957
- error: { code: -32700, message: "Parse error" },
1240
+ id: request?.id ?? null,
1241
+ error: { code: -32603, message: err?.message || "Internal error" },
958
1242
  };
959
1243
  process.stdout.write(JSON.stringify(errorResponse) + "\n");
960
1244
  }