@chrysb/alphaclaw 0.9.0-beta.7 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/bin/alphaclaw.js +25 -25
  2. package/lib/cli/git-runtime.js +97 -0
  3. package/lib/public/css/chat.css +0 -12
  4. package/lib/public/css/explorer.css +48 -0
  5. package/lib/public/css/shell.css +149 -0
  6. package/lib/public/css/tailwind.generated.css +1 -1
  7. package/lib/public/css/theme.css +265 -0
  8. package/lib/public/dist/app.bundle.js +2770 -2762
  9. package/lib/public/js/app.js +26 -14
  10. package/lib/public/js/components/agents-tab/create-channel-modal.js +259 -59
  11. package/lib/public/js/components/gateway.js +0 -286
  12. package/lib/public/js/components/general/index.js +0 -7
  13. package/lib/public/js/components/icons.js +26 -25
  14. package/lib/public/js/components/modal-shell.js +1 -1
  15. package/lib/public/js/components/models-tab/provider-auth-card.js +60 -49
  16. package/lib/public/js/components/models-tab/use-models.js +74 -9
  17. package/lib/public/js/components/models.js +52 -37
  18. package/lib/public/js/components/onboarding/use-welcome-codex.js +34 -24
  19. package/lib/public/js/components/onboarding/welcome-config.js +76 -10
  20. package/lib/public/js/components/onboarding/welcome-form-step.js +2 -7
  21. package/lib/public/js/components/onboarding/welcome-header.js +12 -14
  22. package/lib/public/js/components/onboarding/welcome-setup-step.js +3 -3
  23. package/lib/public/js/components/providers.js +53 -42
  24. package/lib/public/js/components/routes/chat-route.js +2 -9
  25. package/lib/public/js/components/routes/general-route.js +0 -6
  26. package/lib/public/js/components/routes/index.js +0 -1
  27. package/lib/public/js/components/routes/watchdog-route.js +0 -6
  28. package/lib/public/js/components/sidebar.js +21 -7
  29. package/lib/public/js/components/theme-toggle.js +113 -0
  30. package/lib/public/js/components/update-modal.js +174 -51
  31. package/lib/public/js/components/watchdog-tab/index.js +0 -6
  32. package/lib/public/js/components/welcome/index.js +0 -2
  33. package/lib/public/js/components/welcome/use-welcome.js +101 -36
  34. package/lib/public/js/hooks/use-app-shell-controller.js +16 -33
  35. package/lib/public/js/lib/api.js +0 -28
  36. package/lib/public/js/lib/app-navigation.js +0 -2
  37. package/lib/public/js/lib/channel-provider-availability.js +1 -2
  38. package/lib/public/js/lib/codex-oauth-window.js +22 -0
  39. package/lib/public/js/lib/model-catalog.js +20 -0
  40. package/lib/public/js/lib/storage-keys.js +1 -1
  41. package/lib/public/login.html +8 -4
  42. package/lib/public/setup.html +9 -0
  43. package/lib/scripts/git +47 -1
  44. package/lib/server/agents/channels.js +1 -4
  45. package/lib/server/alphaclaw-version.js +590 -132
  46. package/lib/server/constants.js +5 -0
  47. package/lib/server/db/webhooks/index.js +48 -8
  48. package/lib/server/exec-defaults-config.js +163 -0
  49. package/lib/server/init/register-server-routes.js +0 -8
  50. package/lib/server/init/server-lifecycle.js +2 -0
  51. package/lib/server/model-catalog-cache.js +251 -0
  52. package/lib/server/onboarding/index.js +5 -0
  53. package/lib/server/routes/models.js +14 -23
  54. package/lib/server/routes/nodes.js +9 -23
  55. package/lib/server/routes/system.js +3 -16
  56. package/lib/server/routes/webhooks.js +12 -1
  57. package/lib/server/startup.js +8 -0
  58. package/lib/server/watchdog-notify.js +172 -55
  59. package/lib/server.js +17 -2
  60. package/package.json +2 -2
  61. package/patches/openclaw+2026.4.9.patch +13 -0
  62. package/lib/public/js/components/mcp-tab/index.js +0 -237
  63. package/lib/public/js/components/routes/mcp-route.js +0 -7
  64. package/lib/server/mcp-bridge.js +0 -158
  65. package/lib/server/routes/mcp.js +0 -292
  66. package/patches/openclaw+2026.3.28.patch +0 -13
@@ -1,237 +0,0 @@
1
- import { h } from "preact";
2
- import { useState, useCallback } from "preact/hooks";
3
- import htm from "htm";
4
- import {
5
- fetchMcpInfo,
6
- startMcpBridge,
7
- stopMcpBridge,
8
- } from "../../lib/api.js";
9
- import { usePolling } from "../../hooks/usePolling.js";
10
- import { showToast } from "../toast.js";
11
- import { PageHeader } from "../page-header.js";
12
- import { ActionButton } from "../action-button.js";
13
- import { PaneShell } from "../pane-shell.js";
14
-
15
- const html = htm.bind(h);
16
-
17
- const kMcpTools = [
18
- { name: "conversations_list", desc: "List recent routed conversations with filters" },
19
- { name: "conversation_get", desc: "Return a single conversation by session key" },
20
- { name: "messages_read", desc: "Retrieve transcript history for a conversation" },
21
- { name: "attachments_fetch", desc: "Extract non-text content metadata from messages" },
22
- { name: "events_poll", desc: "Read queued live events since a cursor position" },
23
- { name: "events_wait", desc: "Long-poll for next matching event with timeout" },
24
- { name: "messages_send", desc: "Send text replies through existing routes" },
25
- { name: "permissions_list_open", desc: "List pending exec/plugin approval requests" },
26
- { name: "permissions_respond", desc: "Resolve approvals (allow-once, allow-always, deny)" },
27
- ];
28
-
29
- const StatusDot = ({ active }) => html`
30
- <span
31
- class="inline-block w-2 h-2 rounded-full shrink-0 ${active
32
- ? "bg-green-500"
33
- : "bg-gray-600"}"
34
- />
35
- `;
36
-
37
- const buildConfigSnippet = ({ origin, token }) => {
38
- const encodedToken = encodeURIComponent(String(token || ""));
39
- const sseUrl = `${origin}/mcp/sse?token=${encodedToken}`;
40
- return JSON.stringify(
41
- {
42
- mcpServers: {
43
- openclaw: {
44
- url: sseUrl,
45
- },
46
- },
47
- },
48
- null,
49
- 2,
50
- );
51
- };
52
-
53
- export const McpTab = () => {
54
- const [acting, setActing] = useState(false);
55
-
56
- const {
57
- data: info,
58
- refresh,
59
- } = usePolling(fetchMcpInfo, 8000, {
60
- cacheKey: "/api/mcp/info",
61
- });
62
- const loading = !info;
63
-
64
- const running = !!info?.running;
65
- const tokenAvailable = !!info?.tokenAvailable;
66
- const gatewayToken = info?.gatewayToken || "";
67
-
68
- const handleStart = useCallback(async () => {
69
- if (acting) return;
70
- setActing(true);
71
- try {
72
- const result = await startMcpBridge();
73
- if (result?.ok) {
74
- showToast(
75
- result.alreadyRunning
76
- ? "MCP bridge already running"
77
- : "MCP bridge started",
78
- "success",
79
- );
80
- } else {
81
- showToast("Failed to start MCP bridge", "error");
82
- }
83
- await refresh({ force: true });
84
- } catch (err) {
85
- showToast("Failed to start: " + err.message, "error");
86
- } finally {
87
- setActing(false);
88
- }
89
- }, [acting, refresh]);
90
-
91
- const handleStop = useCallback(async () => {
92
- if (acting) return;
93
- setActing(true);
94
- try {
95
- const result = await stopMcpBridge();
96
- if (result?.ok) {
97
- showToast("MCP bridge stopped", "success");
98
- } else {
99
- showToast("Failed to stop MCP bridge", "error");
100
- }
101
- await refresh({ force: true });
102
- } catch (err) {
103
- showToast("Failed to stop: " + err.message, "error");
104
- } finally {
105
- setActing(false);
106
- }
107
- }, [acting, refresh]);
108
-
109
- const handleCopy = useCallback(() => {
110
- const origin = window.location.origin;
111
- const snippet = buildConfigSnippet({ origin, token: gatewayToken });
112
- navigator.clipboard
113
- .writeText(snippet)
114
- .then(() => showToast("Copied to clipboard", "success"))
115
- .catch(() => showToast("Failed to copy", "error"));
116
- }, [gatewayToken]);
117
-
118
- const configSnippet = buildConfigSnippet({
119
- origin: typeof window !== "undefined" ? window.location.origin : "https://your-host",
120
- token: gatewayToken || "<gateway-token>",
121
- });
122
-
123
- if (loading && !info) {
124
- return html`
125
- <${PaneShell} header=${html`<${PageHeader} title="MCP" />`}>
126
- <div class="bg-surface border border-border rounded-xl p-4 text-sm text-fg-muted">
127
- Loading...
128
- </div>
129
- </${PaneShell}>
130
- `;
131
- }
132
-
133
- return html`
134
- <${PaneShell}
135
- header=${html`
136
- <${PageHeader}
137
- title="MCP"
138
- actions=${html`
139
- ${running
140
- ? html`<${ActionButton}
141
- onClick=${handleStop}
142
- disabled=${acting}
143
- loading=${acting}
144
- loadingMode="inline"
145
- tone="secondary"
146
- size="sm"
147
- idleLabel="Stop bridge"
148
- loadingLabel="Stopping…"
149
- className="text-xs"
150
- />`
151
- : html`<${ActionButton}
152
- onClick=${handleStart}
153
- disabled=${acting}
154
- loading=${acting}
155
- loadingMode="inline"
156
- tone="primary"
157
- size="sm"
158
- idleLabel="Start bridge"
159
- loadingLabel="Starting…"
160
- className="text-xs"
161
- />`}
162
- `}
163
- />
164
- `}
165
- >
166
- <!-- Status -->
167
- <div class="bg-surface border border-border rounded-xl overflow-hidden">
168
- <h3 class="card-label text-xs px-4 pt-3 pb-2">Status</h3>
169
- <div class="px-4 pb-3 space-y-2">
170
- <div class="flex items-center gap-2 text-sm">
171
- <${StatusDot} active=${running} />
172
- <span class="text-body">
173
- MCP Bridge: ${running ? "Running" : "Stopped"}
174
- </span>
175
- ${running && info?.pid
176
- ? html`<span class="text-fg-dim text-xs">(PID ${info.pid})</span>`
177
- : null}
178
- </div>
179
- <div class="flex items-center gap-2 text-sm">
180
- <${StatusDot} active=${tokenAvailable} />
181
- <span class="text-body">
182
- Gateway token: ${tokenAvailable ? "Configured" : "Not set"}
183
- </span>
184
- </div>
185
- ${info?.gatewayWsUrl
186
- ? html`<div class="text-xs text-fg-dim">
187
- Gateway: <code class="bg-field px-1 rounded">${info.gatewayWsUrl}</code>
188
- </div>`
189
- : null}
190
- </div>
191
- </div>
192
-
193
- <!-- Config Snippet -->
194
- <div class="bg-surface border border-border rounded-xl overflow-hidden">
195
- <div class="flex items-center justify-between px-4 pt-3 pb-2">
196
- <h3 class="card-label text-xs">Client Config</h3>
197
- <button
198
- onclick=${handleCopy}
199
- class="text-xs px-2 py-0.5 rounded border border-border text-fg-muted hover:text-body hover:border-fg-muted"
200
- >
201
- Copy
202
- </button>
203
- </div>
204
- <div class="px-4 pb-3">
205
- <p class="text-xs text-fg-dim mb-2">
206
- Add this to your MCP client config (Cursor, Claude Desktop, etc.):
207
- </p>
208
- <pre
209
- class="bg-field border border-border rounded-lg p-3 text-xs text-body font-mono overflow-x-auto whitespace-pre"
210
- >${configSnippet}</pre>
211
- ${!running
212
- ? html`<p class="text-xs text-status-warning-muted mt-2">
213
- Start the MCP bridge above before connecting a client.
214
- </p>`
215
- : null}
216
- </div>
217
- </div>
218
-
219
- <!-- Available Tools -->
220
- <div class="bg-surface border border-border rounded-xl overflow-hidden">
221
- <h3 class="card-label text-xs px-4 pt-3 pb-2">Available Tools</h3>
222
- <div class="divide-y divide-border">
223
- ${kMcpTools.map(
224
- (tool) => html`
225
- <div class="flex items-start gap-3 px-4 py-2">
226
- <code class="text-xs shrink-0 pt-0.5" style="min-width: 170px"
227
- >${tool.name}</code
228
- >
229
- <span class="text-xs text-fg-dim">${tool.desc}</span>
230
- </div>
231
- `,
232
- )}
233
- </div>
234
- </div>
235
- </${PaneShell}>
236
- `;
237
- };
@@ -1,7 +0,0 @@
1
- import { h } from "preact";
2
- import htm from "htm";
3
- import { McpTab } from "../mcp-tab/index.js";
4
-
5
- const html = htm.bind(h);
6
-
7
- export const McpRoute = () => html`<${McpTab} />`;
@@ -1,158 +0,0 @@
1
- const { spawn } = require("child_process");
2
-
3
- const kStderrTailLines = 50;
4
-
5
- let mcpChild = null;
6
- let mcpStartedAt = null;
7
- let mcpStderrTail = [];
8
- let stdoutBuffer = Buffer.alloc(0);
9
- let onMcpMessage = null;
10
-
11
- const appendStderrTail = (chunk) => {
12
- const text = Buffer.isBuffer(chunk)
13
- ? chunk.toString("utf8")
14
- : String(chunk ?? "");
15
- for (const line of text.split("\n")) {
16
- const trimmed = line.trimEnd();
17
- if (!trimmed) continue;
18
- mcpStderrTail.push(trimmed);
19
- }
20
- if (mcpStderrTail.length > kStderrTailLines) {
21
- mcpStderrTail = mcpStderrTail.slice(-kStderrTailLines);
22
- }
23
- };
24
-
25
- const isMcpBridgeRunning = () =>
26
- mcpChild !== null && mcpChild.exitCode === null && !mcpChild.killed;
27
-
28
- const getMcpBridgeStatus = () => ({
29
- running: isMcpBridgeRunning(),
30
- pid: isMcpBridgeRunning() ? mcpChild.pid : null,
31
- startedAt: isMcpBridgeRunning() ? mcpStartedAt : null,
32
- stderrTail: mcpStderrTail.slice(-10),
33
- });
34
-
35
- const setOnMcpMessage = (callback) => {
36
- onMcpMessage = typeof callback === "function" ? callback : null;
37
- };
38
-
39
- const drainStdoutLines = () => {
40
- while (stdoutBuffer.length > 0) {
41
- const newlineIndex = stdoutBuffer.indexOf("\n");
42
- if (newlineIndex === -1) break;
43
-
44
- const line = stdoutBuffer.toString("utf8", 0, newlineIndex).replace(/\r$/, "");
45
- stdoutBuffer = stdoutBuffer.slice(newlineIndex + 1);
46
- if (!line.trim()) continue;
47
-
48
- let parsed = null;
49
- try {
50
- parsed = JSON.parse(line);
51
- } catch {
52
- appendStderrTail(`Unparseable MCP stdout line: ${line.slice(0, 120)}`);
53
- continue;
54
- }
55
- console.log(`[mcp-bridge] ← child stdout: method=${parsed?.method || "(response)"} id=${parsed?.id ?? "none"}`);
56
- if (onMcpMessage) {
57
- try {
58
- onMcpMessage(parsed);
59
- } catch (err) {
60
- appendStderrTail(`onMcpMessage error: ${err?.message || err}`);
61
- }
62
- }
63
- }
64
- };
65
-
66
- const startMcpBridge = ({ gatewayEnv, gatewayWsUrl, gatewayToken }) => {
67
- if (isMcpBridgeRunning()) {
68
- return { ok: true, alreadyRunning: true, ...getMcpBridgeStatus() };
69
- }
70
-
71
- const args = ["mcp", "serve"];
72
- if (gatewayWsUrl) {
73
- args.push("--url", gatewayWsUrl);
74
- }
75
- const childEnv = gatewayEnv();
76
- if (gatewayToken) {
77
- childEnv.OPENCLAW_GATEWAY_TOKEN = String(gatewayToken);
78
- }
79
-
80
- mcpStderrTail = [];
81
- stdoutBuffer = Buffer.alloc(0);
82
-
83
- const child = spawn("openclaw", args, {
84
- env: childEnv,
85
- stdio: ["pipe", "pipe", "pipe"],
86
- });
87
-
88
- mcpChild = child;
89
- mcpStartedAt = Date.now();
90
-
91
- child.stdout.on("data", (data) => {
92
- const chunk = Buffer.isBuffer(data)
93
- ? data
94
- : Buffer.from(String(data ?? ""), "utf8");
95
- stdoutBuffer = Buffer.concat([stdoutBuffer, chunk]);
96
- drainStdoutLines();
97
- });
98
-
99
- child.stderr.on("data", (data) => {
100
- appendStderrTail(data);
101
- process.stderr.write(`[mcp-bridge] ${data}`);
102
- });
103
-
104
- child.on("exit", (code, signal) => {
105
- console.log(
106
- `[mcp-bridge] Process exited with code ${code}${signal ? ` signal ${signal}` : ""}`,
107
- );
108
- if (mcpChild === child) mcpChild = null;
109
- mcpStartedAt = null;
110
- });
111
-
112
- child.on("error", (error) => {
113
- appendStderrTail(error?.message || String(error || "unknown process error"));
114
- console.error(
115
- `[mcp-bridge] Process failed to start: ${error?.message || error}`,
116
- );
117
- });
118
-
119
- console.log(`[mcp-bridge] Started MCP bridge (pid ${child.pid})`);
120
- return { ok: true, alreadyRunning: false, ...getMcpBridgeStatus() };
121
- };
122
-
123
- const stopMcpBridge = () => {
124
- if (!isMcpBridgeRunning()) {
125
- return { ok: true, wasStopped: true };
126
- }
127
- const pid = mcpChild.pid;
128
- mcpChild.kill("SIGTERM");
129
- mcpChild = null;
130
- mcpStartedAt = null;
131
- console.log(`[mcp-bridge] Stopped MCP bridge (pid ${pid})`);
132
- return { ok: true, wasStopped: false };
133
- };
134
-
135
- const writeToMcpBridge = (jsonRpcMessage) => {
136
- if (!isMcpBridgeRunning()) return false;
137
- if (jsonRpcMessage == null) return false;
138
- const payload =
139
- typeof jsonRpcMessage === "string"
140
- ? jsonRpcMessage
141
- : JSON.stringify(jsonRpcMessage);
142
- if (!payload) return false;
143
- const method = jsonRpcMessage?.method;
144
- const id = jsonRpcMessage?.id;
145
- const payloadBytes = Buffer.byteLength(payload, "utf8");
146
- console.log(`[mcp-bridge] → child stdin: method=${method || "(response)"} id=${id ?? "none"} bytes=${payloadBytes}`);
147
- mcpChild.stdin.write(`${payload}\n`);
148
- return true;
149
- };
150
-
151
- module.exports = {
152
- isMcpBridgeRunning,
153
- getMcpBridgeStatus,
154
- startMcpBridge,
155
- stopMcpBridge,
156
- writeToMcpBridge,
157
- setOnMcpMessage,
158
- };
@@ -1,292 +0,0 @@
1
- const { randomUUID } = require("crypto");
2
- const { createRequire } = require("module");
3
-
4
- // Load the SDK through openclaw's dependency tree so its express@5 peer
5
- // stays nested and never hoists over AlphaClaw's express@4 at the app root.
6
- const openclawRequire = createRequire(require.resolve("openclaw"));
7
- const {
8
- StreamableHTTPServerTransport,
9
- } = openclawRequire("@modelcontextprotocol/sdk/server/streamableHttp.js");
10
-
11
- const {
12
- isMcpBridgeRunning,
13
- getMcpBridgeStatus,
14
- startMcpBridge,
15
- stopMcpBridge,
16
- writeToMcpBridge,
17
- setOnMcpMessage,
18
- } = require("../mcp-bridge");
19
- const { getGatewayPort } = require("../gateway");
20
- const { readOpenclawConfig } = require("../openclaw-config");
21
-
22
- const resolveGatewayWsUrl = ({ openclawDir, gatewayPort }) => {
23
- const cfg = readOpenclawConfig({ openclawDir, fallback: {} });
24
- const gatewayTlsEnabled = cfg?.gateway?.tls?.enabled === true;
25
- const scheme = gatewayTlsEnabled ? "wss" : "ws";
26
- return `${scheme}://127.0.0.1:${gatewayPort}`;
27
- };
28
-
29
- const sessions = new Map();
30
- let activeTransport = null;
31
- const kSseKeepAliveMs = 15_000;
32
- const kMaxSessions = 8;
33
-
34
- let nextBridgeId = 1;
35
- const pendingRequests = new Map();
36
-
37
- const adoptSession = (sessionId, transport) => {
38
- sessions.set(sessionId, transport);
39
- activeTransport = transport;
40
-
41
- if (sessions.size > kMaxSessions) {
42
- const oldestId = sessions.keys().next().value;
43
- if (oldestId !== sessionId) {
44
- const old = sessions.get(oldestId);
45
- sessions.delete(oldestId);
46
- old.close().catch(() => {});
47
- console.log(`[mcp] Evicted oldest session: ${oldestId}`);
48
- }
49
- }
50
- };
51
-
52
- const forwardToBridge = (message, transport) => {
53
- if (message.id != null) {
54
- const bridgeId = nextBridgeId++;
55
- pendingRequests.set(bridgeId, { originalId: message.id, transport });
56
- writeToMcpBridge({ ...message, id: bridgeId });
57
- } else {
58
- writeToMcpBridge(message);
59
- }
60
- };
61
-
62
- const cleanupTransport = (transport) => {
63
- for (const [id, t] of sessions) {
64
- if (t === transport) {
65
- sessions.delete(id);
66
- break;
67
- }
68
- }
69
- for (const [bridgeId, pending] of pendingRequests) {
70
- if (pending.transport === transport) pendingRequests.delete(bridgeId);
71
- }
72
- if (activeTransport === transport) activeTransport = null;
73
- };
74
-
75
- const closeAllSessions = () => {
76
- for (const [, t] of sessions) t.close().catch(() => {});
77
- sessions.clear();
78
- pendingRequests.clear();
79
- activeTransport = null;
80
- };
81
-
82
- const registerMcpRoutes = ({
83
- app,
84
- requireAuth,
85
- constants,
86
- gatewayEnv,
87
- openclawDir,
88
- }) => {
89
- setOnMcpMessage((message) => {
90
- if (message.id != null) {
91
- const pending = pendingRequests.get(message.id);
92
- if (pending) {
93
- pendingRequests.delete(message.id);
94
- pending.transport
95
- .send({ ...message, id: pending.originalId })
96
- .catch((err) => {
97
- console.error("[mcp] Failed to forward response:", err?.message);
98
- });
99
- }
100
- return;
101
- }
102
- if (activeTransport) {
103
- activeTransport.send(message).catch(() => {});
104
- }
105
- });
106
-
107
- // ── Internal API (session auth) ────────────────────────────────
108
-
109
- app.get("/api/mcp/info", requireAuth, (_req, res) => {
110
- const port = getGatewayPort();
111
- const gatewayWsUrl = resolveGatewayWsUrl({
112
- openclawDir,
113
- gatewayPort: port,
114
- });
115
- res.json({
116
- ok: true,
117
- ...getMcpBridgeStatus(),
118
- gatewayPort: port,
119
- gatewayWsUrl,
120
- tokenAvailable: !!constants.GATEWAY_TOKEN,
121
- gatewayToken: constants.GATEWAY_TOKEN || "",
122
- });
123
- });
124
-
125
- app.post("/api/mcp/start", requireAuth, (_req, res) => {
126
- const port = getGatewayPort();
127
- const result = startMcpBridge({
128
- gatewayEnv,
129
- gatewayWsUrl: resolveGatewayWsUrl({
130
- openclawDir,
131
- gatewayPort: port,
132
- }),
133
- gatewayToken: constants.GATEWAY_TOKEN,
134
- });
135
- res.json(result);
136
- });
137
-
138
- app.post("/api/mcp/stop", requireAuth, async (_req, res) => {
139
- closeAllSessions();
140
- const result = stopMcpBridge();
141
- res.json(result);
142
- });
143
-
144
- // ── MCP transport endpoint (token auth) ────────────────────────
145
-
146
- const validateMcpToken = (req, res) => {
147
- const bearerToken = String(req.get("authorization") || "")
148
- .replace(/^Bearer\s+/i, "")
149
- .trim();
150
- const queryToken = String(req.query?.token || "");
151
- const rawToken = bearerToken || queryToken;
152
- const normalizedToken = rawToken.replace(/ /g, "+");
153
- if (!constants.GATEWAY_TOKEN) {
154
- res
155
- .status(503)
156
- .json({ error: "Gateway token is not configured for MCP transport" });
157
- return false;
158
- }
159
- if (!normalizedToken || normalizedToken !== constants.GATEWAY_TOKEN) {
160
- res.status(401).json({ error: "Invalid or missing token" });
161
- return false;
162
- }
163
- return true;
164
- };
165
-
166
- // Primary MCP endpoint – Streamable HTTP (GET / POST / DELETE)
167
- app.all("/mcp/sse", async (req, res) => {
168
- if (!validateMcpToken(req, res)) return;
169
-
170
- if (!isMcpBridgeRunning()) {
171
- res.status(503).json({ error: "MCP bridge is not running" });
172
- return;
173
- }
174
-
175
- if (req.method === "GET") {
176
- res.setHeader("X-Accel-Buffering", "no");
177
- const keepAliveId = setInterval(() => {
178
- if (res.headersSent && !res.writableEnded) {
179
- res.write(": keepalive\n\n");
180
- }
181
- }, kSseKeepAliveMs);
182
- res.on("close", () => clearInterval(keepAliveId));
183
- }
184
-
185
- const sessionId = req.headers["mcp-session-id"];
186
-
187
- // ── Existing session ───────────────────────────────────────
188
- if (sessionId) {
189
- const transport = sessions.get(sessionId);
190
- if (transport) {
191
- console.log(
192
- `[mcp] ${req.method} sessionId=${sessionId} → routed (sessions=${sessions.size})`,
193
- );
194
- try {
195
- await transport.handleRequest(req, res, req.body);
196
- } catch (err) {
197
- console.error(
198
- "[mcp] handleRequest error (existing session):",
199
- err?.message,
200
- );
201
- if (!res.headersSent) {
202
- res.status(500).json({ error: "Internal transport error" });
203
- }
204
- }
205
- } else {
206
- console.log(
207
- `[mcp] ${req.method} sessionId=${sessionId} → NOT FOUND (sessions=${sessions.size})`,
208
- );
209
- res.status(404).json({
210
- jsonrpc: "2.0",
211
- error: {
212
- code: -32001,
213
- message: "Session not found. The server may have been restarted.",
214
- },
215
- id: null,
216
- });
217
- }
218
- return;
219
- }
220
-
221
- // ── New session (POST without session ID) ────────────────
222
- if (req.method === "POST") {
223
- const transport = new StreamableHTTPServerTransport({
224
- sessionIdGenerator: () => randomUUID(),
225
- enableJsonResponse: true,
226
- onsessioninitialized: (newSessionId) => {
227
- adoptSession(newSessionId, transport);
228
- console.log(
229
- `[mcp] Session adopted: ${newSessionId} (sessions=${sessions.size})`,
230
- );
231
- },
232
- });
233
-
234
- transport.onmessage = (message) => {
235
- forwardToBridge(message, transport);
236
- };
237
-
238
- transport.onclose = () => {
239
- cleanupTransport(transport);
240
- console.log(`[mcp] Transport closed (sessions=${sessions.size})`);
241
- };
242
-
243
- transport.onerror = (err) => {
244
- console.error("[mcp] Transport error:", err?.message);
245
- };
246
-
247
- await transport.start();
248
-
249
- try {
250
- await transport.handleRequest(req, res, req.body);
251
- } catch (err) {
252
- console.error(
253
- "[mcp] handleRequest error (new session):",
254
- err?.message,
255
- );
256
- if (!res.headersSent) {
257
- res.status(500).json({ error: "Failed to initialize MCP session" });
258
- }
259
- }
260
- return;
261
- }
262
-
263
- res.status(400).json({
264
- jsonrpc: "2.0",
265
- error: { code: -32600, message: "Bad Request" },
266
- id: null,
267
- });
268
- });
269
-
270
- // Legacy endpoint for SSE-transport clients that POST to /mcp/message
271
- app.post("/mcp/message", async (req, res) => {
272
- if (!validateMcpToken(req, res)) return;
273
- if (!isMcpBridgeRunning()) {
274
- res.status(503).json({ error: "MCP bridge is not running" });
275
- return;
276
- }
277
- if (!activeTransport) {
278
- res.status(503).json({ error: "No active MCP session" });
279
- return;
280
- }
281
- try {
282
- await activeTransport.handleRequest(req, res, req.body);
283
- } catch (err) {
284
- console.error("[mcp] handleRequest error (/mcp/message):", err?.message);
285
- if (!res.headersSent) {
286
- res.status(500).json({ error: "Internal transport error" });
287
- }
288
- }
289
- });
290
- };
291
-
292
- module.exports = { registerMcpRoutes };
@@ -1,13 +0,0 @@
1
- diff --git a/node_modules/openclaw/dist/gateway-cli-DlnlX7IW.js b/node_modules/openclaw/dist/gateway-cli-DlnlX7IW.js
2
- index ca48b932..c12478c4 100644
3
- --- a/node_modules/openclaw/dist/gateway-cli-DlnlX7IW.js
4
- +++ b/node_modules/openclaw/dist/gateway-cli-DlnlX7IW.js
5
- @@ -25935,7 +25935,7 @@ function attachGatewayWsMessageHandler(params) {
6
- close(1008, truncateCloseReason(authMessage));
7
- };
8
- const clearUnboundScopes = () => {
9
- - if (scopes.length > 0) {
10
- + if (scopes.length > 0 && !sharedAuthOk) {
11
- scopes = [];
12
- connectParams.scopes = scopes;
13
- }