@weavetab/mcp 2.5.0-beta.0 → 2.5.0-beta.2
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/AGENTS.md +1 -0
- package/LICENSE +2 -2
- package/README.md +208 -43
- package/SECURITY.md +3 -3
- package/TROUBLESHOOTING.md +1 -1
- package/dist/cdp/bridge.d.ts +10 -0
- package/dist/cdp/bridge.js +288 -68
- package/dist/cdp/connector.d.ts +5 -3
- package/dist/cdp/connector.js +188 -74
- package/dist/cdp/extension.js +0 -1
- package/dist/cdp/helpers.js +0 -2
- package/dist/cdp/lock.d.ts +9 -6
- package/dist/cdp/lock.js +72 -59
- package/dist/cdp/omni_broker.d.ts +1 -0
- package/dist/cdp/omni_broker.js +12 -6
- package/dist/cdp/profiles.d.ts +10 -0
- package/dist/cdp/profiles.js +65 -0
- package/dist/cdp/walker.js +50 -0
- package/dist/cli/plugin.d.ts +0 -2
- package/dist/cli/plugin.js +31 -683
- package/dist/cli/updater.js +2 -2
- package/dist/config/loader.d.ts +25 -6
- package/dist/config/loader.js +133 -20
- package/dist/index.js +48 -52
- package/dist/lifecycle/mission_manager.d.ts +0 -1
- package/dist/lifecycle/mission_manager.js +0 -3
- package/dist/overlay/ghost.d.ts +4 -0
- package/dist/overlay/ghost.js +118 -94
- package/dist/overlay/injector.d.ts +1 -1
- package/dist/overlay/injector.js +1 -15
- package/dist/plugin/extension_bridge.d.ts +40 -0
- package/dist/plugin/extension_bridge.js +158 -0
- package/dist/plugin/interface.d.ts +78 -16
- package/dist/plugin/loader.d.ts +4 -0
- package/dist/plugin/loader.js +178 -57
- package/dist/plugin/registry.js +11 -1
- package/dist/plugin/storage.d.ts +51 -0
- package/dist/plugin/storage.js +164 -0
- package/dist/server.js +217 -151
- package/dist/state/session.d.ts +2 -2
- package/dist/state/session.js +33 -13
- package/dist/tools/browser_detect.d.ts +2 -1
- package/dist/tools/browser_detect.js +6 -5
- package/dist/tools/burst.d.ts +1 -0
- package/dist/tools/burst.js +4 -6
- package/dist/tools/click.js +23 -28
- package/dist/tools/clipboard.js +31 -24
- package/dist/tools/navigate.d.ts +1 -1
- package/dist/tools/navigate.js +28 -10
- package/dist/tools/read.js +1 -1
- package/dist/tools/tabs.d.ts +10 -10
- package/dist/tools/tabs.js +47 -39
- package/dist/tools/type.js +2 -2
- package/docs/DOCUMENTATION.md +63 -6
- package/docs/architecture/blind-injection.md +20 -0
- package/docs/architecture/dual-injection.md +32 -0
- package/docs/architecture/overview.md +49 -0
- package/docs/architecture/security.md +35 -0
- package/docs/architecture/transport.md +37 -0
- package/docs/captcha-stealth/captcha-solver.md +75 -0
- package/docs/captcha-stealth/stealth-physics.md +60 -0
- package/docs/cli/cli-commands.md +44 -0
- package/docs/cli/cli-flags.md +20 -0
- package/docs/cli/cli-introduction.md +20 -0
- package/docs/cli/cli-scripting.md +32 -0
- package/docs/cli/g-ide/ide-antigravity.md +24 -0
- package/docs/cli/g-ide/ide-claude-code.md +24 -0
- package/docs/cli/g-ide/ide-claude-desktop.md +32 -0
- package/docs/cli/g-ide/ide-cursor.md +31 -0
- package/docs/cli/g-ide/ide-generic.md +34 -0
- package/docs/cli/g-ide/ide-opencode.md +24 -0
- package/docs/cli/g-ide/ide-vscode-copilot.md +24 -0
- package/docs/cli/g-ide/ide-windsurf.md +24 -0
- package/docs/configuration/config-locations.md +24 -0
- package/docs/configuration/configuration.md +73 -0
- package/docs/configuration/env-variables.md +19 -0
- package/docs/configuration/folders.md +54 -0
- package/docs/desktop-automation/electron-apps.md +162 -0
- package/docs/get-started/installation.md +47 -0
- package/docs/get-started/introduction.md +60 -0
- package/docs/get-started/quickstart.md +54 -0
- package/docs/get-started/setup-wizard.md +26 -0
- package/docs/skills-ecosystem/skills-overview.md +35 -0
- package/docs/skills-ecosystem/skills-recipes.md +36 -0
- package/docs/tools/tools.md +293 -0
- package/docs/troubleshooting/trouble-browser.md +27 -0
- package/docs/troubleshooting/trouble-config.md +20 -0
- package/docs/troubleshooting/trouble-loop.md +31 -0
- package/ext-dist/content.js +126 -101
- package/mcp.json +8 -15
- package/package.json +11 -5
- package/server.json +3 -3
- package/dist/tools/plan.d.ts +0 -39
- package/dist/tools/plan.js +0 -161
- package/docs/DEV_MODE.md +0 -68
- package/docs/PLUGINS.md +0 -538
- package/docs/TOOLS.md +0 -104
- package/docs/dev_plugin.md +0 -310
package/dist/server.js
CHANGED
|
@@ -11,7 +11,6 @@ import { checkRateLimit } from "./security/ratelimit.js";
|
|
|
11
11
|
import { checkDomain, isToolAllowedInMode } from "./security/blacklist.js";
|
|
12
12
|
import { logAction, logActionStructured } from "./audit/logger.js";
|
|
13
13
|
import { recordToolUsage, broadcastTelemetry } from "./audit/telemetry.js";
|
|
14
|
-
import { clearLock } from "./cdp/lock.js";
|
|
15
14
|
import { browserMap } from "./tools/read.js";
|
|
16
15
|
import { weaveNavigate } from "./tools/navigate.js";
|
|
17
16
|
import { weaveClick } from "./tools/click.js";
|
|
@@ -25,7 +24,6 @@ import { browserWait } from "./tools/wait.js";
|
|
|
25
24
|
import { browserPointer } from "./tools/pointer.js";
|
|
26
25
|
import { browserSelect } from "./tools/select.js";
|
|
27
26
|
import { browserFill } from "./tools/fill.js";
|
|
28
|
-
import { weavePlan } from "./tools/plan.js";
|
|
29
27
|
import { browserHighlight } from "./tools/highlight.js";
|
|
30
28
|
import { browserUpload } from "./tools/upload.js";
|
|
31
29
|
import { browserFind } from "./tools/find.js";
|
|
@@ -53,7 +51,7 @@ import { browserCaptcha } from "./tools/captcha.js";
|
|
|
53
51
|
import { browserEmulate } from "./tools/emulation.js";
|
|
54
52
|
import { getActiveEngine } from "./cdp/connector.js";
|
|
55
53
|
import { resetLoopCounter } from "./intelligence/trail.js";
|
|
56
|
-
import { discoverPlugins } from "./plugin/loader.js";
|
|
54
|
+
import { discoverPlugins, listInstalledPlugins, loadPluginByName } from "./plugin/loader.js";
|
|
57
55
|
import { agentAction } from "./sensors/pacing.js";
|
|
58
56
|
import { getCachedConfig } from "./config/loader.js";
|
|
59
57
|
import { agentState } from "./state/agent.js";
|
|
@@ -106,7 +104,7 @@ function withTimeoutGuard(fn, timeoutMs, label) {
|
|
|
106
104
|
});
|
|
107
105
|
return Promise.race([fn(), timeoutPromise]).finally(() => clearTimeout(timer));
|
|
108
106
|
}
|
|
109
|
-
async function withSecurity(config, toolName, handler, isOfficial = false) {
|
|
107
|
+
async function withSecurity(config, toolName, handler, isOfficial = false, targetApp) {
|
|
110
108
|
const liveConfig = getCachedConfig();
|
|
111
109
|
const rl = checkRateLimit(liveConfig);
|
|
112
110
|
if (!rl.allowed) {
|
|
@@ -119,7 +117,7 @@ async function withSecurity(config, toolName, handler, isOfficial = false) {
|
|
|
119
117
|
throw new Error(`[!] ${roleToolCheck.reason}`);
|
|
120
118
|
}
|
|
121
119
|
}
|
|
122
|
-
const { session } = await withTimeoutGuard(() => getOrConnect(liveConfig), 15000, `CDP Connection (${toolName})`);
|
|
120
|
+
const { session } = await withTimeoutGuard(() => getOrConnect(liveConfig, targetApp), 15000, `CDP Connection (${toolName}${targetApp ? `:${targetApp}` : ''})`);
|
|
123
121
|
agentState.setSession(session);
|
|
124
122
|
global.__wt_live_config = liveConfig;
|
|
125
123
|
try {
|
|
@@ -143,8 +141,8 @@ async function withSecurity(config, toolName, handler, isOfficial = false) {
|
|
|
143
141
|
}
|
|
144
142
|
if (!isOfficial) {
|
|
145
143
|
// Phase 7c: Domain policy check for read-only / deny / audit-only modes
|
|
146
|
-
const activeClient = sessionRegistry.getActive(liveConfig.agentId);
|
|
147
|
-
if (activeClient?.url) {
|
|
144
|
+
const activeClient = sessionRegistry.getActive(liveConfig.agentId, targetApp);
|
|
145
|
+
if (activeClient?.url && !activeClient.isElectron) {
|
|
148
146
|
const domainCheck = checkDomain(activeClient.url, liveConfig);
|
|
149
147
|
if (!domainCheck.allowed) {
|
|
150
148
|
throw new Error(`[!] ${domainCheck.reason}`);
|
|
@@ -200,7 +198,7 @@ async function withSecurity(config, toolName, handler, isOfficial = false) {
|
|
|
200
198
|
const result = await withTimeoutGuard(() => handler(session, liveConfig), 22000, toolName);
|
|
201
199
|
agentState.reportToolSuccess();
|
|
202
200
|
const ms = Date.now() - t0;
|
|
203
|
-
const activeClient = sessionRegistry.getActive(liveConfig.agentId);
|
|
201
|
+
const activeClient = sessionRegistry.getActive(liveConfig.agentId, targetApp);
|
|
204
202
|
logActionStructured({
|
|
205
203
|
timestamp: new Date(t0).toISOString(),
|
|
206
204
|
sessionId: activeClient?.targetId,
|
|
@@ -324,7 +322,7 @@ async function withSecurity(config, toolName, handler, isOfficial = false) {
|
|
|
324
322
|
const ms = Date.now() - t0;
|
|
325
323
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
326
324
|
agentState.reportToolError();
|
|
327
|
-
const activeClient = sessionRegistry.getActive(liveConfig.agentId);
|
|
325
|
+
const activeClient = sessionRegistry.getActive(liveConfig.agentId, targetApp);
|
|
328
326
|
logActionStructured({
|
|
329
327
|
timestamp: new Date(t0).toISOString(),
|
|
330
328
|
sessionId: activeClient?.targetId,
|
|
@@ -373,6 +371,7 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
373
371
|
delta: z.boolean().optional().describe("Diff mode: return ONLY elements that changed since the last browser_map call (added, removed, or modified). Diffs across time — distinct from prune. Cuts prompt tokens by up to 97% on consecutive calls."),
|
|
374
372
|
ax_tree: z.boolean().optional().describe("Bypass the extension entirely and read raw Chrome Accessibility Tree. Use this if Shadow DOMs hide elements."),
|
|
375
373
|
tab_id: z.string().optional().describe("Read a specific background tab instead of the active one."),
|
|
374
|
+
app: z.string().optional().describe("Electron app name configured in config.json under 'electron'."),
|
|
376
375
|
show_overlays: z.boolean().optional().describe("Default false. Debug only — draws colored element borders via extension (ugly). The blue edge glow is the standard visual indicator, shown automatically during reading states. Only enable this for debugging element boundaries."),
|
|
377
376
|
force: z.boolean().optional().describe("Force a full DOM map rebuild even if loop detection says nothing changed. Bypasses the map skip guard."),
|
|
378
377
|
prune: z.boolean().optional().describe("Semantic DOM pruning: compress the current snapshot before returning. Drops hidden elements with no interactive descendants and not referenced by any toggle trigger (aria-controls etc.). Replaces SVG icons with [SVG Icon: label] tokens. Distinct from delta:true — delta diffs across time, prune compresses this snapshot once. Combine both for maximum token efficiency."),
|
|
@@ -382,12 +381,12 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
382
381
|
let targetSession = session;
|
|
383
382
|
let targetId = "default";
|
|
384
383
|
const { sessionRegistry } = await import("./state/session.js");
|
|
385
|
-
const active = sessionRegistry.getActive(liveConfig.agentId);
|
|
384
|
+
const active = sessionRegistry.getActive(liveConfig.agentId, args.app);
|
|
386
385
|
if (active)
|
|
387
386
|
targetId = active.targetId;
|
|
388
387
|
if (args.tab_id) {
|
|
389
388
|
const { connectToTab } = await import("./cdp/connector.js");
|
|
390
|
-
const client = await connectToTab(args.tab_id);
|
|
389
|
+
const client = await connectToTab(args.tab_id, undefined, undefined, undefined, args.app);
|
|
391
390
|
targetSession = client.session;
|
|
392
391
|
targetId = args.tab_id;
|
|
393
392
|
}
|
|
@@ -411,30 +410,37 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
411
410
|
}
|
|
412
411
|
const result = await agentAction(targetSession, 'reading', () => browserMap(targetSession, args, targetId, liveConfig), 'browser_map');
|
|
413
412
|
return { content: [{ type: "text", text: result }] };
|
|
414
|
-
});
|
|
413
|
+
}, false, args.app);
|
|
415
414
|
}
|
|
416
415
|
catch (e) {
|
|
417
416
|
return wrapError(e);
|
|
418
417
|
}
|
|
419
418
|
});
|
|
420
419
|
// ── 2. browser_navigate ────────────────────────────────────────────────────
|
|
421
|
-
server.tool("browser_navigate", "Navigate to a URL.", {
|
|
422
|
-
url: z.string().describe("Absolute URL to navigate to (must include https://)."),
|
|
420
|
+
server.tool("browser_navigate", "Navigate to a URL or launch/focus a configured Electron app.", {
|
|
421
|
+
url: z.string().optional().describe("Absolute URL to navigate to (must include https:// or app URL). Optional if 'app' is specified."),
|
|
422
|
+
app: z.string().optional().describe("Electron app name configured in config.json under 'electron' (e.g. 'example' or 'my-dev-app'). When specified, targets or launches the Electron app."),
|
|
423
423
|
force: z.boolean().optional().describe("If true, overwrites current tab even if it's protected (anchor tab). Otherwise opens a new tab automatically."),
|
|
424
424
|
tab_id: z.string().optional().describe("Target a specific tab ID for navigation instead of the active tab.")
|
|
425
425
|
}, async (args) => {
|
|
426
426
|
try {
|
|
427
|
+
if (!args.url && !args.app) {
|
|
428
|
+
throw new Error("Either 'url' or 'app' must be provided to browser_navigate.");
|
|
429
|
+
}
|
|
427
430
|
return await withSecurity(config, "browser_navigate", async (session, liveConfig) => {
|
|
428
431
|
let targetSession = session;
|
|
429
432
|
let targetId = "default";
|
|
430
433
|
if (args.tab_id) {
|
|
431
434
|
const { connectToTab } = await import("./cdp/connector.js");
|
|
432
|
-
const client = await connectToTab(args.tab_id);
|
|
435
|
+
const client = await connectToTab(args.tab_id, undefined, undefined, undefined, args.app);
|
|
433
436
|
targetSession = client.session;
|
|
434
437
|
targetId = args.tab_id;
|
|
435
438
|
}
|
|
436
|
-
|
|
437
|
-
|
|
439
|
+
if (args.url) {
|
|
440
|
+
return await agentAction(targetSession, 'navigating', () => weaveNavigate(targetSession, args.url, liveConfig, targetId, args.force), 'browser_navigate');
|
|
441
|
+
}
|
|
442
|
+
return { success: true, app: args.app, message: `Connected to Electron app "${args.app}".` };
|
|
443
|
+
}, false, args.app);
|
|
438
444
|
}
|
|
439
445
|
catch (e) {
|
|
440
446
|
return wrapError(e);
|
|
@@ -443,6 +449,7 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
443
449
|
// ── 3. browser_click ───────────────────────────────────────────────────────
|
|
444
450
|
server.tool("browser_click", "Click a DOM element. Use browser_canvas for charts/canvas. If stuck_warning=true, stop and use browser_map.", {
|
|
445
451
|
id: z.string().optional().describe("Ref ID from browser_map (e.g. 'w:42'). Primary targeting method. DO NOT SPAM. If stuck_warning is returned, stop retrying and call browser_map first."),
|
|
452
|
+
ref: z.string().optional().describe("Alias for id (e.g. 'w:42')."),
|
|
446
453
|
selector: z.string().optional().describe("CSS selector of the element to click."),
|
|
447
454
|
target: z.string().optional().describe("CSS selector or query string. Alias for selector."),
|
|
448
455
|
label: z.string().optional().describe("Visible text or aria-label to click."),
|
|
@@ -452,22 +459,25 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
452
459
|
y: z.number().optional().describe("Raw Y coordinate. Use for precise positional clicks when element ID is not available."),
|
|
453
460
|
include_map: z.boolean().optional().describe("Set true to include a full updated page_map in the response. Default false — omit when you already have ref IDs or don't need a DOM refresh."),
|
|
454
461
|
tab_id: z.string().optional().describe("Execute in a specific tab instead of active."),
|
|
462
|
+
app: z.string().optional().describe("Electron app name configured in config.json under 'electron'."),
|
|
455
463
|
}, async (args) => {
|
|
456
464
|
try {
|
|
457
465
|
return await withSecurity(config, "browser_click", async (session, liveConfig) => {
|
|
458
466
|
let targetSession = session;
|
|
459
467
|
if (args.tab_id) {
|
|
460
468
|
const { connectToTab } = await import("./cdp/connector.js");
|
|
461
|
-
const client = await connectToTab(args.tab_id);
|
|
469
|
+
const client = await connectToTab(args.tab_id, undefined, undefined, undefined, args.app);
|
|
462
470
|
targetSession = client.session;
|
|
463
471
|
}
|
|
464
472
|
const normalizedArgs = { ...args };
|
|
473
|
+
if (args.ref && !normalizedArgs.id)
|
|
474
|
+
normalizedArgs.id = args.ref;
|
|
465
475
|
if (args.selector && !normalizedArgs.id)
|
|
466
476
|
normalizedArgs.id = args.selector;
|
|
467
477
|
if (args.target && !normalizedArgs.id)
|
|
468
478
|
normalizedArgs.id = args.target;
|
|
469
479
|
return await agentAction(targetSession, 'clicking', () => weaveClick(targetSession, normalizedArgs, liveConfig), 'browser_click');
|
|
470
|
-
});
|
|
480
|
+
}, false, args.app);
|
|
471
481
|
}
|
|
472
482
|
catch (e) {
|
|
473
483
|
return wrapError(e);
|
|
@@ -476,6 +486,7 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
476
486
|
// ── 4. browser_type ────────────────────────────────────────────────────────
|
|
477
487
|
server.tool("browser_type", "Type text into a single input field (use browser_fill for smart multi-field forms). If NOT_EDITABLE, fallback to click + browser_key. Use browser_map if stuck.", {
|
|
478
488
|
id: z.string().optional().describe("Ref ID from browser_map. Primary targeting — always prefer this over label when you have a ref ID."),
|
|
489
|
+
ref: z.string().optional().describe("Alias for id (e.g. 'w:42')."),
|
|
479
490
|
selector: z.string().optional().describe("CSS selector of the input field. Use when you know the exact selector."),
|
|
480
491
|
target: z.string().optional().describe("CSS selector or query string. Alias for selector."),
|
|
481
492
|
label: z.string().optional().describe("Field label or placeholder text. Used for fuzzy matching."),
|
|
@@ -487,22 +498,25 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
487
498
|
paste: z.boolean().optional().describe("Instant paste mode via clipboard. Auto-enabled for text longer than 8 chars. Best for long URLs or multi-word strings."),
|
|
488
499
|
include_map: z.boolean().optional().describe("Set true to include a full updated page_map in the response. Default false."),
|
|
489
500
|
tab_id: z.string().optional().describe("Execute in a specific tab instead of active."),
|
|
501
|
+
app: z.string().optional().describe("Electron app name configured in config.json under 'electron'."),
|
|
490
502
|
}, async (args) => {
|
|
491
503
|
try {
|
|
492
504
|
return await withSecurity(config, "browser_type", async (session, liveConfig) => {
|
|
493
505
|
let targetSession = session;
|
|
494
506
|
if (args.tab_id) {
|
|
495
507
|
const { connectToTab } = await import("./cdp/connector.js");
|
|
496
|
-
const client = await connectToTab(args.tab_id);
|
|
508
|
+
const client = await connectToTab(args.tab_id, undefined, undefined, undefined, args.app);
|
|
497
509
|
targetSession = client.session;
|
|
498
510
|
}
|
|
499
511
|
const normalizedArgs = { ...args };
|
|
512
|
+
if (args.ref && !normalizedArgs.id)
|
|
513
|
+
normalizedArgs.id = args.ref;
|
|
500
514
|
if (args.selector && !normalizedArgs.id)
|
|
501
515
|
normalizedArgs.id = args.selector;
|
|
502
516
|
if (args.target && !normalizedArgs.id)
|
|
503
517
|
normalizedArgs.id = args.target;
|
|
504
518
|
return await agentAction(targetSession, 'typing', () => weaveType(targetSession, normalizedArgs, liveConfig), 'browser_type');
|
|
505
|
-
});
|
|
519
|
+
}, false, args.app);
|
|
506
520
|
}
|
|
507
521
|
catch (e) {
|
|
508
522
|
return wrapError(e);
|
|
@@ -511,13 +525,18 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
511
525
|
// ── 4b. browser_type_secret ────────────────────────────────────────────────
|
|
512
526
|
server.tool("browser_type_secret", "Inject a secret from an environment variable.", {
|
|
513
527
|
id: z.string().optional().describe("Ref ID from browser_map."),
|
|
528
|
+
ref: z.string().optional().describe("Alias for id (e.g. 'w:42')."),
|
|
514
529
|
target: z.string().optional().describe("Alternative target input element identifier or CSS selector."),
|
|
515
530
|
envKey: z.string().describe("The environment variable key containing the secret."),
|
|
531
|
+
app: z.string().optional().describe("Electron app name configured in config.json under 'electron'."),
|
|
516
532
|
}, async (args) => {
|
|
517
533
|
try {
|
|
518
534
|
return await withSecurity(config, "browser_type_secret", async (session, liveConfig) => {
|
|
519
|
-
|
|
520
|
-
|
|
535
|
+
const normalizedArgs = { ...args };
|
|
536
|
+
if (args.ref && !normalizedArgs.id)
|
|
537
|
+
normalizedArgs.id = args.ref;
|
|
538
|
+
return await agentAction(session, 'typing', () => weaveTypeSecret(session, normalizedArgs, liveConfig), 'browser_type_secret');
|
|
539
|
+
}, false, args.app);
|
|
521
540
|
}
|
|
522
541
|
catch (e) {
|
|
523
542
|
return wrapError(e);
|
|
@@ -528,18 +547,19 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
528
547
|
key: z.string().describe("Key name: Enter, Escape, Tab, ArrowDown, F5, etc. DO NOT SPAM. If action fails or loops, STOP calling this tool and use browser_map to re-evaluate the page state."),
|
|
529
548
|
modifiers: z.array(z.enum(["Alt", "Ctrl", "Meta", "Shift"])).optional().describe("Array of modifier keys to hold during the press."),
|
|
530
549
|
tab_id: z.string().optional().describe("Execute in a specific tab instead of active."),
|
|
550
|
+
app: z.string().optional().describe("Electron app name configured in config.json under 'electron'."),
|
|
531
551
|
}, async (args) => {
|
|
532
|
-
const { key, modifiers, tab_id } = args;
|
|
552
|
+
const { key, modifiers, tab_id, app } = args;
|
|
533
553
|
try {
|
|
534
554
|
return await withSecurity(config, "browser_key", async (session, liveConfig) => {
|
|
535
555
|
let targetSession = session;
|
|
536
556
|
if (tab_id) {
|
|
537
557
|
const { connectToTab } = await import("./cdp/connector.js");
|
|
538
|
-
const client = await connectToTab(tab_id);
|
|
558
|
+
const client = await connectToTab(tab_id, undefined, undefined, undefined, app);
|
|
539
559
|
targetSession = client.session;
|
|
540
560
|
}
|
|
541
561
|
return await weaveKey(targetSession, { key, modifiers }, liveConfig);
|
|
542
|
-
});
|
|
562
|
+
}, false, app);
|
|
543
563
|
}
|
|
544
564
|
catch (e) {
|
|
545
565
|
return wrapError(e);
|
|
@@ -548,39 +568,48 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
548
568
|
// ── 6. browser_scroll ──────────────────────────────────────────────────────
|
|
549
569
|
server.tool("browser_scroll", "Scroll the page.", {
|
|
550
570
|
id: z.string().optional().describe("Ref ID to scroll into view or scroll inside of. DO NOT SPAM. If action fails or loops, STOP calling this tool and use browser_map to re-evaluate the page state."),
|
|
571
|
+
ref: z.string().optional().describe("Alias for id (e.g. 'w:42')."),
|
|
551
572
|
selector: z.string().optional().describe("CSS Selector of the element to scroll into view."),
|
|
552
573
|
target: z.string().optional().describe("Alternative element identifier or query to scroll into view."),
|
|
553
574
|
label: z.string().optional().describe("Element label to scroll into view."),
|
|
554
575
|
direction: z.enum(["up", "down", "left", "right"]).describe("Scroll direction."),
|
|
555
576
|
distance: z.number().optional().describe("Pixels to scroll (default 400)."),
|
|
556
577
|
tab_id: z.string().optional().describe("Execute in a specific tab instead of active. DO NOT SPAM. If action fails or loops, STOP calling this tool and use browser_map to re-evaluate the page state."),
|
|
578
|
+
app: z.string().optional().describe("Electron app name configured in config.json under 'electron'."),
|
|
557
579
|
}, async (args) => {
|
|
558
580
|
try {
|
|
559
581
|
return await withSecurity(config, "browser_scroll", async (session, liveConfig) => {
|
|
560
582
|
let targetSession = session;
|
|
561
583
|
if (args.tab_id) {
|
|
562
584
|
const { connectToTab } = await import("./cdp/connector.js");
|
|
563
|
-
const client = await connectToTab(args.tab_id);
|
|
585
|
+
const client = await connectToTab(args.tab_id, undefined, undefined, undefined, args.app);
|
|
564
586
|
targetSession = client.session;
|
|
565
587
|
}
|
|
566
588
|
const normalizedArgs = { ...args };
|
|
589
|
+
if (args.ref && !normalizedArgs.id)
|
|
590
|
+
normalizedArgs.id = args.ref;
|
|
567
591
|
if (args.selector && !normalizedArgs.id)
|
|
568
592
|
normalizedArgs.id = args.selector;
|
|
569
593
|
if (args.target && !normalizedArgs.id)
|
|
570
594
|
normalizedArgs.id = args.target;
|
|
571
595
|
return await browserScroll(targetSession, normalizedArgs, liveConfig);
|
|
572
|
-
});
|
|
596
|
+
}, false, args.app);
|
|
573
597
|
}
|
|
574
598
|
catch (e) {
|
|
575
599
|
return wrapError(e);
|
|
576
600
|
}
|
|
577
601
|
});
|
|
578
602
|
// ── 7. browser_dialog ──────────────────────────────────────────────────────
|
|
579
|
-
server.tool("browser_dialog", "Accept or dismiss JS dialogs.", {
|
|
603
|
+
server.tool("browser_dialog", "Accept or dismiss JS dialogs (alert, confirm, prompt). Browser sessions only.", {
|
|
580
604
|
action: z.enum(["accept", "dismiss"]).describe("Action to perform on the dialog."),
|
|
581
605
|
promptText: z.string().optional().describe("Text to enter if it is a prompt dialog.")
|
|
582
606
|
}, async (args) => {
|
|
583
607
|
try {
|
|
608
|
+
const liveConfig = getCachedConfig();
|
|
609
|
+
const active = sessionRegistry.getActive(liveConfig.agentId);
|
|
610
|
+
if (active?.isElectron) {
|
|
611
|
+
return { content: [{ type: "text", text: "[Note] JS dialog interception is only available for web browser sessions (Electron apps handle dialogs natively)." }] };
|
|
612
|
+
}
|
|
584
613
|
return await withSecurity(config, "browser_dialog", async (session) => {
|
|
585
614
|
return await browserDialog(session, args);
|
|
586
615
|
});
|
|
@@ -595,65 +624,67 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
595
624
|
tool: z.enum(["browser_click", "browser_type", "browser_navigate", "browser_key", "browser_scroll", "browser_wait", "browser_map", "browser_pointer", "browser_eval", "browser_select", "type_and_send", "navigate_and_read", "click_and_wait", "scroll_and_read"]),
|
|
596
625
|
args: z.record(z.unknown()).describe("Arguments for the respective tool. For type_and_send: { text, x?, y?, id?, label?, pressEnter? }. For navigate_and_read: { url }. For click_and_wait: { id?, label?, x?, y?, wait_for?, wait_value? }. For scroll_and_read: { direction, distance? }.")
|
|
597
626
|
})).describe("Ordered array of tool calls to execute serially. Use macro steps to save tokens."),
|
|
598
|
-
batch: z.boolean().optional().describe("If true, defers action map generation until all steps are complete to save time.")
|
|
627
|
+
batch: z.boolean().optional().describe("If true, defers action map generation until all steps are complete to save time."),
|
|
628
|
+
app: z.string().optional().describe("Electron app name configured in config.json under 'electron'."),
|
|
599
629
|
}, async (args) => {
|
|
600
630
|
try {
|
|
601
631
|
return await withSecurity(config, "browser_burst", async (session, liveConfig) => {
|
|
602
632
|
const { browserBurst } = await import("./tools/burst.js");
|
|
603
633
|
return await browserBurst(session, args, liveConfig);
|
|
604
|
-
});
|
|
634
|
+
}, false, args.app);
|
|
605
635
|
}
|
|
606
636
|
catch (e) {
|
|
607
637
|
return wrapError(e);
|
|
608
638
|
}
|
|
609
639
|
});
|
|
610
640
|
// ── 9. browser_tabs ───────────────────────────────────────────────────────
|
|
611
|
-
server.tool("browser_tabs", "Manage browser tabs (open, switch, close, list).", {
|
|
641
|
+
server.tool("browser_tabs", "Manage browser and Electron app tabs/windows (open, switch, close, list).", {
|
|
612
642
|
action: z.enum(["list", "open", "close", "switch", "list_windows", "focus_window", "consolidate", "heal"]).describe("Action to perform. 'consolidate' deduplicates tabs. 'heal' recovers crashed tabs."),
|
|
613
643
|
url: z.string().optional().describe("URL to open (required for 'open' action)."),
|
|
644
|
+
app: z.string().optional().describe("Optional Electron app name configured in config.json under 'electron'."),
|
|
614
645
|
tabId: z.string().optional().describe("Target Tab ID (required for 'switch' or 'close')."),
|
|
615
646
|
tabIds: z.array(z.string()).optional().describe("Multiple Tab IDs for batch 'close' action."),
|
|
616
647
|
force: z.boolean().optional().describe("Force close protected (anchor) tabs. Use with extreme caution."),
|
|
617
648
|
windowId: z.number().optional().describe("Window ID to focus (required for 'focus_window')."),
|
|
618
649
|
reuse: z.boolean().optional().describe("Default true. When opening a URL already loaded in another tab, focus that tab instead of opening a duplicate."),
|
|
619
|
-
}, async ({ action, url, tabId, tabIds, force, windowId, reuse }) => {
|
|
650
|
+
}, async ({ action, url, app, tabId, tabIds, force, windowId, reuse }) => {
|
|
620
651
|
try {
|
|
621
652
|
return await withSecurity(config, "browser_tabs", async (_, liveConfig) => {
|
|
622
653
|
if (action === "list")
|
|
623
|
-
return await WeavetabList(liveConfig);
|
|
654
|
+
return await WeavetabList(liveConfig, app);
|
|
624
655
|
if (action === "open") {
|
|
625
656
|
if (!url)
|
|
626
657
|
throw new Error("url is required for action: open");
|
|
627
|
-
return await WeavetabOpen(url, liveConfig, reuse !== false);
|
|
658
|
+
return await WeavetabOpen(url, liveConfig, reuse !== false, app);
|
|
628
659
|
}
|
|
629
660
|
if (action === "switch") {
|
|
630
661
|
if (!tabId)
|
|
631
662
|
throw new Error("tabId is required for action: switch");
|
|
632
|
-
return await WeavetabSwitch(tabId, liveConfig);
|
|
663
|
+
return await WeavetabSwitch(tabId, liveConfig, app);
|
|
633
664
|
}
|
|
634
665
|
if (action === "close") {
|
|
635
666
|
if (tabIds && tabIds.length > 0)
|
|
636
|
-
return await WeavetabCloseBatch(tabIds, liveConfig, force);
|
|
667
|
+
return await WeavetabCloseBatch(tabIds, liveConfig, force, app);
|
|
637
668
|
if (!tabId)
|
|
638
669
|
throw new Error("tabId or tabIds is required for action: close");
|
|
639
|
-
return await WeavetabClose(tabId, liveConfig, force);
|
|
670
|
+
return await WeavetabClose(tabId, liveConfig, force, app);
|
|
640
671
|
}
|
|
641
672
|
if (action === "list_windows") {
|
|
642
|
-
return await weaveWindowList(liveConfig);
|
|
673
|
+
return await weaveWindowList(liveConfig, app);
|
|
643
674
|
}
|
|
644
675
|
if (action === "focus_window") {
|
|
645
676
|
if (windowId === undefined)
|
|
646
677
|
throw new Error("windowId is required for action: focus_window");
|
|
647
|
-
return await weaveFocusWindow(windowId, liveConfig);
|
|
678
|
+
return await weaveFocusWindow(windowId, liveConfig, app);
|
|
648
679
|
}
|
|
649
680
|
if (action === "consolidate") {
|
|
650
|
-
return await WeavetabConsolidate(liveConfig);
|
|
681
|
+
return await WeavetabConsolidate(liveConfig, app);
|
|
651
682
|
}
|
|
652
683
|
if (action === "heal") {
|
|
653
|
-
return await WeavetabHeal(liveConfig);
|
|
684
|
+
return await WeavetabHeal(liveConfig, app);
|
|
654
685
|
}
|
|
655
686
|
throw new Error("Invalid action");
|
|
656
|
-
});
|
|
687
|
+
}, false, app);
|
|
657
688
|
}
|
|
658
689
|
catch (e) {
|
|
659
690
|
return wrapError(e);
|
|
@@ -665,17 +696,18 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
665
696
|
value: z.string().optional().describe("CSS selector, text string, or URL pattern. Not needed for 'stable'/'network'."),
|
|
666
697
|
timeout: z.number().optional().describe("Max wait in ms (default 8000)."),
|
|
667
698
|
tab_id: z.string().optional().describe("Execute in a specific tab instead of active."),
|
|
699
|
+
app: z.string().optional().describe("Electron app name configured in config.json under 'electron'."),
|
|
668
700
|
}, async (args) => {
|
|
669
701
|
try {
|
|
670
702
|
return await withSecurity(config, "browser_wait", async (session, liveConfig) => {
|
|
671
703
|
let targetSession = session;
|
|
672
704
|
if (args.tab_id) {
|
|
673
705
|
const { connectToTab } = await import("./cdp/connector.js");
|
|
674
|
-
const client = await connectToTab(args.tab_id);
|
|
706
|
+
const client = await connectToTab(args.tab_id, undefined, undefined, undefined, args.app);
|
|
675
707
|
targetSession = client.session;
|
|
676
708
|
}
|
|
677
709
|
return await browserWait(targetSession, args, liveConfig);
|
|
678
|
-
});
|
|
710
|
+
}, false, args.app);
|
|
679
711
|
}
|
|
680
712
|
catch (e) {
|
|
681
713
|
return wrapError(e);
|
|
@@ -701,8 +733,20 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
701
733
|
message: z.string().describe("The question to ask, or the feedback/analysis to log."),
|
|
702
734
|
input: z.enum(["sentence", "paragraph"]).optional().describe("Input field type for question mode (default: sentence)."),
|
|
703
735
|
timeout: z.number().optional().describe("Timeout in seconds for question mode before auto-cancel (default: 120)."),
|
|
736
|
+
app: z.string().optional().describe("Optional Electron app name. (Note: thought HUD overlay is browser-only)."),
|
|
704
737
|
}, async (args) => {
|
|
705
738
|
try {
|
|
739
|
+
const liveConfig = getCachedConfig();
|
|
740
|
+
const active = sessionRegistry.getActive(liveConfig.agentId, args.app);
|
|
741
|
+
if (args.app || active?.isElectron) {
|
|
742
|
+
const { logActionStructured } = await import("./audit/logger.js");
|
|
743
|
+
logActionStructured({
|
|
744
|
+
timestamp: new Date().toISOString(),
|
|
745
|
+
tool: "browser_thoughts",
|
|
746
|
+
result: `[ELECTRON APP FEEDBACK]: ${args.message}`
|
|
747
|
+
});
|
|
748
|
+
return { content: [{ type: "text", text: "[Note] Thought/HUD overlay is disabled for Electron desktop apps (DOM-only execution). Feedback recorded in audit log." }] };
|
|
749
|
+
}
|
|
706
750
|
return await withSecurity(config, "browser_thoughts", async (session) => {
|
|
707
751
|
if (args.action === "feedback") {
|
|
708
752
|
const { logActionStructured } = await import("./audit/logger.js");
|
|
@@ -738,7 +782,8 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
738
782
|
end_x: z.number().optional().describe("Destination X coordinate (only for drag_and_drop)."),
|
|
739
783
|
end_y: z.number().optional().describe("Destination Y coordinate (only for drag_and_drop)."),
|
|
740
784
|
waitForChild: z.string().optional().describe("CSS Selector of a child element to wait for (only for hover)."),
|
|
741
|
-
holdMs: z.number().optional().describe("Milliseconds to hold the pointer down (only for hover).")
|
|
785
|
+
holdMs: z.number().optional().describe("Milliseconds to hold the pointer down (only for hover)."),
|
|
786
|
+
app: z.string().optional().describe("Electron app name configured in config.json under 'electron'."),
|
|
742
787
|
}, async (args) => {
|
|
743
788
|
try {
|
|
744
789
|
return await withSecurity(config, "browser_pointer", async (session, liveConfig) => {
|
|
@@ -756,7 +801,7 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
756
801
|
if (args.end_target && !normalizedArgs.end_ref)
|
|
757
802
|
normalizedArgs.end_ref = args.end_target;
|
|
758
803
|
return await browserPointer(session, normalizedArgs, liveConfig);
|
|
759
|
-
});
|
|
804
|
+
}, false, args.app);
|
|
760
805
|
}
|
|
761
806
|
catch (e) {
|
|
762
807
|
return wrapError(e);
|
|
@@ -767,11 +812,12 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
767
812
|
nodeId: z.number().optional().describe("CDP backendNodeId."),
|
|
768
813
|
selector: z.string().optional().describe("CSS Selector."),
|
|
769
814
|
value: z.string().describe("The exact text value to select."),
|
|
815
|
+
app: z.string().optional().describe("Electron app name configured in config.json under 'electron'."),
|
|
770
816
|
}, async (args) => {
|
|
771
817
|
try {
|
|
772
818
|
return await withSecurity(config, "browser_select", async (session, liveConfig) => {
|
|
773
819
|
return await browserSelect(session, args, liveConfig);
|
|
774
|
-
});
|
|
820
|
+
}, false, args.app);
|
|
775
821
|
}
|
|
776
822
|
catch (e) {
|
|
777
823
|
return wrapError(e);
|
|
@@ -784,33 +830,12 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
784
830
|
value: z.string().describe("Value to fill/select/type into the field."),
|
|
785
831
|
})).describe("Array of field ref+value pairs to fill sequentially."),
|
|
786
832
|
submit: z.string().optional().describe("Optional ref ID (w:NN) of a submit button to click after filling all fields."),
|
|
833
|
+
app: z.string().optional().describe("Electron app name configured in config.json under 'electron'."),
|
|
787
834
|
}, async (args) => {
|
|
788
835
|
try {
|
|
789
836
|
return await withSecurity(config, "browser_fill", async (session, liveConfig) => {
|
|
790
837
|
return await agentAction(session, 'typing', () => browserFill(session, args, liveConfig), 'browser_fill');
|
|
791
|
-
});
|
|
792
|
-
}
|
|
793
|
-
catch (e) {
|
|
794
|
-
return wrapError(e);
|
|
795
|
-
}
|
|
796
|
-
});
|
|
797
|
-
// ── 15. browser_plan ───────────────────────────────────────────────────────
|
|
798
|
-
server.tool("browser_plan", "Create a live task panel in the browser.", {
|
|
799
|
-
action: z.enum(["create", "update", "comments", "hide"]).describe("Action to perform."),
|
|
800
|
-
goal: z.string().optional().describe("Mission goal description (required for create)."),
|
|
801
|
-
tasks: z.array(z.object({
|
|
802
|
-
id: z.number(),
|
|
803
|
-
description: z.string(),
|
|
804
|
-
status: z.enum(["pending", "in-progress", "done", "failed"]),
|
|
805
|
-
})).optional().describe("Task list with statuses (required for create)."),
|
|
806
|
-
taskId: z.number().optional().describe("Task ID to update (required for update)."),
|
|
807
|
-
status: z.enum(["pending", "in-progress", "done", "failed"]).optional().describe("New status (required for update)."),
|
|
808
|
-
}, async (args) => {
|
|
809
|
-
try {
|
|
810
|
-
return await withSecurity(config, "browser_plan", async (session, liveConfig) => {
|
|
811
|
-
const result = await weavePlan(session, args, liveConfig);
|
|
812
|
-
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
813
|
-
});
|
|
838
|
+
}, false, args.app);
|
|
814
839
|
}
|
|
815
840
|
catch (e) {
|
|
816
841
|
return wrapError(e);
|
|
@@ -823,7 +848,8 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
823
848
|
ref: z.string().optional().describe("Alternative element Ref ID (e.g. 'w:42')."),
|
|
824
849
|
target: z.string().optional().describe("Alternative element identifier, CSS Selector, or query."),
|
|
825
850
|
nodeId: z.number().optional().describe("CDP backendNodeId to highlight directly."),
|
|
826
|
-
color: z.string().optional().describe("CSS color to use for the overlay (e.g. 'rgba(255,0,0,0.5)').")
|
|
851
|
+
color: z.string().optional().describe("CSS color to use for the overlay (e.g. 'rgba(255,0,0,0.5)')."),
|
|
852
|
+
app: z.string().optional().describe("Electron app name configured in config.json under 'electron'."),
|
|
827
853
|
}, async (args) => {
|
|
828
854
|
try {
|
|
829
855
|
return await withSecurity(config, "browser_highlight", async (session, liveConfig) => {
|
|
@@ -841,7 +867,7 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
841
867
|
delete normalizedArgs.ref;
|
|
842
868
|
delete normalizedArgs.target;
|
|
843
869
|
return await browserHighlight(session, normalizedArgs, liveConfig);
|
|
844
|
-
});
|
|
870
|
+
}, false, args.app);
|
|
845
871
|
}
|
|
846
872
|
catch (e) {
|
|
847
873
|
return wrapError(e);
|
|
@@ -850,12 +876,13 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
850
876
|
// ── 20. browser_upload ──────────────────────────────────────────────────────
|
|
851
877
|
server.tool("browser_upload", "Upload files to a file input.", {
|
|
852
878
|
nodeId: z.number().describe("CDP backendNodeId of the target <input type='file'> element."),
|
|
853
|
-
filePaths: z.array(z.string()).describe("List of absolute system file paths to upload.")
|
|
879
|
+
filePaths: z.array(z.string()).describe("List of absolute system file paths to upload."),
|
|
880
|
+
app: z.string().optional().describe("Electron app name configured in config.json under 'electron'."),
|
|
854
881
|
}, async (args) => {
|
|
855
882
|
try {
|
|
856
883
|
return await withSecurity(config, "browser_upload", async (session, liveConfig) => {
|
|
857
884
|
return await browserUpload(session, args, liveConfig);
|
|
858
|
-
});
|
|
885
|
+
}, false, args.app);
|
|
859
886
|
}
|
|
860
887
|
catch (e) {
|
|
861
888
|
return wrapError(e);
|
|
@@ -863,12 +890,13 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
863
890
|
});
|
|
864
891
|
// ── 22. browser_find ────────────────────────────────────────────────────────
|
|
865
892
|
server.tool("browser_find", "Sub-5ms CDP text search. Zero token waste. Best for locating specific keywords/text on page without a full DOM walk. No refs returned (use browser_map for refs). No shadow roots/regex.", {
|
|
866
|
-
query: z.string().describe("Exact text query to find on the page.")
|
|
893
|
+
query: z.string().describe("Exact text query to find on the page."),
|
|
894
|
+
app: z.string().optional().describe("Electron app name configured in config.json under 'electron'."),
|
|
867
895
|
}, async (args) => {
|
|
868
896
|
try {
|
|
869
897
|
return await withSecurity(config, "browser_find", async (session, liveConfig) => {
|
|
870
898
|
return await agentAction(session, 'reading', () => browserFind(session, args, liveConfig), 'browser_find');
|
|
871
|
-
});
|
|
899
|
+
}, false, args.app);
|
|
872
900
|
}
|
|
873
901
|
catch (e) {
|
|
874
902
|
return wrapError(e);
|
|
@@ -880,11 +908,12 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
880
908
|
action: z.enum(["get", "set", "clear"]).describe("Action to perform on the storage."),
|
|
881
909
|
key: z.string().optional().describe("Key name (required for 'get' and 'set' actions)."),
|
|
882
910
|
value: z.string().optional().describe("String value (required for 'set' action)."),
|
|
911
|
+
app: z.string().optional().describe("Electron app name configured in config.json under 'electron'."),
|
|
883
912
|
}, async (args) => {
|
|
884
913
|
try {
|
|
885
914
|
return await withSecurity(config, "browser_storage", async (session, liveConfig) => {
|
|
886
915
|
return await browserStorage(session, args, liveConfig);
|
|
887
|
-
});
|
|
916
|
+
}, false, args.app);
|
|
888
917
|
}
|
|
889
918
|
catch (e) {
|
|
890
919
|
return wrapError(e);
|
|
@@ -896,11 +925,12 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
896
925
|
pattern: z.string().optional().describe("URL glob pattern to intercept or filter telemetry (e.g. '*api/login*')."),
|
|
897
926
|
mockStatus: z.number().optional().describe("HTTP status code to return when enabling mock (default 200)."),
|
|
898
927
|
mockBody: z.string().optional().describe("JSON or plaintext body payload to return in the mocked response."),
|
|
928
|
+
app: z.string().optional().describe("Electron app name configured in config.json under 'electron'."),
|
|
899
929
|
}, async (args) => {
|
|
900
930
|
try {
|
|
901
931
|
return await withSecurity(config, "browser_network_intercept", async (session, liveConfig) => {
|
|
902
932
|
return await browserNetworkIntercept(session, args, liveConfig);
|
|
903
|
-
});
|
|
933
|
+
}, false, args.app);
|
|
904
934
|
}
|
|
905
935
|
catch (e) {
|
|
906
936
|
return wrapError(e);
|
|
@@ -908,17 +938,18 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
908
938
|
});
|
|
909
939
|
// ── 25. browser_console ─────────────────────────────────────────────────────
|
|
910
940
|
server.tool("browser_console", "1-5ms latency. Read or clear buffered console API messages, JS exceptions, and network errors.", {
|
|
911
|
-
action: z.enum(["read", "clear"]).describe("Action to perform on the console buffer.")
|
|
941
|
+
action: z.enum(["read", "clear"]).describe("Action to perform on the console buffer."),
|
|
942
|
+
app: z.string().optional().describe("Electron app name configured in config.json under 'electron'."),
|
|
912
943
|
}, async (args) => {
|
|
913
944
|
try {
|
|
914
945
|
return await withSecurity(config, "browser_console", async (session, liveConfig) => {
|
|
915
946
|
let targetId = "default";
|
|
916
947
|
const { sessionRegistry } = await import("./state/session.js");
|
|
917
|
-
const active = sessionRegistry.getActive(liveConfig.agentId);
|
|
948
|
+
const active = sessionRegistry.getActive(liveConfig.agentId, args.app);
|
|
918
949
|
if (active)
|
|
919
950
|
targetId = active.targetId;
|
|
920
951
|
return await browserConsole(session, args, liveConfig, targetId);
|
|
921
|
-
});
|
|
952
|
+
}, false, args.app);
|
|
922
953
|
}
|
|
923
954
|
catch (e) {
|
|
924
955
|
return wrapError(e);
|
|
@@ -930,11 +961,12 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
930
961
|
landscape: z.boolean().optional().describe("Print in landscape orientation (default false)."),
|
|
931
962
|
scale: z.number().optional().describe("Scale factor (default 1.0)."),
|
|
932
963
|
pageRanges: z.string().optional().describe("Page ranges to print (e.g. '1-5'). Leave empty for all pages."),
|
|
964
|
+
app: z.string().optional().describe("Electron app name configured in config.json under 'electron'."),
|
|
933
965
|
}, async (args) => {
|
|
934
966
|
try {
|
|
935
967
|
return await withSecurity(config, "browser_pdf", async (session, liveConfig) => {
|
|
936
968
|
return await browserPdf(session, args, liveConfig);
|
|
937
|
-
});
|
|
969
|
+
}, false, args.app);
|
|
938
970
|
}
|
|
939
971
|
catch (e) {
|
|
940
972
|
return wrapError(e);
|
|
@@ -943,12 +975,13 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
943
975
|
// ── 27. browser_eval ────────────────────────────────────────────────────────
|
|
944
976
|
server.tool("browser_eval", "Run arbitrary JS via CDP. Good for extracting app state. Cannot traverse shadow DOM/cross-origin iframes.", {
|
|
945
977
|
expression: z.string().describe("JavaScript code to evaluate in the context of the page."),
|
|
946
|
-
timeout: z.number().optional().describe("Timeout in ms (default 5000).")
|
|
978
|
+
timeout: z.number().optional().describe("Timeout in ms (default 5000)."),
|
|
979
|
+
app: z.string().optional().describe("Electron app name configured in config.json under 'electron'."),
|
|
947
980
|
}, async (args) => {
|
|
948
981
|
try {
|
|
949
982
|
return await withSecurity(config, "browser_eval", async (session, liveConfig) => {
|
|
950
983
|
return await agentAction(session, 'reading', () => browserEval(session, args, liveConfig), 'browser_eval');
|
|
951
|
-
});
|
|
984
|
+
}, false, args.app);
|
|
952
985
|
}
|
|
953
986
|
catch (e) {
|
|
954
987
|
return wrapError(e);
|
|
@@ -962,11 +995,12 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
962
995
|
domain: z.string().optional().describe("Domain for the cookie."),
|
|
963
996
|
path: z.string().optional().describe("Path for the cookie."),
|
|
964
997
|
url: z.string().optional().describe("URL scope for 'get' action (defaults to current page)."),
|
|
998
|
+
app: z.string().optional().describe("Electron app name configured in config.json under 'electron'."),
|
|
965
999
|
}, async (args) => {
|
|
966
1000
|
try {
|
|
967
1001
|
return await withSecurity(config, "browser_cookies", async (session, liveConfig) => {
|
|
968
1002
|
return await browserCookies(session, args, liveConfig);
|
|
969
|
-
});
|
|
1003
|
+
}, false, args.app);
|
|
970
1004
|
}
|
|
971
1005
|
catch (e) {
|
|
972
1006
|
return wrapError(e);
|
|
@@ -980,12 +1014,13 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
980
1014
|
label: z.string().optional().describe("Visible text or aria-label of the canvas element."),
|
|
981
1015
|
ref: z.string().optional().describe("Alternative ref identifier for the canvas element."),
|
|
982
1016
|
backendNodeId: z.number().optional().describe("Raw CDP backendNodeId for direct targeting."),
|
|
983
|
-
points: z.array(z.object({ x: z.number(), y: z.number() })).optional().describe("Array of {x,y} points relative to canvas viewport for 'draw' action. Simulates a drag path across the canvas.")
|
|
1017
|
+
points: z.array(z.object({ x: z.number(), y: z.number() })).optional().describe("Array of {x,y} points relative to canvas viewport for 'draw' action. Simulates a drag path across the canvas."),
|
|
1018
|
+
app: z.string().optional().describe("Electron app name configured in config.json under 'electron'."),
|
|
984
1019
|
}, async (args) => {
|
|
985
1020
|
try {
|
|
986
1021
|
return await withSecurity(config, "browser_canvas", async (session, liveConfig) => {
|
|
987
1022
|
return await browserCanvas(session, args, liveConfig);
|
|
988
|
-
});
|
|
1023
|
+
}, false, args.app);
|
|
989
1024
|
}
|
|
990
1025
|
catch (e) {
|
|
991
1026
|
return wrapError(e);
|
|
@@ -996,12 +1031,13 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
996
1031
|
query: z.string().describe("CSS Selector or XPath query to extract."),
|
|
997
1032
|
type: z.enum(["css", "xpath"]).optional().describe("Query type (default 'css')."),
|
|
998
1033
|
attributes: z.array(z.string()).optional().describe("Attributes to extract (e.g. ['textContent', 'href'], default ['textContent'])."),
|
|
999
|
-
multiple: z.boolean().optional().describe("Whether to extract multiple elements (default true).")
|
|
1034
|
+
multiple: z.boolean().optional().describe("Whether to extract multiple elements (default true)."),
|
|
1035
|
+
app: z.string().optional().describe("Electron app name configured in config.json under 'electron'."),
|
|
1000
1036
|
}, async (args) => {
|
|
1001
1037
|
try {
|
|
1002
1038
|
return await withSecurity(config, "browser_scrape", async (session, liveConfig) => {
|
|
1003
1039
|
return await agentAction(session, 'reading', () => browserScrape(session, args, liveConfig), 'browser_scrape');
|
|
1004
|
-
});
|
|
1040
|
+
}, false, args.app);
|
|
1005
1041
|
}
|
|
1006
1042
|
catch (e) {
|
|
1007
1043
|
return wrapError(e);
|
|
@@ -1010,12 +1046,13 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
1010
1046
|
// ── 32. browser_snapshot ────────────────────────────────────────────────────
|
|
1011
1047
|
server.tool("browser_snapshot", "Capture or restore an MHTML snapshot. Best for offline archival, replay testing, and compliance audits.", {
|
|
1012
1048
|
action: z.enum(["capture", "restore"]).describe("Action to perform."),
|
|
1013
|
-
filePath: z.string().describe("Absolute file path to save/load the .mhtml snapshot.")
|
|
1049
|
+
filePath: z.string().describe("Absolute file path to save/load the .mhtml snapshot."),
|
|
1050
|
+
app: z.string().optional().describe("Electron app name configured in config.json under 'electron'."),
|
|
1014
1051
|
}, async (args) => {
|
|
1015
1052
|
try {
|
|
1016
1053
|
return await withSecurity(config, "browser_snapshot", async (session, liveConfig) => {
|
|
1017
1054
|
return await browserSnapshot(session, args, liveConfig);
|
|
1018
|
-
});
|
|
1055
|
+
}, false, args.app);
|
|
1019
1056
|
}
|
|
1020
1057
|
catch (e) {
|
|
1021
1058
|
return wrapError(e);
|
|
@@ -1031,12 +1068,13 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
1031
1068
|
y: z.number().describe("Y coordinate of clip region."),
|
|
1032
1069
|
width: z.number().describe("Width of clip region."),
|
|
1033
1070
|
height: z.number().describe("Height of clip region.")
|
|
1034
|
-
}).optional().describe("Clip region. Use to capture only part of the page (e.g. just a chart area) to reduce token cost.")
|
|
1071
|
+
}).optional().describe("Clip region. Use to capture only part of the page (e.g. just a chart area) to reduce token cost."),
|
|
1072
|
+
app: z.string().optional().describe("Electron app name configured in config.json under 'electron'."),
|
|
1035
1073
|
}, async (args) => {
|
|
1036
1074
|
try {
|
|
1037
1075
|
return await withSecurity(config, "browser_screenshot", async (session, liveConfig) => {
|
|
1038
1076
|
return await agentAction(session, 'reading', () => browserScreenshot(session, args, liveConfig), 'browser_screenshot');
|
|
1039
|
-
});
|
|
1077
|
+
}, false, args.app);
|
|
1040
1078
|
}
|
|
1041
1079
|
catch (e) {
|
|
1042
1080
|
return wrapError(e);
|
|
@@ -1048,12 +1086,13 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
1048
1086
|
width: z.number().optional().describe("Viewport width in pixels."),
|
|
1049
1087
|
height: z.number().optional().describe("Viewport height in pixels."),
|
|
1050
1088
|
deviceScaleFactor: z.number().optional().describe("Device Pixel Ratio (default 1)."),
|
|
1051
|
-
mobile: z.boolean().optional().describe("Whether to emulate a mobile device (default false).")
|
|
1089
|
+
mobile: z.boolean().optional().describe("Whether to emulate a mobile device (default false)."),
|
|
1090
|
+
app: z.string().optional().describe("Electron app name configured in config.json under 'electron'."),
|
|
1052
1091
|
}, async (args) => {
|
|
1053
1092
|
try {
|
|
1054
1093
|
return await withSecurity(config, "browser_viewport", async (session, liveConfig) => {
|
|
1055
1094
|
return await browserViewport(session, args, liveConfig);
|
|
1056
|
-
});
|
|
1095
|
+
}, false, args.app);
|
|
1057
1096
|
}
|
|
1058
1097
|
catch (e) {
|
|
1059
1098
|
return wrapError(e);
|
|
@@ -1064,12 +1103,13 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
1064
1103
|
action: z.enum(["read", "write"]).describe("Action to perform."),
|
|
1065
1104
|
text: z.string().optional().describe("Text to write to clipboard (for write action)."),
|
|
1066
1105
|
dataUrl: z.string().optional().describe("Base64 data URL of an image to write to clipboard (e.g. 'data:image/png;base64,...')."),
|
|
1067
|
-
mimeType: z.string().optional().describe("MIME type override for image write (auto-detected from dataUrl by default, e.g. 'image/png').")
|
|
1106
|
+
mimeType: z.string().optional().describe("MIME type override for image write (auto-detected from dataUrl by default, e.g. 'image/png')."),
|
|
1107
|
+
app: z.string().optional().describe("Electron app name configured in config.json under 'electron'."),
|
|
1068
1108
|
}, async (args) => {
|
|
1069
1109
|
try {
|
|
1070
1110
|
return await withSecurity(config, "browser_clipboard", async (session, liveConfig) => {
|
|
1071
1111
|
return await browserClipboard(session, args, liveConfig);
|
|
1072
|
-
});
|
|
1112
|
+
}, false, args.app);
|
|
1073
1113
|
}
|
|
1074
1114
|
catch (e) {
|
|
1075
1115
|
return wrapError(e);
|
|
@@ -1079,12 +1119,13 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
1079
1119
|
server.tool("browser_inspect", "15-35ms latency, zero waste. Deep DOM inspection. Crucial for debugging why a click failed, verifying z-index & visibility, or computed styles.", {
|
|
1080
1120
|
nodeId: z.number().optional().describe("CDP backendNodeId."),
|
|
1081
1121
|
selector: z.string().optional().describe("CSS Selector."),
|
|
1082
|
-
ref: z.string().optional().describe("Ref ID (e.g. 'w:42').")
|
|
1122
|
+
ref: z.string().optional().describe("Ref ID (e.g. 'w:42')."),
|
|
1123
|
+
app: z.string().optional().describe("Electron app name configured in config.json under 'electron'."),
|
|
1083
1124
|
}, async (args) => {
|
|
1084
1125
|
try {
|
|
1085
1126
|
return await withSecurity(config, "browser_inspect", async (session, liveConfig) => {
|
|
1086
1127
|
return await agentAction(session, 'reading', () => browserInspect(session, args, liveConfig), 'browser_inspect');
|
|
1087
|
-
});
|
|
1128
|
+
}, false, args.app);
|
|
1088
1129
|
}
|
|
1089
1130
|
catch (e) {
|
|
1090
1131
|
return wrapError(e);
|
|
@@ -1097,12 +1138,13 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
1097
1138
|
outputDir: z.string().optional().describe("Optional custom directory to write extracted files into. If omitted, uses configured designDir (~/.weavetab/designs/<domain>-<timestamp>/)."),
|
|
1098
1139
|
name: z.string().optional().describe("Optional custom name for the saved design folder (e.g. 'resend-hero', 'pricing-section')."),
|
|
1099
1140
|
includeAssets: z.boolean().optional().describe("Whether to capture inline SVGs and asset references (default true)."),
|
|
1100
|
-
hideOverlay: z.boolean().optional().describe("Whether to hide Weavetab overlay during capture (default true).")
|
|
1141
|
+
hideOverlay: z.boolean().optional().describe("Whether to hide Weavetab overlay during capture (default true)."),
|
|
1142
|
+
app: z.string().optional().describe("Electron app name configured in config.json under 'electron'."),
|
|
1101
1143
|
}, async (args) => {
|
|
1102
1144
|
try {
|
|
1103
1145
|
return await withSecurity(config, "browser_extract_design", async (session, liveConfig) => {
|
|
1104
1146
|
return await agentAction(session, 'reading', () => browserExtractDesign(session, args, liveConfig), 'browser_extract_design');
|
|
1105
|
-
});
|
|
1147
|
+
}, false, args.app);
|
|
1106
1148
|
}
|
|
1107
1149
|
catch (e) {
|
|
1108
1150
|
return wrapError(e);
|
|
@@ -1115,12 +1157,13 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
1115
1157
|
script: z.string().optional().describe("JavaScript code to inject and run (required for start)."),
|
|
1116
1158
|
persistent: z.boolean().optional().describe("If true, the script re-runs in a loop (default false)."),
|
|
1117
1159
|
onResult: z.enum(["notify_agent", "store", "ignore"]).optional().describe("How to handle script results (default 'store')."),
|
|
1160
|
+
app: z.string().optional().describe("Electron app name configured in config.json under 'electron'."),
|
|
1118
1161
|
}, async (args) => {
|
|
1119
1162
|
try {
|
|
1120
1163
|
return await withSecurity(config, "browser_automation", async (session, liveConfig) => {
|
|
1121
1164
|
const result = await browserAutomation(session, args, liveConfig);
|
|
1122
1165
|
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
1123
|
-
});
|
|
1166
|
+
}, false, args.app);
|
|
1124
1167
|
}
|
|
1125
1168
|
catch (e) {
|
|
1126
1169
|
return wrapError(e);
|
|
@@ -1134,12 +1177,13 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
1134
1177
|
maxFrames: z.number().optional().describe("Maximum frames to capture (default 500)."),
|
|
1135
1178
|
format: z.enum(["webp", "gif", "webm", "mp4", "frames"]).optional().describe("Output video format when stopping recording. Default 'webp' (pure Node native compilation)."),
|
|
1136
1179
|
keepFrames: z.boolean().optional().describe("If true, keeps raw JPEG frames after video compilation (default false)."),
|
|
1180
|
+
app: z.string().optional().describe("Electron app name configured in config.json under 'electron'."),
|
|
1137
1181
|
}, async (args) => {
|
|
1138
1182
|
try {
|
|
1139
1183
|
return await withSecurity(config, "browser_recording", async (session, liveConfig) => {
|
|
1140
1184
|
const result = await browserRecording(session, args, liveConfig);
|
|
1141
1185
|
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
1142
|
-
});
|
|
1186
|
+
}, false, args.app);
|
|
1143
1187
|
}
|
|
1144
1188
|
catch (e) {
|
|
1145
1189
|
return wrapError(e);
|
|
@@ -1163,12 +1207,14 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
1163
1207
|
}
|
|
1164
1208
|
});
|
|
1165
1209
|
// ── browser_performance ───────────────────────────────────────────────────
|
|
1166
|
-
server.tool("browser_performance", "Retrieve page performance metrics.", {
|
|
1210
|
+
server.tool("browser_performance", "Retrieve page performance metrics.", {
|
|
1211
|
+
app: z.string().optional().describe("Electron app name configured in config.json under 'electron'."),
|
|
1212
|
+
}, async (args) => {
|
|
1167
1213
|
try {
|
|
1168
1214
|
return await withSecurity(config, "browser_performance", async (session, liveConfig) => {
|
|
1169
1215
|
const result = await browserPerformance(session, liveConfig);
|
|
1170
1216
|
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
1171
|
-
});
|
|
1217
|
+
}, false, args.app);
|
|
1172
1218
|
}
|
|
1173
1219
|
catch (e) {
|
|
1174
1220
|
return wrapError(e);
|
|
@@ -1309,11 +1355,12 @@ DO NOT use browser_scrape or browser_map for binary documents — they cannot re
|
|
|
1309
1355
|
}).optional().describe("Override geolocation coordinates"),
|
|
1310
1356
|
timezoneId: z.string().optional().describe("Override browser timezone (e.g. 'America/New_York')"),
|
|
1311
1357
|
userAgent: z.string().optional().describe("Override User Agent string"),
|
|
1358
|
+
app: z.string().optional().describe("Electron app name configured in config.json under 'electron'."),
|
|
1312
1359
|
}, async (args) => {
|
|
1313
1360
|
try {
|
|
1314
1361
|
return await withSecurity(config, "browser_emulate", async (session, liveConfig) => {
|
|
1315
1362
|
return await browserEmulate(session, args, liveConfig);
|
|
1316
|
-
});
|
|
1363
|
+
}, false, args.app);
|
|
1317
1364
|
}
|
|
1318
1365
|
catch (e) {
|
|
1319
1366
|
return wrapError(e);
|
|
@@ -1433,14 +1480,13 @@ DO NOT use browser_scrape or browser_map for binary documents — they cannot re
|
|
|
1433
1480
|
}]
|
|
1434
1481
|
};
|
|
1435
1482
|
});
|
|
1436
|
-
// ── Plugins Lifecycle
|
|
1437
|
-
const plugins = discoverPlugins();
|
|
1483
|
+
// ── Plugins Lifecycle & Dynamic Registry ───────────────────────────
|
|
1438
1484
|
const { getVerifiedPackages, resolvePluginTrust } = await import("./plugin/registry.js");
|
|
1439
1485
|
const verifiedPackages = await getVerifiedPackages();
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1486
|
+
const loadedPluginNames = new Set();
|
|
1487
|
+
const registerPluginTools = async (plugin) => {
|
|
1488
|
+
if (loadedPluginNames.has(plugin.name))
|
|
1489
|
+
return;
|
|
1444
1490
|
const trust = resolvePluginTrust(plugin.name, verifiedPackages);
|
|
1445
1491
|
const isOfficial = trust.tier === "official";
|
|
1446
1492
|
const addedNames = [];
|
|
@@ -1455,7 +1501,6 @@ DO NOT use browser_scrape or browser_map for binary documents — they cannot re
|
|
|
1455
1501
|
if (server._registeredTools?.[toolName]) {
|
|
1456
1502
|
delete server._registeredTools[toolName];
|
|
1457
1503
|
removedNames.push(toolName);
|
|
1458
|
-
pluginRemovedCount++;
|
|
1459
1504
|
}
|
|
1460
1505
|
}
|
|
1461
1506
|
// 2. Tool Overrides (replace or wrap built-in tools)
|
|
@@ -1485,7 +1530,6 @@ DO NOT use browser_scrape or browser_map for binary documents — they cannot re
|
|
|
1485
1530
|
}
|
|
1486
1531
|
});
|
|
1487
1532
|
overriddenNames.push(ov.name);
|
|
1488
|
-
pluginOverrideCount++;
|
|
1489
1533
|
}
|
|
1490
1534
|
// 3. Tool Additions (new tools)
|
|
1491
1535
|
const newTools = plugin.tools || [];
|
|
@@ -1493,7 +1537,7 @@ DO NOT use browser_scrape or browser_map for binary documents — they cannot re
|
|
|
1493
1537
|
if (server._registeredTools?.[td.name]) {
|
|
1494
1538
|
delete server._registeredTools[td.name];
|
|
1495
1539
|
}
|
|
1496
|
-
server.tool(td.name, td.description, td.schema, async (args) => {
|
|
1540
|
+
server.tool(td.name, td.description, td.schema || {}, async (args) => {
|
|
1497
1541
|
try {
|
|
1498
1542
|
return await withSecurity(config, td.name, async (session, liveConfig) => {
|
|
1499
1543
|
const result = await td.handler(args, session, liveConfig);
|
|
@@ -1505,45 +1549,69 @@ DO NOT use browser_scrape or browser_map for binary documents — they cannot re
|
|
|
1505
1549
|
}
|
|
1506
1550
|
});
|
|
1507
1551
|
addedNames.push(td.name);
|
|
1508
|
-
pluginAddedCount++;
|
|
1509
1552
|
}
|
|
1510
|
-
//
|
|
1553
|
+
// Pass the SDK context so the plugin can dynamically register tools itself
|
|
1554
|
+
const pluginCtx = {
|
|
1555
|
+
mcp: {
|
|
1556
|
+
registerTool: (tool) => {
|
|
1557
|
+
server.tool(tool.name, tool.description, tool.schema, async (args) => {
|
|
1558
|
+
return await tool.handler(args);
|
|
1559
|
+
});
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
};
|
|
1511
1563
|
if (typeof plugin.onLoad === "function") {
|
|
1512
1564
|
try {
|
|
1513
|
-
await plugin.onLoad();
|
|
1565
|
+
await plugin.onLoad(pluginCtx);
|
|
1514
1566
|
}
|
|
1515
1567
|
catch (err) {
|
|
1516
1568
|
process.stderr.write(` \u001b[31m[!] Plugin ${plugin.name} onLoad failed: ${err?.message}\u001b[0m\n`);
|
|
1517
1569
|
}
|
|
1518
1570
|
}
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
if (removedNames.length > 0)
|
|
1526
|
-
ops.push(`✖removed [${removedNames.join(", ")}]`);
|
|
1527
|
-
const opsStr = ops.length > 0 ? ` (${ops.join(", ")})` : "";
|
|
1528
|
-
let permWarning = "";
|
|
1529
|
-
if (trust.tier === "unverified" && plugin.manifest?.permissions) {
|
|
1530
|
-
const perms = plugin.manifest.permissions;
|
|
1531
|
-
const flags = [];
|
|
1532
|
-
if (perms.network)
|
|
1533
|
-
flags.push("network");
|
|
1534
|
-
if (perms.cdp)
|
|
1535
|
-
flags.push("cdp");
|
|
1536
|
-
if (perms.fs)
|
|
1537
|
-
flags.push("fs");
|
|
1538
|
-
if (perms.tools?.override?.length)
|
|
1539
|
-
flags.push(`override:${perms.tools.override.join(",")}`);
|
|
1540
|
-
if (perms.tools?.remove?.length)
|
|
1541
|
-
flags.push(`remove:${perms.tools.remove.join(",")}`);
|
|
1542
|
-
if (flags.length > 0) {
|
|
1543
|
-
permWarning = ` \u001b[33m— requests: ${flags.join(", ")}\u001b[0m`;
|
|
1571
|
+
loadedPluginNames.add(plugin.name);
|
|
1572
|
+
process.stderr.write(` [Plugin Loaded] ${plugin.name} v${plugin.version}\n`);
|
|
1573
|
+
// Notify the client that tools have changed
|
|
1574
|
+
try {
|
|
1575
|
+
if (server.server) {
|
|
1576
|
+
server.server.sendToolListChanged();
|
|
1544
1577
|
}
|
|
1545
1578
|
}
|
|
1546
|
-
|
|
1579
|
+
catch (e) {
|
|
1580
|
+
// Ignore if client doesn't support it or not connected yet
|
|
1581
|
+
}
|
|
1582
|
+
};
|
|
1583
|
+
// Add the built-in ecosystem tools
|
|
1584
|
+
server.tool("list_plugins", "List all available Weavetab plugins that can be loaded.", {}, async () => {
|
|
1585
|
+
const installed = listInstalledPlugins();
|
|
1586
|
+
const available = installed
|
|
1587
|
+
.filter(info => !loadedPluginNames.has(info.packageName))
|
|
1588
|
+
.map(info => `- ${info.packageName}: ${info.manifest?.description || "No description"}`);
|
|
1589
|
+
if (available.length === 0) {
|
|
1590
|
+
return { content: [{ type: "text", text: "No un-loaded plugins available." }] };
|
|
1591
|
+
}
|
|
1592
|
+
return { content: [{ type: "text", text: `Available plugins:\n${available.join("\n")}\n\nUse load_plugin to load one.` }] };
|
|
1593
|
+
});
|
|
1594
|
+
server.tool("load_plugin", "Load a Weavetab plugin into the MCP context dynamically.", {
|
|
1595
|
+
name: z.string().describe("The exact name of the plugin to load")
|
|
1596
|
+
}, async ({ name }) => {
|
|
1597
|
+
if (loadedPluginNames.has(name)) {
|
|
1598
|
+
return { content: [{ type: "text", text: `Plugin ${name} is already loaded.` }] };
|
|
1599
|
+
}
|
|
1600
|
+
const plugin = loadPluginByName(name);
|
|
1601
|
+
if (!plugin) {
|
|
1602
|
+
return { content: [{ type: "text", text: `Plugin ${name} not found.` }] };
|
|
1603
|
+
}
|
|
1604
|
+
await registerPluginTools(plugin);
|
|
1605
|
+
return { content: [{ type: "text", text: `Successfully loaded plugin ${name}. The tools have been registered in your context.` }] };
|
|
1606
|
+
});
|
|
1607
|
+
// We load official core plugins by default, but leave others lazy.
|
|
1608
|
+
const allPlugins = discoverPlugins();
|
|
1609
|
+
for (const plugin of allPlugins) {
|
|
1610
|
+
const trust = resolvePluginTrust(plugin.name, verifiedPackages);
|
|
1611
|
+
const isLazy = plugin.manifest?.environments?.includes("mcp");
|
|
1612
|
+
if (trust.tier === "official" || !isLazy) {
|
|
1613
|
+
await registerPluginTools(plugin);
|
|
1614
|
+
}
|
|
1547
1615
|
}
|
|
1548
1616
|
let transport;
|
|
1549
1617
|
if (transportMode === "ws") {
|
|
@@ -1678,10 +1746,8 @@ process.on("SIGTERM", async () => {
|
|
|
1678
1746
|
process.exit(0);
|
|
1679
1747
|
});
|
|
1680
1748
|
process.on("exit", () => {
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
}
|
|
1686
|
-
catch { /* ignore */ }
|
|
1749
|
+
// We purposefully do NOT clear the lock file here.
|
|
1750
|
+
// Weavetab launches the browser detached, so it survives the server dying.
|
|
1751
|
+
// Leaving the lock file allows the next MCP server run to find and reuse
|
|
1752
|
+
// the existing browser instead of launching a duplicate.
|
|
1687
1753
|
});
|