jishushell 0.4.24 → 0.5.15
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/INSTALL-NOTICE +11 -0
- package/apps/anythingllm-container.yaml +287 -0
- package/apps/browserless-chromium-container.yaml +90 -0
- package/apps/filebrowser-container.yaml +163 -0
- package/apps/hermes-container.yaml +36 -2
- package/apps/ollama-binary.yaml +91 -90
- package/apps/ollama-cpu-container.yaml +8 -1
- package/apps/ollama-with-hollama-binary.yaml +91 -90
- package/apps/openclaw-binary.yaml +38 -1
- package/apps/openclaw-container.yaml +45 -2
- package/apps/openclaw-with-ollama-container.yaml +11 -2
- package/apps/openclaw-with-searxng-container.yaml +26 -2
- package/apps/openwebui-container.yaml +45 -1
- package/apps/playwright-container.yaml +7 -1
- package/apps/searxng-container.yaml +58 -7
- package/apps/weknora-container.yaml +471 -0
- package/dist/cli/app.js +79 -9
- package/dist/cli/app.js.map +1 -1
- package/dist/cli/doctor.d.ts +12 -12
- package/dist/cli/doctor.js +242 -55
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/llm.d.ts +4 -3
- package/dist/cli/llm.js +4 -3
- package/dist/cli/llm.js.map +1 -1
- package/dist/cli/panel.d.ts +6 -5
- package/dist/cli/panel.js +10 -9
- package/dist/cli/panel.js.map +1 -1
- package/dist/config.d.ts +19 -0
- package/dist/config.js +99 -1
- package/dist/config.js.map +1 -1
- package/dist/control.d.ts +7 -6
- package/dist/control.js +7 -6
- package/dist/control.js.map +1 -1
- package/dist/install.js +3 -3
- package/dist/install.js.map +1 -1
- package/dist/routes/agent-apps.d.ts +1 -1
- package/dist/routes/agent-apps.js +1 -1
- package/dist/routes/apps.js +44 -11
- package/dist/routes/apps.js.map +1 -1
- package/dist/routes/auth.js +5 -2
- package/dist/routes/auth.js.map +1 -1
- package/dist/routes/backup.js +64 -11
- package/dist/routes/backup.js.map +1 -1
- package/dist/routes/external-mounts.d.ts +17 -0
- package/dist/routes/external-mounts.js +73 -0
- package/dist/routes/external-mounts.js.map +1 -0
- package/dist/routes/file-mounts.d.ts +13 -0
- package/dist/routes/file-mounts.js +90 -0
- package/dist/routes/file-mounts.js.map +1 -0
- package/dist/routes/files-organize.d.ts +28 -0
- package/dist/routes/files-organize.js +167 -0
- package/dist/routes/files-organize.js.map +1 -0
- package/dist/routes/files.d.ts +31 -0
- package/dist/routes/files.js +321 -0
- package/dist/routes/files.js.map +1 -0
- package/dist/routes/instances.js +826 -17
- package/dist/routes/instances.js.map +1 -1
- package/dist/routes/internal.d.ts +2 -0
- package/dist/routes/internal.js +59 -0
- package/dist/routes/internal.js.map +1 -0
- package/dist/routes/llm.js +24 -35
- package/dist/routes/llm.js.map +1 -1
- package/dist/routes/setup.js +10 -10
- package/dist/routes/setup.js.map +1 -1
- package/dist/routes/system.js +1 -1
- package/dist/routes/system.js.map +1 -1
- package/dist/routes/webdav.d.ts +17 -0
- package/dist/routes/webdav.js +114 -0
- package/dist/routes/webdav.js.map +1 -0
- package/dist/server.d.ts +9 -0
- package/dist/server.js +751 -20
- package/dist/server.js.map +1 -1
- package/dist/services/agent-apps/catalog.js +4 -3
- package/dist/services/agent-apps/catalog.js.map +1 -1
- package/dist/services/agent-apps/index.d.ts +1 -1
- package/dist/services/agent-apps/index.js +1 -1
- package/dist/services/agent-apps/installers/adapter.d.ts +1 -1
- package/dist/services/agent-apps/installers/adapter.js +1 -1
- package/dist/services/agent-apps/installers/shell-script.d.ts +1 -1
- package/dist/services/agent-apps/installers/shell-script.js +3 -3
- package/dist/services/agent-apps/installers/shell-script.js.map +1 -1
- package/dist/services/agent-apps/types.d.ts +2 -2
- package/dist/services/agent-apps/types.js +1 -1
- package/dist/services/app/app-compiler.d.ts +1 -1
- package/dist/services/app/app-compiler.js +5 -5
- package/dist/services/app/app-compiler.js.map +1 -1
- package/dist/services/app/app-manager.d.ts +25 -1
- package/dist/services/app/app-manager.js +829 -150
- package/dist/services/app/app-manager.js.map +1 -1
- package/dist/services/app/custom-manager.js.map +1 -1
- package/dist/services/app/hermes-agent-manager.js +7 -4
- package/dist/services/app/hermes-agent-manager.js.map +1 -1
- package/dist/services/app/ollama-manager.js +1 -1
- package/dist/services/app/ollama-manager.js.map +1 -1
- package/dist/services/app/openclaw-manager.js +20 -3
- package/dist/services/app/openclaw-manager.js.map +1 -1
- package/dist/services/app/platform-transform.d.ts +32 -0
- package/dist/services/app/platform-transform.js +65 -0
- package/dist/services/app/platform-transform.js.map +1 -0
- package/dist/services/app/provide-resolver.d.ts +29 -0
- package/dist/services/app/provide-resolver.js +112 -0
- package/dist/services/app/provide-resolver.js.map +1 -0
- package/dist/services/app-passwords.d.ts +61 -0
- package/dist/services/app-passwords.js +173 -0
- package/dist/services/app-passwords.js.map +1 -0
- package/dist/services/backup-manager.d.ts +11 -0
- package/dist/services/backup-manager.js +177 -4
- package/dist/services/backup-manager.js.map +1 -1
- package/dist/services/capability-endpoint-validator.d.ts +41 -0
- package/dist/services/capability-endpoint-validator.js +104 -0
- package/dist/services/capability-endpoint-validator.js.map +1 -0
- package/dist/services/capability-health.d.ts +16 -0
- package/dist/services/capability-health.js +121 -0
- package/dist/services/capability-health.js.map +1 -0
- package/dist/services/capability-registry.d.ts +106 -0
- package/dist/services/capability-registry.js +313 -0
- package/dist/services/capability-registry.js.map +1 -0
- package/dist/services/connection-apply.d.ts +91 -0
- package/dist/services/connection-apply.js +475 -0
- package/dist/services/connection-apply.js.map +1 -0
- package/dist/services/connection-resolver.d.ts +65 -0
- package/dist/services/connection-resolver.js +281 -0
- package/dist/services/connection-resolver.js.map +1 -0
- package/dist/services/connection-transactor.d.ts +39 -0
- package/dist/services/connection-transactor.js +351 -0
- package/dist/services/connection-transactor.js.map +1 -0
- package/dist/services/external-mounts.d.ts +40 -0
- package/dist/services/external-mounts.js +187 -0
- package/dist/services/external-mounts.js.map +1 -0
- package/dist/services/files-manager.d.ts +252 -0
- package/dist/services/files-manager.js +1075 -0
- package/dist/services/files-manager.js.map +1 -0
- package/dist/services/files-mounts.d.ts +42 -0
- package/dist/services/files-mounts.js +207 -0
- package/dist/services/files-mounts.js.map +1 -0
- package/dist/services/instance-manager.d.ts +13 -0
- package/dist/services/instance-manager.js +138 -46
- package/dist/services/instance-manager.js.map +1 -1
- package/dist/services/llm-proxy/index.d.ts +16 -2
- package/dist/services/llm-proxy/index.js +48 -44
- package/dist/services/llm-proxy/index.js.map +1 -1
- package/dist/services/llm-proxy/probe.d.ts +6 -0
- package/dist/services/llm-proxy/probe.js +85 -0
- package/dist/services/llm-proxy/probe.js.map +1 -0
- package/dist/services/llm-proxy/ssrf.d.ts +1 -0
- package/dist/services/llm-proxy/ssrf.js +24 -9
- package/dist/services/llm-proxy/ssrf.js.map +1 -1
- package/dist/services/nomad-manager.d.ts +4 -0
- package/dist/services/nomad-manager.js +428 -35
- package/dist/services/nomad-manager.js.map +1 -1
- package/dist/services/organize/applier.d.ts +46 -0
- package/dist/services/organize/applier.js +218 -0
- package/dist/services/organize/applier.js.map +1 -0
- package/dist/services/organize/rules.d.ts +57 -0
- package/dist/services/organize/rules.js +286 -0
- package/dist/services/organize/rules.js.map +1 -0
- package/dist/services/organize/scanner.d.ts +50 -0
- package/dist/services/organize/scanner.js +366 -0
- package/dist/services/organize/scanner.js.map +1 -0
- package/dist/services/organize/store.d.ts +14 -0
- package/dist/services/organize/store.js +82 -0
- package/dist/services/organize/store.js.map +1 -0
- package/dist/services/panel-manager.js +20 -1
- package/dist/services/panel-manager.js.map +1 -1
- package/dist/services/process-manager.js +4 -3
- package/dist/services/process-manager.js.map +1 -1
- package/dist/services/runtime/adapters/hermes.d.ts +30 -1
- package/dist/services/runtime/adapters/hermes.js +219 -6
- package/dist/services/runtime/adapters/hermes.js.map +1 -1
- package/dist/services/runtime/adapters/openclaw-mcporter.d.ts +45 -0
- package/dist/services/runtime/adapters/openclaw-mcporter.js +108 -0
- package/dist/services/runtime/adapters/openclaw-mcporter.js.map +1 -0
- package/dist/services/runtime/adapters/openclaw-routes.d.ts +8 -2
- package/dist/services/runtime/adapters/openclaw-routes.js +68 -0
- package/dist/services/runtime/adapters/openclaw-routes.js.map +1 -1
- package/dist/services/runtime/adapters/openclaw.d.ts +177 -0
- package/dist/services/runtime/adapters/openclaw.js +1171 -11
- package/dist/services/runtime/adapters/openclaw.js.map +1 -1
- package/dist/services/runtime/instance.d.ts +1 -1
- package/dist/services/runtime/instance.js +1 -1
- package/dist/services/runtime/instance.js.map +1 -1
- package/dist/services/runtime/mcp-shims/anythingllm-shim.d.ts +46 -0
- package/dist/services/runtime/mcp-shims/anythingllm-shim.js +281 -0
- package/dist/services/runtime/mcp-shims/anythingllm-shim.js.map +1 -0
- package/dist/services/runtime/mcp-shims/drive-shim.d.ts +54 -0
- package/dist/services/runtime/mcp-shims/drive-shim.js +489 -0
- package/dist/services/runtime/mcp-shims/drive-shim.js.map +1 -0
- package/dist/services/runtime/mcp-shims/firewall.d.ts +26 -0
- package/dist/services/runtime/mcp-shims/firewall.js +129 -0
- package/dist/services/runtime/mcp-shims/firewall.js.map +1 -0
- package/dist/services/runtime/mcp-shims/searxng-shim.d.ts +27 -0
- package/dist/services/runtime/mcp-shims/searxng-shim.js +125 -0
- package/dist/services/runtime/mcp-shims/searxng-shim.js.map +1 -0
- package/dist/services/runtime/mcp-shims/write-mcp-entry.d.ts +83 -0
- package/dist/services/runtime/mcp-shims/write-mcp-entry.js +127 -0
- package/dist/services/runtime/mcp-shims/write-mcp-entry.js.map +1 -0
- package/dist/services/runtime/migrations.d.ts +8 -0
- package/dist/services/runtime/migrations.js +100 -0
- package/dist/services/runtime/migrations.js.map +1 -1
- package/dist/services/runtime/types.d.ts +46 -0
- package/dist/services/setup-manager.js +99 -24
- package/dist/services/setup-manager.js.map +1 -1
- package/dist/services/suggestions.d.ts +27 -0
- package/dist/services/suggestions.js +133 -0
- package/dist/services/suggestions.js.map +1 -0
- package/dist/services/task-registry.js +4 -2
- package/dist/services/task-registry.js.map +1 -1
- package/dist/services/telemetry/device-fingerprint.d.ts +1 -1
- package/dist/services/telemetry/device-fingerprint.js +1 -1
- package/dist/services/types-shim.d.ts +16 -0
- package/dist/services/types-shim.js +2 -0
- package/dist/services/types-shim.js.map +1 -0
- package/dist/services/webdav/server.d.ts +24 -0
- package/dist/services/webdav/server.js +420 -0
- package/dist/services/webdav/server.js.map +1 -0
- package/dist/services/webdav/xml-builder.d.ts +73 -0
- package/dist/services/webdav/xml-builder.js +156 -0
- package/dist/services/webdav/xml-builder.js.map +1 -0
- package/dist/services/workspace-builder.d.ts +29 -0
- package/dist/services/workspace-builder.js +188 -0
- package/dist/services/workspace-builder.js.map +1 -0
- package/dist/types.d.ts +231 -1
- package/dist/utils/instance-lock.d.ts +22 -0
- package/dist/utils/instance-lock.js +48 -0
- package/dist/utils/instance-lock.js.map +1 -0
- package/dist/utils/path-locks.d.ts +30 -0
- package/dist/utils/path-locks.js +63 -0
- package/dist/utils/path-locks.js.map +1 -0
- package/dist/utils/path-safety.d.ts +41 -0
- package/dist/utils/path-safety.js +119 -0
- package/dist/utils/path-safety.js.map +1 -0
- package/dist/utils/safe-json.js +55 -22
- package/dist/utils/safe-json.js.map +1 -1
- package/dist/utils/safe-write.d.ts +24 -0
- package/dist/utils/safe-write.js +82 -0
- package/dist/utils/safe-write.js.map +1 -0
- package/install/jishu-install.sh +323 -27
- package/install/jishu-uninstall.sh +353 -20
- package/package.json +18 -1
- package/public/assets/Dashboard-BdWPtroF.js +1 -0
- package/public/assets/{HermesChatPanel-mFSureyc.js → HermesChatPanel-B_2HlVBQ.js} +1 -1
- package/public/assets/HermesConfigForm-DVlhg3WV.js +4 -0
- package/public/assets/{InitPassword-CVA8wQA6.js → InitPassword-D7glTExX.js} +1 -1
- package/public/assets/InstanceDetail-CxSy2cpe.js +92 -0
- package/public/assets/{Login-BWsZH2mu.js → Login-Cfr5c2sv.js} +1 -1
- package/public/assets/NewInstance-BIYDmJis.js +1 -0
- package/public/assets/ProviderRecommendations-BuRnvRcI.js +1 -0
- package/public/assets/Settings-Cc-tYBil.js +1 -0
- package/public/assets/Setup-lGZEk5jq.js +1 -0
- package/public/assets/{WeixinLoginPanel-CnjR8xMu.js → WeixinLoginPanel-CoGqzxeV.js} +2 -2
- package/public/assets/index-87IJXG-w.css +1 -0
- package/public/assets/index-BZc5zH7u.js +19 -0
- package/public/assets/providers-DtNXh9JD.js +1 -0
- package/public/assets/registry-BWnkJgZ1.js +2 -0
- package/public/assets/{usePolling-Do5Erqm_.js → usePolling-CwwT9KrC.js} +1 -1
- package/public/assets/{vendor-i18n-ucpM0OR0.js → vendor-i18n-y9V7Sfuu.js} +1 -1
- package/public/assets/{vendor-react-Bk1hRGiY.js → vendor-react-BWrEVJVb.js} +6 -6
- package/public/index.html +4 -4
- package/scripts/check-app-spec.mjs +457 -0
- package/scripts/check-i18n.mjs +154 -0
- package/scripts/check-new-file-tests.mjs +230 -0
- package/scripts/check-quarantine-expiry.mjs +105 -0
- package/scripts/perf/README.md +49 -0
- package/scripts/perf/auth.js +99 -0
- package/scripts/perf/config.js +63 -0
- package/scripts/perf/instances.js +143 -0
- package/scripts/perf/proxy.js +96 -0
- package/scripts/run.sh +4 -4
- package/scripts/smoke/files-w1.sh +142 -0
- package/scripts/smoke-backend.mjs +122 -0
- package/scripts/smoke-post-publish.mjs +346 -0
- package/public/assets/Dashboard-B-JoOjBQ.js +0 -1
- package/public/assets/HermesConfigForm-DvR05LK1.js +0 -4
- package/public/assets/InstanceDetail-DcZW2QGO.js +0 -91
- package/public/assets/NewInstance-BCIrAd86.js +0 -1
- package/public/assets/Settings-xkDcduFz.js +0 -1
- package/public/assets/Setup-Cfuwj4gV.js +0 -1
- package/public/assets/index-CPhVFEsx.css +0 -1
- package/public/assets/index-DQsM6Joa.js +0 -19
- package/public/assets/providers-V-vwrExZ.js +0 -1
- package/public/assets/registry-B4UFJdpA.js +0 -2
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drive MCP shim — exposes the panel's file + organize API to the
|
|
3
|
+
* agent inside the OpenClaw container (M1 W1.6).
|
|
4
|
+
*
|
|
5
|
+
* Why a shim, not a direct MCP server in panel:
|
|
6
|
+
* - Panel and agent run in different processes (and often different
|
|
7
|
+
* containers). Stdio MCP is the cheapest contract — the container
|
|
8
|
+
* already runs node, and `mcporter.json` already wires shim
|
|
9
|
+
* stdio servers via `command/args`.
|
|
10
|
+
* - The shim translates JSON-RPC tool calls into HTTP calls back
|
|
11
|
+
* to the panel via `host.docker.internal:8090`, authenticated
|
|
12
|
+
* with the per-host internal token (`X-Jishushell-Internal-Token`)
|
|
13
|
+
* plus the per-instance id (`X-Jishushell-Instance`).
|
|
14
|
+
*
|
|
15
|
+
* Tool surface (matches panel REST one-to-one):
|
|
16
|
+
* drive_list → GET /api/files
|
|
17
|
+
* drive_read_preview → GET /api/files/preview
|
|
18
|
+
* drive_quota → GET /api/files/quota
|
|
19
|
+
* drive_mkdir → POST /api/files/mkdir
|
|
20
|
+
* drive_organize_scan → POST /api/files/organize/scan
|
|
21
|
+
* drive_organize_apply → PUT /api/files/organize/batches/:id/apply
|
|
22
|
+
* drive_organize_revert → POST /api/files/organize/batches/:id/revert
|
|
23
|
+
* drive_search → GET /api/files/search
|
|
24
|
+
* drive_get_meta → GET /api/files/meta
|
|
25
|
+
* drive_set_meta → PUT /api/files/meta
|
|
26
|
+
* drive_reindex → POST /api/files/reindex
|
|
27
|
+
* drive_resolve_local_path → GET /api/files/resolve (v0.4 — IM-plugin bridge)
|
|
28
|
+
* drive_write_binary → PUT /api/files (v0.4 — base64 body)
|
|
29
|
+
*
|
|
30
|
+
* The user-facing chat experience this enables:
|
|
31
|
+
* user: "整理一下 inbox 里的发票"
|
|
32
|
+
* agent → drive_organize_scan({ path: "inbox" })
|
|
33
|
+
* agent → drive_organize_apply({ batch_id, selected_ids: [...] })
|
|
34
|
+
* agent: "✅ 整理了 8 张发票到 finance/2026/05/, 30 秒内可撤销"
|
|
35
|
+
*
|
|
36
|
+
* Stdio framing: NDJSON JSON-RPC 2.0 (MCP standard). Pure Node
|
|
37
|
+
* builtins — no npm install needed inside the runtime image.
|
|
38
|
+
*/
|
|
39
|
+
export const DRIVE_MCP_SHIM_VERSION = "0.4.0";
|
|
40
|
+
export const DRIVE_MCP_SHIM_SOURCE = `#!/usr/bin/env node
|
|
41
|
+
// jishushell-drive-shim — generated, do not edit by hand.
|
|
42
|
+
// Source: src/services/runtime/mcp-shims/drive-shim.ts
|
|
43
|
+
import { createInterface } from "node:readline";
|
|
44
|
+
|
|
45
|
+
// Per-instance values are baked in by the adapter at install time via
|
|
46
|
+
// substituteDriveShimPlaceholders(). OpenClaw scrubs env when spawning MCP
|
|
47
|
+
// subprocesses, so the env-based fallback is unreliable in production —
|
|
48
|
+
// the literal substitution path below is the source of truth. Env vars
|
|
49
|
+
// are kept as a backstop for hand-running the shim during development.
|
|
50
|
+
const PANEL_URL_LIT = "__JS_PANEL_URL__";
|
|
51
|
+
const TOKEN_LIT = "__JS_TOKEN__";
|
|
52
|
+
const INSTANCE_ID_LIT = "__JS_INSTANCE_ID__";
|
|
53
|
+
const PANEL_URL = (PANEL_URL_LIT.startsWith("__JS_") ? (process.env.JISHUSHELL_PANEL_URL || "http://host.docker.internal:8090") : PANEL_URL_LIT).replace(/\\/+$/, "");
|
|
54
|
+
const TOKEN = TOKEN_LIT.startsWith("__JS_") ? (process.env.JISHUSHELL_INTERNAL_TOKEN || "") : TOKEN_LIT;
|
|
55
|
+
const INSTANCE_ID = INSTANCE_ID_LIT.startsWith("__JS_") ? (process.env.JISHUSHELL_INSTANCE_ID || "") : INSTANCE_ID_LIT;
|
|
56
|
+
|
|
57
|
+
if (!TOKEN || !INSTANCE_ID) {
|
|
58
|
+
process.stderr.write("[drive-shim] missing JISHUSHELL_INTERNAL_TOKEN or JISHUSHELL_INSTANCE_ID env\\n");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function authedHeaders(extra) {
|
|
62
|
+
return Object.assign({
|
|
63
|
+
"X-Jishushell-Internal-Token": TOKEN,
|
|
64
|
+
"X-Jishushell-Instance": INSTANCE_ID,
|
|
65
|
+
}, extra || {});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function panelGet(path, query) {
|
|
69
|
+
const url = new URL(PANEL_URL + path);
|
|
70
|
+
for (const [k, v] of Object.entries(query || {})) {
|
|
71
|
+
if (v !== undefined && v !== null) url.searchParams.set(k, String(v));
|
|
72
|
+
}
|
|
73
|
+
const res = await fetch(url, { headers: authedHeaders() });
|
|
74
|
+
const body = await res.text();
|
|
75
|
+
let parsed;
|
|
76
|
+
try { parsed = body ? JSON.parse(body) : null; } catch { parsed = body; }
|
|
77
|
+
return { status: res.status, body: parsed };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function panelSend(method, path, query, jsonBody) {
|
|
81
|
+
const url = new URL(PANEL_URL + path);
|
|
82
|
+
for (const [k, v] of Object.entries(query || {})) {
|
|
83
|
+
if (v !== undefined && v !== null) url.searchParams.set(k, String(v));
|
|
84
|
+
}
|
|
85
|
+
const res = await fetch(url, {
|
|
86
|
+
method,
|
|
87
|
+
headers: authedHeaders({ "Content-Type": "application/json" }),
|
|
88
|
+
body: jsonBody !== undefined ? JSON.stringify(jsonBody) : undefined,
|
|
89
|
+
});
|
|
90
|
+
const body = await res.text();
|
|
91
|
+
let parsed;
|
|
92
|
+
try { parsed = body ? JSON.parse(body) : null; } catch { parsed = body; }
|
|
93
|
+
return { status: res.status, body: parsed };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function panelPutBinary(path, query, buf) {
|
|
97
|
+
const url = new URL(PANEL_URL + path);
|
|
98
|
+
for (const [k, v] of Object.entries(query || {})) {
|
|
99
|
+
if (v !== undefined && v !== null) url.searchParams.set(k, String(v));
|
|
100
|
+
}
|
|
101
|
+
const res = await fetch(url, {
|
|
102
|
+
method: "PUT",
|
|
103
|
+
headers: authedHeaders({
|
|
104
|
+
"Content-Type": "application/octet-stream",
|
|
105
|
+
"Content-Length": String(buf.length),
|
|
106
|
+
}),
|
|
107
|
+
body: buf,
|
|
108
|
+
});
|
|
109
|
+
const body = await res.text();
|
|
110
|
+
let parsed;
|
|
111
|
+
try { parsed = body ? JSON.parse(body) : null; } catch { parsed = body; }
|
|
112
|
+
return { status: res.status, body: parsed };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const TOOLS = [
|
|
116
|
+
{
|
|
117
|
+
name: "drive_list",
|
|
118
|
+
description:
|
|
119
|
+
"List a directory in the user's NAS (~/.jishushell/files/). Returns entries with name, is_dir, size, mtime, mime, etag. These paths are also valid host filesystem paths inside your container — call drive_resolve_local_path to get an absolute path you can hand to native IM channel send_file tools (Feishu / WeChat / ...).",
|
|
120
|
+
inputSchema: {
|
|
121
|
+
type: "object",
|
|
122
|
+
properties: {
|
|
123
|
+
path: { type: "string", description: "directory path relative to files/ root; \\"\\" for root" },
|
|
124
|
+
show_hidden: { type: "boolean" },
|
|
125
|
+
},
|
|
126
|
+
required: [],
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
name: "drive_read_preview",
|
|
131
|
+
description:
|
|
132
|
+
"Read a small text/JSON file preview (up to max_kb KB, default 256). Returns content + truncated flag.",
|
|
133
|
+
inputSchema: {
|
|
134
|
+
type: "object",
|
|
135
|
+
properties: {
|
|
136
|
+
path: { type: "string" },
|
|
137
|
+
max_kb: { type: "integer", description: "max kilobytes to read; default 256" },
|
|
138
|
+
},
|
|
139
|
+
required: ["path"],
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
name: "drive_quota",
|
|
144
|
+
description: "Get NAS storage quota: quota_mb, used_mb, available_mb, trash_mb.",
|
|
145
|
+
inputSchema: { type: "object", properties: {}, required: [] },
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
name: "drive_mkdir",
|
|
149
|
+
description: "Create a new directory in the user's NAS.",
|
|
150
|
+
inputSchema: {
|
|
151
|
+
type: "object",
|
|
152
|
+
properties: { path: { type: "string" } },
|
|
153
|
+
required: ["path"],
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
name: "drive_organize_scan",
|
|
158
|
+
description:
|
|
159
|
+
"Scan a directory and propose organize moves based on user-defined rules. Returns batch.id and suggestions[].",
|
|
160
|
+
inputSchema: {
|
|
161
|
+
type: "object",
|
|
162
|
+
properties: {
|
|
163
|
+
path: { type: "string", description: "subdirectory to scan; default scans the entire root" },
|
|
164
|
+
},
|
|
165
|
+
required: [],
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
name: "drive_organize_apply",
|
|
170
|
+
description:
|
|
171
|
+
"Apply previously-scanned organize suggestions. selected_ids defaults to all suggestions in the batch. Returns report.applied/skipped/failed and an undo window of 30 seconds.",
|
|
172
|
+
inputSchema: {
|
|
173
|
+
type: "object",
|
|
174
|
+
properties: {
|
|
175
|
+
batch_id: { type: "string" },
|
|
176
|
+
selected_ids: { type: "array", items: { type: "string" } },
|
|
177
|
+
},
|
|
178
|
+
required: ["batch_id"],
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
name: "drive_organize_revert",
|
|
183
|
+
description:
|
|
184
|
+
"Revert an applied organize batch. Only valid within the 30-second undo window; returns 410 expired afterwards.",
|
|
185
|
+
inputSchema: {
|
|
186
|
+
type: "object",
|
|
187
|
+
properties: { batch_id: { type: "string" } },
|
|
188
|
+
required: ["batch_id"],
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
name: "drive_read_full",
|
|
193
|
+
description:
|
|
194
|
+
"Read a TEXT file in full (up to 1MB). Use for summarizing PDFs/notes/code; for binary files this returns a hint to use the file URL instead. Always check the truncated flag.",
|
|
195
|
+
inputSchema: {
|
|
196
|
+
type: "object",
|
|
197
|
+
properties: {
|
|
198
|
+
path: { type: "string" },
|
|
199
|
+
max_kb: {
|
|
200
|
+
type: "integer",
|
|
201
|
+
description: "max kilobytes (default 1024 = 1MB; hard ceiling 4096)",
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
required: ["path"],
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
name: "drive_write_text",
|
|
209
|
+
description:
|
|
210
|
+
"Write a text file (overwrites if it exists). Use this when the agent generates a report / summary / draft and wants to persist it. Path is files/-relative; agents typically write under agent-data/{instance}/. To then SEND the file via an IM channel, call drive_resolve_local_path on the same path and pass the abs_path to the channel plugin's send_file tool.",
|
|
211
|
+
inputSchema: {
|
|
212
|
+
type: "object",
|
|
213
|
+
properties: {
|
|
214
|
+
path: { type: "string" },
|
|
215
|
+
content: { type: "string" },
|
|
216
|
+
},
|
|
217
|
+
required: ["path", "content"],
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
name: "drive_move",
|
|
222
|
+
description:
|
|
223
|
+
"Move or rename a single file/directory. Both paths are files/-relative. Default does NOT overwrite — pass overwrite=true to replace.",
|
|
224
|
+
inputSchema: {
|
|
225
|
+
type: "object",
|
|
226
|
+
properties: {
|
|
227
|
+
from: { type: "string" },
|
|
228
|
+
to: { type: "string" },
|
|
229
|
+
overwrite: { type: "boolean" },
|
|
230
|
+
},
|
|
231
|
+
required: ["from", "to"],
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
name: "drive_delete",
|
|
236
|
+
description:
|
|
237
|
+
"Soft-delete a file/directory (moves it to .trash/{date}/, recoverable for 30 days). Files-relative path; do not include leading slash.",
|
|
238
|
+
inputSchema: {
|
|
239
|
+
type: "object",
|
|
240
|
+
properties: { path: { type: "string" } },
|
|
241
|
+
required: ["path"],
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
name: "drive_search",
|
|
246
|
+
description: "Search indexed file content via FTS5; returns relative paths + snippets + sha256.",
|
|
247
|
+
inputSchema: {
|
|
248
|
+
type: "object",
|
|
249
|
+
properties: {
|
|
250
|
+
query: { type: "string", description: "FTS5 query string (supports operators: \\"AND\\", \\"OR\\", quoted phrases)." },
|
|
251
|
+
limit: { type: "number", description: "Max hits (default 20, max 100).", minimum: 1, maximum: 100 },
|
|
252
|
+
path: { type: "string", description: "Optional relative-path prefix to scope the search." },
|
|
253
|
+
},
|
|
254
|
+
required: ["query"],
|
|
255
|
+
},
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
name: "drive_get_meta",
|
|
259
|
+
description: "Read the sidecar metadata (tags/summary/links/notes) for a file by sha256 or by path. Returns null if no sidecar exists yet.",
|
|
260
|
+
inputSchema: {
|
|
261
|
+
type: "object",
|
|
262
|
+
properties: {
|
|
263
|
+
sha256: { type: "string", pattern: "^[a-fA-F0-9]{64}$", description: "64-hex content hash." },
|
|
264
|
+
path: { type: "string", description: "Relative path under the user file root." },
|
|
265
|
+
},
|
|
266
|
+
oneOf: [{ required: ["sha256"] }, { required: ["path"] }],
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
name: "drive_set_meta",
|
|
271
|
+
description: "Write or merge sidecar metadata for a file. payload is free-form JSON; merge=true (default) shallow-merges with the existing sidecar.",
|
|
272
|
+
inputSchema: {
|
|
273
|
+
type: "object",
|
|
274
|
+
properties: {
|
|
275
|
+
sha256: { type: "string", pattern: "^[a-fA-F0-9]{64}$" },
|
|
276
|
+
payload: { type: "object", description: "Sidecar fields to write. Schema: { tags?: string[], summary?: string, links?: [{path, rel}], agent_notes?: string, ... }." },
|
|
277
|
+
merge: { type: "boolean", description: "When true (default), shallow-merge with existing sidecar; when false, replace." },
|
|
278
|
+
},
|
|
279
|
+
required: ["sha256", "payload"],
|
|
280
|
+
},
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
name: "drive_reindex",
|
|
284
|
+
description: "Rescan the file tree (or a sub-path) and rebuild FTS5 entries for text/markdown/json/yaml/code files. Explicit trigger only.",
|
|
285
|
+
inputSchema: {
|
|
286
|
+
type: "object",
|
|
287
|
+
properties: {
|
|
288
|
+
path: { type: "string", description: "Optional relative prefix to limit the rescan." },
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
name: "drive_resolve_local_path",
|
|
294
|
+
description:
|
|
295
|
+
"Resolve a files/-relative path to its ABSOLUTE host filesystem path. Use this whenever you need to hand a path to a native IM channel plugin (Feishu / WeChat / Telegram / ... send_file tool) — those plugins read bytes from the local filesystem, not via HTTP. The returned abs_path is valid inside your container (panel binds host==container for files mounts) and on the host (raw_exec / process service managers run the agent natively on the host). Returns { abs_path, exists, is_dir?, size?, mtime?, external_mount?, external_mount_mode? }. Check exists before sending; check external_mount_mode='ro' before trying to overwrite.",
|
|
296
|
+
inputSchema: {
|
|
297
|
+
type: "object",
|
|
298
|
+
properties: {
|
|
299
|
+
path: { type: "string", description: "files/-relative path, e.g. \\"reports/q4.pdf\\" or \\"agent-data/<instance>/outbox/summary.pdf\\"." },
|
|
300
|
+
},
|
|
301
|
+
required: ["path"],
|
|
302
|
+
},
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
name: "drive_write_binary",
|
|
306
|
+
description:
|
|
307
|
+
"Write a binary file (overwrites if it exists). Use this when the agent generates a PDF / image / audio / archive that needs to be persisted or handed to an IM channel for sending. content_base64 is standard base64 of the file bytes (no data: URL prefix). Path is files/-relative; agents typically write under agent-data/{instance}/outbox/ so the file is clearly outbound. After writing, call drive_resolve_local_path on the same path to get the absolute path to pass to the IM channel's send_file tool. Soft cap: 10 MB raw (~13.5 MB base64) per call.",
|
|
308
|
+
inputSchema: {
|
|
309
|
+
type: "object",
|
|
310
|
+
properties: {
|
|
311
|
+
path: { type: "string" },
|
|
312
|
+
content_base64: { type: "string", description: "Standard base64 (RFC 4648). No data: URL prefix." },
|
|
313
|
+
},
|
|
314
|
+
required: ["path", "content_base64"],
|
|
315
|
+
},
|
|
316
|
+
},
|
|
317
|
+
];
|
|
318
|
+
|
|
319
|
+
async function callTool(name, args) {
|
|
320
|
+
switch (name) {
|
|
321
|
+
case "drive_list": {
|
|
322
|
+
const r = await panelGet("/api/files", { path: args.path || "", show_hidden: args.show_hidden });
|
|
323
|
+
return r;
|
|
324
|
+
}
|
|
325
|
+
case "drive_read_preview": {
|
|
326
|
+
const r = await panelGet("/api/files/preview", { path: args.path, max_kb: args.max_kb });
|
|
327
|
+
return r;
|
|
328
|
+
}
|
|
329
|
+
case "drive_quota": {
|
|
330
|
+
return panelGet("/api/files/quota");
|
|
331
|
+
}
|
|
332
|
+
case "drive_mkdir": {
|
|
333
|
+
return panelSend("POST", "/api/files/mkdir", null, { path: args.path });
|
|
334
|
+
}
|
|
335
|
+
case "drive_organize_scan": {
|
|
336
|
+
return panelSend("POST", "/api/files/organize/scan", null, { path: args.path || "" });
|
|
337
|
+
}
|
|
338
|
+
case "drive_organize_apply": {
|
|
339
|
+
// If selected_ids is missing or empty, fetch the batch and apply ALL suggestions.
|
|
340
|
+
let selected = Array.isArray(args.selected_ids) ? args.selected_ids : null;
|
|
341
|
+
if (!selected || selected.length === 0) {
|
|
342
|
+
const got = await panelGet("/api/files/organize/batches/" + encodeURIComponent(args.batch_id));
|
|
343
|
+
if (got.status === 200 && got.body && Array.isArray(got.body.suggestions)) {
|
|
344
|
+
selected = got.body.suggestions.map((s) => s.id);
|
|
345
|
+
} else {
|
|
346
|
+
return got;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
return panelSend("PUT", "/api/files/organize/batches/" + encodeURIComponent(args.batch_id) + "/apply", null, { selected_ids: selected });
|
|
350
|
+
}
|
|
351
|
+
case "drive_organize_revert": {
|
|
352
|
+
return panelSend("POST", "/api/files/organize/batches/" + encodeURIComponent(args.batch_id) + "/revert", null);
|
|
353
|
+
}
|
|
354
|
+
case "drive_read_full": {
|
|
355
|
+
const ceiling = 4096;
|
|
356
|
+
const wanted = Math.min(Number.isFinite(+args.max_kb) ? +args.max_kb : 1024, ceiling);
|
|
357
|
+
return panelGet("/api/files/preview", { path: args.path, max_kb: wanted });
|
|
358
|
+
}
|
|
359
|
+
case "drive_write_text": {
|
|
360
|
+
const text = String(args.content == null ? "" : args.content);
|
|
361
|
+
const buf = Buffer.from(text, "utf8");
|
|
362
|
+
return panelPutBinary("/api/files", { path: args.path, overwrite: "true", client_mime: "text/plain" }, buf);
|
|
363
|
+
}
|
|
364
|
+
case "drive_move": {
|
|
365
|
+
const overwrite = args.overwrite === true;
|
|
366
|
+
return panelSend("POST", "/api/files/move", null, {
|
|
367
|
+
from: args.from,
|
|
368
|
+
to: args.to,
|
|
369
|
+
overwrite,
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
case "drive_delete": {
|
|
373
|
+
return panelSend("DELETE", "/api/files", { path: args.path }, undefined);
|
|
374
|
+
}
|
|
375
|
+
case "drive_search": {
|
|
376
|
+
const qs = new URLSearchParams({ q: args.query });
|
|
377
|
+
if (args.limit != null) qs.set("limit", String(args.limit));
|
|
378
|
+
if (args.path) qs.set("path", args.path);
|
|
379
|
+
return panelGet("/api/files/search?" + qs.toString(), null);
|
|
380
|
+
}
|
|
381
|
+
case "drive_get_meta": {
|
|
382
|
+
const qs = new URLSearchParams();
|
|
383
|
+
if (args.sha256) qs.set("sha", args.sha256);
|
|
384
|
+
else if (args.path) qs.set("path", args.path);
|
|
385
|
+
return panelGet("/api/files/meta?" + qs.toString(), null);
|
|
386
|
+
}
|
|
387
|
+
case "drive_set_meta": {
|
|
388
|
+
const qs = new URLSearchParams({ sha: args.sha256 });
|
|
389
|
+
if (args.merge === false) qs.set("merge", "false");
|
|
390
|
+
return panelSend("PUT", "/api/files/meta?" + qs.toString(), null, args.payload);
|
|
391
|
+
}
|
|
392
|
+
case "drive_reindex": {
|
|
393
|
+
const qs = args.path ? "?path=" + encodeURIComponent(args.path) : "";
|
|
394
|
+
return panelSend("POST", "/api/files/reindex" + qs, null, undefined);
|
|
395
|
+
}
|
|
396
|
+
case "drive_resolve_local_path": {
|
|
397
|
+
return panelGet("/api/files/resolve", { path: args.path });
|
|
398
|
+
}
|
|
399
|
+
case "drive_write_binary": {
|
|
400
|
+
const b64 = String(args.content_base64 == null ? "" : args.content_base64);
|
|
401
|
+
if (!b64) {
|
|
402
|
+
return { status: 400, body: { detail: "content_base64 required" } };
|
|
403
|
+
}
|
|
404
|
+
let buf;
|
|
405
|
+
try { buf = Buffer.from(b64, "base64"); } catch (e) {
|
|
406
|
+
return { status: 400, body: { detail: "content_base64 not valid base64" } };
|
|
407
|
+
}
|
|
408
|
+
// Soft cap so MCP stdio framing (NDJSON) stays healthy. Bigger files
|
|
409
|
+
// should be staged via Filebrowser / WebDAV directly into files/.
|
|
410
|
+
if (buf.length > 10 * 1024 * 1024) {
|
|
411
|
+
return { status: 413, body: { detail: "content_base64 exceeds 10MB soft cap; split into smaller writes or stage the file via Filebrowser/WebDAV" } };
|
|
412
|
+
}
|
|
413
|
+
return panelPutBinary("/api/files", { path: args.path, overwrite: "true" }, buf);
|
|
414
|
+
}
|
|
415
|
+
default:
|
|
416
|
+
throw new Error("unknown tool: " + name);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function send(msg) {
|
|
421
|
+
process.stdout.write(JSON.stringify(msg) + "\\n");
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function ok(id, result) { send({ jsonrpc: "2.0", id, result }); }
|
|
425
|
+
function err(id, code, message, data) {
|
|
426
|
+
send({ jsonrpc: "2.0", id, error: { code, message, data } });
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
async function dispatch(msg) {
|
|
430
|
+
if (msg.method === "initialize") {
|
|
431
|
+
return ok(msg.id, {
|
|
432
|
+
protocolVersion: "2024-11-05",
|
|
433
|
+
capabilities: { tools: {} },
|
|
434
|
+
serverInfo: { name: "jishushell-drive-shim", version: ${JSON.stringify(DRIVE_MCP_SHIM_VERSION)} },
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
if (msg.method === "tools/list") {
|
|
438
|
+
return ok(msg.id, { tools: TOOLS });
|
|
439
|
+
}
|
|
440
|
+
if (msg.method === "tools/call") {
|
|
441
|
+
const name = msg.params && msg.params.name;
|
|
442
|
+
const args = (msg.params && msg.params.arguments) || {};
|
|
443
|
+
try {
|
|
444
|
+
const r = await callTool(name, args);
|
|
445
|
+
const text = r.status >= 200 && r.status < 300
|
|
446
|
+
? "ok " + r.status + ": " + JSON.stringify(r.body)
|
|
447
|
+
: "error " + r.status + ": " + JSON.stringify(r.body);
|
|
448
|
+
return ok(msg.id, {
|
|
449
|
+
content: [{ type: "text", text: text }],
|
|
450
|
+
isError: !(r.status >= 200 && r.status < 300),
|
|
451
|
+
});
|
|
452
|
+
} catch (e) {
|
|
453
|
+
return err(msg.id, -32000, "tool execution failed", { error: String(e && e.message || e) });
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
if (msg.method === "ping") return ok(msg.id, {});
|
|
457
|
+
if (msg.method && msg.method.startsWith("notifications/")) return; // no reply
|
|
458
|
+
if (msg.id !== undefined) err(msg.id, -32601, "method not found: " + msg.method);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const rl = createInterface({ input: process.stdin, terminal: false });
|
|
462
|
+
rl.on("line", (line) => {
|
|
463
|
+
const trimmed = line.trim();
|
|
464
|
+
if (!trimmed) return;
|
|
465
|
+
let msg;
|
|
466
|
+
try { msg = JSON.parse(trimmed); } catch { return; }
|
|
467
|
+
Promise.resolve(dispatch(msg)).catch((e) => {
|
|
468
|
+
process.stderr.write("[drive-shim] dispatch crashed: " + (e && e.stack || e) + "\\n");
|
|
469
|
+
});
|
|
470
|
+
});
|
|
471
|
+
process.stdin.on("end", () => process.exit(0));
|
|
472
|
+
`;
|
|
473
|
+
/**
|
|
474
|
+
* Bake per-instance values into the shim template. Returns the source to
|
|
475
|
+
* write to disk. Must be called before writing because OpenClaw scrubs
|
|
476
|
+
* env when spawning MCP subprocesses, so the env-based fallback in the
|
|
477
|
+
* template is unreliable in production.
|
|
478
|
+
*
|
|
479
|
+
* Placeholders use non-JS-identifier marker `__JS_*__` so they grep cleanly
|
|
480
|
+
* and never collide with real code paths.
|
|
481
|
+
*/
|
|
482
|
+
export function substituteDriveShimPlaceholders(params) {
|
|
483
|
+
const escapeStr = (s) => s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
484
|
+
return DRIVE_MCP_SHIM_SOURCE
|
|
485
|
+
.replace(/__JS_PANEL_URL__/g, escapeStr(params.panelUrl))
|
|
486
|
+
.replace(/__JS_TOKEN__/g, escapeStr(params.token))
|
|
487
|
+
.replace(/__JS_INSTANCE_ID__/g, escapeStr(params.instanceId));
|
|
488
|
+
}
|
|
489
|
+
//# sourceMappingURL=drive-shim.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drive-shim.js","sourceRoot":"","sources":["../../../../src/services/runtime/mcp-shims/drive-shim.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAEH,MAAM,CAAC,MAAM,sBAAsB,GAAG,OAAO,CAAC;AAE9C,MAAM,CAAC,MAAM,qBAAqB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8DA0YyB,IAAI,CAAC,SAAS,CAAC,sBAAsB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsCnG,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,UAAU,+BAA+B,CAC7C,MAA+D;IAE/D,MAAM,SAAS,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAC/E,OAAO,qBAAqB;SACzB,OAAO,CAAC,mBAAmB,EAAE,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;SACxD,OAAO,CAAC,eAAe,EAAE,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;SACjD,OAAO,CAAC,qBAAqB,EAAE,SAAS,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC;AAClE,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic MCP firewall — see docs/app-interconnect-design.md §17.
|
|
3
|
+
*
|
|
4
|
+
* Exports the firewall .mjs source as a string so adapters can drop it
|
|
5
|
+
* into agent-home at applyConnectionEnv time. Single source of truth for
|
|
6
|
+
* all third-party MCP wrapping; replaces the per-capability shim files
|
|
7
|
+
* introduced in PR 7 (which remain only as a one-release fallback).
|
|
8
|
+
*
|
|
9
|
+
* The firewall:
|
|
10
|
+
* 1. Reads `--config <path>.json` describing the canonical tool_schema
|
|
11
|
+
* and how to spawn the upstream MCP package.
|
|
12
|
+
* 2. Spawns the upstream as a stdio MCP server.
|
|
13
|
+
* 3. Intercepts `tools/list` — never asks upstream, always returns the
|
|
14
|
+
* canonical schema. This is the prompt-injection defense.
|
|
15
|
+
* 4. Forwards `tools/call` to upstream; optionally wraps results in
|
|
16
|
+
* `{untrusted: true, source: <capability_id>, content: ...}` so the
|
|
17
|
+
* consuming LLM treats outputs as data, not instructions.
|
|
18
|
+
* 5. Forwards everything else (initialize, ping, notifications) and
|
|
19
|
+
* rewrites `serverInfo` to identify as the firewall, not the
|
|
20
|
+
* upstream package, on the way back.
|
|
21
|
+
*
|
|
22
|
+
* Stdio framing: NDJSON JSON-RPC 2.0 (MCP stdio transport). Uses Node
|
|
23
|
+
* built-ins only — no npm install needed inside the runtime container.
|
|
24
|
+
*/
|
|
25
|
+
export declare const MCP_FIREWALL_VERSION = "0.1.0";
|
|
26
|
+
export declare const MCP_FIREWALL_SOURCE = "#!/usr/bin/env node\n// jishushell-mcp-firewall \u2014 generated, do not edit by hand.\n// Source: src/services/runtime/mcp-shims/firewall.ts\nimport { spawn } from \"node:child_process\";\nimport { createInterface } from \"node:readline\";\nimport { readFileSync } from \"node:fs\";\n\nfunction loadConfig() {\n const idx = process.argv.indexOf(\"--config\");\n if (idx < 0 || idx + 1 >= process.argv.length) {\n process.stderr.write(\"[mcp-firewall] missing --config <path>\\n\");\n process.exit(2);\n }\n const path = process.argv[idx + 1];\n let raw;\n try { raw = readFileSync(path, \"utf-8\"); }\n catch (e) { process.stderr.write(\"[mcp-firewall] cannot read \" + path + \": \" + e.message + \"\\n\"); process.exit(2); }\n let cfg;\n try { cfg = JSON.parse(raw); }\n catch (e) { process.stderr.write(\"[mcp-firewall] config is not valid JSON: \" + e.message + \"\\n\"); process.exit(2); }\n if (!cfg?.capability_id || !cfg?.tool_schema?.name || !cfg?.upstream?.command) {\n process.stderr.write(\"[mcp-firewall] config missing capability_id / tool_schema.name / upstream.command\\n\");\n process.exit(2);\n }\n return cfg;\n}\n\nconst CONFIG = loadConfig();\nconst TOOL = {\n name: CONFIG.tool_schema.name,\n description: CONFIG.tool_schema.description ?? \"\",\n inputSchema: CONFIG.tool_schema.inputSchema ?? CONFIG.tool_schema.parameters ?? { type: \"object\", properties: {} }\n};\nconst WRAP_OUTPUTS = CONFIG.wrap_outputs !== false;\nconst SERVER_NAME = CONFIG.server_info_name ?? \"jishushell-mcp-firewall\";\n\nconst upstream = spawn(CONFIG.upstream.command, CONFIG.upstream.args ?? [], {\n env: { ...process.env, ...(CONFIG.upstream.env ?? {}) },\n stdio: [\"pipe\", \"pipe\", \"inherit\"]\n});\nupstream.on(\"exit\", (code, signal) => {\n process.stderr.write(\"[mcp-firewall] upstream exited code=\" + code + \" signal=\" + signal + \"; firewall exiting too\\n\");\n process.exit(code ?? 1);\n});\nupstream.on(\"error\", (e) => { process.stderr.write(\"[mcp-firewall] failed to spawn upstream: \" + e.message + \"\\n\"); process.exit(2); });\n\nfunction send(out, msg) { out.write(JSON.stringify(msg) + \"\\n\"); }\nfunction reply(id, result) { if (id != null) send(process.stdout, { jsonrpc: \"2.0\", id, result }); }\nfunction replyError(id, code, message) { if (id != null) send(process.stdout, { jsonrpc: \"2.0\", id, error: { code, message } }); }\n\nlet nextUpstreamId = 1;\nconst pending = new Map();\nfunction forwardToUpstream(req, kind) {\n const upstreamId = nextUpstreamId++;\n pending.set(upstreamId, { clientId: req.id, kind });\n send(upstream.stdin, { ...req, id: upstreamId });\n}\nfunction forwardNotificationToUpstream(req) { send(upstream.stdin, req); }\n\nconst clientReader = createInterface({ input: process.stdin });\nclientReader.on(\"line\", (line) => {\n const t = line.trim(); if (!t) return;\n let req; try { req = JSON.parse(t); } catch { return; }\n const { id, method } = req;\n if (method === \"tools/list\") { reply(id, { tools: [TOOL] }); return; }\n if (method === \"initialize\") { forwardToUpstream(req, \"initialize\"); return; }\n if (method === \"tools/call\") {\n if (req.params?.name !== TOOL.name) { replyError(id, -32601, \"unknown tool: \" + req.params?.name); return; }\n forwardToUpstream(req, \"tools/call\");\n return;\n }\n if (id == null) { forwardNotificationToUpstream(req); return; }\n forwardToUpstream(req, \"passthrough\");\n});\n\nconst upstreamReader = createInterface({ input: upstream.stdout });\nupstreamReader.on(\"line\", (line) => {\n const t = line.trim(); if (!t) return;\n let msg; try { msg = JSON.parse(t); } catch { return; }\n if (msg.id == null) { send(process.stdout, msg); return; }\n const tracking = pending.get(msg.id);\n if (!tracking) { send(process.stdout, msg); return; }\n pending.delete(msg.id);\n const out = { ...msg, id: tracking.clientId };\n if (tracking.kind === \"initialize\" && out.result?.serverInfo) {\n out.result = { ...out.result, serverInfo: { name: SERVER_NAME, version: \"0.1.0\" } };\n }\n if (tracking.kind === \"tools/call\" && WRAP_OUTPUTS && out.result?.content) {\n out.result = {\n ...out.result,\n content: [{\n type: \"text\",\n text: JSON.stringify({ untrusted: true, source: CONFIG.capability_id, content: out.result.content }, null, 2)\n }]\n };\n }\n send(process.stdout, out);\n});\n\nprocess.on(\"SIGTERM\", () => { try { upstream.kill(\"SIGTERM\"); } catch {} process.exit(0); });\nprocess.on(\"SIGINT\", () => { try { upstream.kill(\"SIGINT\"); } catch {} process.exit(0); });\nprocess.stdin.on(\"close\", () => { try { upstream.kill(\"SIGTERM\"); } catch {} process.exit(0); });\n";
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic MCP firewall — see docs/app-interconnect-design.md §17.
|
|
3
|
+
*
|
|
4
|
+
* Exports the firewall .mjs source as a string so adapters can drop it
|
|
5
|
+
* into agent-home at applyConnectionEnv time. Single source of truth for
|
|
6
|
+
* all third-party MCP wrapping; replaces the per-capability shim files
|
|
7
|
+
* introduced in PR 7 (which remain only as a one-release fallback).
|
|
8
|
+
*
|
|
9
|
+
* The firewall:
|
|
10
|
+
* 1. Reads `--config <path>.json` describing the canonical tool_schema
|
|
11
|
+
* and how to spawn the upstream MCP package.
|
|
12
|
+
* 2. Spawns the upstream as a stdio MCP server.
|
|
13
|
+
* 3. Intercepts `tools/list` — never asks upstream, always returns the
|
|
14
|
+
* canonical schema. This is the prompt-injection defense.
|
|
15
|
+
* 4. Forwards `tools/call` to upstream; optionally wraps results in
|
|
16
|
+
* `{untrusted: true, source: <capability_id>, content: ...}` so the
|
|
17
|
+
* consuming LLM treats outputs as data, not instructions.
|
|
18
|
+
* 5. Forwards everything else (initialize, ping, notifications) and
|
|
19
|
+
* rewrites `serverInfo` to identify as the firewall, not the
|
|
20
|
+
* upstream package, on the way back.
|
|
21
|
+
*
|
|
22
|
+
* Stdio framing: NDJSON JSON-RPC 2.0 (MCP stdio transport). Uses Node
|
|
23
|
+
* built-ins only — no npm install needed inside the runtime container.
|
|
24
|
+
*/
|
|
25
|
+
export const MCP_FIREWALL_VERSION = "0.1.0";
|
|
26
|
+
export const MCP_FIREWALL_SOURCE = `#!/usr/bin/env node
|
|
27
|
+
// jishushell-mcp-firewall — generated, do not edit by hand.
|
|
28
|
+
// Source: src/services/runtime/mcp-shims/firewall.ts
|
|
29
|
+
import { spawn } from "node:child_process";
|
|
30
|
+
import { createInterface } from "node:readline";
|
|
31
|
+
import { readFileSync } from "node:fs";
|
|
32
|
+
|
|
33
|
+
function loadConfig() {
|
|
34
|
+
const idx = process.argv.indexOf("--config");
|
|
35
|
+
if (idx < 0 || idx + 1 >= process.argv.length) {
|
|
36
|
+
process.stderr.write("[mcp-firewall] missing --config <path>\\n");
|
|
37
|
+
process.exit(2);
|
|
38
|
+
}
|
|
39
|
+
const path = process.argv[idx + 1];
|
|
40
|
+
let raw;
|
|
41
|
+
try { raw = readFileSync(path, "utf-8"); }
|
|
42
|
+
catch (e) { process.stderr.write("[mcp-firewall] cannot read " + path + ": " + e.message + "\\n"); process.exit(2); }
|
|
43
|
+
let cfg;
|
|
44
|
+
try { cfg = JSON.parse(raw); }
|
|
45
|
+
catch (e) { process.stderr.write("[mcp-firewall] config is not valid JSON: " + e.message + "\\n"); process.exit(2); }
|
|
46
|
+
if (!cfg?.capability_id || !cfg?.tool_schema?.name || !cfg?.upstream?.command) {
|
|
47
|
+
process.stderr.write("[mcp-firewall] config missing capability_id / tool_schema.name / upstream.command\\n");
|
|
48
|
+
process.exit(2);
|
|
49
|
+
}
|
|
50
|
+
return cfg;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const CONFIG = loadConfig();
|
|
54
|
+
const TOOL = {
|
|
55
|
+
name: CONFIG.tool_schema.name,
|
|
56
|
+
description: CONFIG.tool_schema.description ?? "",
|
|
57
|
+
inputSchema: CONFIG.tool_schema.inputSchema ?? CONFIG.tool_schema.parameters ?? { type: "object", properties: {} }
|
|
58
|
+
};
|
|
59
|
+
const WRAP_OUTPUTS = CONFIG.wrap_outputs !== false;
|
|
60
|
+
const SERVER_NAME = CONFIG.server_info_name ?? "jishushell-mcp-firewall";
|
|
61
|
+
|
|
62
|
+
const upstream = spawn(CONFIG.upstream.command, CONFIG.upstream.args ?? [], {
|
|
63
|
+
env: { ...process.env, ...(CONFIG.upstream.env ?? {}) },
|
|
64
|
+
stdio: ["pipe", "pipe", "inherit"]
|
|
65
|
+
});
|
|
66
|
+
upstream.on("exit", (code, signal) => {
|
|
67
|
+
process.stderr.write("[mcp-firewall] upstream exited code=" + code + " signal=" + signal + "; firewall exiting too\\n");
|
|
68
|
+
process.exit(code ?? 1);
|
|
69
|
+
});
|
|
70
|
+
upstream.on("error", (e) => { process.stderr.write("[mcp-firewall] failed to spawn upstream: " + e.message + "\\n"); process.exit(2); });
|
|
71
|
+
|
|
72
|
+
function send(out, msg) { out.write(JSON.stringify(msg) + "\\n"); }
|
|
73
|
+
function reply(id, result) { if (id != null) send(process.stdout, { jsonrpc: "2.0", id, result }); }
|
|
74
|
+
function replyError(id, code, message) { if (id != null) send(process.stdout, { jsonrpc: "2.0", id, error: { code, message } }); }
|
|
75
|
+
|
|
76
|
+
let nextUpstreamId = 1;
|
|
77
|
+
const pending = new Map();
|
|
78
|
+
function forwardToUpstream(req, kind) {
|
|
79
|
+
const upstreamId = nextUpstreamId++;
|
|
80
|
+
pending.set(upstreamId, { clientId: req.id, kind });
|
|
81
|
+
send(upstream.stdin, { ...req, id: upstreamId });
|
|
82
|
+
}
|
|
83
|
+
function forwardNotificationToUpstream(req) { send(upstream.stdin, req); }
|
|
84
|
+
|
|
85
|
+
const clientReader = createInterface({ input: process.stdin });
|
|
86
|
+
clientReader.on("line", (line) => {
|
|
87
|
+
const t = line.trim(); if (!t) return;
|
|
88
|
+
let req; try { req = JSON.parse(t); } catch { return; }
|
|
89
|
+
const { id, method } = req;
|
|
90
|
+
if (method === "tools/list") { reply(id, { tools: [TOOL] }); return; }
|
|
91
|
+
if (method === "initialize") { forwardToUpstream(req, "initialize"); return; }
|
|
92
|
+
if (method === "tools/call") {
|
|
93
|
+
if (req.params?.name !== TOOL.name) { replyError(id, -32601, "unknown tool: " + req.params?.name); return; }
|
|
94
|
+
forwardToUpstream(req, "tools/call");
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (id == null) { forwardNotificationToUpstream(req); return; }
|
|
98
|
+
forwardToUpstream(req, "passthrough");
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const upstreamReader = createInterface({ input: upstream.stdout });
|
|
102
|
+
upstreamReader.on("line", (line) => {
|
|
103
|
+
const t = line.trim(); if (!t) return;
|
|
104
|
+
let msg; try { msg = JSON.parse(t); } catch { return; }
|
|
105
|
+
if (msg.id == null) { send(process.stdout, msg); return; }
|
|
106
|
+
const tracking = pending.get(msg.id);
|
|
107
|
+
if (!tracking) { send(process.stdout, msg); return; }
|
|
108
|
+
pending.delete(msg.id);
|
|
109
|
+
const out = { ...msg, id: tracking.clientId };
|
|
110
|
+
if (tracking.kind === "initialize" && out.result?.serverInfo) {
|
|
111
|
+
out.result = { ...out.result, serverInfo: { name: SERVER_NAME, version: "${MCP_FIREWALL_VERSION}" } };
|
|
112
|
+
}
|
|
113
|
+
if (tracking.kind === "tools/call" && WRAP_OUTPUTS && out.result?.content) {
|
|
114
|
+
out.result = {
|
|
115
|
+
...out.result,
|
|
116
|
+
content: [{
|
|
117
|
+
type: "text",
|
|
118
|
+
text: JSON.stringify({ untrusted: true, source: CONFIG.capability_id, content: out.result.content }, null, 2)
|
|
119
|
+
}]
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
send(process.stdout, out);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
process.on("SIGTERM", () => { try { upstream.kill("SIGTERM"); } catch {} process.exit(0); });
|
|
126
|
+
process.on("SIGINT", () => { try { upstream.kill("SIGINT"); } catch {} process.exit(0); });
|
|
127
|
+
process.stdin.on("close", () => { try { upstream.kill("SIGTERM"); } catch {} process.exit(0); });
|
|
128
|
+
`;
|
|
129
|
+
//# sourceMappingURL=firewall.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"firewall.js","sourceRoot":"","sources":["../../../../src/services/runtime/mcp-shims/firewall.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,MAAM,CAAC,MAAM,oBAAoB,GAAG,OAAO,CAAC;AAE5C,MAAM,CAAC,MAAM,mBAAmB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;+EAqF4C,oBAAoB;;;;;;;;;;;;;;;;;CAiBlG,CAAC"}
|