shadow-claw 1.27.0 → 1.28.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/ai-catalog.json +11 -0
- package/.well-known/mcp/index.html +19 -0
- package/.well-known/mcp/server-card.json +52 -0
- package/.well-known/mcp/servers.json +15 -0
- package/.well-known/mcp.json +52 -0
- package/.well-known/oauth-protected-resource +10 -0
- package/.well-known/oauth-protected-resource.json +10 -0
- package/bin/assert-version-bump.mjs +61 -3
- package/bin/assert-version-bump.test.mjs +77 -1
- package/bin/build/build.mjs +116 -49
- package/bin/build/build.test.mjs +103 -0
- package/bin/cli.mjs +299 -237
- package/bin/cli.test.mjs +27 -0
- package/bin/commands/mcp.mjs +132 -2
- package/bin/commands/mcp.test.mjs +166 -0
- package/bin/commands/skills-index.mjs +139 -20
- package/bin/commands/skills-index.test.mjs +82 -0
- package/bin/utils/control-client.mjs +17 -0
- package/bin/utils/control-client.test.mjs +46 -0
- package/bin/version.mjs +208 -0
- package/bin/version.test.mjs +138 -0
- package/dist/electron/main.cjs +195 -185
- 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/shadow-claw.d.ts +1 -0
- package/dist/lib/components/shadow-claw/utils/setupAppLifecycle.d.ts +23 -0
- 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-oODzltaC.js → configurePeerJs-D8Ph0-A9.js} +2 -2
- package/dist/lib/{configurePeerJs-oODzltaC.js.map → configurePeerJs-D8Ph0-A9.js.map} +1 -1
- package/dist/lib/core/control-plane-client.d.ts +2 -0
- package/dist/lib/core/orchestrator/orchestrator.d.ts +10 -0
- package/dist/lib/core/orchestrator/utils/operations/channel.d.ts +2 -2
- package/dist/lib/core/utils/initControlPlane.d.ts +6 -0
- package/dist/lib/index.js +21 -21
- package/dist/lib/{initControlPlane-C2zURVIS.js → initControlPlane-BIrWmpzc.js} +76 -9
- package/dist/lib/initControlPlane-BIrWmpzc.js.map +1 -0
- package/dist/lib/{markdown-C6EHl9zs.js → markdown-DEUoM1c6.js} +2 -2
- package/dist/lib/{markdown-C6EHl9zs.js.map → markdown-DEUoM1c6.js.map} +1 -1
- package/dist/lib/{orchestrator-KRjx0eba.js → orchestrator-BtVj9bBB.js} +11 -10
- package/dist/lib/{orchestrator-KRjx0eba.js.map → orchestrator-BtVj9bBB.js.map} +1 -1
- package/dist/lib/{orchestrator-CACLa_Ro.js → orchestrator-CFCW-l1O.js} +52 -4
- package/dist/lib/orchestrator-CFCW-l1O.js.map +1 -0
- package/dist/lib/{peerjs-DFWOBcXf.js → peerjs-BoBkx9DJ.js} +23 -3
- package/dist/lib/peerjs-BoBkx9DJ.js.map +1 -0
- package/dist/lib/{push-client-_tiWQsCT.js → push-client-D6hbzDNV.js} +10 -4
- package/dist/lib/push-client-D6hbzDNV.js.map +1 -0
- package/dist/lib/{shadow-claw-6UHdiy4a.js → shadow-claw-Cuf2O5bZ.js} +71 -4
- package/dist/lib/shadow-claw-Cuf2O5bZ.js.map +1 -0
- package/dist/lib/{shadow-claw-channel-config-DmfwJc6G.js → shadow-claw-channel-config-BDPKt6Bd.js} +3 -3
- package/dist/lib/{shadow-claw-channel-config-DmfwJc6G.js.map → shadow-claw-channel-config-BDPKt6Bd.js.map} +1 -1
- package/dist/lib/{shadow-claw-channels-1N6Jl-By.js → shadow-claw-channels-BsP5JABM.js} +2 -2
- package/dist/lib/{shadow-claw-channels-1N6Jl-By.js.map → shadow-claw-channels-BsP5JABM.js.map} +1 -1
- package/dist/lib/{shadow-claw-chat-BMoqQJRK.js → shadow-claw-chat-Cam2lX4k.js} +4 -4
- package/dist/lib/{shadow-claw-chat-BMoqQJRK.js.map → shadow-claw-chat-Cam2lX4k.js.map} +1 -1
- package/dist/lib/{shadow-claw-control-plane-UG0o7pCz.js → shadow-claw-control-plane-DB2qcCFs.js} +20 -10
- package/dist/lib/shadow-claw-control-plane-DB2qcCFs.js.map +1 -0
- package/dist/lib/{shadow-claw-conversations-BwXep3iu.js → shadow-claw-conversations-C9Vf4XGx.js} +2 -2
- package/dist/lib/{shadow-claw-conversations-BwXep3iu.js.map → shadow-claw-conversations-C9Vf4XGx.js.map} +1 -1
- package/dist/lib/{shadow-claw-file-viewer-DUxzVxDT.js → shadow-claw-file-viewer-B4HYiI0v.js} +3 -3
- package/dist/lib/{shadow-claw-file-viewer-DUxzVxDT.js.map → shadow-claw-file-viewer-B4HYiI0v.js.map} +1 -1
- package/dist/lib/{shadow-claw-files-DIREEnT2.js → shadow-claw-files-DVROUESO.js} +2 -2
- package/dist/lib/{shadow-claw-files-DIREEnT2.js.map → shadow-claw-files-DVROUESO.js.map} +1 -1
- package/dist/lib/{shadow-claw-git-Bg1SWgsF.js → shadow-claw-git-BqrO7cHJ.js} +3 -3
- package/dist/lib/{shadow-claw-git-Bg1SWgsF.js.map → shadow-claw-git-BqrO7cHJ.js.map} +1 -1
- package/dist/lib/{shadow-claw-llm-CazBrVpM.js → shadow-claw-llm-BexUQdYo.js} +2 -2
- package/dist/lib/{shadow-claw-llm-CazBrVpM.js.map → shadow-claw-llm-BexUQdYo.js.map} +1 -1
- package/dist/lib/{shadow-claw-networking-Df85tCbO.js → shadow-claw-networking-CfXRGpUD.js} +2 -2
- package/dist/lib/{shadow-claw-networking-Df85tCbO.js.map → shadow-claw-networking-CfXRGpUD.js.map} +1 -1
- package/dist/lib/{shadow-claw-notifications-v62FryTc.js → shadow-claw-notifications-C9KPMgx_.js} +2 -2
- package/dist/lib/{shadow-claw-notifications-v62FryTc.js.map → shadow-claw-notifications-C9KPMgx_.js.map} +1 -1
- package/dist/lib/{shadow-claw-pages-BgPyIEZO.js → shadow-claw-pages-CGzGXmrI.js} +3 -3
- package/dist/lib/{shadow-claw-pages-BgPyIEZO.js.map → shadow-claw-pages-CGzGXmrI.js.map} +1 -1
- package/dist/lib/{shadow-claw-peerjs-xkBXILed.js → shadow-claw-peerjs-B0ZB7yhB.js} +3 -3
- package/dist/lib/{shadow-claw-peerjs-xkBXILed.js.map → shadow-claw-peerjs-B0ZB7yhB.js.map} +1 -1
- package/dist/lib/{shadow-claw-settings-CN89xRVU.js → shadow-claw-settings-DBfMZdDS.js} +2 -2
- package/dist/lib/{shadow-claw-settings-CN89xRVU.js.map → shadow-claw-settings-DBfMZdDS.js.map} +1 -1
- package/dist/lib/{shadow-claw-storage-VnhcObCe.js → shadow-claw-storage-CM-w6mgG.js} +2 -2
- package/dist/lib/{shadow-claw-storage-VnhcObCe.js.map → shadow-claw-storage-CM-w6mgG.js.map} +1 -1
- package/dist/lib/{shadow-claw-task-server-jgEC2hHB.js → shadow-claw-task-server-DV4b1g2-.js} +4 -2
- package/dist/lib/{shadow-claw-task-server-jgEC2hHB.js.map → shadow-claw-task-server-DV4b1g2-.js.map} +1 -1
- package/dist/lib/{shadow-claw-tasks-i_evIufy.js → shadow-claw-tasks-K-foOHSE.js} +3 -3
- package/dist/lib/{shadow-claw-tasks-i_evIufy.js.map → shadow-claw-tasks-K-foOHSE.js.map} +1 -1
- package/dist/lib/{shadow-claw-terminal-CwScROlu.js → shadow-claw-terminal-CpAknKyA.js} +2 -2
- package/dist/lib/{shadow-claw-terminal-CwScROlu.js.map → shadow-claw-terminal-CpAknKyA.js.map} +1 -1
- package/dist/lib/{shadow-claw-tools-DD3fyXP4.js → shadow-claw-tools-D98XZ0Ep.js} +3 -3
- package/dist/lib/{shadow-claw-tools-DD3fyXP4.js.map → shadow-claw-tools-D98XZ0Ep.js.map} +1 -1
- package/dist/lib/{shadow-claw-webvm-DbJq4I3-.js → shadow-claw-webvm-DLOb8dMI.js} +2 -2
- package/dist/lib/{shadow-claw-webvm-DbJq4I3-.js.map → shadow-claw-webvm-DLOb8dMI.js.map} +1 -1
- package/dist/lib/subsystems/channels/imessage.d.ts +1 -0
- package/dist/lib/subsystems/channels/peerjs.d.ts +1 -0
- package/dist/lib/subsystems/channels/telegram.d.ts +1 -0
- package/dist/lib/subsystems/channels/types.d.ts +2 -0
- package/dist/lib/subsystems/notifications/push-client.d.ts +1 -1
- package/dist/lib/{syncWebMcpRegistration-DNLnQ-jz.js → syncWebMcpRegistration-NspKefOI.js} +2 -2
- package/dist/lib/{syncWebMcpRegistration-DNLnQ-jz.js.map → syncWebMcpRegistration-NspKefOI.js.map} +1 -1
- package/dist/public/.well-known/ai-catalog.json +11 -0
- package/dist/public/.well-known/mcp/index.html +19 -0
- package/dist/public/.well-known/mcp/server-card.json +52 -0
- package/dist/public/.well-known/mcp/servers.json +15 -0
- package/dist/public/.well-known/mcp.json +52 -0
- package/dist/public/.well-known/oauth-protected-resource +10 -0
- package/dist/public/.well-known/oauth-protected-resource.json +10 -0
- package/dist/public/404.html +28 -1
- package/dist/public/AGENTS.md +1 -1
- package/dist/public/agent.worker.js +1 -1
- package/dist/public/{configurePeerJs-BqgeBjP7.js → configurePeerJs-BhBokQlC.js} +1 -1
- package/dist/public/docs/example/article/index.html +3 -3
- package/dist/public/docs/publishing/index.html +3 -3
- package/dist/public/docs/skill-creator/index.html +3 -3
- package/dist/public/docs/subsystems/cli.md +11 -8
- package/dist/public/docs/subsystems/mcp-server.md +1 -0
- package/dist/public/docs/subsystems/notifications.md +10 -0
- package/dist/public/{e2e-bridge-Ceqr5kGc.js → e2e-bridge-DcBoeFvd.js} +1 -1
- package/dist/public/{file-viewer-C-6Jc_tb.js → file-viewer-CKtVC9Ix.js} +1 -1
- package/dist/public/index.css +1 -1
- package/dist/public/index.html +3 -3
- package/dist/public/index.js +1 -1
- package/dist/public/initControlPlane-Bi8ouFEb.js +1 -0
- package/dist/public/llms.txt +36 -1
- package/dist/public/main/index.html +3 -3
- package/dist/public/main/memory/index.html +3 -3
- package/dist/public/{markdown-C3M4OW_A.js → markdown-C-hCVJtc.js} +1 -1
- package/dist/public/orchestrator-8qNGfw8r.js +15 -0
- package/dist/public/{orchestrator-DHjdLtqa.js → orchestrator-BT3Ks5ip.js} +2 -2
- package/dist/public/peerjs-DfhwEiiq.js +1 -0
- package/dist/public/push-client-BVLrE9pK.js +1 -0
- package/dist/public/service-worker/fetch-proxy.js +1 -1
- package/dist/public/service-worker.js +1 -1
- package/dist/public/{shadow-claw-EAt2ml5Y.js → shadow-claw-DCjxNDCX.js} +3 -3
- package/dist/public/{shadow-claw-channels-XsfuWFF8.js → shadow-claw-channels-CITDrqMl.js} +1 -1
- package/dist/public/{shadow-claw-chat-CbSaOZ4q.js → shadow-claw-chat-BQrSgSKF.js} +1 -1
- package/dist/public/{shadow-claw-control-plane-DHvN3Ukg.js → shadow-claw-control-plane-Cp001OY0.js} +2 -2
- package/dist/public/{shadow-claw-conversations-BWs5VVfs.js → shadow-claw-conversations-EhX4RvIj.js} +1 -1
- package/dist/public/{shadow-claw-file-viewer-BWsoW5QS.js → shadow-claw-file-viewer-DiQXIjZD.js} +1 -1
- package/dist/public/{shadow-claw-files-CG7MqWiV.js → shadow-claw-files-lkTnSJsn.js} +1 -1
- package/dist/public/{shadow-claw-git-DBRPZNav.js → shadow-claw-git-BlcA_z8x.js} +2 -2
- package/dist/public/{shadow-claw-llm-DMvLgKXo.js → shadow-claw-llm-CW16X9Rg.js} +1 -1
- package/dist/public/{shadow-claw-networking-Dd8Rw31D.js → shadow-claw-networking-ySsMBY_i.js} +1 -1
- package/dist/public/{shadow-claw-notifications-Dbevj-lq.js → shadow-claw-notifications-CyV2zCQ8.js} +1 -1
- package/dist/public/{shadow-claw-pages-3_lQAv0t.js → shadow-claw-pages-Cx7cF9ah.js} +1 -1
- package/dist/public/{shadow-claw-settings-t8TIlgrM.js → shadow-claw-settings-zm4PHHGi.js} +2 -2
- package/dist/public/{shadow-claw-storage-CfEXoSIE.js → shadow-claw-storage-dy9JAEGd.js} +1 -1
- package/dist/public/{shadow-claw-task-server-DxQSsgRZ.js → shadow-claw-task-server-Bleww48s.js} +2 -2
- package/dist/public/{shadow-claw-tasks-DNpicQwz.js → shadow-claw-tasks-CTI5oaLN.js} +1 -1
- package/dist/public/{shadow-claw-terminal-DpY6zU_r.js → shadow-claw-terminal-mZ3i4HaQ.js} +1 -1
- package/dist/public/{shadow-claw-tools-WSwWohnk.js → shadow-claw-tools-B_nuk8Zl.js} +1 -1
- package/dist/public/{shadow-claw-webvm-CP6oFjg5.js → shadow-claw-webvm-Vl_jH5YL.js} +1 -1
- package/dist/public/{syncWebMcpRegistration-DOn9gLnD.js → syncWebMcpRegistration-n2vAMJ4w.js} +1 -1
- package/dist/server.js +197 -187
- package/package.json +2 -1
- package/src/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.test.ts +6 -0
- package/src/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.ts +19 -9
- package/src/components/settings/shadow-claw-task-server/shadow-claw-task-server.test.ts +14 -0
- package/src/components/settings/shadow-claw-task-server/shadow-claw-task-server.ts +12 -0
- package/src/components/shadow-claw/shadow-claw.ts +6 -0
- package/src/components/shadow-claw/utils/bindEventListeners.test.ts +57 -0
- package/src/components/shadow-claw/utils/bindEventListeners.ts +14 -0
- package/src/components/shadow-claw/utils/setupAppLifecycle.test.ts +201 -0
- package/src/components/shadow-claw/utils/setupAppLifecycle.ts +101 -0
- package/src/core/control-plane-client.test.ts +82 -1
- package/src/core/control-plane-client.ts +35 -1
- package/src/core/orchestrator/orchestrator.test.ts +49 -0
- package/src/core/orchestrator/orchestrator.ts +43 -0
- package/src/core/orchestrator/utils/operations/channel.test.ts +17 -0
- package/src/core/orchestrator/utils/operations/channel.ts +11 -5
- package/src/core/orchestrator/utils/operations/task.test.ts +26 -0
- package/src/core/orchestrator/utils/operations/task.ts +8 -1
- package/src/core/utils/initControlPlane.test.ts +138 -0
- package/src/core/utils/initControlPlane.ts +82 -0
- package/src/core/utils/initializeApp.test.ts +27 -1
- package/src/core/utils/initializeApp.ts +6 -13
- package/src/server/mcp/mcp-server.test.ts +6 -0
- package/src/server/mcp/mcp-server.ts +2 -1
- package/src/server/mcp/tools/built-in-tools.ts +88 -1
- package/src/server/mcp/tools/client-tool-relay.ts +6 -0
- package/src/server/mcp/tools/mcp-tools.test.ts +128 -0
- package/src/server/openapi/openapi-coverage.test.ts +2 -1
- package/src/server/openapi/openapi-spec.ts +2 -1
- package/src/server/openapi/paths/mcp.ts +104 -0
- package/src/server/openapi/paths/push.ts +3 -0
- package/src/server/routes/mcp.test.ts +92 -0
- package/src/server/routes/mcp.ts +138 -2
- package/src/server/utils/packageVersion.test.ts +10 -0
- package/src/server/utils/packageVersion.ts +32 -0
- package/src/service-worker/fetch-proxy-rules.test.ts +73 -0
- package/src/service-worker/fetch-proxy-rules.ts +44 -0
- package/src/service-worker/fetch-proxy.ts +1 -1
- package/src/subsystems/channels/imessage.test.ts +36 -0
- package/src/subsystems/channels/imessage.ts +11 -0
- package/src/subsystems/channels/peerjs.test.ts +56 -0
- package/src/subsystems/channels/peerjs.ts +29 -0
- package/src/subsystems/channels/telegram.test.ts +38 -0
- package/src/subsystems/channels/telegram.ts +13 -0
- package/src/subsystems/channels/types.ts +2 -0
- package/src/subsystems/notifications/push-client.test.ts +70 -0
- package/src/subsystems/notifications/push-client.ts +28 -3
- package/src/subsystems/notifications/push-routes.test.ts +82 -0
- package/src/subsystems/notifications/push-routes.ts +70 -16
- package/src/subsystems/notifications/push-store.test.ts +59 -0
- package/src/subsystems/notifications/push-store.ts +113 -19
- package/src/testing/jest-setup.ts +15 -0
- package/dist/lib/initControlPlane-C2zURVIS.js.map +0 -1
- package/dist/lib/orchestrator-CACLa_Ro.js.map +0 -1
- package/dist/lib/peerjs-DFWOBcXf.js.map +0 -1
- package/dist/lib/push-client-_tiWQsCT.js.map +0 -1
- package/dist/lib/shadow-claw-6UHdiy4a.js.map +0 -1
- package/dist/lib/shadow-claw-control-plane-UG0o7pCz.js.map +0 -1
- package/dist/public/initControlPlane-BN9cWyO0.js +0 -1
- package/dist/public/orchestrator-CMbCTjBf.js +0 -15
- package/dist/public/peerjs-DUCKH-m9.js +0 -1
- package/dist/public/push-client-D0lkwrK0.js +0 -1
|
@@ -108,9 +108,35 @@ describe("initializeApp", () => {
|
|
|
108
108
|
} as unknown as Document;
|
|
109
109
|
|
|
110
110
|
const { initializeApp } = await import("./initializeApp.js");
|
|
111
|
-
|
|
112
111
|
const result = await initializeApp(doc, true);
|
|
113
112
|
expect(result).toBeUndefined();
|
|
114
113
|
expect(doc.querySelector).not.toHaveBeenCalled();
|
|
115
114
|
});
|
|
115
|
+
|
|
116
|
+
it("calls ensureAllConnections({ force: false }) on uiElement.orchestrator after whenReady", async () => {
|
|
117
|
+
const mockEnsureAllConnections = jest
|
|
118
|
+
.fn()
|
|
119
|
+
.mockResolvedValue(undefined as never);
|
|
120
|
+
const doc = {
|
|
121
|
+
querySelector: jest.fn(() => ({
|
|
122
|
+
orchestrator: {
|
|
123
|
+
ensureAllConnections: mockEnsureAllConnections,
|
|
124
|
+
},
|
|
125
|
+
})),
|
|
126
|
+
body: { appendChild: jest.fn((el) => el) },
|
|
127
|
+
createElement: jest.fn(() => ({
|
|
128
|
+
orchestrator: {
|
|
129
|
+
ensureAllConnections: mockEnsureAllConnections,
|
|
130
|
+
},
|
|
131
|
+
})),
|
|
132
|
+
} as unknown as Document;
|
|
133
|
+
|
|
134
|
+
const { initializeApp } = await import("./initializeApp.js");
|
|
135
|
+
|
|
136
|
+
const promise = initializeApp(doc, false);
|
|
137
|
+
resolveReady();
|
|
138
|
+
await promise;
|
|
139
|
+
|
|
140
|
+
expect(mockEnsureAllConnections).toHaveBeenCalledWith({ force: false });
|
|
141
|
+
});
|
|
116
142
|
});
|
|
@@ -35,21 +35,14 @@ export async function initializeApp(
|
|
|
35
35
|
|
|
36
36
|
await orchestratorStore.whenReady;
|
|
37
37
|
|
|
38
|
-
if (
|
|
38
|
+
if (
|
|
39
|
+
typeof window !== "undefined" &&
|
|
40
|
+
typeof uiElement.orchestrator?.ensureAllConnections === "function"
|
|
41
|
+
) {
|
|
39
42
|
try {
|
|
40
|
-
|
|
41
|
-
createDefaultControlPlaneClient,
|
|
42
|
-
shouldConnectControlPlane,
|
|
43
|
-
isControlPlaneEnabledSync,
|
|
44
|
-
} = await import("./initControlPlane.js");
|
|
45
|
-
|
|
46
|
-
if (isControlPlaneEnabledSync() && shouldConnectControlPlane()) {
|
|
47
|
-
createDefaultControlPlaneClient({
|
|
48
|
-
orchestrator: uiElement.orchestrator,
|
|
49
|
-
});
|
|
50
|
-
}
|
|
43
|
+
await uiElement.orchestrator.ensureAllConnections({ force: false });
|
|
51
44
|
} catch (err) {
|
|
52
|
-
console.warn("[
|
|
45
|
+
console.warn("[App] Failed to ensure connections on initialize:", err);
|
|
53
46
|
}
|
|
54
47
|
}
|
|
55
48
|
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
MCP_PROTOCOL_VERSION_2026_07_28,
|
|
6
6
|
type McpTool,
|
|
7
7
|
} from "./types.js";
|
|
8
|
+
import { getPackageVersion } from "../utils/packageVersion.js";
|
|
8
9
|
|
|
9
10
|
describe("McpServer", () => {
|
|
10
11
|
function createServer(tools: McpTool[] = []) {
|
|
@@ -17,6 +18,11 @@ describe("McpServer", () => {
|
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
describe("server/discover", () => {
|
|
21
|
+
it("defaults version to package.json version when no version is provided", () => {
|
|
22
|
+
const server = new McpServer();
|
|
23
|
+
expect(server.serverInfo.version).toBe(getPackageVersion());
|
|
24
|
+
});
|
|
25
|
+
|
|
20
26
|
it("returns discovery response with 2026-07-28 and capabilities", async () => {
|
|
21
27
|
const server = createServer();
|
|
22
28
|
const res = await server.handleRequest({
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
type McpToolsListResult,
|
|
25
25
|
type ServerDiscoverResult,
|
|
26
26
|
} from "./types.js";
|
|
27
|
+
import { getPackageVersion } from "../utils/packageVersion.js";
|
|
27
28
|
|
|
28
29
|
export type McpToolHandler = (
|
|
29
30
|
args: Record<string, unknown>,
|
|
@@ -47,7 +48,7 @@ export class McpServer {
|
|
|
47
48
|
|
|
48
49
|
constructor(options: McpServerOptions = {}) {
|
|
49
50
|
this._name = options.name || "shadow-claw";
|
|
50
|
-
this._version = options.version ||
|
|
51
|
+
this._version = options.version || getPackageVersion();
|
|
51
52
|
this._toolProvider = options.toolProvider;
|
|
52
53
|
|
|
53
54
|
if (options.tools) {
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
|
|
8
8
|
import type { McpServer } from "../mcp-server.js";
|
|
9
9
|
import type { McpTool } from "../types.js";
|
|
10
|
+
import { broadcastPush } from "../../../subsystems/notifications/push-routes.js";
|
|
11
|
+
import { getPackageVersion } from "../../utils/packageVersion.js";
|
|
10
12
|
|
|
11
13
|
export const SHADOWCLAW_BUILTIN_TOOLS: McpTool[] = [
|
|
12
14
|
{
|
|
@@ -130,6 +132,30 @@ export const SHADOWCLAW_BUILTIN_TOOLS: McpTool[] = [
|
|
|
130
132
|
properties: {},
|
|
131
133
|
},
|
|
132
134
|
},
|
|
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
|
+
},
|
|
133
159
|
];
|
|
134
160
|
|
|
135
161
|
export function resolveTargetClientId(
|
|
@@ -444,7 +470,7 @@ export function registerBuiltInTools(
|
|
|
444
470
|
text: JSON.stringify(
|
|
445
471
|
{
|
|
446
472
|
server: "ShadowClaw",
|
|
447
|
-
version:
|
|
473
|
+
version: getPackageVersion(),
|
|
448
474
|
connectedClients: clients.length,
|
|
449
475
|
status: "healthy",
|
|
450
476
|
},
|
|
@@ -456,6 +482,67 @@ export function registerBuiltInTools(
|
|
|
456
482
|
};
|
|
457
483
|
}
|
|
458
484
|
|
|
485
|
+
case "shadowclaw_send_notification":
|
|
486
|
+
case "send_notification": {
|
|
487
|
+
const body = String(args.body || "").trim();
|
|
488
|
+
if (!body) {
|
|
489
|
+
return {
|
|
490
|
+
isError: true,
|
|
491
|
+
content: [
|
|
492
|
+
{
|
|
493
|
+
type: "text",
|
|
494
|
+
text: "Error: Notification 'body' parameter cannot be empty.",
|
|
495
|
+
},
|
|
496
|
+
],
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
const title = String(args.title || "ShadowClaw").trim();
|
|
501
|
+
const targetClientId =
|
|
502
|
+
typeof args.clientId === "string" && args.clientId.trim()
|
|
503
|
+
? args.clientId.trim()
|
|
504
|
+
: undefined;
|
|
505
|
+
|
|
506
|
+
try {
|
|
507
|
+
const res = await broadcastPush(
|
|
508
|
+
{ title, body },
|
|
509
|
+
targetClientId ? { clientId: targetClientId } : undefined,
|
|
510
|
+
);
|
|
511
|
+
const sentCount = res?.sent ?? 0;
|
|
512
|
+
const failedCount = res?.failed ?? 0;
|
|
513
|
+
let msg: string;
|
|
514
|
+
|
|
515
|
+
if (res?.notFound) {
|
|
516
|
+
msg = `Warning: No push subscriptions found for client '${targetClientId}'. Ensure push notifications were registered by this client in ShadowClaw Settings.`;
|
|
517
|
+
} else if (res?.noSubscribers) {
|
|
518
|
+
msg =
|
|
519
|
+
"Warning: Push notification broadcast completed, but no devices are currently subscribed to push notifications. Enable push notifications in ShadowClaw Settings on the client first.";
|
|
520
|
+
} else if (targetClientId) {
|
|
521
|
+
msg = `Push notification sent to client '${targetClientId}': ${sentCount} recipient(s) delivered, ${failedCount} failed.`;
|
|
522
|
+
} else {
|
|
523
|
+
msg = `Push notification broadcast sent: ${sentCount} recipient(s) delivered, ${failedCount} failed.`;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
return {
|
|
527
|
+
content: [{ type: "text", text: msg }],
|
|
528
|
+
_meta: {
|
|
529
|
+
...res,
|
|
530
|
+
...(targetClientId ? { targetClientId } : {}),
|
|
531
|
+
},
|
|
532
|
+
};
|
|
533
|
+
} catch (err: any) {
|
|
534
|
+
return {
|
|
535
|
+
isError: true,
|
|
536
|
+
content: [
|
|
537
|
+
{
|
|
538
|
+
type: "text",
|
|
539
|
+
text: `Failed to send push notification: ${err.message}`,
|
|
540
|
+
},
|
|
541
|
+
],
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
459
546
|
default:
|
|
460
547
|
return {
|
|
461
548
|
isError: true,
|
|
@@ -73,6 +73,12 @@ export function createClientToolRelay(
|
|
|
73
73
|
Array.isArray(res.data.tools)
|
|
74
74
|
) {
|
|
75
75
|
for (const t of res.data.tools) {
|
|
76
|
+
if (
|
|
77
|
+
t.name === "send_notification" ||
|
|
78
|
+
t.name.startsWith("shadowclaw_")
|
|
79
|
+
) {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
76
82
|
if (!toolDefMap.has(t.name)) {
|
|
77
83
|
toolDefMap.set(t.name, t);
|
|
78
84
|
toolSupportingClientsMap.set(t.name, []);
|
|
@@ -6,6 +6,12 @@ import {
|
|
|
6
6
|
} from "./built-in-tools.js";
|
|
7
7
|
import { createClientToolRelay } from "./client-tool-relay.js";
|
|
8
8
|
import { McpServer } from "../mcp-server.js";
|
|
9
|
+
import {
|
|
10
|
+
openPushStore,
|
|
11
|
+
closePushStore,
|
|
12
|
+
saveSubscription,
|
|
13
|
+
} from "../../../subsystems/notifications/push-store.js";
|
|
14
|
+
import { getPackageVersion } from "../../utils/packageVersion.js";
|
|
9
15
|
|
|
10
16
|
describe("ShadowClaw Built-in MCP Tools", () => {
|
|
11
17
|
it("registers all built-in ShadowClaw control tools", () => {
|
|
@@ -35,6 +41,7 @@ describe("ShadowClaw Built-in MCP Tools", () => {
|
|
|
35
41
|
expect(toolNames).toContain("shadowclaw_read_state");
|
|
36
42
|
expect(toolNames).toContain("shadowclaw_list_tasks");
|
|
37
43
|
expect(toolNames).toContain("shadowclaw_manage_backup");
|
|
44
|
+
expect(toolNames).toContain("shadowclaw_send_notification");
|
|
38
45
|
});
|
|
39
46
|
|
|
40
47
|
it("executes shadowclaw_list_clients", async () => {
|
|
@@ -351,6 +358,127 @@ describe("ShadowClaw Built-in MCP Tools", () => {
|
|
|
351
358
|
if (res && "result" in res) {
|
|
352
359
|
expect(res.result.content[0].text).toContain("ShadowClaw");
|
|
353
360
|
expect(res.result.content[0].text).toContain("healthy");
|
|
361
|
+
const statusData = JSON.parse(res.result.content[0].text);
|
|
362
|
+
expect(statusData.version).toBe(getPackageVersion());
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
it("executes shadowclaw_send_notification and handles empty subscribers / validation", async () => {
|
|
367
|
+
openPushStore(":memory:");
|
|
368
|
+
try {
|
|
369
|
+
const server = new McpServer();
|
|
370
|
+
const mockControlPlane: any = {
|
|
371
|
+
getConnectedClients: jest.fn().mockReturnValue([]),
|
|
372
|
+
};
|
|
373
|
+
registerBuiltInTools(server, mockControlPlane);
|
|
374
|
+
|
|
375
|
+
// 1. Validation error on empty body
|
|
376
|
+
const resEmpty = await server.handleRequest({
|
|
377
|
+
jsonrpc: "2.0",
|
|
378
|
+
id: 16,
|
|
379
|
+
method: "tools/call",
|
|
380
|
+
params: {
|
|
381
|
+
name: "shadowclaw_send_notification",
|
|
382
|
+
arguments: { body: "" },
|
|
383
|
+
},
|
|
384
|
+
});
|
|
385
|
+
expect(resEmpty).not.toBeNull();
|
|
386
|
+
if (resEmpty && "result" in resEmpty) {
|
|
387
|
+
expect(resEmpty.result.isError).toBe(true);
|
|
388
|
+
expect(resEmpty.result.content[0].text).toContain("cannot be empty");
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// 2. Broadcast with 0 subscribers returns informative warning
|
|
392
|
+
const resBroadcast = await server.handleRequest({
|
|
393
|
+
jsonrpc: "2.0",
|
|
394
|
+
id: 17,
|
|
395
|
+
method: "tools/call",
|
|
396
|
+
params: {
|
|
397
|
+
name: "shadowclaw_send_notification",
|
|
398
|
+
arguments: {
|
|
399
|
+
title: "Test Alert",
|
|
400
|
+
body: "Testing push",
|
|
401
|
+
},
|
|
402
|
+
},
|
|
403
|
+
});
|
|
404
|
+
expect(resBroadcast).not.toBeNull();
|
|
405
|
+
if (resBroadcast && "result" in resBroadcast) {
|
|
406
|
+
expect(resBroadcast.result.isError).toBeFalsy();
|
|
407
|
+
expect(resBroadcast.result.content[0].text).toContain(
|
|
408
|
+
"no devices are currently subscribed",
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// 3. Alias send_notification works as well
|
|
413
|
+
const resAlias = await server.handleRequest({
|
|
414
|
+
jsonrpc: "2.0",
|
|
415
|
+
id: 18,
|
|
416
|
+
method: "tools/call",
|
|
417
|
+
params: {
|
|
418
|
+
name: "send_notification",
|
|
419
|
+
arguments: {
|
|
420
|
+
body: "Testing alias push",
|
|
421
|
+
},
|
|
422
|
+
},
|
|
423
|
+
});
|
|
424
|
+
expect(resAlias).not.toBeNull();
|
|
425
|
+
if (resAlias && "result" in resAlias) {
|
|
426
|
+
expect(resAlias.result.isError).toBeFalsy();
|
|
427
|
+
expect(resAlias.result.content[0].text).toContain(
|
|
428
|
+
"no devices are currently subscribed",
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// 4. Targeted push with unknown client returns informative warning
|
|
433
|
+
const resNotFound = await server.handleRequest({
|
|
434
|
+
jsonrpc: "2.0",
|
|
435
|
+
id: 19,
|
|
436
|
+
method: "tools/call",
|
|
437
|
+
params: {
|
|
438
|
+
name: "shadowclaw_send_notification",
|
|
439
|
+
arguments: {
|
|
440
|
+
body: "Direct message",
|
|
441
|
+
clientId: "nonexistent-client-id",
|
|
442
|
+
},
|
|
443
|
+
},
|
|
444
|
+
});
|
|
445
|
+
expect(resNotFound).not.toBeNull();
|
|
446
|
+
if (resNotFound && "result" in resNotFound) {
|
|
447
|
+
expect(resNotFound.result.isError).toBeFalsy();
|
|
448
|
+
expect(resNotFound.result.content[0].text).toContain(
|
|
449
|
+
"No push subscriptions found for client 'nonexistent-client-id'",
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// 5. Targeted push delivering to a registered client
|
|
454
|
+
saveSubscription({
|
|
455
|
+
endpoint: "https://fcm.googleapis.com/fcm/send/target-device",
|
|
456
|
+
keys: { p256dh: "key-target", auth: "auth-target" },
|
|
457
|
+
clientId: "client-target-55",
|
|
458
|
+
deviceLabel: "Workstation",
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
const resTargeted = await server.handleRequest({
|
|
462
|
+
jsonrpc: "2.0",
|
|
463
|
+
id: 20,
|
|
464
|
+
method: "tools/call",
|
|
465
|
+
params: {
|
|
466
|
+
name: "shadowclaw_send_notification",
|
|
467
|
+
arguments: {
|
|
468
|
+
body: "Direct workstation alert",
|
|
469
|
+
clientId: "client-target-55",
|
|
470
|
+
},
|
|
471
|
+
},
|
|
472
|
+
});
|
|
473
|
+
expect(resTargeted).not.toBeNull();
|
|
474
|
+
if (resTargeted && "result" in resTargeted) {
|
|
475
|
+
expect(resTargeted.result.isError).toBeFalsy();
|
|
476
|
+
expect(resTargeted.result.content[0].text).toContain(
|
|
477
|
+
"Push notification sent to client 'client-target-55'",
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
} finally {
|
|
481
|
+
closePushStore();
|
|
354
482
|
}
|
|
355
483
|
});
|
|
356
484
|
});
|
|
@@ -6,6 +6,7 @@ import { createApp } from "../app.js";
|
|
|
6
6
|
import { createControlPlane } from "../control-plane.js";
|
|
7
7
|
import { registerMcpRoutes } from "../routes/mcp.js";
|
|
8
8
|
import { openApiSpec } from "./openapi-spec.js";
|
|
9
|
+
import { getPackageVersion } from "../utils/packageVersion.js";
|
|
9
10
|
import type { ServerConfig } from "../config.js";
|
|
10
11
|
|
|
11
12
|
const REST_METHODS = new Set(["get", "post", "put", "delete", "patch"]);
|
|
@@ -105,7 +106,7 @@ describe("OpenAPI Route Coverage & Drift Guardrail", () => {
|
|
|
105
106
|
it("should have valid OpenAPI 3.1 root metadata", () => {
|
|
106
107
|
expect(openApiSpec.openapi).toBe("3.1.0");
|
|
107
108
|
expect(openApiSpec.info.title).toBe("ShadowClaw API");
|
|
108
|
-
expect(openApiSpec.info.version).
|
|
109
|
+
expect(openApiSpec.info.version).toBe(getPackageVersion());
|
|
109
110
|
expect(openApiSpec.paths).toBeDefined();
|
|
110
111
|
});
|
|
111
112
|
|
|
@@ -8,12 +8,13 @@ import { integrationPaths } from "./paths/integrations.js";
|
|
|
8
8
|
import { proxyPaths } from "./paths/proxies.js";
|
|
9
9
|
import { diagnosticPaths } from "./paths/diagnostics.js";
|
|
10
10
|
import { mcpPaths } from "./paths/mcp.js";
|
|
11
|
+
import { getPackageVersion } from "../utils/packageVersion.js";
|
|
11
12
|
|
|
12
13
|
export const openApiSpec: OpenApiSpec = {
|
|
13
14
|
openapi: "3.1.0",
|
|
14
15
|
info: {
|
|
15
16
|
title: "ShadowClaw API",
|
|
16
|
-
version:
|
|
17
|
+
version: getPackageVersion(),
|
|
17
18
|
description:
|
|
18
19
|
"Official REST and streaming API specification for ShadowClaw personal AI assistant and control plane server.",
|
|
19
20
|
license: {
|
|
@@ -38,4 +38,108 @@ export const mcpPaths: Record<string, OpenApiPathItem> = {
|
|
|
38
38
|
},
|
|
39
39
|
},
|
|
40
40
|
},
|
|
41
|
+
"/.well-known/mcp": {
|
|
42
|
+
post: {
|
|
43
|
+
tags: ["Model Context Protocol"],
|
|
44
|
+
summary: "Stateless MCP Endpoint (Well-Known)",
|
|
45
|
+
description:
|
|
46
|
+
"Alternative well-known path for Stateless MCP (2026-07-28) Streamable HTTP requests.",
|
|
47
|
+
security: [{ ControlTokenAuth: [] }, { BearerAuth: [] }],
|
|
48
|
+
responses: {
|
|
49
|
+
"200": { description: "JSON-RPC response" },
|
|
50
|
+
"401": { description: "Unauthorized" },
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
get: {
|
|
54
|
+
tags: ["Model Context Protocol"],
|
|
55
|
+
summary: "MCP Server Card Discovery",
|
|
56
|
+
description: "Returns SEP-2127 MCP Server Card discovery metadata.",
|
|
57
|
+
responses: {
|
|
58
|
+
"200": {
|
|
59
|
+
description: "MCP Server Card",
|
|
60
|
+
content: { "application/json": { schema: { type: "object" } } },
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
"/.well-known/mcp.json": {
|
|
66
|
+
get: {
|
|
67
|
+
tags: ["Model Context Protocol"],
|
|
68
|
+
summary: "MCP Server Card (SEP-2127)",
|
|
69
|
+
description: "Returns SEP-2127 MCP Server Card discovery metadata.",
|
|
70
|
+
responses: {
|
|
71
|
+
"200": {
|
|
72
|
+
description: "MCP Server Card",
|
|
73
|
+
content: { "application/json": { schema: { type: "object" } } },
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
"/mcp/server-card": {
|
|
79
|
+
get: {
|
|
80
|
+
tags: ["Model Context Protocol"],
|
|
81
|
+
summary: "MCP Server Card (Canonical Endpoint)",
|
|
82
|
+
description: "Returns SEP-2127 MCP Server Card for this server.",
|
|
83
|
+
responses: {
|
|
84
|
+
"200": {
|
|
85
|
+
description: "MCP Server Card",
|
|
86
|
+
content: { "application/json": { schema: { type: "object" } } },
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
"/.well-known/ai-catalog.json": {
|
|
92
|
+
get: {
|
|
93
|
+
tags: ["Model Context Protocol"],
|
|
94
|
+
summary: "AI Catalog Discovery (SEP-2127)",
|
|
95
|
+
description: "Returns AI Catalog referencing this MCP Server Card.",
|
|
96
|
+
responses: {
|
|
97
|
+
"200": {
|
|
98
|
+
description: "AI Catalog manifest",
|
|
99
|
+
content: { "application/json": { schema: { type: "object" } } },
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
"/.well-known/mcp/servers.json": {
|
|
105
|
+
get: {
|
|
106
|
+
tags: ["Model Context Protocol"],
|
|
107
|
+
summary: "MCP Servers Manifest (Discussion #1147)",
|
|
108
|
+
description: "Returns servers manifest per Discussion #1147.",
|
|
109
|
+
responses: {
|
|
110
|
+
"200": {
|
|
111
|
+
description: "MCP servers manifest",
|
|
112
|
+
content: { "application/json": { schema: { type: "object" } } },
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
"/.well-known/oauth-protected-resource": {
|
|
118
|
+
get: {
|
|
119
|
+
tags: ["Model Context Protocol"],
|
|
120
|
+
summary: "Protected Resource Metadata (RFC 9728)",
|
|
121
|
+
description:
|
|
122
|
+
"OAuth 2.0 Protected Resource Metadata pointing clients to authorization servers.",
|
|
123
|
+
responses: {
|
|
124
|
+
"200": {
|
|
125
|
+
description: "Protected Resource Metadata",
|
|
126
|
+
content: { "application/json": { schema: { type: "object" } } },
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
"/.well-known/oauth-protected-resource.json": {
|
|
132
|
+
get: {
|
|
133
|
+
tags: ["Model Context Protocol"],
|
|
134
|
+
summary: "Protected Resource Metadata JSON (RFC 9728)",
|
|
135
|
+
description:
|
|
136
|
+
"OAuth 2.0 Protected Resource Metadata pointing clients to authorization servers.",
|
|
137
|
+
responses: {
|
|
138
|
+
"200": {
|
|
139
|
+
description: "Protected Resource Metadata",
|
|
140
|
+
content: { "application/json": { schema: { type: "object" } } },
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
},
|
|
41
145
|
};
|
|
@@ -45,6 +45,8 @@ export const pushPaths: Record<string, OpenApiPathItem> = {
|
|
|
45
45
|
auth: { type: "string" },
|
|
46
46
|
},
|
|
47
47
|
},
|
|
48
|
+
clientId: { type: "string" },
|
|
49
|
+
deviceLabel: { type: "string" },
|
|
48
50
|
},
|
|
49
51
|
},
|
|
50
52
|
},
|
|
@@ -165,6 +167,7 @@ export const pushPaths: Record<string, OpenApiPathItem> = {
|
|
|
165
167
|
properties: {
|
|
166
168
|
title: { type: "string", default: "ShadowClaw" },
|
|
167
169
|
body: { type: "string" },
|
|
170
|
+
clientId: { type: "string" },
|
|
168
171
|
},
|
|
169
172
|
},
|
|
170
173
|
},
|
|
@@ -4,6 +4,7 @@ import http from "node:http";
|
|
|
4
4
|
import type { AddressInfo } from "node:net";
|
|
5
5
|
import { registerMcpRoutes } from "./mcp.js";
|
|
6
6
|
import { McpServer } from "../mcp/mcp-server.js";
|
|
7
|
+
import { getPackageVersion } from "../utils/packageVersion.js";
|
|
7
8
|
|
|
8
9
|
function makeHttpRequest(options: {
|
|
9
10
|
method?: string;
|
|
@@ -219,6 +220,12 @@ describe("MCP Streamable HTTP Route (POST /mcp)", () => {
|
|
|
219
220
|
});
|
|
220
221
|
|
|
221
222
|
expect(res.status).toBe(401);
|
|
223
|
+
expect(res.headers["www-authenticate"]).toContain(
|
|
224
|
+
'Bearer resource_metadata="',
|
|
225
|
+
);
|
|
226
|
+
expect(res.headers["www-authenticate"]).toContain(
|
|
227
|
+
"/.well-known/oauth-protected-resource",
|
|
228
|
+
);
|
|
222
229
|
});
|
|
223
230
|
|
|
224
231
|
it("authenticates via Bearer Authorization header and query parameter", async () => {
|
|
@@ -301,4 +308,89 @@ describe("MCP Streamable HTTP Route (POST /mcp)", () => {
|
|
|
301
308
|
});
|
|
302
309
|
expect(resLocal.status).toBe(200);
|
|
303
310
|
});
|
|
311
|
+
|
|
312
|
+
it("handles Streamable HTTP requests at POST /.well-known/mcp", async () => {
|
|
313
|
+
const res = await makeHttpRequest({
|
|
314
|
+
port,
|
|
315
|
+
path: "/.well-known/mcp",
|
|
316
|
+
method: "POST",
|
|
317
|
+
headers: {
|
|
318
|
+
"x-control-token": testToken,
|
|
319
|
+
"mcp-protocol-version": "2026-07-28",
|
|
320
|
+
},
|
|
321
|
+
body: {
|
|
322
|
+
jsonrpc: "2.0",
|
|
323
|
+
id: 101,
|
|
324
|
+
method: "server/discover",
|
|
325
|
+
},
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
expect(res.status).toBe(200);
|
|
329
|
+
expect(res.data.result.serverInfo.name).toBe("shadow-claw-http-test");
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
it("serves MCP Server Card discovery at GET /.well-known/mcp, /.well-known/mcp.json, and /mcp/server-card", async () => {
|
|
333
|
+
for (const p of [
|
|
334
|
+
"/.well-known/mcp",
|
|
335
|
+
"/.well-known/mcp.json",
|
|
336
|
+
"/mcp/server-card",
|
|
337
|
+
]) {
|
|
338
|
+
const res = await makeHttpRequest({
|
|
339
|
+
port,
|
|
340
|
+
path: p,
|
|
341
|
+
method: "GET",
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
expect(res.status).toBe(200);
|
|
345
|
+
expect(res.headers["content-type"]).toContain("application/json");
|
|
346
|
+
expect(res.data.name).toBe("shadow-claw");
|
|
347
|
+
expect(res.data.version).toBe(getPackageVersion());
|
|
348
|
+
expect(res.data.title).toBe("ShadowClaw MCP Server");
|
|
349
|
+
expect(Array.isArray(res.data.remotes)).toBe(true);
|
|
350
|
+
expect(res.data.remotes[0].type).toBe("streamable-http");
|
|
351
|
+
expect(res.data.remotes[0].supportedProtocolVersions).toContain(
|
|
352
|
+
"2026-07-28",
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
it("serves AI Catalog discovery at GET /.well-known/ai-catalog.json", async () => {
|
|
358
|
+
const res = await makeHttpRequest({
|
|
359
|
+
port,
|
|
360
|
+
path: "/.well-known/ai-catalog.json",
|
|
361
|
+
method: "GET",
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
expect(res.status).toBe(200);
|
|
365
|
+
expect(res.headers["content-type"]).toContain("application/json");
|
|
366
|
+
expect(res.data.name).toBe("ShadowClaw AI Catalog");
|
|
367
|
+
expect(Array.isArray(res.data.servers)).toBe(true);
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
it("serves MCP Servers manifest at GET /.well-known/mcp/servers.json", async () => {
|
|
371
|
+
const res = await makeHttpRequest({
|
|
372
|
+
port,
|
|
373
|
+
path: "/.well-known/mcp/servers.json",
|
|
374
|
+
method: "GET",
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
expect(res.status).toBe(200);
|
|
378
|
+
expect(res.headers["content-type"]).toContain("application/json");
|
|
379
|
+
expect(res.data.version).toBe("1.0");
|
|
380
|
+
expect(res.data.servers[0].name).toBe("shadow-claw");
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
it("serves Protected Resource Metadata at GET /.well-known/oauth-protected-resource", async () => {
|
|
384
|
+
const res = await makeHttpRequest({
|
|
385
|
+
port,
|
|
386
|
+
path: "/.well-known/oauth-protected-resource",
|
|
387
|
+
method: "GET",
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
expect(res.status).toBe(200);
|
|
391
|
+
expect(res.headers["content-type"]).toContain("application/json");
|
|
392
|
+
expect(res.data.resource).toContain("/mcp");
|
|
393
|
+
expect(Array.isArray(res.data.authorization_servers)).toBe(true);
|
|
394
|
+
expect(res.data.scopes_supported).toBeUndefined();
|
|
395
|
+
});
|
|
304
396
|
});
|