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.
- package/.well-known/mcp/server-card.json +1 -1
- package/.well-known/mcp.json +1 -1
- package/README.md +96 -278
- package/bin/cli.mjs +4 -0
- package/bin/commands/mcp.mjs +409 -125
- package/bin/commands/mcp.test.mjs +151 -18
- package/bin/utils/control-client.mjs +338 -103
- package/bin/utils/control-client.test.mjs +14 -0
- package/dist/electron/main.cjs +179 -179
- package/dist/lib/components/index.js +21 -21
- package/dist/lib/components/settings/shadow-claw-channel-config.js +1 -1
- package/dist/lib/components/settings/shadow-claw-control-plane.js +1 -1
- package/dist/lib/components/settings/shadow-claw-git.js +1 -1
- package/dist/lib/components/settings/shadow-claw-llm.js +1 -1
- package/dist/lib/components/settings/shadow-claw-networking.js +1 -1
- package/dist/lib/components/settings/shadow-claw-notifications.js +1 -1
- package/dist/lib/components/settings/shadow-claw-peerjs.js +1 -1
- package/dist/lib/components/settings/shadow-claw-storage.js +1 -1
- package/dist/lib/components/settings/shadow-claw-task-server.js +1 -1
- package/dist/lib/components/settings/shadow-claw-webvm.js +1 -1
- package/dist/lib/components/shadow-claw-channel-config.js +1 -1
- package/dist/lib/components/shadow-claw-channels.js +1 -1
- package/dist/lib/components/shadow-claw-chat.js +1 -1
- package/dist/lib/components/shadow-claw-control-plane.js +1 -1
- package/dist/lib/components/shadow-claw-conversations.js +1 -1
- package/dist/lib/components/shadow-claw-file-viewer.js +1 -1
- package/dist/lib/components/shadow-claw-files.js +1 -1
- package/dist/lib/components/shadow-claw-git.js +1 -1
- package/dist/lib/components/shadow-claw-llm.js +1 -1
- package/dist/lib/components/shadow-claw-networking.js +1 -1
- package/dist/lib/components/shadow-claw-notifications.js +1 -1
- package/dist/lib/components/shadow-claw-pages.js +1 -1
- package/dist/lib/components/shadow-claw-peerjs.js +1 -1
- package/dist/lib/components/shadow-claw-settings.js +1 -1
- package/dist/lib/components/shadow-claw-storage.js +1 -1
- package/dist/lib/components/shadow-claw-task-server.js +1 -1
- package/dist/lib/components/shadow-claw-tasks.js +1 -1
- package/dist/lib/components/shadow-claw-terminal.js +1 -1
- package/dist/lib/components/shadow-claw-tools.js +1 -1
- package/dist/lib/components/shadow-claw-webvm.js +1 -1
- package/dist/lib/components/shadow-claw.js +1 -1
- package/dist/lib/{configurePeerJs-D8Ph0-A9.js → configurePeerJs-DG4hliKp.js} +2 -2
- package/dist/lib/{configurePeerJs-D8Ph0-A9.js.map → configurePeerJs-DG4hliKp.js.map} +1 -1
- package/dist/lib/index.js +21 -21
- package/dist/lib/{initControlPlane-BIrWmpzc.js → initControlPlane-DnpAzphg.js} +8 -8
- package/dist/lib/{initControlPlane-BIrWmpzc.js.map → initControlPlane-DnpAzphg.js.map} +1 -1
- package/dist/lib/{markdown-DEUoM1c6.js → markdown-DgdSt2GD.js} +2 -2
- package/dist/lib/{markdown-DEUoM1c6.js.map → markdown-DgdSt2GD.js.map} +1 -1
- package/dist/lib/{orchestrator-CFCW-l1O.js → orchestrator-CjEau_RG.js} +5 -5
- package/dist/lib/{orchestrator-CFCW-l1O.js.map → orchestrator-CjEau_RG.js.map} +1 -1
- package/dist/lib/{orchestrator-BtVj9bBB.js → orchestrator-ClXbJ4Mb.js} +2 -2
- package/dist/lib/{orchestrator-BtVj9bBB.js.map → orchestrator-ClXbJ4Mb.js.map} +1 -1
- package/dist/lib/{peerjs-BoBkx9DJ.js → peerjs-BdfpltIe.js} +3 -3
- package/dist/lib/{peerjs-BoBkx9DJ.js.map → peerjs-BdfpltIe.js.map} +1 -1
- package/dist/lib/{push-client-D6hbzDNV.js → push-client-DjwKknNg.js} +9 -3
- package/dist/lib/push-client-DjwKknNg.js.map +1 -0
- package/dist/lib/{shadow-claw-Cuf2O5bZ.js → shadow-claw-DcPGKenn.js} +4 -4
- package/dist/lib/{shadow-claw-Cuf2O5bZ.js.map → shadow-claw-DcPGKenn.js.map} +1 -1
- package/dist/lib/{shadow-claw-channel-config-BDPKt6Bd.js → shadow-claw-channel-config-DEL-aZ7N.js} +3 -3
- package/dist/lib/{shadow-claw-channel-config-BDPKt6Bd.js.map → shadow-claw-channel-config-DEL-aZ7N.js.map} +1 -1
- package/dist/lib/{shadow-claw-channels-BsP5JABM.js → shadow-claw-channels-BgaJiPdE.js} +2 -2
- package/dist/lib/{shadow-claw-channels-BsP5JABM.js.map → shadow-claw-channels-BgaJiPdE.js.map} +1 -1
- package/dist/lib/{shadow-claw-chat-Cam2lX4k.js → shadow-claw-chat-mXPQZbwT.js} +4 -4
- package/dist/lib/{shadow-claw-chat-Cam2lX4k.js.map → shadow-claw-chat-mXPQZbwT.js.map} +1 -1
- package/dist/lib/{shadow-claw-control-plane-DB2qcCFs.js → shadow-claw-control-plane-DO6foOLV.js} +6 -6
- package/dist/lib/{shadow-claw-control-plane-DB2qcCFs.js.map → shadow-claw-control-plane-DO6foOLV.js.map} +1 -1
- package/dist/lib/{shadow-claw-conversations-C9Vf4XGx.js → shadow-claw-conversations-BLwHTwdW.js} +2 -2
- package/dist/lib/{shadow-claw-conversations-C9Vf4XGx.js.map → shadow-claw-conversations-BLwHTwdW.js.map} +1 -1
- package/dist/lib/{shadow-claw-file-viewer-B4HYiI0v.js → shadow-claw-file-viewer-BSbZvSl-.js} +3 -3
- package/dist/lib/{shadow-claw-file-viewer-B4HYiI0v.js.map → shadow-claw-file-viewer-BSbZvSl-.js.map} +1 -1
- package/dist/lib/{shadow-claw-files-DVROUESO.js → shadow-claw-files-BLVr9Pua.js} +2 -2
- package/dist/lib/{shadow-claw-files-DVROUESO.js.map → shadow-claw-files-BLVr9Pua.js.map} +1 -1
- package/dist/lib/{shadow-claw-git-BqrO7cHJ.js → shadow-claw-git-CHC1H86J.js} +3 -3
- package/dist/lib/{shadow-claw-git-BqrO7cHJ.js.map → shadow-claw-git-CHC1H86J.js.map} +1 -1
- package/dist/lib/{shadow-claw-llm-BexUQdYo.js → shadow-claw-llm-CKXfxd3D.js} +2 -2
- package/dist/lib/{shadow-claw-llm-BexUQdYo.js.map → shadow-claw-llm-CKXfxd3D.js.map} +1 -1
- package/dist/lib/{shadow-claw-networking-CfXRGpUD.js → shadow-claw-networking-dpYvfchh.js} +2 -2
- package/dist/lib/{shadow-claw-networking-CfXRGpUD.js.map → shadow-claw-networking-dpYvfchh.js.map} +1 -1
- package/dist/lib/{shadow-claw-notifications-C9KPMgx_.js → shadow-claw-notifications-_YeAVPga.js} +2 -2
- package/dist/lib/{shadow-claw-notifications-C9KPMgx_.js.map → shadow-claw-notifications-_YeAVPga.js.map} +1 -1
- package/dist/lib/{shadow-claw-pages-CGzGXmrI.js → shadow-claw-pages-DVzZriZ7.js} +3 -3
- package/dist/lib/{shadow-claw-pages-CGzGXmrI.js.map → shadow-claw-pages-DVzZriZ7.js.map} +1 -1
- package/dist/lib/{shadow-claw-peerjs-B0ZB7yhB.js → shadow-claw-peerjs-BvixjeBz.js} +3 -3
- package/dist/lib/{shadow-claw-peerjs-B0ZB7yhB.js.map → shadow-claw-peerjs-BvixjeBz.js.map} +1 -1
- package/dist/lib/{shadow-claw-settings-DBfMZdDS.js → shadow-claw-settings-DV9-gD4Q.js} +2 -2
- package/dist/lib/{shadow-claw-settings-DBfMZdDS.js.map → shadow-claw-settings-DV9-gD4Q.js.map} +1 -1
- package/dist/lib/{shadow-claw-storage-CM-w6mgG.js → shadow-claw-storage-Bv__kXG6.js} +2 -2
- package/dist/lib/{shadow-claw-storage-CM-w6mgG.js.map → shadow-claw-storage-Bv__kXG6.js.map} +1 -1
- package/dist/lib/{shadow-claw-task-server-DV4b1g2-.js → shadow-claw-task-server-DNOe8PWK.js} +2 -2
- package/dist/lib/{shadow-claw-task-server-DV4b1g2-.js.map → shadow-claw-task-server-DNOe8PWK.js.map} +1 -1
- package/dist/lib/{shadow-claw-tasks-K-foOHSE.js → shadow-claw-tasks-qmcDT4PU.js} +3 -3
- package/dist/lib/{shadow-claw-tasks-K-foOHSE.js.map → shadow-claw-tasks-qmcDT4PU.js.map} +1 -1
- package/dist/lib/{shadow-claw-terminal-CpAknKyA.js → shadow-claw-terminal-0QUWP8of.js} +2 -2
- package/dist/lib/{shadow-claw-terminal-CpAknKyA.js.map → shadow-claw-terminal-0QUWP8of.js.map} +1 -1
- package/dist/lib/{shadow-claw-tools-D98XZ0Ep.js → shadow-claw-tools-Bs_bmI6-.js} +3 -3
- package/dist/lib/{shadow-claw-tools-D98XZ0Ep.js.map → shadow-claw-tools-Bs_bmI6-.js.map} +1 -1
- package/dist/lib/{shadow-claw-webvm-DLOb8dMI.js → shadow-claw-webvm-DYaoL7QY.js} +2 -2
- package/dist/lib/{shadow-claw-webvm-DLOb8dMI.js.map → shadow-claw-webvm-DYaoL7QY.js.map} +1 -1
- package/dist/lib/{syncWebMcpRegistration-NspKefOI.js → syncWebMcpRegistration-BhbqI6R2.js} +2 -2
- package/dist/lib/{syncWebMcpRegistration-NspKefOI.js.map → syncWebMcpRegistration-BhbqI6R2.js.map} +1 -1
- package/dist/public/.well-known/mcp/server-card.json +1 -1
- package/dist/public/.well-known/mcp.json +1 -1
- package/dist/public/AGENTS.md +22 -3
- package/dist/public/README.md +96 -278
- package/dist/public/agent.worker.js +50 -50
- package/dist/public/{base-task-model-CYkpwnvU-DDfHUUIS.js → base-task-model-CYkpwnvU-DrXr4G1R.js} +1 -1
- package/dist/public/{configurePeerJs-BhBokQlC.js → configurePeerJs-B470pdeU.js} +1 -1
- package/dist/public/docs/README.md +31 -31
- package/dist/public/docs/architecture/context-management.md +7 -0
- package/dist/public/docs/architecture/storage.md +10 -1
- package/dist/public/docs/decisions/bundled-typescript-architecture.md +0 -1
- package/dist/public/docs/decisions/native-web-components-and-signals.md +0 -1
- package/dist/public/docs/example/article/index.html +1 -1
- package/dist/public/docs/guides/publishing-to-github-pages.md +334 -0
- package/dist/public/docs/guides/server-development-configuration.md +15 -0
- package/dist/public/docs/publishing/index.html +1 -1
- package/dist/public/docs/skill-creator/index.html +1 -1
- package/dist/public/docs/subsystems/cli.md +10 -2
- package/dist/public/docs/subsystems/control-plane.md +9 -8
- package/dist/public/docs/subsystems/crypto.md +9 -0
- package/dist/public/docs/subsystems/custom-element-security.md +2 -0
- package/dist/public/docs/subsystems/mcp-server.md +31 -16
- package/dist/public/docs/subsystems/notifications.md +8 -5
- package/dist/public/docs/subsystems/providers.md +9 -0
- package/dist/public/docs/subsystems/share-target.md +107 -0
- package/dist/public/docs/subsystems/webmcp.md +43 -0
- package/dist/public/e2e/README.md +4 -2
- package/dist/public/{e2e-bridge-DcBoeFvd.js → e2e-bridge-CSW0S5Yz.js} +1 -1
- package/dist/public/{file-viewer-CKtVC9Ix.js → file-viewer-Cs-riNvy.js} +1 -1
- package/dist/public/{firebase-OSDX3viP.js → firebase-cwAl_ABm.js} +1 -1
- package/dist/public/{git-CkhCJonj.js → git-GD09WATp.js} +25 -25
- package/dist/public/index.html +1 -1
- package/dist/public/index.js +1 -1
- package/dist/public/{initControlPlane-Bi8ouFEb.js → initControlPlane-BqCY0NQy.js} +1 -1
- package/dist/public/initControlPlane-C-IXaKYX.js +1 -0
- package/dist/public/{language-detector-BRKrlra0.js → language-detector-CIeEuQFk.js} +1 -1
- package/dist/public/llms.txt +4 -2
- package/dist/public/main/index.html +1 -1
- package/dist/public/main/memory/index.html +1 -1
- package/dist/public/{markdown-C-hCVJtc.js → markdown-Cw7vCtTg.js} +1 -1
- package/dist/public/{openai-Bpd71Oja.js → openai-BJIb2c5j.js} +3 -3
- package/dist/public/{orchestrator-8qNGfw8r.js → orchestrator-BWkHHO9Y.js} +1 -1
- package/dist/public/{orchestrator-BT3Ks5ip.js → orchestrator-DpkkeD6T.js} +1 -1
- package/dist/public/{peerjs-DfhwEiiq.js → peerjs-tf2EmWjB.js} +1 -1
- package/dist/public/{prompt-api-polyfill-Dl1kbJhR.js → prompt-api-polyfill-CXd75leW.js} +1 -1
- package/dist/public/{push-client-BVLrE9pK.js → push-client-CHjSbrWs.js} +1 -1
- package/dist/public/{rewriter-j6D9LDER.js → rewriter-BEHn9gll.js} +1 -1
- package/dist/public/service-worker.js +1 -1
- package/dist/public/{shadow-claw-DCjxNDCX.js → shadow-claw-BBPV_9X8.js} +2 -2
- package/dist/public/{shadow-claw-channels-CITDrqMl.js → shadow-claw-channels-BJvrKQjg.js} +1 -1
- package/dist/public/{shadow-claw-chat-BQrSgSKF.js → shadow-claw-chat-BG2EaRaw.js} +1 -1
- package/dist/public/{shadow-claw-control-plane-Cp001OY0.js → shadow-claw-control-plane-BBa7WbTg.js} +2 -2
- package/dist/public/{shadow-claw-conversations-EhX4RvIj.js → shadow-claw-conversations-CFaWFmkm.js} +1 -1
- package/dist/public/{shadow-claw-file-viewer-DiQXIjZD.js → shadow-claw-file-viewer-Z-GIKaE9.js} +1 -1
- package/dist/public/{shadow-claw-files-lkTnSJsn.js → shadow-claw-files-BBx4iMsk.js} +1 -1
- package/dist/public/{shadow-claw-git-BlcA_z8x.js → shadow-claw-git-vTOKatwd.js} +2 -2
- package/dist/public/{shadow-claw-llm-CW16X9Rg.js → shadow-claw-llm-Ce1Ax6-x.js} +1 -1
- package/dist/public/{shadow-claw-networking-ySsMBY_i.js → shadow-claw-networking-q4XIzdB5.js} +1 -1
- package/dist/public/{shadow-claw-notifications-CyV2zCQ8.js → shadow-claw-notifications-D2cz0gmm.js} +1 -1
- package/dist/public/{shadow-claw-pages-Cx7cF9ah.js → shadow-claw-pages-3335lmSC.js} +1 -1
- package/dist/public/{shadow-claw-settings-zm4PHHGi.js → shadow-claw-settings-4ptYuJwO.js} +2 -2
- package/dist/public/{shadow-claw-storage-dy9JAEGd.js → shadow-claw-storage-bIrPKFhF.js} +1 -1
- package/dist/public/{shadow-claw-task-server-Bleww48s.js → shadow-claw-task-server-5yqVBxy1.js} +1 -1
- package/dist/public/{shadow-claw-tasks-CTI5oaLN.js → shadow-claw-tasks-DcqYHPLZ.js} +1 -1
- package/dist/public/{shadow-claw-terminal-mZ3i4HaQ.js → shadow-claw-terminal-fARaDONr.js} +1 -1
- package/dist/public/{shadow-claw-tools-B_nuk8Zl.js → shadow-claw-tools-paEAhcJr.js} +1 -1
- package/dist/public/{shadow-claw-webvm-Vl_jH5YL.js → shadow-claw-webvm-CbzH_vyX.js} +1 -1
- package/dist/public/{summarizer-VJNr-7xG.js → summarizer-BT3AuFed.js} +1 -1
- package/dist/public/{syncWebMcpRegistration-n2vAMJ4w.js → syncWebMcpRegistration-CWSYaHex.js} +1 -1
- package/dist/public/{transformers-BhQMWQG5.js → transformers-1VTxqKjT.js} +1 -1
- package/dist/public/{translator-DEQXb7ZL.js → translator-B50RYlFt.js} +1 -1
- package/dist/public/{webllm-CHN5H-Sy.js → webllm-FmyJWWpt.js} +1 -1
- package/dist/public/{writer-CD9oN2pq.js → writer-Bhb4YYRE.js} +1 -1
- package/dist/server.js +205 -203
- package/package.json +1 -1
- package/src/components/settings/shadow-claw-llm/shadow-claw-llm.test.ts +2 -0
- package/src/core/orchestrator/utils/configurePeerJs.test.ts +2 -0
- package/src/server/client-registry.test.ts +9 -0
- package/src/server/client-registry.ts +14 -0
- package/src/server/mcp/mcp-integration.test.ts +68 -16
- package/src/server/mcp/tools/built-in-tools.ts +125 -34
- package/src/server/mcp/tools/client-tool-relay.ts +81 -44
- package/src/server/mcp/tools/mcp-tools.test.ts +103 -13
- package/src/server/mcp/types.ts +3 -0
- package/src/server/openapi/paths/push.ts +48 -0
- package/src/server/server.ts +30 -0
- package/src/subsystems/notifications/push-client.test.ts +17 -0
- package/src/subsystems/notifications/push-client.ts +22 -2
- package/src/subsystems/notifications/push-routes.test.ts +31 -0
- package/src/subsystems/notifications/push-routes.ts +10 -0
- package/src/subsystems/notifications/push-store.test.ts +49 -0
- package/src/subsystems/notifications/push-store.ts +45 -0
- package/src/worker/worker.test.ts +2 -0
- package/dist/lib/push-client-D6hbzDNV.js.map +0 -1
package/package.json
CHANGED
|
@@ -58,6 +58,8 @@ jest.unstable_mockModule("../../../config/config.js", () => ({
|
|
|
58
58
|
TRANSFORMERS_JS_DTYPE_STRATEGY: "transformers_js_dtype_strategy",
|
|
59
59
|
},
|
|
60
60
|
DEFAULT_MAX_ITERATIONS: 50,
|
|
61
|
+
DEFAULT_CONTROL_PLANE_ENABLED: false,
|
|
62
|
+
DEFAULT_CONTROL_PLANE_TRANSPORT: "http",
|
|
61
63
|
DEFAULT_SUBAGENT_MAX_PARALLEL: 5,
|
|
62
64
|
DEFAULT_SUBAGENT_WORKSPACE_MODE: "automatic",
|
|
63
65
|
DEFAULT_PROMPT_API_FALLBACK_MODEL: "onnx-community/Qwen3-0.6B-ONNX",
|
|
@@ -60,6 +60,8 @@ const mockConfig = {
|
|
|
60
60
|
getModelMaxTokens: jest.fn().mockReturnValue(128000),
|
|
61
61
|
buildTriggerPattern: jest.fn().mockReturnValue(new RegExp("")),
|
|
62
62
|
DEFAULT_GROUP_ID: "br:main",
|
|
63
|
+
DEFAULT_CONTROL_PLANE_ENABLED: false,
|
|
64
|
+
DEFAULT_CONTROL_PLANE_TRANSPORT: "http",
|
|
63
65
|
OPFS_ROOT: "shadowclaw",
|
|
64
66
|
LLAMAFILE_PROXY_URL: "/proxy/llamafile",
|
|
65
67
|
};
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
getControlTokenFilePath,
|
|
15
15
|
} from "./client-registry.js";
|
|
16
16
|
import fs from "node:fs";
|
|
17
|
+
import { tmpdir } from "node:os";
|
|
17
18
|
import path from "node:path";
|
|
18
19
|
|
|
19
20
|
beforeEach(() => {
|
|
@@ -246,5 +247,13 @@ describe("client-registry", () => {
|
|
|
246
247
|
expect(rawJson.token).toBe("my-secret-test-token");
|
|
247
248
|
expect(rawJson.createdAt).toBe(1700000000000);
|
|
248
249
|
});
|
|
250
|
+
|
|
251
|
+
it("mirrors saved token to system tmp directory", () => {
|
|
252
|
+
saveControlTokenFile("tmp-mirrored-token", tempCacheDir, 1700000000000);
|
|
253
|
+
const tmpFile = path.join(tmpdir(), "shadow-claw", "control-token.json");
|
|
254
|
+
expect(fs.existsSync(tmpFile)).toBe(true);
|
|
255
|
+
const content = JSON.parse(fs.readFileSync(tmpFile, "utf8"));
|
|
256
|
+
expect(content.token).toBe("tmp-mirrored-token");
|
|
257
|
+
});
|
|
249
258
|
});
|
|
250
259
|
});
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import { randomBytes } from "node:crypto";
|
|
9
9
|
import fs from "node:fs";
|
|
10
|
+
import { tmpdir } from "node:os";
|
|
10
11
|
import path from "node:path";
|
|
11
12
|
import { DatabaseSync } from "node:sqlite";
|
|
12
13
|
import type { ClientInfo, BackupRecord } from "./control-plane-types.js";
|
|
@@ -281,6 +282,19 @@ export function saveControlTokenFile(
|
|
|
281
282
|
fs.writeFileSync(filePath, JSON.stringify(payload, null, 2) + "\n", "utf8");
|
|
282
283
|
} catch (_) {}
|
|
283
284
|
|
|
285
|
+
// Mirror to system tmp directory so CLI commands run from any working directory can locate it
|
|
286
|
+
try {
|
|
287
|
+
const tmpCacheDir = path.join(tmpdir(), "shadow-claw");
|
|
288
|
+
if (!fs.existsSync(tmpCacheDir)) {
|
|
289
|
+
fs.mkdirSync(tmpCacheDir, { recursive: true });
|
|
290
|
+
}
|
|
291
|
+
fs.writeFileSync(
|
|
292
|
+
path.join(tmpCacheDir, "control-token.json"),
|
|
293
|
+
JSON.stringify(payload, null, 2) + "\n",
|
|
294
|
+
"utf8",
|
|
295
|
+
);
|
|
296
|
+
} catch (_) {}
|
|
297
|
+
|
|
284
298
|
return payload;
|
|
285
299
|
}
|
|
286
300
|
|
|
@@ -239,17 +239,18 @@ describe("End-to-End MCP Server & Control Plane Relay", () => {
|
|
|
239
239
|
const toolNames = res.body.result.tools.map((t: any) => t.name);
|
|
240
240
|
|
|
241
241
|
// Built-in tools
|
|
242
|
-
expect(toolNames).toContain("
|
|
243
|
-
expect(toolNames).toContain("
|
|
244
|
-
expect(toolNames).toContain("
|
|
245
|
-
expect(toolNames).toContain("
|
|
242
|
+
expect(toolNames).toContain("shadowclaw_server_list_clients");
|
|
243
|
+
expect(toolNames).toContain("shadowclaw_server_send_message");
|
|
244
|
+
expect(toolNames).toContain("shadowclaw_server_read_state");
|
|
245
|
+
expect(toolNames).toContain("shadowclaw_server_list_tasks");
|
|
246
246
|
|
|
247
247
|
// Relayed browser tools
|
|
248
|
-
expect(toolNames).toContain("
|
|
249
|
-
expect(toolNames).toContain("
|
|
248
|
+
expect(toolNames).toContain("shadowclaw_client_read_file");
|
|
249
|
+
expect(toolNames).toContain("shadowclaw_client_workspace_status");
|
|
250
|
+
expect(toolNames).toContain("shadowclaw_client_ask_user");
|
|
250
251
|
});
|
|
251
252
|
|
|
252
|
-
it("calls built-in tool
|
|
253
|
+
it("calls built-in tool shadowclaw_server_read_state and returns browser state", async () => {
|
|
253
254
|
const res = await postMcp(
|
|
254
255
|
port,
|
|
255
256
|
token,
|
|
@@ -258,11 +259,14 @@ describe("End-to-End MCP Server & Control Plane Relay", () => {
|
|
|
258
259
|
id: 3,
|
|
259
260
|
method: "tools/call",
|
|
260
261
|
params: {
|
|
261
|
-
name: "
|
|
262
|
+
name: "shadowclaw_server_read_state",
|
|
262
263
|
arguments: { clientId: "test-browser-client-e2e" },
|
|
263
264
|
},
|
|
264
265
|
},
|
|
265
|
-
{
|
|
266
|
+
{
|
|
267
|
+
"mcp-method": "tools/call",
|
|
268
|
+
"mcp-name": "shadowclaw_server_read_state",
|
|
269
|
+
},
|
|
266
270
|
);
|
|
267
271
|
|
|
268
272
|
expect(res.status).toBe(200);
|
|
@@ -270,9 +274,28 @@ describe("End-to-End MCP Server & Control Plane Relay", () => {
|
|
|
270
274
|
const content = res.body.result.content[0].text;
|
|
271
275
|
expect(content).toContain("br:main");
|
|
272
276
|
expect(content).toContain("claude-3-5-sonnet");
|
|
277
|
+
|
|
278
|
+
// Calling via legacy alias shadowclaw_read_state also succeeds
|
|
279
|
+
const resAlias = await postMcp(
|
|
280
|
+
port,
|
|
281
|
+
token,
|
|
282
|
+
{
|
|
283
|
+
jsonrpc: "2.0",
|
|
284
|
+
id: 31,
|
|
285
|
+
method: "tools/call",
|
|
286
|
+
params: {
|
|
287
|
+
name: "shadowclaw_read_state",
|
|
288
|
+
arguments: { clientId: "test-browser-client-e2e" },
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
{ "mcp-method": "tools/call", "mcp-name": "shadowclaw_read_state" },
|
|
292
|
+
);
|
|
293
|
+
expect(resAlias.status).toBe(200);
|
|
294
|
+
expect(resAlias.body.result.resultType).toBe("complete");
|
|
295
|
+
expect(resAlias.body.result.content[0].text).toContain("br:main");
|
|
273
296
|
});
|
|
274
297
|
|
|
275
|
-
it("calls relayed browser tool
|
|
298
|
+
it("calls relayed browser tool shadowclaw_client_read_file across Control Plane and receives response", async () => {
|
|
276
299
|
const res = await postMcp(
|
|
277
300
|
port,
|
|
278
301
|
token,
|
|
@@ -281,11 +304,14 @@ describe("End-to-End MCP Server & Control Plane Relay", () => {
|
|
|
281
304
|
id: 4,
|
|
282
305
|
method: "tools/call",
|
|
283
306
|
params: {
|
|
284
|
-
name: "
|
|
307
|
+
name: "shadowclaw_client_read_file",
|
|
285
308
|
arguments: { path: "src/main.ts" },
|
|
286
309
|
},
|
|
287
310
|
},
|
|
288
|
-
{
|
|
311
|
+
{
|
|
312
|
+
"mcp-method": "tools/call",
|
|
313
|
+
"mcp-name": "shadowclaw_client_read_file",
|
|
314
|
+
},
|
|
289
315
|
);
|
|
290
316
|
|
|
291
317
|
expect(res.status).toBe(200);
|
|
@@ -295,6 +321,26 @@ describe("End-to-End MCP Server & Control Plane Relay", () => {
|
|
|
295
321
|
expect(text).toContain(
|
|
296
322
|
"Content of file src/main.ts: Hello from Browser OPFS!",
|
|
297
323
|
);
|
|
324
|
+
|
|
325
|
+
// Also verify backward compatibility: calling with unprefixed name also proxies
|
|
326
|
+
const resUnprefixed = await postMcp(
|
|
327
|
+
port,
|
|
328
|
+
token,
|
|
329
|
+
{
|
|
330
|
+
jsonrpc: "2.0",
|
|
331
|
+
id: 41,
|
|
332
|
+
method: "tools/call",
|
|
333
|
+
params: {
|
|
334
|
+
name: "read_file",
|
|
335
|
+
arguments: { path: "src/main.ts" },
|
|
336
|
+
},
|
|
337
|
+
},
|
|
338
|
+
{ "mcp-method": "tools/call", "mcp-name": "read_file" },
|
|
339
|
+
);
|
|
340
|
+
expect(resUnprefixed.status).toBe(200);
|
|
341
|
+
expect(resUnprefixed.body.result.content[0].text).toContain(
|
|
342
|
+
"Content of file src/main.ts: Hello from Browser OPFS!",
|
|
343
|
+
);
|
|
298
344
|
});
|
|
299
345
|
|
|
300
346
|
it("handles MRTR interactive flow for ask_user tool", async () => {
|
|
@@ -307,11 +353,14 @@ describe("End-to-End MCP Server & Control Plane Relay", () => {
|
|
|
307
353
|
id: 5,
|
|
308
354
|
method: "tools/call",
|
|
309
355
|
params: {
|
|
310
|
-
name: "
|
|
356
|
+
name: "shadowclaw_client_ask_user",
|
|
311
357
|
arguments: { question: "Are you ready?" },
|
|
312
358
|
},
|
|
313
359
|
},
|
|
314
|
-
{
|
|
360
|
+
{
|
|
361
|
+
"mcp-method": "tools/call",
|
|
362
|
+
"mcp-name": "shadowclaw_client_ask_user",
|
|
363
|
+
},
|
|
315
364
|
);
|
|
316
365
|
|
|
317
366
|
expect(res1.status).toBe(200);
|
|
@@ -328,12 +377,15 @@ describe("End-to-End MCP Server & Control Plane Relay", () => {
|
|
|
328
377
|
id: 6,
|
|
329
378
|
method: "tools/call",
|
|
330
379
|
params: {
|
|
331
|
-
name: "
|
|
380
|
+
name: "shadowclaw_client_ask_user",
|
|
332
381
|
arguments: { question: "Are you ready?" },
|
|
333
382
|
inputResponses: { response: "Yes, ready to proceed!" },
|
|
334
383
|
},
|
|
335
384
|
},
|
|
336
|
-
{
|
|
385
|
+
{
|
|
386
|
+
"mcp-method": "tools/call",
|
|
387
|
+
"mcp-name": "shadowclaw_client_ask_user",
|
|
388
|
+
},
|
|
337
389
|
);
|
|
338
390
|
|
|
339
391
|
expect(res2.status).toBe(200);
|
|
@@ -7,12 +7,100 @@
|
|
|
7
7
|
|
|
8
8
|
import type { McpServer } from "../mcp-server.js";
|
|
9
9
|
import type { McpTool } from "../types.js";
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
broadcastPush,
|
|
12
|
+
getRegisteredPushClients,
|
|
13
|
+
type PushClientRecord,
|
|
14
|
+
} from "../../../subsystems/notifications/push-routes.js";
|
|
11
15
|
import { getPackageVersion } from "../../utils/packageVersion.js";
|
|
12
16
|
|
|
17
|
+
export function buildSendNotificationTool(
|
|
18
|
+
pushClients: Array<
|
|
19
|
+
PushClientRecord | { clientId: string; deviceLabel?: string }
|
|
20
|
+
> = [],
|
|
21
|
+
name: string = "shadowclaw_server_send_notification",
|
|
22
|
+
): McpTool {
|
|
23
|
+
const sortedPushClients = [...pushClients].sort((a, b) =>
|
|
24
|
+
a.clientId.localeCompare(b.clientId),
|
|
25
|
+
);
|
|
26
|
+
const pushClientIds = sortedPushClients
|
|
27
|
+
.map((c) => c.clientId)
|
|
28
|
+
.filter(Boolean);
|
|
29
|
+
|
|
30
|
+
const baseDescription =
|
|
31
|
+
"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.";
|
|
32
|
+
|
|
33
|
+
const clientIdProp: any = {
|
|
34
|
+
type: "string",
|
|
35
|
+
description:
|
|
36
|
+
pushClientIds.length > 0
|
|
37
|
+
? `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(", ")}`
|
|
38
|
+
: "Target client ID, prefix, or device label of a specific client that has registered in the past. If omitted, broadcasts to all subscribed devices.",
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
if (pushClientIds.length > 0) {
|
|
42
|
+
clientIdProp.enum = pushClientIds;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
name,
|
|
47
|
+
description: baseDescription,
|
|
48
|
+
inputSchema: {
|
|
49
|
+
type: "object",
|
|
50
|
+
properties: {
|
|
51
|
+
title: {
|
|
52
|
+
type: "string",
|
|
53
|
+
description: "Notification title (default: 'ShadowClaw').",
|
|
54
|
+
},
|
|
55
|
+
body: {
|
|
56
|
+
type: "string",
|
|
57
|
+
description: "Notification body message text.",
|
|
58
|
+
},
|
|
59
|
+
clientId: clientIdProp,
|
|
60
|
+
},
|
|
61
|
+
required: ["body"],
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function getDynamicSendNotificationTool(
|
|
67
|
+
controlPlane?: any,
|
|
68
|
+
name: string = "shadowclaw_server_send_notification",
|
|
69
|
+
): McpTool {
|
|
70
|
+
let pushClients: PushClientRecord[] = [];
|
|
71
|
+
try {
|
|
72
|
+
pushClients = getRegisteredPushClients();
|
|
73
|
+
} catch (_) {}
|
|
74
|
+
|
|
75
|
+
if (controlPlane && Array.isArray(pushClients) && pushClients.length > 0) {
|
|
76
|
+
try {
|
|
77
|
+
const connected =
|
|
78
|
+
typeof controlPlane.getConnectedClients === "function"
|
|
79
|
+
? controlPlane.getConnectedClients()
|
|
80
|
+
: typeof controlPlane.listClients === "function"
|
|
81
|
+
? controlPlane.listClients()
|
|
82
|
+
: [];
|
|
83
|
+
if (Array.isArray(connected)) {
|
|
84
|
+
for (const pc of pushClients) {
|
|
85
|
+
if (!pc.deviceLabel) {
|
|
86
|
+
const match = connected.find(
|
|
87
|
+
(c: any) => (c.clientId || c.id) === pc.clientId,
|
|
88
|
+
);
|
|
89
|
+
if (match && match.deviceLabel) {
|
|
90
|
+
pc.deviceLabel = match.deviceLabel;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
} catch (_) {}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return buildSendNotificationTool(pushClients, name);
|
|
99
|
+
}
|
|
100
|
+
|
|
13
101
|
export const SHADOWCLAW_BUILTIN_TOOLS: McpTool[] = [
|
|
14
102
|
{
|
|
15
|
-
name: "
|
|
103
|
+
name: "shadowclaw_server_list_clients",
|
|
16
104
|
description:
|
|
17
105
|
"List connected browser and Electron clients, including device type, ID, and active capabilities.",
|
|
18
106
|
inputSchema: {
|
|
@@ -21,7 +109,7 @@ export const SHADOWCLAW_BUILTIN_TOOLS: McpTool[] = [
|
|
|
21
109
|
},
|
|
22
110
|
},
|
|
23
111
|
{
|
|
24
|
-
name: "
|
|
112
|
+
name: "shadowclaw_server_send_message",
|
|
25
113
|
description:
|
|
26
114
|
"Send a message or prompt to a connected ShadowClaw client's active AI orchestrator queue.",
|
|
27
115
|
inputSchema: {
|
|
@@ -46,7 +134,7 @@ export const SHADOWCLAW_BUILTIN_TOOLS: McpTool[] = [
|
|
|
46
134
|
},
|
|
47
135
|
},
|
|
48
136
|
{
|
|
49
|
-
name: "
|
|
137
|
+
name: "shadowclaw_server_read_state",
|
|
50
138
|
description:
|
|
51
139
|
"Read the current orchestrator state (idle, responding, etc.), active group ID, and capabilities from a connected client.",
|
|
52
140
|
inputSchema: {
|
|
@@ -61,7 +149,7 @@ export const SHADOWCLAW_BUILTIN_TOOLS: McpTool[] = [
|
|
|
61
149
|
},
|
|
62
150
|
},
|
|
63
151
|
{
|
|
64
|
-
name: "
|
|
152
|
+
name: "shadowclaw_server_list_tasks",
|
|
65
153
|
description:
|
|
66
154
|
"List scheduled background tasks configured on a connected client (optionally filtered by conversation group).",
|
|
67
155
|
inputSchema: {
|
|
@@ -80,7 +168,7 @@ export const SHADOWCLAW_BUILTIN_TOOLS: McpTool[] = [
|
|
|
80
168
|
},
|
|
81
169
|
},
|
|
82
170
|
{
|
|
83
|
-
name: "
|
|
171
|
+
name: "shadowclaw_server_manage_backup",
|
|
84
172
|
description:
|
|
85
173
|
"Trigger or manage OPFS workspace file backups for a connected client (trigger, list, or delete).",
|
|
86
174
|
inputSchema: {
|
|
@@ -108,7 +196,7 @@ export const SHADOWCLAW_BUILTIN_TOOLS: McpTool[] = [
|
|
|
108
196
|
},
|
|
109
197
|
},
|
|
110
198
|
{
|
|
111
|
-
name: "
|
|
199
|
+
name: "shadowclaw_server_set_active_client",
|
|
112
200
|
description:
|
|
113
201
|
"Set the active default connected client used when no clientId is explicitly provided in tool calls.",
|
|
114
202
|
inputSchema: {
|
|
@@ -132,30 +220,7 @@ export const SHADOWCLAW_BUILTIN_TOOLS: McpTool[] = [
|
|
|
132
220
|
properties: {},
|
|
133
221
|
},
|
|
134
222
|
},
|
|
135
|
-
|
|
136
|
-
name: "shadowclaw_send_notification",
|
|
137
|
-
description:
|
|
138
|
-
"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.",
|
|
139
|
-
inputSchema: {
|
|
140
|
-
type: "object",
|
|
141
|
-
properties: {
|
|
142
|
-
title: {
|
|
143
|
-
type: "string",
|
|
144
|
-
description: "Notification title (default: 'ShadowClaw').",
|
|
145
|
-
},
|
|
146
|
-
body: {
|
|
147
|
-
type: "string",
|
|
148
|
-
description: "Notification body message text.",
|
|
149
|
-
},
|
|
150
|
-
clientId: {
|
|
151
|
-
type: "string",
|
|
152
|
-
description:
|
|
153
|
-
"Target client ID, prefix, or device label of a specific client that has registered in the past. If omitted, broadcasts to all subscribed devices.",
|
|
154
|
-
},
|
|
155
|
-
},
|
|
156
|
-
required: ["body"],
|
|
157
|
-
},
|
|
158
|
-
},
|
|
223
|
+
buildSendNotificationTool(),
|
|
159
224
|
];
|
|
160
225
|
|
|
161
226
|
export function resolveTargetClientId(
|
|
@@ -214,9 +279,14 @@ export function registerBuiltInTools(
|
|
|
214
279
|
server: McpServer,
|
|
215
280
|
controlPlane: any,
|
|
216
281
|
): void {
|
|
282
|
+
server.setToolProvider(async () => {
|
|
283
|
+
return [getDynamicSendNotificationTool(controlPlane)];
|
|
284
|
+
});
|
|
285
|
+
|
|
217
286
|
for (const tool of SHADOWCLAW_BUILTIN_TOOLS) {
|
|
218
|
-
|
|
287
|
+
const handler = async (args: Record<string, any>) => {
|
|
219
288
|
switch (tool.name) {
|
|
289
|
+
case "shadowclaw_server_list_clients":
|
|
220
290
|
case "shadowclaw_list_clients": {
|
|
221
291
|
const clients =
|
|
222
292
|
typeof controlPlane.getConnectedClients === "function"
|
|
@@ -235,6 +305,7 @@ export function registerBuiltInTools(
|
|
|
235
305
|
};
|
|
236
306
|
}
|
|
237
307
|
|
|
308
|
+
case "shadowclaw_server_send_message":
|
|
238
309
|
case "shadowclaw_send_message": {
|
|
239
310
|
const text = String(args.text || "").trim();
|
|
240
311
|
if (!text) {
|
|
@@ -278,6 +349,7 @@ export function registerBuiltInTools(
|
|
|
278
349
|
};
|
|
279
350
|
}
|
|
280
351
|
|
|
352
|
+
case "shadowclaw_server_read_state":
|
|
281
353
|
case "shadowclaw_read_state": {
|
|
282
354
|
const targetId = resolveTargetClientId(controlPlane, args.clientId);
|
|
283
355
|
if (!targetId) {
|
|
@@ -308,6 +380,7 @@ export function registerBuiltInTools(
|
|
|
308
380
|
};
|
|
309
381
|
}
|
|
310
382
|
|
|
383
|
+
case "shadowclaw_server_list_tasks":
|
|
311
384
|
case "shadowclaw_list_tasks": {
|
|
312
385
|
const targetId = resolveTargetClientId(controlPlane, args.clientId);
|
|
313
386
|
if (!targetId) {
|
|
@@ -337,6 +410,7 @@ export function registerBuiltInTools(
|
|
|
337
410
|
};
|
|
338
411
|
}
|
|
339
412
|
|
|
413
|
+
case "shadowclaw_server_manage_backup":
|
|
340
414
|
case "shadowclaw_manage_backup": {
|
|
341
415
|
const action = String(args.action || "trigger").toLowerCase();
|
|
342
416
|
const targetId = resolveTargetClientId(controlPlane, args.clientId);
|
|
@@ -426,6 +500,7 @@ export function registerBuiltInTools(
|
|
|
426
500
|
};
|
|
427
501
|
}
|
|
428
502
|
|
|
503
|
+
case "shadowclaw_server_set_active_client":
|
|
429
504
|
case "shadowclaw_set_active_client": {
|
|
430
505
|
const targetId = resolveTargetClientId(controlPlane, args.clientId);
|
|
431
506
|
if (!targetId) {
|
|
@@ -457,7 +532,8 @@ export function registerBuiltInTools(
|
|
|
457
532
|
};
|
|
458
533
|
}
|
|
459
534
|
|
|
460
|
-
case "shadowclaw_server_status":
|
|
535
|
+
case "shadowclaw_server_status":
|
|
536
|
+
case "shadowclaw_status": {
|
|
461
537
|
const clients =
|
|
462
538
|
typeof controlPlane.getConnectedClients === "function"
|
|
463
539
|
? controlPlane.getConnectedClients()
|
|
@@ -482,6 +558,7 @@ export function registerBuiltInTools(
|
|
|
482
558
|
};
|
|
483
559
|
}
|
|
484
560
|
|
|
561
|
+
case "shadowclaw_server_send_notification":
|
|
485
562
|
case "shadowclaw_send_notification":
|
|
486
563
|
case "send_notification": {
|
|
487
564
|
const body = String(args.body || "").trim();
|
|
@@ -549,6 +626,20 @@ export function registerBuiltInTools(
|
|
|
549
626
|
content: [{ type: "text", text: `Unknown tool: '${tool.name}'` }],
|
|
550
627
|
};
|
|
551
628
|
}
|
|
552
|
-
}
|
|
629
|
+
};
|
|
630
|
+
|
|
631
|
+
server.registerTool(tool, handler);
|
|
632
|
+
|
|
633
|
+
if (tool.name.startsWith("shadowclaw_server_")) {
|
|
634
|
+
const legacyAlias = `shadowclaw_${tool.name.slice("shadowclaw_server_".length)}`;
|
|
635
|
+
server.registerToolHandler(legacyAlias, handler);
|
|
636
|
+
}
|
|
637
|
+
if (
|
|
638
|
+
tool.name === "shadowclaw_server_send_notification" ||
|
|
639
|
+
tool.name === "shadowclaw_send_notification"
|
|
640
|
+
) {
|
|
641
|
+
server.registerToolHandler("shadowclaw_send_notification", handler);
|
|
642
|
+
server.registerToolHandler("send_notification", handler);
|
|
643
|
+
}
|
|
553
644
|
}
|
|
554
645
|
}
|