openclaw-navigator 5.0.1 → 5.1.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/cli.mjs +308 -48
- package/mcp.mjs +69 -3
- package/package.json +1 -1
package/cli.mjs
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import { spawn } from "node:child_process";
|
|
18
|
-
import { randomUUID } from "node:crypto";
|
|
18
|
+
import { randomUUID, createHash } from "node:crypto";
|
|
19
19
|
import { existsSync, appendFileSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
20
20
|
import { createServer, request as httpRequest } from "node:http";
|
|
21
21
|
import { connect as netConnect } from "node:net";
|
|
@@ -57,6 +57,24 @@ const recentEvents = [];
|
|
|
57
57
|
const MAX_EVENTS = 200;
|
|
58
58
|
const validTokens = new Set();
|
|
59
59
|
|
|
60
|
+
// ── Chat session state ────────────────────────────────────────────────────
|
|
61
|
+
const chatSessions = new Map(); // sessionKey → { messages: [...] }
|
|
62
|
+
const wsClients = new Set(); // connected WebSocket clients
|
|
63
|
+
|
|
64
|
+
function getChatSession(key = "main") {
|
|
65
|
+
if (!chatSessions.has(key)) {
|
|
66
|
+
chatSessions.set(key, { messages: [] });
|
|
67
|
+
}
|
|
68
|
+
return chatSessions.get(key);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function broadcastToWS(event) {
|
|
72
|
+
const data = JSON.stringify(event);
|
|
73
|
+
for (const ws of wsClients) {
|
|
74
|
+
try { ws.send(data); } catch {}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
60
78
|
// OC Web UI reverse proxy target (configurable via --ui-port or env)
|
|
61
79
|
let ocUIPort = parseInt(process.env.OPENCLAW_UI_PORT ?? "4000", 10);
|
|
62
80
|
|
|
@@ -445,8 +463,9 @@ function handleRequest(req, res) {
|
|
|
445
463
|
},
|
|
446
464
|
routing: {
|
|
447
465
|
"/ui/*": `localhost:${ocUIPort}`,
|
|
448
|
-
"/api/*":
|
|
449
|
-
"/
|
|
466
|
+
"/api/sessions/*": "bridge (in-process chat)",
|
|
467
|
+
"/api/*": `localhost:${ocGatewayPort} (fallback)`,
|
|
468
|
+
"/ws": "bridge (in-process WebSocket)",
|
|
450
469
|
},
|
|
451
470
|
tunnel: activeTunnelURL
|
|
452
471
|
? {
|
|
@@ -469,7 +488,9 @@ function handleRequest(req, res) {
|
|
|
469
488
|
if (!bridgeState.connected) {
|
|
470
489
|
bridgeState.connected = true;
|
|
471
490
|
bridgeState.connectedAt = Date.now();
|
|
491
|
+
const activeURL = activeTunnelURL ?? `http://localhost:${bridgePort}`;
|
|
472
492
|
console.log(`\n${GREEN}✓${RESET} ${BOLD}Navigator connected!${RESET}`);
|
|
493
|
+
console.log(` ${DIM}Tunnel: ${activeURL}${RESET}`);
|
|
473
494
|
}
|
|
474
495
|
bridgeState.lastHeartbeat = Date.now();
|
|
475
496
|
|
|
@@ -903,8 +924,117 @@ function handleRequest(req, res) {
|
|
|
903
924
|
return;
|
|
904
925
|
}
|
|
905
926
|
|
|
927
|
+
// ── POST /api/sessions/send — Chat: receive user message ──
|
|
928
|
+
if (req.method === "POST" && path === "/api/sessions/send") {
|
|
929
|
+
readBody(req).then(raw => {
|
|
930
|
+
const body = JSON.parse(raw);
|
|
931
|
+
const sessionKey = body.sessionKey || "main";
|
|
932
|
+
const message = body.message;
|
|
933
|
+
if (!message) {
|
|
934
|
+
sendJSON(res, 400, { ok: false, error: "Missing 'message'" });
|
|
935
|
+
return;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
const session = getChatSession(sessionKey);
|
|
939
|
+
const userMsg = {
|
|
940
|
+
role: "user",
|
|
941
|
+
content: message,
|
|
942
|
+
timestamp: Date.now(),
|
|
943
|
+
id: randomUUID(),
|
|
944
|
+
};
|
|
945
|
+
session.messages.push(userMsg);
|
|
946
|
+
|
|
947
|
+
// Also push as a bridge event so MCP tools can see it
|
|
948
|
+
recentEvents.push({
|
|
949
|
+
type: "chat.user_message",
|
|
950
|
+
sessionKey,
|
|
951
|
+
message,
|
|
952
|
+
messageId: userMsg.id,
|
|
953
|
+
timestamp: userMsg.timestamp,
|
|
954
|
+
});
|
|
955
|
+
if (recentEvents.length > MAX_EVENTS) recentEvents.shift();
|
|
956
|
+
|
|
957
|
+
// Broadcast to WebSocket clients
|
|
958
|
+
broadcastToWS({ type: "chat.user_message", sessionKey, message, messageId: userMsg.id });
|
|
959
|
+
|
|
960
|
+
sendJSON(res, 200, { ok: true, messageId: userMsg.id });
|
|
961
|
+
}).catch(() => {
|
|
962
|
+
sendJSON(res, 400, { ok: false, error: "Invalid JSON" });
|
|
963
|
+
});
|
|
964
|
+
return;
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
// ── GET /api/sessions/history — Chat: fetch history ──
|
|
968
|
+
if (req.method === "GET" && path === "/api/sessions/history") {
|
|
969
|
+
const sessionKey = url.searchParams.get("sessionKey") || "main";
|
|
970
|
+
const session = getChatSession(sessionKey);
|
|
971
|
+
sendJSON(res, 200, { ok: true, messages: session.messages });
|
|
972
|
+
return;
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
// ── POST /api/sessions/respond — Chat: agent sends response ──
|
|
976
|
+
// Used by MCP tools to send agent responses to the chat
|
|
977
|
+
if (req.method === "POST" && path === "/api/sessions/respond") {
|
|
978
|
+
readBody(req).then(raw => {
|
|
979
|
+
const body = JSON.parse(raw);
|
|
980
|
+
const sessionKey = body.sessionKey || "main";
|
|
981
|
+
const content = body.content || body.message;
|
|
982
|
+
if (!content) {
|
|
983
|
+
sendJSON(res, 400, { ok: false, error: "Missing 'content'" });
|
|
984
|
+
return;
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
const session = getChatSession(sessionKey);
|
|
988
|
+
const assistantMsg = {
|
|
989
|
+
role: "assistant",
|
|
990
|
+
content,
|
|
991
|
+
timestamp: Date.now(),
|
|
992
|
+
id: randomUUID(),
|
|
993
|
+
};
|
|
994
|
+
session.messages.push(assistantMsg);
|
|
995
|
+
|
|
996
|
+
// Broadcast final response via WebSocket
|
|
997
|
+
broadcastToWS({
|
|
998
|
+
type: "chat.final",
|
|
999
|
+
text: content,
|
|
1000
|
+
content,
|
|
1001
|
+
runId: body.runId || null,
|
|
1002
|
+
sessionKey,
|
|
1003
|
+
});
|
|
1004
|
+
|
|
1005
|
+
sendJSON(res, 200, { ok: true, messageId: assistantMsg.id });
|
|
1006
|
+
}).catch(() => {
|
|
1007
|
+
sendJSON(res, 400, { ok: false, error: "Invalid JSON" });
|
|
1008
|
+
});
|
|
1009
|
+
return;
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
// ── POST /api/sessions/stream — Chat: agent streams partial text ──
|
|
1013
|
+
if (req.method === "POST" && path === "/api/sessions/stream") {
|
|
1014
|
+
readBody(req).then(raw => {
|
|
1015
|
+
const body = JSON.parse(raw);
|
|
1016
|
+
const text = body.text || body.delta || "";
|
|
1017
|
+
const runId = body.runId || null;
|
|
1018
|
+
const sessionKey = body.sessionKey || "main";
|
|
1019
|
+
|
|
1020
|
+
broadcastToWS({
|
|
1021
|
+
type: "chat.delta",
|
|
1022
|
+
text,
|
|
1023
|
+
delta: text,
|
|
1024
|
+
runId,
|
|
1025
|
+
sessionKey,
|
|
1026
|
+
});
|
|
1027
|
+
|
|
1028
|
+
sendJSON(res, 200, { ok: true });
|
|
1029
|
+
}).catch(() => {
|
|
1030
|
+
sendJSON(res, 400, { ok: false, error: "Invalid JSON" });
|
|
1031
|
+
});
|
|
1032
|
+
return;
|
|
1033
|
+
}
|
|
1034
|
+
|
|
906
1035
|
// ── Reverse proxy: /api/* → OC Gateway (localhost:ocGatewayPort) ─────
|
|
907
1036
|
// Keeps /api/ prefix intact so /api/sessions/send → localhost:18789/api/sessions/send
|
|
1037
|
+
// This is a fallback — specific chat paths above are handled directly.
|
|
908
1038
|
if (path.startsWith("/api/")) {
|
|
909
1039
|
const targetURL = `${path}${url.search}`;
|
|
910
1040
|
|
|
@@ -1147,11 +1277,12 @@ ${BOLD}Stability (recommended for production):${RESET}
|
|
|
1147
1277
|
--tunnel-hostname <host> Hostname for named tunnel (e.g. nav.yourdomain.com)
|
|
1148
1278
|
|
|
1149
1279
|
${BOLD}Routing (through Cloudflare tunnel):${RESET}
|
|
1150
|
-
/ui/*
|
|
1151
|
-
/api/*
|
|
1152
|
-
/ws, WebSocket
|
|
1153
|
-
/
|
|
1154
|
-
/
|
|
1280
|
+
/ui/* → localhost:<ui-port> Web UI (login page, dashboard)
|
|
1281
|
+
/api/sessions/* → bridge (in-process) Chat send/history/respond/stream
|
|
1282
|
+
/ws, WebSocket → bridge (in-process) Real-time chat events
|
|
1283
|
+
/api/* (other) → localhost:<gateway-port> Gateway API fallback
|
|
1284
|
+
/health → bridge itself Health check
|
|
1285
|
+
/navigator/* → bridge itself Navigator control endpoints
|
|
1155
1286
|
|
|
1156
1287
|
${BOLD}Environment variables:${RESET}
|
|
1157
1288
|
OPENCLAW_UI_PORT=4000 Where the web UI runs
|
|
@@ -1237,49 +1368,142 @@ module.exports = {
|
|
|
1237
1368
|
server.listen(port, bindHost, () => resolve());
|
|
1238
1369
|
});
|
|
1239
1370
|
|
|
1240
|
-
// ── WebSocket
|
|
1241
|
-
// Proxies WebSocket connections so Navigator can stream chat events
|
|
1371
|
+
// ── WebSocket handler — manages chat connections directly ─────────────
|
|
1242
1372
|
server.on("upgrade", (req, socket, head) => {
|
|
1243
1373
|
const reqUrl = new URL(req.url ?? "/", "http://localhost");
|
|
1244
1374
|
const reqPath = reqUrl.pathname;
|
|
1245
1375
|
|
|
1246
|
-
// Only
|
|
1247
|
-
if (reqPath
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
// Pipe bidirectionally
|
|
1263
|
-
socket.pipe(proxy);
|
|
1264
|
-
proxy.pipe(socket);
|
|
1265
|
-
});
|
|
1376
|
+
// Only handle /ws paths
|
|
1377
|
+
if (reqPath !== "/ws" && !reqPath.startsWith("/ws/")) {
|
|
1378
|
+
socket.destroy();
|
|
1379
|
+
return;
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
// Perform WebSocket handshake
|
|
1383
|
+
const key = req.headers["sec-websocket-key"];
|
|
1384
|
+
if (!key) {
|
|
1385
|
+
socket.destroy();
|
|
1386
|
+
return;
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
const accept = createHash("sha1")
|
|
1390
|
+
.update(key + "258EAFA5-E914-47DA-95CA-5AB5DC799C07")
|
|
1391
|
+
.digest("base64");
|
|
1266
1392
|
|
|
1267
|
-
|
|
1268
|
-
|
|
1393
|
+
socket.write(
|
|
1394
|
+
"HTTP/1.1 101 Switching Protocols\r\n" +
|
|
1395
|
+
"Upgrade: websocket\r\n" +
|
|
1396
|
+
"Connection: Upgrade\r\n" +
|
|
1397
|
+
`Sec-WebSocket-Accept: ${accept}\r\n` +
|
|
1398
|
+
"\r\n"
|
|
1399
|
+
);
|
|
1400
|
+
|
|
1401
|
+
// Create a minimal WebSocket wrapper
|
|
1402
|
+
const ws = {
|
|
1403
|
+
socket,
|
|
1404
|
+
send(data) {
|
|
1405
|
+
const payload = Buffer.from(data);
|
|
1406
|
+
const frame = [];
|
|
1407
|
+
frame.push(0x81); // text frame, FIN bit
|
|
1408
|
+
if (payload.length < 126) {
|
|
1409
|
+
frame.push(payload.length);
|
|
1410
|
+
} else if (payload.length < 65536) {
|
|
1411
|
+
frame.push(126, (payload.length >> 8) & 0xFF, payload.length & 0xFF);
|
|
1412
|
+
} else {
|
|
1413
|
+
frame.push(127);
|
|
1414
|
+
for (let i = 7; i >= 0; i--) {
|
|
1415
|
+
frame.push((payload.length >> (i * 8)) & 0xFF);
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
socket.write(Buffer.concat([Buffer.from(frame), payload]));
|
|
1419
|
+
},
|
|
1420
|
+
close() {
|
|
1421
|
+
wsClients.delete(ws);
|
|
1269
1422
|
socket.destroy();
|
|
1270
|
-
}
|
|
1423
|
+
}
|
|
1424
|
+
};
|
|
1271
1425
|
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1426
|
+
wsClients.add(ws);
|
|
1427
|
+
console.log(` ${DIM}WebSocket client connected (${wsClients.size} active)${RESET}`);
|
|
1428
|
+
|
|
1429
|
+
// Send connected event
|
|
1430
|
+
ws.send(JSON.stringify({ type: "connected" }));
|
|
1431
|
+
|
|
1432
|
+
// Handle incoming WebSocket frames (simplified parser)
|
|
1433
|
+
let frameBuffer = Buffer.alloc(0);
|
|
1434
|
+
socket.on("data", (chunk) => {
|
|
1435
|
+
frameBuffer = Buffer.concat([frameBuffer, chunk]);
|
|
1436
|
+
|
|
1437
|
+
while (frameBuffer.length >= 2) {
|
|
1438
|
+
const firstByte = frameBuffer[0];
|
|
1439
|
+
const secondByte = frameBuffer[1];
|
|
1440
|
+
const opcode = firstByte & 0x0F;
|
|
1441
|
+
const masked = (secondByte & 0x80) !== 0;
|
|
1442
|
+
let payloadLen = secondByte & 0x7F;
|
|
1443
|
+
let offset = 2;
|
|
1444
|
+
|
|
1445
|
+
if (payloadLen === 126) {
|
|
1446
|
+
if (frameBuffer.length < 4) return;
|
|
1447
|
+
payloadLen = (frameBuffer[2] << 8) | frameBuffer[3];
|
|
1448
|
+
offset = 4;
|
|
1449
|
+
} else if (payloadLen === 127) {
|
|
1450
|
+
if (frameBuffer.length < 10) return;
|
|
1451
|
+
payloadLen = 0;
|
|
1452
|
+
for (let i = 0; i < 8; i++) {
|
|
1453
|
+
payloadLen = payloadLen * 256 + frameBuffer[2 + i];
|
|
1454
|
+
}
|
|
1455
|
+
offset = 10;
|
|
1456
|
+
}
|
|
1275
1457
|
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1458
|
+
const maskSize = masked ? 4 : 0;
|
|
1459
|
+
const totalFrameLen = offset + maskSize + payloadLen;
|
|
1460
|
+
if (frameBuffer.length < totalFrameLen) return;
|
|
1461
|
+
|
|
1462
|
+
let payload;
|
|
1463
|
+
if (masked) {
|
|
1464
|
+
const mask = frameBuffer.slice(offset, offset + 4);
|
|
1465
|
+
payload = Buffer.alloc(payloadLen);
|
|
1466
|
+
for (let i = 0; i < payloadLen; i++) {
|
|
1467
|
+
payload[i] = frameBuffer[offset + 4 + i] ^ mask[i % 4];
|
|
1468
|
+
}
|
|
1469
|
+
} else {
|
|
1470
|
+
payload = frameBuffer.slice(offset, offset + payloadLen);
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
// Handle by opcode
|
|
1474
|
+
if (opcode === 0x08) {
|
|
1475
|
+
// Close frame
|
|
1476
|
+
ws.close();
|
|
1477
|
+
return;
|
|
1478
|
+
} else if (opcode === 0x09) {
|
|
1479
|
+
// Ping — send pong
|
|
1480
|
+
const pongFrame = Buffer.from([0x8A, payload.length, ...payload]);
|
|
1481
|
+
socket.write(pongFrame);
|
|
1482
|
+
} else if (opcode === 0x01) {
|
|
1483
|
+
// Text frame — handle as message
|
|
1484
|
+
const text = payload.toString("utf8");
|
|
1485
|
+
try {
|
|
1486
|
+
const msg = JSON.parse(text);
|
|
1487
|
+
if (msg.type === "ping") {
|
|
1488
|
+
ws.send(JSON.stringify({ type: "pong" }));
|
|
1489
|
+
}
|
|
1490
|
+
} catch {
|
|
1491
|
+
// Non-JSON message, ignore
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
frameBuffer = frameBuffer.slice(totalFrameLen);
|
|
1496
|
+
}
|
|
1497
|
+
});
|
|
1498
|
+
|
|
1499
|
+
socket.on("close", () => {
|
|
1500
|
+
wsClients.delete(ws);
|
|
1501
|
+
console.log(` ${DIM}WebSocket client disconnected (${wsClients.size} active)${RESET}`);
|
|
1502
|
+
});
|
|
1503
|
+
|
|
1504
|
+
socket.on("error", () => {
|
|
1505
|
+
wsClients.delete(ws);
|
|
1506
|
+
});
|
|
1283
1507
|
});
|
|
1284
1508
|
|
|
1285
1509
|
ok(`Bridge server running on ${bindHost}:${port}`);
|
|
@@ -1490,9 +1714,10 @@ module.exports = {
|
|
|
1490
1714
|
// ── Show OC Web UI access + routing info ─────────────────────────────
|
|
1491
1715
|
const uiURL = tunnelURL ? `${tunnelURL}/ui/` : `http://localhost:${port}/ui/`;
|
|
1492
1716
|
console.log(` ${BOLD}OC Web UI:${RESET} ${CYAN}${uiURL}${RESET}`);
|
|
1493
|
-
info(` /ui/*
|
|
1494
|
-
info(` /api/*
|
|
1495
|
-
info(` /ws
|
|
1717
|
+
info(` /ui/* → localhost:${ocUIPort} (web UI)`);
|
|
1718
|
+
info(` /api/sessions/* → bridge (in-process chat)`);
|
|
1719
|
+
info(` /ws → bridge (in-process WebSocket)`);
|
|
1720
|
+
info(` /api/* (other) → localhost:${ocGatewayPort} (gateway fallback)`);
|
|
1496
1721
|
|
|
1497
1722
|
// ── Startup health checks ──────────────────────────────────────────
|
|
1498
1723
|
console.log("");
|
|
@@ -1625,7 +1850,7 @@ module.exports = {
|
|
|
1625
1850
|
|
|
1626
1851
|
mkdirSync(mcporterDir, { recursive: true });
|
|
1627
1852
|
writeFileSync(mcporterConfigPath, JSON.stringify(mcporterConfig, null, 2) + "\n", "utf8");
|
|
1628
|
-
ok("Registered MCP server with mcporter (16 browser
|
|
1853
|
+
ok("Registered MCP server with mcporter (34 tools: 16 browser + 10 AI + 5 profiling + 3 chat)");
|
|
1629
1854
|
info(` Config: ${mcporterConfigPath}`);
|
|
1630
1855
|
|
|
1631
1856
|
// Step 3: Install the navigator-bridge skill so the OC agent knows about all tools
|
|
@@ -1670,7 +1895,9 @@ module.exports = {
|
|
|
1670
1895
|
"mcporter call navigator.navigator_status",
|
|
1671
1896
|
BT,
|
|
1672
1897
|
"",
|
|
1673
|
-
"## All
|
|
1898
|
+
"## All 34 tools",
|
|
1899
|
+
"",
|
|
1900
|
+
"### Browser Control (16 tools)",
|
|
1674
1901
|
"",
|
|
1675
1902
|
"| Tool | What it does |",
|
|
1676
1903
|
"|------|-------------|",
|
|
@@ -1691,6 +1918,39 @@ module.exports = {
|
|
|
1691
1918
|
"| `navigator_query_element` | Inspect DOM element |",
|
|
1692
1919
|
"| `navigator_wait_ready` | Wait for page load |",
|
|
1693
1920
|
"",
|
|
1921
|
+
"### AI Browser Intelligence (10 tools)",
|
|
1922
|
+
"",
|
|
1923
|
+
"| Tool | What it does |",
|
|
1924
|
+
"|------|-------------|",
|
|
1925
|
+
"| `navigator_analyze_page` | Full page analysis — forms, buttons, inputs, links, tables, modals |",
|
|
1926
|
+
"| `navigator_find_element` | Find element by natural language intent (e.g. 'login button') |",
|
|
1927
|
+
"| `navigator_is_ready` | Smart page readiness — checks spinners, skeletons, pending XHR |",
|
|
1928
|
+
"| `navigator_wait_for_element` | Wait for CSS selector to appear (MutationObserver) |",
|
|
1929
|
+
"| `navigator_extract_data` | Extract structured data — text, table, JSON, list, or HTML |",
|
|
1930
|
+
"| `navigator_smart_fill` | Fill form fields by label/name matching (React-safe) |",
|
|
1931
|
+
"| `navigator_intercept_api` | Capture fetch/XHR calls — see what APIs the page uses |",
|
|
1932
|
+
"| `navigator_set_cookies` | Set cookies on the current domain |",
|
|
1933
|
+
"| `navigator_get_performance` | Core Web Vitals and resource transfer stats |",
|
|
1934
|
+
"| `navigator_get_page_state` | Full page state — URL, title, readyState, analysis, timestamp |",
|
|
1935
|
+
"",
|
|
1936
|
+
"### User Profiling (5 tools)",
|
|
1937
|
+
"",
|
|
1938
|
+
"| Tool | What it does |",
|
|
1939
|
+
"|------|-------------|",
|
|
1940
|
+
"| `navigator_get_page_visits` | Get browsing history (auto-recorded page visits) |",
|
|
1941
|
+
"| `navigator_save_page_summary` | Save AI-generated summary of a page |",
|
|
1942
|
+
"| `navigator_get_page_summaries` | Retrieve saved page summaries |",
|
|
1943
|
+
"| `navigator_get_user_profile` | Get aggregated user interest profile |",
|
|
1944
|
+
"| `navigator_save_user_profile` | Save/update user profile from browsing patterns |",
|
|
1945
|
+
"",
|
|
1946
|
+
"### Chat (3 tools)",
|
|
1947
|
+
"",
|
|
1948
|
+
"| Tool | What it does |",
|
|
1949
|
+
"|------|-------------|",
|
|
1950
|
+
"| `navigator_get_chat_messages` | Get recent messages from the Navigator chat pane |",
|
|
1951
|
+
"| `navigator_chat_respond` | Send a response message to the Navigator chat pane |",
|
|
1952
|
+
"| `navigator_chat_stream` | Stream partial text to the chat pane (typing effect) |",
|
|
1953
|
+
"",
|
|
1694
1954
|
"## Usage",
|
|
1695
1955
|
"",
|
|
1696
1956
|
BT + "bash",
|
package/mcp.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* openclaw-navigator MCP server v5.0
|
|
4
|
+
* openclaw-navigator MCP server v5.1.0
|
|
5
5
|
*
|
|
6
6
|
* Exposes the Navigator bridge HTTP API as MCP tools so the OpenClaw agent
|
|
7
7
|
* can control the browser natively via its tool schema.
|
|
@@ -204,7 +204,7 @@ async function sendCommand(command, payload, poll) {
|
|
|
204
204
|
};
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
-
// ── Tool definitions (
|
|
207
|
+
// ── Tool definitions (34 tools: 16 browser + 10 AI intelligence + 5 profiling + 3 chat) ────────────────
|
|
208
208
|
|
|
209
209
|
const TOOLS = [
|
|
210
210
|
// ── Direct HTTP ──
|
|
@@ -596,6 +596,44 @@ const TOOLS = [
|
|
|
596
596
|
required: ["profile"],
|
|
597
597
|
},
|
|
598
598
|
},
|
|
599
|
+
|
|
600
|
+
// ── Chat Tools ──
|
|
601
|
+
{
|
|
602
|
+
name: "navigator_get_chat_messages",
|
|
603
|
+
description: "Get recent chat messages from the Navigator chat pane. Use this to see what the user is asking.",
|
|
604
|
+
inputSchema: {
|
|
605
|
+
type: "object",
|
|
606
|
+
properties: {
|
|
607
|
+
sessionKey: { type: "string", description: "Chat session key (default: main)", default: "main" },
|
|
608
|
+
limit: { type: "number", description: "Max messages to return (default: 20)", default: 20 },
|
|
609
|
+
},
|
|
610
|
+
},
|
|
611
|
+
},
|
|
612
|
+
{
|
|
613
|
+
name: "navigator_chat_respond",
|
|
614
|
+
description: "Send a response message to the Navigator chat pane. The user will see this as an agent message.",
|
|
615
|
+
inputSchema: {
|
|
616
|
+
type: "object",
|
|
617
|
+
properties: {
|
|
618
|
+
message: { type: "string", description: "The response message to send" },
|
|
619
|
+
sessionKey: { type: "string", description: "Chat session key (default: main)", default: "main" },
|
|
620
|
+
},
|
|
621
|
+
required: ["message"],
|
|
622
|
+
},
|
|
623
|
+
},
|
|
624
|
+
{
|
|
625
|
+
name: "navigator_chat_stream",
|
|
626
|
+
description: "Stream a partial response to the Navigator chat pane (for real-time typing effect).",
|
|
627
|
+
inputSchema: {
|
|
628
|
+
type: "object",
|
|
629
|
+
properties: {
|
|
630
|
+
text: { type: "string", description: "Partial text to stream" },
|
|
631
|
+
sessionKey: { type: "string", description: "Chat session key (default: main)", default: "main" },
|
|
632
|
+
runId: { type: "string", description: "Run ID for grouping streamed chunks" },
|
|
633
|
+
},
|
|
634
|
+
required: ["text"],
|
|
635
|
+
},
|
|
636
|
+
},
|
|
599
637
|
];
|
|
600
638
|
|
|
601
639
|
// ── Tool handler dispatch ─────────────────────────────────────────────────
|
|
@@ -823,6 +861,34 @@ async function handleTool(name, args) {
|
|
|
823
861
|
}),
|
|
824
862
|
);
|
|
825
863
|
|
|
864
|
+
// ── Chat Tools ──
|
|
865
|
+
case "navigator_get_chat_messages": {
|
|
866
|
+
const sessionKey = args.sessionKey || "main";
|
|
867
|
+
const limit = args.limit ?? 20;
|
|
868
|
+
const data = await bridgeGet(`/api/sessions/history?sessionKey=${encodeURIComponent(sessionKey)}`);
|
|
869
|
+
if (data.ok && Array.isArray(data.messages)) {
|
|
870
|
+
return jsonResult({ ok: true, messages: data.messages.slice(-limit) });
|
|
871
|
+
}
|
|
872
|
+
return jsonResult(data);
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
case "navigator_chat_respond":
|
|
876
|
+
return jsonResult(
|
|
877
|
+
await bridgePost("/api/sessions/respond", {
|
|
878
|
+
content: args.message,
|
|
879
|
+
sessionKey: args.sessionKey || "main",
|
|
880
|
+
}),
|
|
881
|
+
);
|
|
882
|
+
|
|
883
|
+
case "navigator_chat_stream":
|
|
884
|
+
return jsonResult(
|
|
885
|
+
await bridgePost("/api/sessions/stream", {
|
|
886
|
+
text: args.text,
|
|
887
|
+
sessionKey: args.sessionKey || "main",
|
|
888
|
+
runId: args.runId || null,
|
|
889
|
+
}),
|
|
890
|
+
);
|
|
891
|
+
|
|
826
892
|
default:
|
|
827
893
|
return errorResult(`Unknown tool: ${name}`);
|
|
828
894
|
}
|
|
@@ -835,7 +901,7 @@ async function handleTool(name, args) {
|
|
|
835
901
|
// ── MCP server wiring ─────────────────────────────────────────────────────
|
|
836
902
|
|
|
837
903
|
const server = new Server(
|
|
838
|
-
{ name: "openclaw-navigator", version: "5.
|
|
904
|
+
{ name: "openclaw-navigator", version: "5.1.0" },
|
|
839
905
|
{ capabilities: { tools: {} } },
|
|
840
906
|
);
|
|
841
907
|
|