@weavetab/mcp 2.5.0-beta.1 → 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 +284 -78
- package/dist/cdp/connector.d.ts +5 -3
- package/dist/cdp/connector.js +188 -71
- 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/overlay/ghost.js +2 -2
- package/dist/overlay/injector.js +1 -3
- 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 +213 -121
- 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 -3
- 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/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 +0 -1
- package/mcp.json +8 -6
- package/package.json +11 -5
- package/server.json +3 -3
- 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
|
@@ -51,7 +51,7 @@ import { browserCaptcha } from "./tools/captcha.js";
|
|
|
51
51
|
import { browserEmulate } from "./tools/emulation.js";
|
|
52
52
|
import { getActiveEngine } from "./cdp/connector.js";
|
|
53
53
|
import { resetLoopCounter } from "./intelligence/trail.js";
|
|
54
|
-
import { discoverPlugins } from "./plugin/loader.js";
|
|
54
|
+
import { discoverPlugins, listInstalledPlugins, loadPluginByName } from "./plugin/loader.js";
|
|
55
55
|
import { agentAction } from "./sensors/pacing.js";
|
|
56
56
|
import { getCachedConfig } from "./config/loader.js";
|
|
57
57
|
import { agentState } from "./state/agent.js";
|
|
@@ -104,7 +104,7 @@ function withTimeoutGuard(fn, timeoutMs, label) {
|
|
|
104
104
|
});
|
|
105
105
|
return Promise.race([fn(), timeoutPromise]).finally(() => clearTimeout(timer));
|
|
106
106
|
}
|
|
107
|
-
async function withSecurity(config, toolName, handler, isOfficial = false) {
|
|
107
|
+
async function withSecurity(config, toolName, handler, isOfficial = false, targetApp) {
|
|
108
108
|
const liveConfig = getCachedConfig();
|
|
109
109
|
const rl = checkRateLimit(liveConfig);
|
|
110
110
|
if (!rl.allowed) {
|
|
@@ -117,7 +117,7 @@ async function withSecurity(config, toolName, handler, isOfficial = false) {
|
|
|
117
117
|
throw new Error(`[!] ${roleToolCheck.reason}`);
|
|
118
118
|
}
|
|
119
119
|
}
|
|
120
|
-
const { session } = await withTimeoutGuard(() => getOrConnect(liveConfig), 15000, `CDP Connection (${toolName})`);
|
|
120
|
+
const { session } = await withTimeoutGuard(() => getOrConnect(liveConfig, targetApp), 15000, `CDP Connection (${toolName}${targetApp ? `:${targetApp}` : ''})`);
|
|
121
121
|
agentState.setSession(session);
|
|
122
122
|
global.__wt_live_config = liveConfig;
|
|
123
123
|
try {
|
|
@@ -141,8 +141,8 @@ async function withSecurity(config, toolName, handler, isOfficial = false) {
|
|
|
141
141
|
}
|
|
142
142
|
if (!isOfficial) {
|
|
143
143
|
// Phase 7c: Domain policy check for read-only / deny / audit-only modes
|
|
144
|
-
const activeClient = sessionRegistry.getActive(liveConfig.agentId);
|
|
145
|
-
if (activeClient?.url) {
|
|
144
|
+
const activeClient = sessionRegistry.getActive(liveConfig.agentId, targetApp);
|
|
145
|
+
if (activeClient?.url && !activeClient.isElectron) {
|
|
146
146
|
const domainCheck = checkDomain(activeClient.url, liveConfig);
|
|
147
147
|
if (!domainCheck.allowed) {
|
|
148
148
|
throw new Error(`[!] ${domainCheck.reason}`);
|
|
@@ -198,7 +198,7 @@ async function withSecurity(config, toolName, handler, isOfficial = false) {
|
|
|
198
198
|
const result = await withTimeoutGuard(() => handler(session, liveConfig), 22000, toolName);
|
|
199
199
|
agentState.reportToolSuccess();
|
|
200
200
|
const ms = Date.now() - t0;
|
|
201
|
-
const activeClient = sessionRegistry.getActive(liveConfig.agentId);
|
|
201
|
+
const activeClient = sessionRegistry.getActive(liveConfig.agentId, targetApp);
|
|
202
202
|
logActionStructured({
|
|
203
203
|
timestamp: new Date(t0).toISOString(),
|
|
204
204
|
sessionId: activeClient?.targetId,
|
|
@@ -322,7 +322,7 @@ async function withSecurity(config, toolName, handler, isOfficial = false) {
|
|
|
322
322
|
const ms = Date.now() - t0;
|
|
323
323
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
324
324
|
agentState.reportToolError();
|
|
325
|
-
const activeClient = sessionRegistry.getActive(liveConfig.agentId);
|
|
325
|
+
const activeClient = sessionRegistry.getActive(liveConfig.agentId, targetApp);
|
|
326
326
|
logActionStructured({
|
|
327
327
|
timestamp: new Date(t0).toISOString(),
|
|
328
328
|
sessionId: activeClient?.targetId,
|
|
@@ -371,6 +371,7 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
371
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."),
|
|
372
372
|
ax_tree: z.boolean().optional().describe("Bypass the extension entirely and read raw Chrome Accessibility Tree. Use this if Shadow DOMs hide elements."),
|
|
373
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'."),
|
|
374
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."),
|
|
375
376
|
force: z.boolean().optional().describe("Force a full DOM map rebuild even if loop detection says nothing changed. Bypasses the map skip guard."),
|
|
376
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."),
|
|
@@ -380,12 +381,12 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
380
381
|
let targetSession = session;
|
|
381
382
|
let targetId = "default";
|
|
382
383
|
const { sessionRegistry } = await import("./state/session.js");
|
|
383
|
-
const active = sessionRegistry.getActive(liveConfig.agentId);
|
|
384
|
+
const active = sessionRegistry.getActive(liveConfig.agentId, args.app);
|
|
384
385
|
if (active)
|
|
385
386
|
targetId = active.targetId;
|
|
386
387
|
if (args.tab_id) {
|
|
387
388
|
const { connectToTab } = await import("./cdp/connector.js");
|
|
388
|
-
const client = await connectToTab(args.tab_id);
|
|
389
|
+
const client = await connectToTab(args.tab_id, undefined, undefined, undefined, args.app);
|
|
389
390
|
targetSession = client.session;
|
|
390
391
|
targetId = args.tab_id;
|
|
391
392
|
}
|
|
@@ -409,30 +410,37 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
409
410
|
}
|
|
410
411
|
const result = await agentAction(targetSession, 'reading', () => browserMap(targetSession, args, targetId, liveConfig), 'browser_map');
|
|
411
412
|
return { content: [{ type: "text", text: result }] };
|
|
412
|
-
});
|
|
413
|
+
}, false, args.app);
|
|
413
414
|
}
|
|
414
415
|
catch (e) {
|
|
415
416
|
return wrapError(e);
|
|
416
417
|
}
|
|
417
418
|
});
|
|
418
419
|
// ── 2. browser_navigate ────────────────────────────────────────────────────
|
|
419
|
-
server.tool("browser_navigate", "Navigate to a URL.", {
|
|
420
|
-
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."),
|
|
421
423
|
force: z.boolean().optional().describe("If true, overwrites current tab even if it's protected (anchor tab). Otherwise opens a new tab automatically."),
|
|
422
424
|
tab_id: z.string().optional().describe("Target a specific tab ID for navigation instead of the active tab.")
|
|
423
425
|
}, async (args) => {
|
|
424
426
|
try {
|
|
427
|
+
if (!args.url && !args.app) {
|
|
428
|
+
throw new Error("Either 'url' or 'app' must be provided to browser_navigate.");
|
|
429
|
+
}
|
|
425
430
|
return await withSecurity(config, "browser_navigate", async (session, liveConfig) => {
|
|
426
431
|
let targetSession = session;
|
|
427
432
|
let targetId = "default";
|
|
428
433
|
if (args.tab_id) {
|
|
429
434
|
const { connectToTab } = await import("./cdp/connector.js");
|
|
430
|
-
const client = await connectToTab(args.tab_id);
|
|
435
|
+
const client = await connectToTab(args.tab_id, undefined, undefined, undefined, args.app);
|
|
431
436
|
targetSession = client.session;
|
|
432
437
|
targetId = args.tab_id;
|
|
433
438
|
}
|
|
434
|
-
|
|
435
|
-
|
|
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);
|
|
436
444
|
}
|
|
437
445
|
catch (e) {
|
|
438
446
|
return wrapError(e);
|
|
@@ -441,6 +449,7 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
441
449
|
// ── 3. browser_click ───────────────────────────────────────────────────────
|
|
442
450
|
server.tool("browser_click", "Click a DOM element. Use browser_canvas for charts/canvas. If stuck_warning=true, stop and use browser_map.", {
|
|
443
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')."),
|
|
444
453
|
selector: z.string().optional().describe("CSS selector of the element to click."),
|
|
445
454
|
target: z.string().optional().describe("CSS selector or query string. Alias for selector."),
|
|
446
455
|
label: z.string().optional().describe("Visible text or aria-label to click."),
|
|
@@ -450,22 +459,25 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
450
459
|
y: z.number().optional().describe("Raw Y coordinate. Use for precise positional clicks when element ID is not available."),
|
|
451
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."),
|
|
452
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'."),
|
|
453
463
|
}, async (args) => {
|
|
454
464
|
try {
|
|
455
465
|
return await withSecurity(config, "browser_click", async (session, liveConfig) => {
|
|
456
466
|
let targetSession = session;
|
|
457
467
|
if (args.tab_id) {
|
|
458
468
|
const { connectToTab } = await import("./cdp/connector.js");
|
|
459
|
-
const client = await connectToTab(args.tab_id);
|
|
469
|
+
const client = await connectToTab(args.tab_id, undefined, undefined, undefined, args.app);
|
|
460
470
|
targetSession = client.session;
|
|
461
471
|
}
|
|
462
472
|
const normalizedArgs = { ...args };
|
|
473
|
+
if (args.ref && !normalizedArgs.id)
|
|
474
|
+
normalizedArgs.id = args.ref;
|
|
463
475
|
if (args.selector && !normalizedArgs.id)
|
|
464
476
|
normalizedArgs.id = args.selector;
|
|
465
477
|
if (args.target && !normalizedArgs.id)
|
|
466
478
|
normalizedArgs.id = args.target;
|
|
467
479
|
return await agentAction(targetSession, 'clicking', () => weaveClick(targetSession, normalizedArgs, liveConfig), 'browser_click');
|
|
468
|
-
});
|
|
480
|
+
}, false, args.app);
|
|
469
481
|
}
|
|
470
482
|
catch (e) {
|
|
471
483
|
return wrapError(e);
|
|
@@ -474,6 +486,7 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
474
486
|
// ── 4. browser_type ────────────────────────────────────────────────────────
|
|
475
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.", {
|
|
476
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')."),
|
|
477
490
|
selector: z.string().optional().describe("CSS selector of the input field. Use when you know the exact selector."),
|
|
478
491
|
target: z.string().optional().describe("CSS selector or query string. Alias for selector."),
|
|
479
492
|
label: z.string().optional().describe("Field label or placeholder text. Used for fuzzy matching."),
|
|
@@ -485,22 +498,25 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
485
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."),
|
|
486
499
|
include_map: z.boolean().optional().describe("Set true to include a full updated page_map in the response. Default false."),
|
|
487
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'."),
|
|
488
502
|
}, async (args) => {
|
|
489
503
|
try {
|
|
490
504
|
return await withSecurity(config, "browser_type", async (session, liveConfig) => {
|
|
491
505
|
let targetSession = session;
|
|
492
506
|
if (args.tab_id) {
|
|
493
507
|
const { connectToTab } = await import("./cdp/connector.js");
|
|
494
|
-
const client = await connectToTab(args.tab_id);
|
|
508
|
+
const client = await connectToTab(args.tab_id, undefined, undefined, undefined, args.app);
|
|
495
509
|
targetSession = client.session;
|
|
496
510
|
}
|
|
497
511
|
const normalizedArgs = { ...args };
|
|
512
|
+
if (args.ref && !normalizedArgs.id)
|
|
513
|
+
normalizedArgs.id = args.ref;
|
|
498
514
|
if (args.selector && !normalizedArgs.id)
|
|
499
515
|
normalizedArgs.id = args.selector;
|
|
500
516
|
if (args.target && !normalizedArgs.id)
|
|
501
517
|
normalizedArgs.id = args.target;
|
|
502
518
|
return await agentAction(targetSession, 'typing', () => weaveType(targetSession, normalizedArgs, liveConfig), 'browser_type');
|
|
503
|
-
});
|
|
519
|
+
}, false, args.app);
|
|
504
520
|
}
|
|
505
521
|
catch (e) {
|
|
506
522
|
return wrapError(e);
|
|
@@ -509,13 +525,18 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
509
525
|
// ── 4b. browser_type_secret ────────────────────────────────────────────────
|
|
510
526
|
server.tool("browser_type_secret", "Inject a secret from an environment variable.", {
|
|
511
527
|
id: z.string().optional().describe("Ref ID from browser_map."),
|
|
528
|
+
ref: z.string().optional().describe("Alias for id (e.g. 'w:42')."),
|
|
512
529
|
target: z.string().optional().describe("Alternative target input element identifier or CSS selector."),
|
|
513
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'."),
|
|
514
532
|
}, async (args) => {
|
|
515
533
|
try {
|
|
516
534
|
return await withSecurity(config, "browser_type_secret", async (session, liveConfig) => {
|
|
517
|
-
|
|
518
|
-
|
|
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);
|
|
519
540
|
}
|
|
520
541
|
catch (e) {
|
|
521
542
|
return wrapError(e);
|
|
@@ -526,18 +547,19 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
526
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."),
|
|
527
548
|
modifiers: z.array(z.enum(["Alt", "Ctrl", "Meta", "Shift"])).optional().describe("Array of modifier keys to hold during the press."),
|
|
528
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'."),
|
|
529
551
|
}, async (args) => {
|
|
530
|
-
const { key, modifiers, tab_id } = args;
|
|
552
|
+
const { key, modifiers, tab_id, app } = args;
|
|
531
553
|
try {
|
|
532
554
|
return await withSecurity(config, "browser_key", async (session, liveConfig) => {
|
|
533
555
|
let targetSession = session;
|
|
534
556
|
if (tab_id) {
|
|
535
557
|
const { connectToTab } = await import("./cdp/connector.js");
|
|
536
|
-
const client = await connectToTab(tab_id);
|
|
558
|
+
const client = await connectToTab(tab_id, undefined, undefined, undefined, app);
|
|
537
559
|
targetSession = client.session;
|
|
538
560
|
}
|
|
539
561
|
return await weaveKey(targetSession, { key, modifiers }, liveConfig);
|
|
540
|
-
});
|
|
562
|
+
}, false, app);
|
|
541
563
|
}
|
|
542
564
|
catch (e) {
|
|
543
565
|
return wrapError(e);
|
|
@@ -546,39 +568,48 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
546
568
|
// ── 6. browser_scroll ──────────────────────────────────────────────────────
|
|
547
569
|
server.tool("browser_scroll", "Scroll the page.", {
|
|
548
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')."),
|
|
549
572
|
selector: z.string().optional().describe("CSS Selector of the element to scroll into view."),
|
|
550
573
|
target: z.string().optional().describe("Alternative element identifier or query to scroll into view."),
|
|
551
574
|
label: z.string().optional().describe("Element label to scroll into view."),
|
|
552
575
|
direction: z.enum(["up", "down", "left", "right"]).describe("Scroll direction."),
|
|
553
576
|
distance: z.number().optional().describe("Pixels to scroll (default 400)."),
|
|
554
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'."),
|
|
555
579
|
}, async (args) => {
|
|
556
580
|
try {
|
|
557
581
|
return await withSecurity(config, "browser_scroll", async (session, liveConfig) => {
|
|
558
582
|
let targetSession = session;
|
|
559
583
|
if (args.tab_id) {
|
|
560
584
|
const { connectToTab } = await import("./cdp/connector.js");
|
|
561
|
-
const client = await connectToTab(args.tab_id);
|
|
585
|
+
const client = await connectToTab(args.tab_id, undefined, undefined, undefined, args.app);
|
|
562
586
|
targetSession = client.session;
|
|
563
587
|
}
|
|
564
588
|
const normalizedArgs = { ...args };
|
|
589
|
+
if (args.ref && !normalizedArgs.id)
|
|
590
|
+
normalizedArgs.id = args.ref;
|
|
565
591
|
if (args.selector && !normalizedArgs.id)
|
|
566
592
|
normalizedArgs.id = args.selector;
|
|
567
593
|
if (args.target && !normalizedArgs.id)
|
|
568
594
|
normalizedArgs.id = args.target;
|
|
569
595
|
return await browserScroll(targetSession, normalizedArgs, liveConfig);
|
|
570
|
-
});
|
|
596
|
+
}, false, args.app);
|
|
571
597
|
}
|
|
572
598
|
catch (e) {
|
|
573
599
|
return wrapError(e);
|
|
574
600
|
}
|
|
575
601
|
});
|
|
576
602
|
// ── 7. browser_dialog ──────────────────────────────────────────────────────
|
|
577
|
-
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.", {
|
|
578
604
|
action: z.enum(["accept", "dismiss"]).describe("Action to perform on the dialog."),
|
|
579
605
|
promptText: z.string().optional().describe("Text to enter if it is a prompt dialog.")
|
|
580
606
|
}, async (args) => {
|
|
581
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
|
+
}
|
|
582
613
|
return await withSecurity(config, "browser_dialog", async (session) => {
|
|
583
614
|
return await browserDialog(session, args);
|
|
584
615
|
});
|
|
@@ -593,65 +624,67 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
593
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"]),
|
|
594
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? }.")
|
|
595
626
|
})).describe("Ordered array of tool calls to execute serially. Use macro steps to save tokens."),
|
|
596
|
-
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'."),
|
|
597
629
|
}, async (args) => {
|
|
598
630
|
try {
|
|
599
631
|
return await withSecurity(config, "browser_burst", async (session, liveConfig) => {
|
|
600
632
|
const { browserBurst } = await import("./tools/burst.js");
|
|
601
633
|
return await browserBurst(session, args, liveConfig);
|
|
602
|
-
});
|
|
634
|
+
}, false, args.app);
|
|
603
635
|
}
|
|
604
636
|
catch (e) {
|
|
605
637
|
return wrapError(e);
|
|
606
638
|
}
|
|
607
639
|
});
|
|
608
640
|
// ── 9. browser_tabs ───────────────────────────────────────────────────────
|
|
609
|
-
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).", {
|
|
610
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."),
|
|
611
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'."),
|
|
612
645
|
tabId: z.string().optional().describe("Target Tab ID (required for 'switch' or 'close')."),
|
|
613
646
|
tabIds: z.array(z.string()).optional().describe("Multiple Tab IDs for batch 'close' action."),
|
|
614
647
|
force: z.boolean().optional().describe("Force close protected (anchor) tabs. Use with extreme caution."),
|
|
615
648
|
windowId: z.number().optional().describe("Window ID to focus (required for 'focus_window')."),
|
|
616
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."),
|
|
617
|
-
}, async ({ action, url, tabId, tabIds, force, windowId, reuse }) => {
|
|
650
|
+
}, async ({ action, url, app, tabId, tabIds, force, windowId, reuse }) => {
|
|
618
651
|
try {
|
|
619
652
|
return await withSecurity(config, "browser_tabs", async (_, liveConfig) => {
|
|
620
653
|
if (action === "list")
|
|
621
|
-
return await WeavetabList(liveConfig);
|
|
654
|
+
return await WeavetabList(liveConfig, app);
|
|
622
655
|
if (action === "open") {
|
|
623
656
|
if (!url)
|
|
624
657
|
throw new Error("url is required for action: open");
|
|
625
|
-
return await WeavetabOpen(url, liveConfig, reuse !== false);
|
|
658
|
+
return await WeavetabOpen(url, liveConfig, reuse !== false, app);
|
|
626
659
|
}
|
|
627
660
|
if (action === "switch") {
|
|
628
661
|
if (!tabId)
|
|
629
662
|
throw new Error("tabId is required for action: switch");
|
|
630
|
-
return await WeavetabSwitch(tabId, liveConfig);
|
|
663
|
+
return await WeavetabSwitch(tabId, liveConfig, app);
|
|
631
664
|
}
|
|
632
665
|
if (action === "close") {
|
|
633
666
|
if (tabIds && tabIds.length > 0)
|
|
634
|
-
return await WeavetabCloseBatch(tabIds, liveConfig, force);
|
|
667
|
+
return await WeavetabCloseBatch(tabIds, liveConfig, force, app);
|
|
635
668
|
if (!tabId)
|
|
636
669
|
throw new Error("tabId or tabIds is required for action: close");
|
|
637
|
-
return await WeavetabClose(tabId, liveConfig, force);
|
|
670
|
+
return await WeavetabClose(tabId, liveConfig, force, app);
|
|
638
671
|
}
|
|
639
672
|
if (action === "list_windows") {
|
|
640
|
-
return await weaveWindowList(liveConfig);
|
|
673
|
+
return await weaveWindowList(liveConfig, app);
|
|
641
674
|
}
|
|
642
675
|
if (action === "focus_window") {
|
|
643
676
|
if (windowId === undefined)
|
|
644
677
|
throw new Error("windowId is required for action: focus_window");
|
|
645
|
-
return await weaveFocusWindow(windowId, liveConfig);
|
|
678
|
+
return await weaveFocusWindow(windowId, liveConfig, app);
|
|
646
679
|
}
|
|
647
680
|
if (action === "consolidate") {
|
|
648
|
-
return await WeavetabConsolidate(liveConfig);
|
|
681
|
+
return await WeavetabConsolidate(liveConfig, app);
|
|
649
682
|
}
|
|
650
683
|
if (action === "heal") {
|
|
651
|
-
return await WeavetabHeal(liveConfig);
|
|
684
|
+
return await WeavetabHeal(liveConfig, app);
|
|
652
685
|
}
|
|
653
686
|
throw new Error("Invalid action");
|
|
654
|
-
});
|
|
687
|
+
}, false, app);
|
|
655
688
|
}
|
|
656
689
|
catch (e) {
|
|
657
690
|
return wrapError(e);
|
|
@@ -663,17 +696,18 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
663
696
|
value: z.string().optional().describe("CSS selector, text string, or URL pattern. Not needed for 'stable'/'network'."),
|
|
664
697
|
timeout: z.number().optional().describe("Max wait in ms (default 8000)."),
|
|
665
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'."),
|
|
666
700
|
}, async (args) => {
|
|
667
701
|
try {
|
|
668
702
|
return await withSecurity(config, "browser_wait", async (session, liveConfig) => {
|
|
669
703
|
let targetSession = session;
|
|
670
704
|
if (args.tab_id) {
|
|
671
705
|
const { connectToTab } = await import("./cdp/connector.js");
|
|
672
|
-
const client = await connectToTab(args.tab_id);
|
|
706
|
+
const client = await connectToTab(args.tab_id, undefined, undefined, undefined, args.app);
|
|
673
707
|
targetSession = client.session;
|
|
674
708
|
}
|
|
675
709
|
return await browserWait(targetSession, args, liveConfig);
|
|
676
|
-
});
|
|
710
|
+
}, false, args.app);
|
|
677
711
|
}
|
|
678
712
|
catch (e) {
|
|
679
713
|
return wrapError(e);
|
|
@@ -699,8 +733,20 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
699
733
|
message: z.string().describe("The question to ask, or the feedback/analysis to log."),
|
|
700
734
|
input: z.enum(["sentence", "paragraph"]).optional().describe("Input field type for question mode (default: sentence)."),
|
|
701
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)."),
|
|
702
737
|
}, async (args) => {
|
|
703
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
|
+
}
|
|
704
750
|
return await withSecurity(config, "browser_thoughts", async (session) => {
|
|
705
751
|
if (args.action === "feedback") {
|
|
706
752
|
const { logActionStructured } = await import("./audit/logger.js");
|
|
@@ -736,7 +782,8 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
736
782
|
end_x: z.number().optional().describe("Destination X coordinate (only for drag_and_drop)."),
|
|
737
783
|
end_y: z.number().optional().describe("Destination Y coordinate (only for drag_and_drop)."),
|
|
738
784
|
waitForChild: z.string().optional().describe("CSS Selector of a child element to wait for (only for hover)."),
|
|
739
|
-
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'."),
|
|
740
787
|
}, async (args) => {
|
|
741
788
|
try {
|
|
742
789
|
return await withSecurity(config, "browser_pointer", async (session, liveConfig) => {
|
|
@@ -754,7 +801,7 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
754
801
|
if (args.end_target && !normalizedArgs.end_ref)
|
|
755
802
|
normalizedArgs.end_ref = args.end_target;
|
|
756
803
|
return await browserPointer(session, normalizedArgs, liveConfig);
|
|
757
|
-
});
|
|
804
|
+
}, false, args.app);
|
|
758
805
|
}
|
|
759
806
|
catch (e) {
|
|
760
807
|
return wrapError(e);
|
|
@@ -765,11 +812,12 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
765
812
|
nodeId: z.number().optional().describe("CDP backendNodeId."),
|
|
766
813
|
selector: z.string().optional().describe("CSS Selector."),
|
|
767
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'."),
|
|
768
816
|
}, async (args) => {
|
|
769
817
|
try {
|
|
770
818
|
return await withSecurity(config, "browser_select", async (session, liveConfig) => {
|
|
771
819
|
return await browserSelect(session, args, liveConfig);
|
|
772
|
-
});
|
|
820
|
+
}, false, args.app);
|
|
773
821
|
}
|
|
774
822
|
catch (e) {
|
|
775
823
|
return wrapError(e);
|
|
@@ -782,11 +830,12 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
782
830
|
value: z.string().describe("Value to fill/select/type into the field."),
|
|
783
831
|
})).describe("Array of field ref+value pairs to fill sequentially."),
|
|
784
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'."),
|
|
785
834
|
}, async (args) => {
|
|
786
835
|
try {
|
|
787
836
|
return await withSecurity(config, "browser_fill", async (session, liveConfig) => {
|
|
788
837
|
return await agentAction(session, 'typing', () => browserFill(session, args, liveConfig), 'browser_fill');
|
|
789
|
-
});
|
|
838
|
+
}, false, args.app);
|
|
790
839
|
}
|
|
791
840
|
catch (e) {
|
|
792
841
|
return wrapError(e);
|
|
@@ -799,7 +848,8 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
799
848
|
ref: z.string().optional().describe("Alternative element Ref ID (e.g. 'w:42')."),
|
|
800
849
|
target: z.string().optional().describe("Alternative element identifier, CSS Selector, or query."),
|
|
801
850
|
nodeId: z.number().optional().describe("CDP backendNodeId to highlight directly."),
|
|
802
|
-
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'."),
|
|
803
853
|
}, async (args) => {
|
|
804
854
|
try {
|
|
805
855
|
return await withSecurity(config, "browser_highlight", async (session, liveConfig) => {
|
|
@@ -817,7 +867,7 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
817
867
|
delete normalizedArgs.ref;
|
|
818
868
|
delete normalizedArgs.target;
|
|
819
869
|
return await browserHighlight(session, normalizedArgs, liveConfig);
|
|
820
|
-
});
|
|
870
|
+
}, false, args.app);
|
|
821
871
|
}
|
|
822
872
|
catch (e) {
|
|
823
873
|
return wrapError(e);
|
|
@@ -826,12 +876,13 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
826
876
|
// ── 20. browser_upload ──────────────────────────────────────────────────────
|
|
827
877
|
server.tool("browser_upload", "Upload files to a file input.", {
|
|
828
878
|
nodeId: z.number().describe("CDP backendNodeId of the target <input type='file'> element."),
|
|
829
|
-
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'."),
|
|
830
881
|
}, async (args) => {
|
|
831
882
|
try {
|
|
832
883
|
return await withSecurity(config, "browser_upload", async (session, liveConfig) => {
|
|
833
884
|
return await browserUpload(session, args, liveConfig);
|
|
834
|
-
});
|
|
885
|
+
}, false, args.app);
|
|
835
886
|
}
|
|
836
887
|
catch (e) {
|
|
837
888
|
return wrapError(e);
|
|
@@ -839,12 +890,13 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
839
890
|
});
|
|
840
891
|
// ── 22. browser_find ────────────────────────────────────────────────────────
|
|
841
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.", {
|
|
842
|
-
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'."),
|
|
843
895
|
}, async (args) => {
|
|
844
896
|
try {
|
|
845
897
|
return await withSecurity(config, "browser_find", async (session, liveConfig) => {
|
|
846
898
|
return await agentAction(session, 'reading', () => browserFind(session, args, liveConfig), 'browser_find');
|
|
847
|
-
});
|
|
899
|
+
}, false, args.app);
|
|
848
900
|
}
|
|
849
901
|
catch (e) {
|
|
850
902
|
return wrapError(e);
|
|
@@ -856,11 +908,12 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
856
908
|
action: z.enum(["get", "set", "clear"]).describe("Action to perform on the storage."),
|
|
857
909
|
key: z.string().optional().describe("Key name (required for 'get' and 'set' actions)."),
|
|
858
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'."),
|
|
859
912
|
}, async (args) => {
|
|
860
913
|
try {
|
|
861
914
|
return await withSecurity(config, "browser_storage", async (session, liveConfig) => {
|
|
862
915
|
return await browserStorage(session, args, liveConfig);
|
|
863
|
-
});
|
|
916
|
+
}, false, args.app);
|
|
864
917
|
}
|
|
865
918
|
catch (e) {
|
|
866
919
|
return wrapError(e);
|
|
@@ -872,11 +925,12 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
872
925
|
pattern: z.string().optional().describe("URL glob pattern to intercept or filter telemetry (e.g. '*api/login*')."),
|
|
873
926
|
mockStatus: z.number().optional().describe("HTTP status code to return when enabling mock (default 200)."),
|
|
874
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'."),
|
|
875
929
|
}, async (args) => {
|
|
876
930
|
try {
|
|
877
931
|
return await withSecurity(config, "browser_network_intercept", async (session, liveConfig) => {
|
|
878
932
|
return await browserNetworkIntercept(session, args, liveConfig);
|
|
879
|
-
});
|
|
933
|
+
}, false, args.app);
|
|
880
934
|
}
|
|
881
935
|
catch (e) {
|
|
882
936
|
return wrapError(e);
|
|
@@ -884,17 +938,18 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
884
938
|
});
|
|
885
939
|
// ── 25. browser_console ─────────────────────────────────────────────────────
|
|
886
940
|
server.tool("browser_console", "1-5ms latency. Read or clear buffered console API messages, JS exceptions, and network errors.", {
|
|
887
|
-
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'."),
|
|
888
943
|
}, async (args) => {
|
|
889
944
|
try {
|
|
890
945
|
return await withSecurity(config, "browser_console", async (session, liveConfig) => {
|
|
891
946
|
let targetId = "default";
|
|
892
947
|
const { sessionRegistry } = await import("./state/session.js");
|
|
893
|
-
const active = sessionRegistry.getActive(liveConfig.agentId);
|
|
948
|
+
const active = sessionRegistry.getActive(liveConfig.agentId, args.app);
|
|
894
949
|
if (active)
|
|
895
950
|
targetId = active.targetId;
|
|
896
951
|
return await browserConsole(session, args, liveConfig, targetId);
|
|
897
|
-
});
|
|
952
|
+
}, false, args.app);
|
|
898
953
|
}
|
|
899
954
|
catch (e) {
|
|
900
955
|
return wrapError(e);
|
|
@@ -906,11 +961,12 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
906
961
|
landscape: z.boolean().optional().describe("Print in landscape orientation (default false)."),
|
|
907
962
|
scale: z.number().optional().describe("Scale factor (default 1.0)."),
|
|
908
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'."),
|
|
909
965
|
}, async (args) => {
|
|
910
966
|
try {
|
|
911
967
|
return await withSecurity(config, "browser_pdf", async (session, liveConfig) => {
|
|
912
968
|
return await browserPdf(session, args, liveConfig);
|
|
913
|
-
});
|
|
969
|
+
}, false, args.app);
|
|
914
970
|
}
|
|
915
971
|
catch (e) {
|
|
916
972
|
return wrapError(e);
|
|
@@ -919,12 +975,13 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
919
975
|
// ── 27. browser_eval ────────────────────────────────────────────────────────
|
|
920
976
|
server.tool("browser_eval", "Run arbitrary JS via CDP. Good for extracting app state. Cannot traverse shadow DOM/cross-origin iframes.", {
|
|
921
977
|
expression: z.string().describe("JavaScript code to evaluate in the context of the page."),
|
|
922
|
-
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'."),
|
|
923
980
|
}, async (args) => {
|
|
924
981
|
try {
|
|
925
982
|
return await withSecurity(config, "browser_eval", async (session, liveConfig) => {
|
|
926
983
|
return await agentAction(session, 'reading', () => browserEval(session, args, liveConfig), 'browser_eval');
|
|
927
|
-
});
|
|
984
|
+
}, false, args.app);
|
|
928
985
|
}
|
|
929
986
|
catch (e) {
|
|
930
987
|
return wrapError(e);
|
|
@@ -938,11 +995,12 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
938
995
|
domain: z.string().optional().describe("Domain for the cookie."),
|
|
939
996
|
path: z.string().optional().describe("Path for the cookie."),
|
|
940
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'."),
|
|
941
999
|
}, async (args) => {
|
|
942
1000
|
try {
|
|
943
1001
|
return await withSecurity(config, "browser_cookies", async (session, liveConfig) => {
|
|
944
1002
|
return await browserCookies(session, args, liveConfig);
|
|
945
|
-
});
|
|
1003
|
+
}, false, args.app);
|
|
946
1004
|
}
|
|
947
1005
|
catch (e) {
|
|
948
1006
|
return wrapError(e);
|
|
@@ -956,12 +1014,13 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
956
1014
|
label: z.string().optional().describe("Visible text or aria-label of the canvas element."),
|
|
957
1015
|
ref: z.string().optional().describe("Alternative ref identifier for the canvas element."),
|
|
958
1016
|
backendNodeId: z.number().optional().describe("Raw CDP backendNodeId for direct targeting."),
|
|
959
|
-
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'."),
|
|
960
1019
|
}, async (args) => {
|
|
961
1020
|
try {
|
|
962
1021
|
return await withSecurity(config, "browser_canvas", async (session, liveConfig) => {
|
|
963
1022
|
return await browserCanvas(session, args, liveConfig);
|
|
964
|
-
});
|
|
1023
|
+
}, false, args.app);
|
|
965
1024
|
}
|
|
966
1025
|
catch (e) {
|
|
967
1026
|
return wrapError(e);
|
|
@@ -972,12 +1031,13 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
972
1031
|
query: z.string().describe("CSS Selector or XPath query to extract."),
|
|
973
1032
|
type: z.enum(["css", "xpath"]).optional().describe("Query type (default 'css')."),
|
|
974
1033
|
attributes: z.array(z.string()).optional().describe("Attributes to extract (e.g. ['textContent', 'href'], default ['textContent'])."),
|
|
975
|
-
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'."),
|
|
976
1036
|
}, async (args) => {
|
|
977
1037
|
try {
|
|
978
1038
|
return await withSecurity(config, "browser_scrape", async (session, liveConfig) => {
|
|
979
1039
|
return await agentAction(session, 'reading', () => browserScrape(session, args, liveConfig), 'browser_scrape');
|
|
980
|
-
});
|
|
1040
|
+
}, false, args.app);
|
|
981
1041
|
}
|
|
982
1042
|
catch (e) {
|
|
983
1043
|
return wrapError(e);
|
|
@@ -986,12 +1046,13 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
986
1046
|
// ── 32. browser_snapshot ────────────────────────────────────────────────────
|
|
987
1047
|
server.tool("browser_snapshot", "Capture or restore an MHTML snapshot. Best for offline archival, replay testing, and compliance audits.", {
|
|
988
1048
|
action: z.enum(["capture", "restore"]).describe("Action to perform."),
|
|
989
|
-
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'."),
|
|
990
1051
|
}, async (args) => {
|
|
991
1052
|
try {
|
|
992
1053
|
return await withSecurity(config, "browser_snapshot", async (session, liveConfig) => {
|
|
993
1054
|
return await browserSnapshot(session, args, liveConfig);
|
|
994
|
-
});
|
|
1055
|
+
}, false, args.app);
|
|
995
1056
|
}
|
|
996
1057
|
catch (e) {
|
|
997
1058
|
return wrapError(e);
|
|
@@ -1007,12 +1068,13 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
1007
1068
|
y: z.number().describe("Y coordinate of clip region."),
|
|
1008
1069
|
width: z.number().describe("Width of clip region."),
|
|
1009
1070
|
height: z.number().describe("Height of clip region.")
|
|
1010
|
-
}).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'."),
|
|
1011
1073
|
}, async (args) => {
|
|
1012
1074
|
try {
|
|
1013
1075
|
return await withSecurity(config, "browser_screenshot", async (session, liveConfig) => {
|
|
1014
1076
|
return await agentAction(session, 'reading', () => browserScreenshot(session, args, liveConfig), 'browser_screenshot');
|
|
1015
|
-
});
|
|
1077
|
+
}, false, args.app);
|
|
1016
1078
|
}
|
|
1017
1079
|
catch (e) {
|
|
1018
1080
|
return wrapError(e);
|
|
@@ -1024,12 +1086,13 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
1024
1086
|
width: z.number().optional().describe("Viewport width in pixels."),
|
|
1025
1087
|
height: z.number().optional().describe("Viewport height in pixels."),
|
|
1026
1088
|
deviceScaleFactor: z.number().optional().describe("Device Pixel Ratio (default 1)."),
|
|
1027
|
-
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'."),
|
|
1028
1091
|
}, async (args) => {
|
|
1029
1092
|
try {
|
|
1030
1093
|
return await withSecurity(config, "browser_viewport", async (session, liveConfig) => {
|
|
1031
1094
|
return await browserViewport(session, args, liveConfig);
|
|
1032
|
-
});
|
|
1095
|
+
}, false, args.app);
|
|
1033
1096
|
}
|
|
1034
1097
|
catch (e) {
|
|
1035
1098
|
return wrapError(e);
|
|
@@ -1040,12 +1103,13 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
1040
1103
|
action: z.enum(["read", "write"]).describe("Action to perform."),
|
|
1041
1104
|
text: z.string().optional().describe("Text to write to clipboard (for write action)."),
|
|
1042
1105
|
dataUrl: z.string().optional().describe("Base64 data URL of an image to write to clipboard (e.g. 'data:image/png;base64,...')."),
|
|
1043
|
-
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'."),
|
|
1044
1108
|
}, async (args) => {
|
|
1045
1109
|
try {
|
|
1046
1110
|
return await withSecurity(config, "browser_clipboard", async (session, liveConfig) => {
|
|
1047
1111
|
return await browserClipboard(session, args, liveConfig);
|
|
1048
|
-
});
|
|
1112
|
+
}, false, args.app);
|
|
1049
1113
|
}
|
|
1050
1114
|
catch (e) {
|
|
1051
1115
|
return wrapError(e);
|
|
@@ -1055,12 +1119,13 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
1055
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.", {
|
|
1056
1120
|
nodeId: z.number().optional().describe("CDP backendNodeId."),
|
|
1057
1121
|
selector: z.string().optional().describe("CSS Selector."),
|
|
1058
|
-
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'."),
|
|
1059
1124
|
}, async (args) => {
|
|
1060
1125
|
try {
|
|
1061
1126
|
return await withSecurity(config, "browser_inspect", async (session, liveConfig) => {
|
|
1062
1127
|
return await agentAction(session, 'reading', () => browserInspect(session, args, liveConfig), 'browser_inspect');
|
|
1063
|
-
});
|
|
1128
|
+
}, false, args.app);
|
|
1064
1129
|
}
|
|
1065
1130
|
catch (e) {
|
|
1066
1131
|
return wrapError(e);
|
|
@@ -1073,12 +1138,13 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
1073
1138
|
outputDir: z.string().optional().describe("Optional custom directory to write extracted files into. If omitted, uses configured designDir (~/.weavetab/designs/<domain>-<timestamp>/)."),
|
|
1074
1139
|
name: z.string().optional().describe("Optional custom name for the saved design folder (e.g. 'resend-hero', 'pricing-section')."),
|
|
1075
1140
|
includeAssets: z.boolean().optional().describe("Whether to capture inline SVGs and asset references (default true)."),
|
|
1076
|
-
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'."),
|
|
1077
1143
|
}, async (args) => {
|
|
1078
1144
|
try {
|
|
1079
1145
|
return await withSecurity(config, "browser_extract_design", async (session, liveConfig) => {
|
|
1080
1146
|
return await agentAction(session, 'reading', () => browserExtractDesign(session, args, liveConfig), 'browser_extract_design');
|
|
1081
|
-
});
|
|
1147
|
+
}, false, args.app);
|
|
1082
1148
|
}
|
|
1083
1149
|
catch (e) {
|
|
1084
1150
|
return wrapError(e);
|
|
@@ -1091,12 +1157,13 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
1091
1157
|
script: z.string().optional().describe("JavaScript code to inject and run (required for start)."),
|
|
1092
1158
|
persistent: z.boolean().optional().describe("If true, the script re-runs in a loop (default false)."),
|
|
1093
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'."),
|
|
1094
1161
|
}, async (args) => {
|
|
1095
1162
|
try {
|
|
1096
1163
|
return await withSecurity(config, "browser_automation", async (session, liveConfig) => {
|
|
1097
1164
|
const result = await browserAutomation(session, args, liveConfig);
|
|
1098
1165
|
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
1099
|
-
});
|
|
1166
|
+
}, false, args.app);
|
|
1100
1167
|
}
|
|
1101
1168
|
catch (e) {
|
|
1102
1169
|
return wrapError(e);
|
|
@@ -1110,12 +1177,13 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
1110
1177
|
maxFrames: z.number().optional().describe("Maximum frames to capture (default 500)."),
|
|
1111
1178
|
format: z.enum(["webp", "gif", "webm", "mp4", "frames"]).optional().describe("Output video format when stopping recording. Default 'webp' (pure Node native compilation)."),
|
|
1112
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'."),
|
|
1113
1181
|
}, async (args) => {
|
|
1114
1182
|
try {
|
|
1115
1183
|
return await withSecurity(config, "browser_recording", async (session, liveConfig) => {
|
|
1116
1184
|
const result = await browserRecording(session, args, liveConfig);
|
|
1117
1185
|
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
1118
|
-
});
|
|
1186
|
+
}, false, args.app);
|
|
1119
1187
|
}
|
|
1120
1188
|
catch (e) {
|
|
1121
1189
|
return wrapError(e);
|
|
@@ -1139,12 +1207,14 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
|
|
|
1139
1207
|
}
|
|
1140
1208
|
});
|
|
1141
1209
|
// ── browser_performance ───────────────────────────────────────────────────
|
|
1142
|
-
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) => {
|
|
1143
1213
|
try {
|
|
1144
1214
|
return await withSecurity(config, "browser_performance", async (session, liveConfig) => {
|
|
1145
1215
|
const result = await browserPerformance(session, liveConfig);
|
|
1146
1216
|
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
1147
|
-
});
|
|
1217
|
+
}, false, args.app);
|
|
1148
1218
|
}
|
|
1149
1219
|
catch (e) {
|
|
1150
1220
|
return wrapError(e);
|
|
@@ -1285,11 +1355,12 @@ DO NOT use browser_scrape or browser_map for binary documents — they cannot re
|
|
|
1285
1355
|
}).optional().describe("Override geolocation coordinates"),
|
|
1286
1356
|
timezoneId: z.string().optional().describe("Override browser timezone (e.g. 'America/New_York')"),
|
|
1287
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'."),
|
|
1288
1359
|
}, async (args) => {
|
|
1289
1360
|
try {
|
|
1290
1361
|
return await withSecurity(config, "browser_emulate", async (session, liveConfig) => {
|
|
1291
1362
|
return await browserEmulate(session, args, liveConfig);
|
|
1292
|
-
});
|
|
1363
|
+
}, false, args.app);
|
|
1293
1364
|
}
|
|
1294
1365
|
catch (e) {
|
|
1295
1366
|
return wrapError(e);
|
|
@@ -1409,14 +1480,13 @@ DO NOT use browser_scrape or browser_map for binary documents — they cannot re
|
|
|
1409
1480
|
}]
|
|
1410
1481
|
};
|
|
1411
1482
|
});
|
|
1412
|
-
// ── Plugins Lifecycle
|
|
1413
|
-
const plugins = discoverPlugins();
|
|
1483
|
+
// ── Plugins Lifecycle & Dynamic Registry ───────────────────────────
|
|
1414
1484
|
const { getVerifiedPackages, resolvePluginTrust } = await import("./plugin/registry.js");
|
|
1415
1485
|
const verifiedPackages = await getVerifiedPackages();
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1486
|
+
const loadedPluginNames = new Set();
|
|
1487
|
+
const registerPluginTools = async (plugin) => {
|
|
1488
|
+
if (loadedPluginNames.has(plugin.name))
|
|
1489
|
+
return;
|
|
1420
1490
|
const trust = resolvePluginTrust(plugin.name, verifiedPackages);
|
|
1421
1491
|
const isOfficial = trust.tier === "official";
|
|
1422
1492
|
const addedNames = [];
|
|
@@ -1431,7 +1501,6 @@ DO NOT use browser_scrape or browser_map for binary documents — they cannot re
|
|
|
1431
1501
|
if (server._registeredTools?.[toolName]) {
|
|
1432
1502
|
delete server._registeredTools[toolName];
|
|
1433
1503
|
removedNames.push(toolName);
|
|
1434
|
-
pluginRemovedCount++;
|
|
1435
1504
|
}
|
|
1436
1505
|
}
|
|
1437
1506
|
// 2. Tool Overrides (replace or wrap built-in tools)
|
|
@@ -1461,7 +1530,6 @@ DO NOT use browser_scrape or browser_map for binary documents — they cannot re
|
|
|
1461
1530
|
}
|
|
1462
1531
|
});
|
|
1463
1532
|
overriddenNames.push(ov.name);
|
|
1464
|
-
pluginOverrideCount++;
|
|
1465
1533
|
}
|
|
1466
1534
|
// 3. Tool Additions (new tools)
|
|
1467
1535
|
const newTools = plugin.tools || [];
|
|
@@ -1469,7 +1537,7 @@ DO NOT use browser_scrape or browser_map for binary documents — they cannot re
|
|
|
1469
1537
|
if (server._registeredTools?.[td.name]) {
|
|
1470
1538
|
delete server._registeredTools[td.name];
|
|
1471
1539
|
}
|
|
1472
|
-
server.tool(td.name, td.description, td.schema, async (args) => {
|
|
1540
|
+
server.tool(td.name, td.description, td.schema || {}, async (args) => {
|
|
1473
1541
|
try {
|
|
1474
1542
|
return await withSecurity(config, td.name, async (session, liveConfig) => {
|
|
1475
1543
|
const result = await td.handler(args, session, liveConfig);
|
|
@@ -1481,45 +1549,69 @@ DO NOT use browser_scrape or browser_map for binary documents — they cannot re
|
|
|
1481
1549
|
}
|
|
1482
1550
|
});
|
|
1483
1551
|
addedNames.push(td.name);
|
|
1484
|
-
pluginAddedCount++;
|
|
1485
1552
|
}
|
|
1486
|
-
//
|
|
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
|
+
};
|
|
1487
1563
|
if (typeof plugin.onLoad === "function") {
|
|
1488
1564
|
try {
|
|
1489
|
-
await plugin.onLoad();
|
|
1565
|
+
await plugin.onLoad(pluginCtx);
|
|
1490
1566
|
}
|
|
1491
1567
|
catch (err) {
|
|
1492
1568
|
process.stderr.write(` \u001b[31m[!] Plugin ${plugin.name} onLoad failed: ${err?.message}\u001b[0m\n`);
|
|
1493
1569
|
}
|
|
1494
1570
|
}
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
if (removedNames.length > 0)
|
|
1502
|
-
ops.push(`✖removed [${removedNames.join(", ")}]`);
|
|
1503
|
-
const opsStr = ops.length > 0 ? ` (${ops.join(", ")})` : "";
|
|
1504
|
-
let permWarning = "";
|
|
1505
|
-
if (trust.tier === "unverified" && plugin.manifest?.permissions) {
|
|
1506
|
-
const perms = plugin.manifest.permissions;
|
|
1507
|
-
const flags = [];
|
|
1508
|
-
if (perms.network)
|
|
1509
|
-
flags.push("network");
|
|
1510
|
-
if (perms.cdp)
|
|
1511
|
-
flags.push("cdp");
|
|
1512
|
-
if (perms.fs)
|
|
1513
|
-
flags.push("fs");
|
|
1514
|
-
if (perms.tools?.override?.length)
|
|
1515
|
-
flags.push(`override:${perms.tools.override.join(",")}`);
|
|
1516
|
-
if (perms.tools?.remove?.length)
|
|
1517
|
-
flags.push(`remove:${perms.tools.remove.join(",")}`);
|
|
1518
|
-
if (flags.length > 0) {
|
|
1519
|
-
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();
|
|
1520
1577
|
}
|
|
1521
1578
|
}
|
|
1522
|
-
|
|
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
|
+
}
|
|
1523
1615
|
}
|
|
1524
1616
|
let transport;
|
|
1525
1617
|
if (transportMode === "ws") {
|