@weavetab/mcp 2.5.0-beta.5 → 2.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/AGENTS.md +49 -49
  2. package/ARCHITECTURE.md +590 -590
  3. package/README.md +216 -100
  4. package/SECURITY.md +207 -207
  5. package/TROUBLESHOOTING.md +373 -365
  6. package/dist/anticaptcha/detector.js +395 -395
  7. package/dist/anticaptcha/solver.js +130 -130
  8. package/dist/cdp/bridge.js +73 -73
  9. package/dist/cdp/connector.js +46 -22
  10. package/dist/cdp/extension.js +34 -34
  11. package/dist/cdp/helpers.js +174 -174
  12. package/dist/cdp/pruner.js +195 -195
  13. package/dist/cdp/ref_cache.js +66 -66
  14. package/dist/cdp/security.js +10 -10
  15. package/dist/cdp/verify.js +57 -57
  16. package/dist/cdp/walker.js +563 -525
  17. package/dist/cli/plugin.js +26 -3
  18. package/dist/config/loader.d.ts +0 -3
  19. package/dist/config/loader.js +0 -9
  20. package/dist/index.js +0 -4
  21. package/dist/intelligence/intent.js +81 -81
  22. package/dist/intelligence/sessionMemory.js +2 -2
  23. package/dist/overlay/ghost.js +22 -22
  24. package/dist/overlay/injector.js +89 -89
  25. package/dist/plugin/extension_bridge.js +54 -54
  26. package/dist/plugin/interface.d.ts +7 -0
  27. package/dist/plugin/loader.js +3 -4
  28. package/dist/plugin/schema.d.ts +11 -0
  29. package/dist/plugin/schema.js +59 -0
  30. package/dist/security/guard.js +18 -18
  31. package/dist/sensors/actionability.js +167 -167
  32. package/dist/sensors/guard.js +29 -29
  33. package/dist/sensors/network.js +15 -17
  34. package/dist/server.d.ts +1 -1
  35. package/dist/server.js +173 -144
  36. package/dist/state/delta.js +6 -7
  37. package/dist/tools/automation.js +85 -85
  38. package/dist/tools/canvas.js +48 -25
  39. package/dist/tools/checkpoint.js +94 -35
  40. package/dist/tools/click.js +52 -41
  41. package/dist/tools/clipboard.js +80 -74
  42. package/dist/tools/design_extract.js +441 -441
  43. package/dist/tools/dialog.js +13 -13
  44. package/dist/tools/emulation.d.ts +2 -0
  45. package/dist/tools/emulation.js +7 -0
  46. package/dist/tools/fill.js +344 -347
  47. package/dist/tools/find.js +78 -52
  48. package/dist/tools/highlight.js +67 -67
  49. package/dist/tools/inspect.js +54 -25
  50. package/dist/tools/key.js +42 -35
  51. package/dist/tools/navigate.d.ts +1 -1
  52. package/dist/tools/navigate.js +35 -35
  53. package/dist/tools/office/parse.js +62 -18
  54. package/dist/tools/pdf.d.ts +9 -0
  55. package/dist/tools/pdf.js +7 -4
  56. package/dist/tools/performance.d.ts +11 -4
  57. package/dist/tools/performance.js +94 -42
  58. package/dist/tools/performance_test.d.ts +2 -2
  59. package/dist/tools/performance_test.js +69 -69
  60. package/dist/tools/pointer.js +116 -116
  61. package/dist/tools/read.js +99 -67
  62. package/dist/tools/recording.js +9 -0
  63. package/dist/tools/scrape.js +146 -146
  64. package/dist/tools/screenshot.d.ts +2 -0
  65. package/dist/tools/screenshot.js +46 -4
  66. package/dist/tools/scroll.d.ts +3 -1
  67. package/dist/tools/scroll.js +13 -15
  68. package/dist/tools/select.d.ts +3 -0
  69. package/dist/tools/select.js +131 -124
  70. package/dist/tools/snapshot.js +8 -6
  71. package/dist/tools/storage.d.ts +5 -1
  72. package/dist/tools/storage.js +16 -0
  73. package/dist/tools/tabs.d.ts +7 -5
  74. package/dist/tools/tabs.js +79 -26
  75. package/dist/tools/thought.js +8 -8
  76. package/dist/tools/type.js +274 -274
  77. package/dist/tools/type_secret.js +23 -23
  78. package/dist/tools/upload.js +3 -3
  79. package/dist/tools/video_compiler.js +9 -0
  80. package/dist/tools/wait.js +35 -35
  81. package/dist/version.d.ts +1 -1
  82. package/dist/version.js +1 -1
  83. package/docs/DOCUMENTATION.md +583 -585
  84. package/docs/PLUGINS.md +139 -121
  85. package/docs/TOOLS.md +3 -4
  86. package/docs/architecture/blind-injection.md +83 -83
  87. package/docs/architecture/overview.md +49 -49
  88. package/docs/configuration/configuration.md +39 -27
  89. package/docs/configuration/folders.md +62 -55
  90. package/docs/desktop-automation/electron-apps.md +225 -214
  91. package/docs/get-started/installation.md +1 -1
  92. package/docs/get-started/introduction.md +60 -60
  93. package/docs/get-started/quickstart.md +2 -2
  94. package/docs/get-started/setup-wizard.md +1 -1
  95. package/docs/intelligence/site-profiles.md +151 -151
  96. package/docs/tools/tools.md +582 -631
  97. package/docs/troubleshooting/trouble-loop.md +31 -31
  98. package/ext-dist/background.js +415 -415
  99. package/ext-dist/content.js +2527 -2527
  100. package/ext-dist/dashboard.html +583 -583
  101. package/ext-dist/hud/hud.js +3 -3
  102. package/ext-dist/hud/index.html +20 -20
  103. package/ext-dist/manifest.json +73 -73
  104. package/ext-dist/styles.css +223 -223
  105. package/mcp.json +796 -805
  106. package/package.json +8 -8
  107. package/server.json +23 -23
  108. package/smithery.yaml +7 -11
  109. package/dist/cdp/walker.test.d.ts +0 -2
  110. package/dist/cdp/walker.test.js +0 -172
  111. package/dist/intelligence/sessionMemory.test.d.ts +0 -2
  112. package/dist/intelligence/sessionMemory.test.js +0 -62
  113. package/dist/intelligence/trail.test.d.ts +0 -2
  114. package/dist/intelligence/trail.test.js +0 -110
  115. package/dist/state/cache.test.d.ts +0 -2
  116. package/dist/state/cache.test.js +0 -76
  117. package/dist/state/delta.test.d.ts +0 -2
  118. package/dist/state/delta.test.js +0 -279
  119. package/dist/state/groups.d.ts +0 -15
  120. package/dist/state/groups.js +0 -80
  121. package/dist/tools/accessibility.d.ts +0 -14
  122. package/dist/tools/accessibility.js +0 -32
  123. package/dist/tools/ask.d.ts +0 -14
  124. package/dist/tools/ask.js +0 -49
  125. package/dist/tools/click_and_wait.d.ts +0 -33
  126. package/dist/tools/click_and_wait.js +0 -149
  127. package/dist/tools/drag.d.ts +0 -15
  128. package/dist/tools/drag.js +0 -29
  129. package/dist/tools/geo.d.ts +0 -19
  130. package/dist/tools/geo.js +0 -29
  131. package/dist/tools/github/analyze.d.ts +0 -4
  132. package/dist/tools/github/analyze.js +0 -113
  133. package/dist/tools/github/cache.d.ts +0 -21
  134. package/dist/tools/github/cache.js +0 -28
  135. package/dist/tools/github/index.d.ts +0 -19
  136. package/dist/tools/github/index.js +0 -40
  137. package/dist/tools/github/issues.d.ts +0 -9
  138. package/dist/tools/github/issues.js +0 -88
  139. package/dist/tools/github/pr.d.ts +0 -5
  140. package/dist/tools/github/pr.js +0 -88
  141. package/dist/tools/github/rateLimiter.d.ts +0 -4
  142. package/dist/tools/github/rateLimiter.js +0 -46
  143. package/dist/tools/github/read.d.ts +0 -34
  144. package/dist/tools/github/read.js +0 -74
  145. package/dist/tools/github/repoContext.d.ts +0 -20
  146. package/dist/tools/github/repoContext.js +0 -129
  147. package/dist/tools/hover.d.ts +0 -19
  148. package/dist/tools/hover.js +0 -57
  149. package/dist/tools/integration.test.d.ts +0 -2
  150. package/dist/tools/integration.test.js +0 -96
  151. package/dist/tools/macro_compiler.d.ts +0 -7
  152. package/dist/tools/macro_compiler.js +0 -82
  153. package/dist/tools/mouse.d.ts +0 -21
  154. package/dist/tools/mouse.js +0 -62
  155. package/dist/tools/response.d.ts +0 -129
  156. package/dist/tools/response.js +0 -5
  157. package/dist/tools/wait_for.d.ts +0 -23
  158. package/dist/tools/wait_for.js +0 -30
  159. package/dist/tools/windows.d.ts +0 -22
  160. package/dist/tools/windows.js +0 -57
package/dist/server.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Weavetab — server.ts
3
- * MCP server: 44 browser automation tools with security, rate limiting, and visual overlay.
3
+ * MCP server: 41 browser automation tools with security, rate limiting, and visual overlay.
4
4
  * Weavetab by fy2ne
5
5
  */
6
6
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -55,20 +55,19 @@ import { resetLoopCounter } from "./intelligence/trail.js";
55
55
  import { getSiteProfile, learnPattern, recordPatternExecution, recordQuirk, getPatternReliability, formatSiteProfileHint } from "./intelligence/patternCompiler.js";
56
56
  import { discoverPlugins } from "./plugin/loader.js";
57
57
  import { hookPipeline } from "./plugin/pipeline.js";
58
+ import { toPluginInputShape } from "./plugin/schema.js";
58
59
  import { agentAction } from "./sensors/pacing.js";
59
60
  import { getCachedConfig, getElectronApps } from "./config/loader.js";
60
61
  import { agentState } from "./state/agent.js";
61
62
  import { SessionSecretTracker } from "./security/secrets.js";
62
63
  import { sessionRegistry } from "./state/session.js";
63
- // GitHub tools
64
- import { browserGithub } from "./tools/github/index.js";
65
64
  // Office document tools
66
65
  import { browserOfficeParse, browserOfficeList } from "./tools/office/index.js";
67
66
  const activeLoadedPlugins = new Map();
68
67
  const ROLE_TOOL_ALLOW = {
69
68
  admin: new Set(["*"]),
70
69
  operator: new Set(["*"]),
71
- viewer: new Set(["browser_map", "browser_find", "browser_scrape", "browser_console", "browser_eval", "browser_inspect", "browser_extract_design", "browser_snapshot", "browser_screenshot", "browser_pdf", "browser_cookies", "browser_tabs", "browser_wait", "browser_pattern_learn", "browser_github"]),
70
+ viewer: new Set(["browser_map", "browser_find", "browser_scrape", "browser_console", "browser_eval", "browser_inspect", "browser_extract_design", "browser_snapshot", "browser_screenshot", "browser_pdf", "browser_cookies", "browser_tabs", "browser_wait", "browser_pattern_learn"]),
72
71
  automation: new Set(["*"]), // Allowed all except interactive tools (enforced in isToolAllowedForRole)
73
72
  system: new Set(["browser_thoughts", "browser_detect", "browser_performance", "browser_checkpoint", "browser_pattern_learn"]),
74
73
  };
@@ -83,7 +82,7 @@ function isToolAllowedForRole(role, toolName) {
83
82
  return { allowed: true };
84
83
  if (allowed.has(toolName))
85
84
  return { allowed: true };
86
- return { allowed: false, reason: `Role '${role}' does not have permission to use '${toolName}'. ${role === "viewer" ? "Viewer role only permits read-only tools (map/find/scrape/console/eval/inspect/snapshot/pdf/cookies/browser_github)." : ""}` };
85
+ return { allowed: false, reason: `Role '${role}' does not have permission to use '${toolName}'. ${role === "viewer" ? "Viewer role only permits read-only tools (map/find/scrape/console/eval/inspect/snapshot/pdf/cookies)." : ""}` };
87
86
  }
88
87
  /** Push a rich log card to every active browser session's overlay. Only shown when devMode is on. */
89
88
  async function pushRichLog(agentId, toolName, title, details, status, devMode = false) {
@@ -168,7 +167,7 @@ function inferTargetApp(requestedApp, url, tabId) {
168
167
  }
169
168
  return undefined;
170
169
  }
171
- async function withSecurity(config, toolName, handler, isOfficial = false, targetApp) {
170
+ async function withSecurity(config, toolName, handler, isOfficial = false, targetApp, requiresSession = true) {
172
171
  const liveConfig = getCachedConfig();
173
172
  const rl = checkRateLimit(liveConfig);
174
173
  if (!rl.allowed) {
@@ -181,79 +180,83 @@ async function withSecurity(config, toolName, handler, isOfficial = false, targe
181
180
  throw new Error(`[!] ${roleToolCheck.reason}`);
182
181
  }
183
182
  }
184
- const resolvedTargetApp = inferTargetApp(targetApp);
185
- const { session } = await withTimeoutGuard(() => getOrConnect(liveConfig, resolvedTargetApp), 15000, `CDP Connection (${toolName}${resolvedTargetApp ? `:${resolvedTargetApp}` : ''})`);
186
- agentState.setSession(session);
187
- global.__wt_live_config = liveConfig;
188
- try {
189
- const stopCheck = await session.Runtime.evaluate({
190
- expression: 'window.__wt_stop_requested===true',
191
- returnByValue: true,
192
- });
193
- if (stopCheck?.result?.value === true) {
194
- await session.Runtime.evaluate({ expression: 'window.__wt_stop_requested=false;' }).catch(() => { });
195
- const { sendToExtension } = await import("./cdp/helpers.js");
196
- await sendToExtension(session, { type: 'Weavetab_GROUP_STATE', state: 'idle' }).catch(() => { });
197
- await sendToExtension(session, { type: 'Weavetab_STATE', hud: { action: '' } }).catch(() => { });
198
- await sendToExtension(session, { type: 'Weavetab_CLEAR_THOUGHT' }).catch(() => { });
199
- agentState.reportTransportClosed();
200
- throw new Error('[!] mission stopped by user via HUD.');
201
- }
202
- }
203
- catch (e) {
204
- if (e instanceof Error && e.message.startsWith('[!]'))
205
- throw e;
183
+ const resolvedTargetApp = requiresSession ? inferTargetApp(targetApp) : undefined;
184
+ let session;
185
+ if (requiresSession) {
186
+ ({ session } = await withTimeoutGuard(() => getOrConnect(liveConfig, resolvedTargetApp), 15000, `CDP Connection (${toolName}${resolvedTargetApp ? `:${resolvedTargetApp}` : ''})`));
187
+ agentState.setSession(session);
206
188
  }
207
- if (!isOfficial) {
208
- // Phase 7c: Domain policy check for read-only / deny / audit-only modes
209
- const activeClient = sessionRegistry.getActive(liveConfig.agentId, targetApp);
210
- if (activeClient?.url && !activeClient.isElectron) {
211
- const domainCheck = checkDomain(activeClient.url, liveConfig);
212
- if (!domainCheck.allowed) {
213
- throw new Error(`[!] ${domainCheck.reason}`);
214
- }
215
- if (domainCheck.mode === "deny") {
216
- throw new Error(`[!] Domain denied by policy`);
189
+ global.__wt_live_config = liveConfig;
190
+ const { sendToExtension } = await import("./cdp/helpers.js");
191
+ const chatMessages = [];
192
+ if (requiresSession) {
193
+ try {
194
+ const stopCheck = await session.Runtime.evaluate({
195
+ expression: 'window.__wt_stop_requested===true',
196
+ returnByValue: true,
197
+ });
198
+ if (stopCheck?.result?.value === true) {
199
+ await session.Runtime.evaluate({ expression: 'window.__wt_stop_requested=false;' }).catch(() => { });
200
+ await sendToExtension(session, { type: 'Weavetab_GROUP_STATE', state: 'idle' }).catch(() => { });
201
+ await sendToExtension(session, { type: 'Weavetab_STATE', hud: { action: '' } }).catch(() => { });
202
+ await sendToExtension(session, { type: 'Weavetab_CLEAR_THOUGHT' }).catch(() => { });
203
+ agentState.reportTransportClosed();
204
+ throw new Error('[!] mission stopped by user via HUD.');
217
205
  }
218
- if (domainCheck.mode === "read-only") {
219
- const toolCheck = isToolAllowedInMode(toolName, "read-only");
220
- if (!toolCheck.allowed) {
221
- throw new Error(`[!] ${toolCheck.reason}`);
206
+ }
207
+ catch (e) {
208
+ if (e instanceof Error && e.message.startsWith('[!]'))
209
+ throw e;
210
+ }
211
+ if (!isOfficial) {
212
+ // Phase 7c: Domain policy check for read-only / deny / audit-only modes
213
+ const activeClient = sessionRegistry.getActive(liveConfig.agentId, targetApp);
214
+ if (activeClient?.url && !activeClient.isElectron) {
215
+ const domainCheck = checkDomain(activeClient.url, liveConfig);
216
+ if (!domainCheck.allowed) {
217
+ throw new Error(`[!] ${domainCheck.reason}`);
218
+ }
219
+ if (domainCheck.mode === "deny") {
220
+ throw new Error(`[!] Domain denied by policy`);
221
+ }
222
+ if (domainCheck.mode === "read-only") {
223
+ const toolCheck = isToolAllowedInMode(toolName, "read-only");
224
+ if (!toolCheck.allowed) {
225
+ throw new Error(`[!] ${toolCheck.reason}`);
226
+ }
222
227
  }
223
228
  }
224
229
  }
225
- }
226
- const { sendToExtension } = await import("./cdp/helpers.js");
227
- // Push config to extension once per session (idempotent, ignored if already set)
228
- sendToExtension(session, {
229
- type: 'Weavetab_CONFIG',
230
- groupTabs: liveConfig.groupTabs,
231
- devMode: liveConfig.devMode,
232
- }).catch(() => { });
233
- if (liveConfig.devMode) {
234
- sendToExtension(session, { type: 'Weavetab_STATE', hud: { action: 'Mission Running', detail: toolName } }).catch(() => { });
235
- }
236
- const chatMessages = [];
237
- try {
238
- const { result: hasMsg } = await session.Runtime.evaluate({
239
- expression: 'window.__wt_chat_queue&&window.__wt_chat_queue.length>0',
240
- returnByValue: true,
241
- });
242
- if (hasMsg?.value === true) {
243
- const { result: qResult } = await session.Runtime.evaluate({
244
- expression: `(function(){ const q=window.__wt_chat_queue||[];window.__wt_chat_queue=[];return JSON.stringify(q); })()`,
230
+ // Push config to extension once per session (idempotent, ignored if already set)
231
+ sendToExtension(session, {
232
+ type: 'Weavetab_CONFIG',
233
+ groupTabs: liveConfig.groupTabs,
234
+ devMode: liveConfig.devMode,
235
+ }).catch(() => { });
236
+ if (liveConfig.devMode) {
237
+ sendToExtension(session, { type: 'Weavetab_STATE', hud: { action: 'Mission Running', detail: toolName } }).catch(() => { });
238
+ }
239
+ try {
240
+ const { result: hasMsg } = await session.Runtime.evaluate({
241
+ expression: 'window.__wt_chat_queue&&window.__wt_chat_queue.length>0',
245
242
  returnByValue: true,
246
243
  });
247
- const raw = qResult?.value;
248
- if (raw && raw !== '[]') {
249
- chatMessages.push(...JSON.parse(raw));
250
- for (const msg of chatMessages) {
251
- logAction('chat_queue', toolName, msg);
244
+ if (hasMsg?.value === true) {
245
+ const { result: qResult } = await session.Runtime.evaluate({
246
+ expression: `(function(){ const q=window.__wt_chat_queue||[];window.__wt_chat_queue=[];return JSON.stringify(q); })()`,
247
+ returnByValue: true,
248
+ });
249
+ const raw = qResult?.value;
250
+ if (raw && raw !== '[]') {
251
+ chatMessages.push(...JSON.parse(raw));
252
+ for (const msg of chatMessages) {
253
+ logAction('chat_queue', toolName, msg);
254
+ }
252
255
  }
253
256
  }
254
257
  }
258
+ catch { /* non-fatal */ }
255
259
  }
256
- catch { /* non-fatal */ }
257
260
  // Emit a "started" info log so the user sees the tool immediately
258
261
  const argsPreview = '(executing…)';
259
262
  pushRichLog(liveConfig.agentId, toolName, ` ${toolName} ${argsPreview}`, '', 'info', !!liveConfig.devMode);
@@ -305,7 +308,9 @@ async function withSecurity(config, toolName, handler, isOfficial = false, targe
305
308
  details = String(result);
306
309
  }
307
310
  await pushRichLog(liveConfig.agentId, toolName, `${toolName} · ${ms}ms${tokenStr}`, details, 'success', true);
308
- await sendToExtension(session, { type: 'Weavetab_STATE', hud: { action: `[OK] ${toolName} (${ms}ms${tokenStr})` } });
311
+ if (session) {
312
+ await sendToExtension(session, { type: 'Weavetab_STATE', hud: { action: `[OK] ${toolName} (${ms}ms${tokenStr})` } });
313
+ }
309
314
  }
310
315
  // ── Image block output: return proper MCP image block for screenshot results ──
311
316
  // If the result contains a dataUrl (e.g. from browser_screenshot), extract the
@@ -321,14 +326,15 @@ async function withSecurity(config, toolName, handler, isOfficial = false, targe
321
326
  if (resultAny && typeof resultAny === 'object' && typeof resultAny.dataUrl === 'string' && resultAny.dataUrl.startsWith('data:')) {
322
327
  const dataUrl = resultAny.dataUrl;
323
328
  const mimeMatch = dataUrl.match(/^data:([^;]+);base64,/);
324
- const mimeType = mimeMatch ? mimeMatch[1] : 'image/png';
325
- const base64Data = dataUrl.replace(/^data:[^;]+;base64,/, '');
326
- // Return metadata (without the giant dataUrl) + native image block
329
+ const base64Data = mimeMatch ? dataUrl.slice(mimeMatch[0].length) : '';
330
+ const isB64 = base64Data.length > 0 && base64Data.length % 4 === 0 && /^[A-Za-z0-9+/]+={0,2}$/.test(base64Data);
327
331
  const { dataUrl: _omit, ...metaOnly } = resultAny;
328
332
  content = [
329
333
  { type: "text", text: JSON.stringify(metaOnly) },
330
- { type: "image", data: base64Data, mimeType },
331
334
  ];
335
+ if (mimeMatch && isB64) {
336
+ content.push({ type: "image", data: base64Data, mimeType: mimeMatch[1] });
337
+ }
332
338
  }
333
339
  else if (resultAny && typeof resultAny === 'object' && Array.isArray(resultAny.content)) {
334
340
  // Passthrough if already conforming McpToolResult (prevents double JSON wrapping)
@@ -406,7 +412,9 @@ async function withSecurity(config, toolName, handler, isOfficial = false, targe
406
412
  });
407
413
  if (liveConfig.devMode) {
408
414
  await pushRichLog(liveConfig.agentId, toolName, `${toolName} · FAILED · ${ms}ms`, errMsg, 'error', true);
409
- await sendToExtension(session, { type: 'Weavetab_STATE', hud: { action: `[ERR] ${toolName} Error` } });
415
+ if (session) {
416
+ await sendToExtension(session, { type: 'Weavetab_STATE', hud: { action: `[ERR] ${toolName} Error` } });
417
+ }
410
418
  }
411
419
  throw err;
412
420
  }
@@ -464,7 +472,9 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
464
472
  // ── 1. browser_map ─────────────────────────────────────────────────────────
465
473
  server.tool("browser_map", "Snapshot interactive DOM with volatile ref IDs that invalidate after DOM mutations. For complex SPAs (e.g. GitHub dashboards), use `scope` or `query` to reduce tokens. For forms/pagination/sequential loops, ALWAYS use `delta: true` to save 97% tokens. For canvas/WebGL/Figma pages where the DOM has < 5 interactive elements, use `visual: \"auto\"` to get a numbered screenshot — then use browser_click({ mark: N }) to interact.", {
466
474
  full: z.boolean().optional().describe("Set to true to return a full markdown table (heavier context) instead of the lite list."),
467
- scope: z.enum(["navigation", "main_content", "sidebar", "form", "modal", "utility"]).optional().describe("Restrict parsing to a semantic page area to save tokens. Useful on dense dashboards."),
475
+ lite: z.boolean().optional().describe("Return a compact, token-optimized interactive element list."),
476
+ limit: z.number().optional().describe("Maximum number of elements to return."),
477
+ scope: z.enum(["navigation", "main_content", "main", "sidebar", "form", "modal", "utility"]).optional().describe("Restrict parsing to a semantic page area to save tokens. Useful on dense dashboards. 'main' is an alias for 'main_content'."),
468
478
  query: z.string().optional().describe("Filter returned elements by label or role substring. Useful on dense dashboards."),
469
479
  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."),
470
480
  ax_tree: z.boolean().optional().describe("Bypass the extension entirely and read raw Chrome Accessibility Tree. Use this if Shadow DOMs hide elements."),
@@ -551,7 +561,7 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
551
561
  targetId = args.tab_id;
552
562
  }
553
563
  if (args.url) {
554
- const navResult = await agentAction(targetSession, 'navigating', () => weaveNavigate(targetSession, args.url, liveConfig, targetId, args.force), 'browser_navigate');
564
+ const navResult = await agentAction(targetSession, 'navigating', () => weaveNavigate(targetSession, args.url, liveConfig, targetId, args.force, false, resolvedApp), 'browser_navigate');
555
565
  await hookPipeline.dispatchPageNavigate(args.url, targetSession);
556
566
  return navResult;
557
567
  }
@@ -569,7 +579,7 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
569
579
  selector: z.string().optional().describe("CSS selector of the element to click."),
570
580
  target: z.string().optional().describe("CSS selector or query string. Alias for selector."),
571
581
  label: z.string().optional().describe("Visible text or aria-label to click."),
572
- intent: z.string().optional().describe("Semantic description of the action (e.g. 'submit login form'). WeaveTab checks learned memory first — if a high-confidence selector is found it clicks immediately without DOM resolution. Falls through to normal resolution on a miss; does not error."),
582
+ intent: z.string().optional().describe("Semantic description of the action (e.g. 'submit login form'). Weavetab checks learned memory first — if a high-confidence selector is found it clicks immediately without DOM resolution. Falls through to normal resolution on a miss; does not error."),
573
583
  mark: z.number().int().positive().optional().describe("Mark number from browser_map({ visual: true/\"auto\" }). Clicks the center of the numbered badge seen in the annotated screenshot. Zero DOM walk — instant coordinate dispatch. Use when the page is canvas/WebGL and DOM refs are unavailable."),
574
584
  backendNodeId: z.number().optional().describe("Raw CDP backendNodeId for direct hardware targeting."),
575
585
  x: z.number().optional().describe("Raw X coordinate. Use for precise positional clicks when element ID is not available."),
@@ -691,6 +701,9 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
691
701
  label: z.string().optional().describe("Element label to scroll into view."),
692
702
  direction: z.enum(["up", "down", "left", "right"]).describe("Scroll direction."),
693
703
  distance: z.number().optional().describe("Pixels to scroll (default 400)."),
704
+ pixels: z.number().optional().describe("Alias for distance."),
705
+ amount: z.number().optional().describe("Alias for distance."),
706
+ speed: z.enum(["human", "read", "fast"]).optional().describe("Scroll animation speed profile."),
694
707
  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."),
695
708
  app: z.string().optional().describe("Electron app name configured in config.json under 'electron'."),
696
709
  }, async (args) => {
@@ -709,6 +722,7 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
709
722
  normalizedArgs.id = args.selector;
710
723
  if (args.target && !normalizedArgs.id)
711
724
  normalizedArgs.id = args.target;
725
+ normalizedArgs.distance = args.distance ?? args.pixels ?? args.amount ?? 400;
712
726
  return await browserScroll(targetSession, normalizedArgs, liveConfig);
713
727
  }, false, args.app);
714
728
  }
@@ -777,19 +791,19 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
777
791
  return await WeavetabList(liveConfig, resolvedApp);
778
792
  if (action === "open") {
779
793
  if (!url)
780
- throw new Error("url is required for action: open");
794
+ return { success: false, error_code: "MISSING_PARAM", message: "url is required for action: open" };
781
795
  return await WeavetabOpen(url, liveConfig, reuse !== false, resolvedApp);
782
796
  }
783
797
  if (action === "switch") {
784
798
  if (!tabId)
785
- throw new Error("tabId is required for action: switch");
799
+ return { success: false, error_code: "MISSING_PARAM", message: "tabId is required for action: switch" };
786
800
  return await WeavetabSwitch(tabId, liveConfig, resolvedApp);
787
801
  }
788
802
  if (action === "close") {
789
803
  if (tabIds && tabIds.length > 0)
790
804
  return await WeavetabCloseBatch(tabIds, liveConfig, force, resolvedApp);
791
805
  if (!tabId)
792
- throw new Error("tabId or tabIds is required for action: close");
806
+ return { success: false, error_code: "MISSING_PARAM", message: "tabId or tabIds is required for action: close" };
793
807
  return await WeavetabClose(tabId, liveConfig, force, resolvedApp);
794
808
  }
795
809
  if (action === "list_windows") {
@@ -797,7 +811,7 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
797
811
  }
798
812
  if (action === "focus_window") {
799
813
  if (windowId === undefined)
800
- throw new Error("windowId is required for action: focus_window");
814
+ return { success: false, error_code: "MISSING_PARAM", message: "windowId is required for action: focus_window" };
801
815
  return await weaveFocusWindow(windowId, liveConfig, resolvedApp);
802
816
  }
803
817
  if (action === "consolidate") {
@@ -806,7 +820,7 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
806
820
  if (action === "heal") {
807
821
  return await WeavetabHeal(liveConfig, resolvedApp);
808
822
  }
809
- throw new Error("Invalid action");
823
+ return { success: false, error_code: "INVALID_ACTION", message: `Invalid action: ${action}` };
810
824
  }, false, resolvedApp);
811
825
  }
812
826
  catch (e) {
@@ -949,7 +963,10 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
949
963
  }
950
964
  });
951
965
  // ── 14. browser_select ───────────────────────────────────────────────────────
952
- server.tool("browser_select", "Select an option from a native dropdown.", {
966
+ server.tool("browser_select", "Select an option from a native dropdown or custom UI dropdown.", {
967
+ id: z.string().optional().describe("Element ref ID (e.g. 'w:42') or selector."),
968
+ ref: z.string().optional().describe("Alias for id."),
969
+ target: z.string().optional().describe("Alternative selector or ref ID."),
953
970
  nodeId: z.number().optional().describe("CDP backendNodeId."),
954
971
  selector: z.string().optional().describe("CSS Selector."),
955
972
  value: z.string().describe("The exact text value to select."),
@@ -957,7 +974,12 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
957
974
  }, async (args) => {
958
975
  try {
959
976
  return await withSecurity(config, "browser_select", async (session, liveConfig) => {
960
- return await browserSelect(session, args, liveConfig);
977
+ const normalized = { ...args };
978
+ if (args.ref && !normalized.id)
979
+ normalized.id = args.ref;
980
+ if (args.target && !normalized.id)
981
+ normalized.id = args.target;
982
+ return await browserSelect(session, normalized, liveConfig);
961
983
  }, false, args.app);
962
984
  }
963
985
  catch (e) {
@@ -1060,8 +1082,8 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
1060
1082
  // ── 23. browser_storage ─────────────────────────────────────────────────────
1061
1083
  server.tool("browser_storage", "Manage localStorage and sessionStorage.", {
1062
1084
  scope: z.enum(["local", "session"]).describe("Storage scope to manipulate."),
1063
- action: z.enum(["get", "set", "clear"]).describe("Action to perform on the storage."),
1064
- key: z.string().optional().describe("Key name (required for 'get' and 'set' actions)."),
1085
+ action: z.enum(["get", "set", "remove", "list", "clear"]).describe("'get'/'set' a key, 'remove' a single key, 'list' all keys with values, or 'clear' the scope."),
1086
+ key: z.string().optional().describe("Key name (required for 'get', 'set', and 'remove' actions)."),
1065
1087
  value: z.string().optional().describe("String value (required for 'set' action)."),
1066
1088
  app: z.string().optional().describe("Electron app name configured in config.json under 'electron'."),
1067
1089
  }, async (args) => {
@@ -1075,7 +1097,7 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
1075
1097
  }
1076
1098
  });
1077
1099
  // ── 24. browser_network_intercept ───────────────────────────────────────────
1078
- server.tool("browser_network_intercept", `Intercept/stub network responses (e.g. mock APIs, block tracking) OR retrieve detailed raw protocol network telemetry (TLS/SSL certificate info, microsecond timing breakdown, and OS-level failure reasons).`, {
1100
+ server.tool("browser_network_intercept", `Intercept/stub network responses (e.g. mock APIs) OR retrieve captured network telemetry: per-request TLS/SSL certificate details (when the connection exposes them), millisecond timing phases, and failure reasons.`, {
1079
1101
  action: z.enum(["enable", "disable", "get_telemetry", "clear_telemetry"]).describe("Action to perform: 'enable'/'disable' network response interception, 'get_telemetry' to read captured requests with TLS/SSL certs & timing breakdown, or 'clear_telemetry' to reset buffer."),
1080
1102
  pattern: z.string().optional().describe("URL glob pattern to intercept or filter telemetry (e.g. '*api/login*')."),
1081
1103
  mockStatus: z.number().optional().describe("HTTP status code to return when enabling mock (default 200)."),
@@ -1116,6 +1138,15 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
1116
1138
  landscape: z.boolean().optional().describe("Print in landscape orientation (default false)."),
1117
1139
  scale: z.number().optional().describe("Scale factor (default 1.0)."),
1118
1140
  pageRanges: z.string().optional().describe("Page ranges to print (e.g. '1-5'). Leave empty for all pages."),
1141
+ margin: z.union([
1142
+ z.number(),
1143
+ z.object({
1144
+ top: z.number().optional(),
1145
+ bottom: z.number().optional(),
1146
+ left: z.number().optional(),
1147
+ right: z.number().optional()
1148
+ })
1149
+ ]).optional().describe("Page margin in inches (default 0.4). Pass a single number (all sides) or an object {top,bottom,left,right}."),
1119
1150
  app: z.string().optional().describe("Electron app name configured in config.json under 'electron'."),
1120
1151
  }, async (args) => {
1121
1152
  try {
@@ -1149,6 +1180,10 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
1149
1180
  value: z.string().optional().describe("Cookie value (required for 'set')."),
1150
1181
  domain: z.string().optional().describe("Domain for the cookie."),
1151
1182
  path: z.string().optional().describe("Path for the cookie."),
1183
+ expires: z.number().optional().describe("Expiry as a Unix timestamp in seconds (omit for a session cookie)."),
1184
+ httpOnly: z.boolean().optional().describe("Set the HttpOnly flag (default false)."),
1185
+ secure: z.boolean().optional().describe("Set the Secure flag (auto-true for https pages)."),
1186
+ sameSite: z.enum(["Strict", "Lax", "None"]).optional().describe("SameSite attribute."),
1152
1187
  url: z.string().optional().describe("URL scope for 'get' action (defaults to current page)."),
1153
1188
  app: z.string().optional().describe("Electron app name configured in config.json under 'electron'."),
1154
1189
  }, async (args) => {
@@ -1202,6 +1237,7 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
1202
1237
  server.tool("browser_snapshot", "Capture or restore an MHTML snapshot. Best for offline archival, replay testing, and compliance audits.", {
1203
1238
  action: z.enum(["capture", "restore"]).describe("Action to perform."),
1204
1239
  filePath: z.string().describe("Absolute file path to save/load the .mhtml snapshot."),
1240
+ hideOverlay: z.boolean().optional().describe("If false, keeps the Weavetab HUD and overlay visible in snapshot. Default true."),
1205
1241
  app: z.string().optional().describe("Electron app name configured in config.json under 'electron'."),
1206
1242
  }, async (args) => {
1207
1243
  try {
@@ -1224,6 +1260,7 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
1224
1260
  width: z.number().describe("Width of clip region."),
1225
1261
  height: z.number().describe("Height of clip region.")
1226
1262
  }).optional().describe("Clip region. Use to capture only part of the page (e.g. just a chart area) to reduce token cost."),
1263
+ selector: z.string().optional().describe("CSS selector of an element to crop the capture to (its bounding rect)."),
1227
1264
  app: z.string().optional().describe("Electron app name configured in config.json under 'electron'."),
1228
1265
  }, async (args) => {
1229
1266
  try {
@@ -1236,12 +1273,20 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
1236
1273
  }
1237
1274
  });
1238
1275
  // ── 33. browser_viewport ────────────────────────────────────────────────────
1239
- server.tool("browser_viewport", "Set viewport dimensions and device emulation.", {
1240
- action: z.enum(["set", "clear"]).describe("Action to perform."),
1276
+ server.tool("browser_viewport", "Set viewport dimensions and device emulation (device presets, geolocation, locale, timezone, color scheme, touch, orientation).", {
1277
+ action: z.enum(["set", "clear", "list", "preset"]).describe("'set' viewport/emulation, 'clear' all overrides, 'list' available device presets, 'preset' apply a named device."),
1241
1278
  width: z.number().optional().describe("Viewport width in pixels."),
1242
1279
  height: z.number().optional().describe("Viewport height in pixels."),
1243
1280
  deviceScaleFactor: z.number().optional().describe("Device Pixel Ratio (default 1)."),
1244
1281
  mobile: z.boolean().optional().describe("Whether to emulate a mobile device (default false)."),
1282
+ devicePreset: z.string().optional().describe("Device preset name for action='preset' (see action='list')."),
1283
+ userAgent: z.string().optional().describe("Override the User Agent string."),
1284
+ geolocation: z.object({ latitude: z.number(), longitude: z.number(), accuracy: z.number().optional() }).optional().describe("Override geolocation coordinates."),
1285
+ locale: z.string().optional().describe("Override locale (e.g. 'en-US')."),
1286
+ timezoneId: z.string().optional().describe("Override timezone (e.g. 'America/New_York')."),
1287
+ colorScheme: z.enum(["light", "dark", "no-preference"]).optional().describe("Emulate prefers-color-scheme."),
1288
+ hasTouch: z.boolean().optional().describe("Whether to emulate touch support (defaults to the 'mobile' flag)."),
1289
+ screenOrientation: z.enum(["portrait", "landscape", "none"]).optional().describe("Emulated screen orientation."),
1245
1290
  app: z.string().optional().describe("Electron app name configured in config.json under 'electron'."),
1246
1291
  }, async (args) => {
1247
1292
  try {
@@ -1355,12 +1400,13 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
1355
1400
  }
1356
1401
  });
1357
1402
  // ── browser_performance ───────────────────────────────────────────────────
1358
- server.tool("browser_performance", "Retrieve page performance metrics.", {
1403
+ server.tool("browser_performance", "Retrieve page performance metrics: Core Web Vitals (FCP, LCP, CLS, INP, TTFB), JS heap memory, navigation timing, slowest resources, and raw CDP counters.", {
1404
+ metrics: z.array(z.enum(["vitals", "memory", "navigation", "resources", "raw"])).optional().describe("Sections to return. Defaults to all sections."),
1359
1405
  app: z.string().optional().describe("Electron app name configured in config.json under 'electron'."),
1360
1406
  }, async (args) => {
1361
1407
  try {
1362
1408
  return await withSecurity(config, "browser_performance", async (session, liveConfig) => {
1363
- const result = await browserPerformance(session, liveConfig);
1409
+ const result = await browserPerformance(session, liveConfig, args);
1364
1410
  return { content: [{ type: "text", text: JSON.stringify(result) }] };
1365
1411
  }, false, args.app);
1366
1412
  }
@@ -1444,6 +1490,9 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
1444
1490
  if (args.success === undefined)
1445
1491
  return { content: [{ type: "text", text: JSON.stringify({ error: "success (boolean) is required for action='record_execution'" }) }] };
1446
1492
  const updated = await recordPatternExecution(args.origin, args.name, args.success, args.duration_ms);
1493
+ if (!updated) {
1494
+ return { content: [{ type: "text", text: JSON.stringify({ ok: false, error_code: "PATTERN_NOT_FOUND", message: `No pattern named '${args.name}' exists for origin '${args.origin}'. Learn it first with action='learn'.` }) }] };
1495
+ }
1447
1496
  return { content: [{ type: "text", text: JSON.stringify({ ok: true, pattern: updated }) }] };
1448
1497
  }
1449
1498
  if (args.action === "record_quirk") {
@@ -1460,36 +1509,15 @@ export async function startServer(config, transportMode = "stdio", wsPort = 3000
1460
1509
  return wrapError(e);
1461
1510
  }
1462
1511
  });
1463
- // ── GitHub Tool (v2.5.0 Consolidated) ──────────────────────────────────
1464
- server.tool("browser_github", "Interact with GitHub repositories without burning browser turns. Analyze repo trees, read raw source files, search/inspect issues, and examine PR diffs and review comments.", {
1465
- action: z.enum(["analyze", "read", "issues", "pr"]).describe("GitHub operation: 'analyze' (repo structure & README), 'read' (raw file contents), 'issues' (list/search/inspect issues), 'pr' (PR details, diff files & comments)."),
1466
- repo: z.string().optional().describe("Optional 'owner/repo' slug or GitHub URL. If omitted, uses active tab's repository."),
1467
- paths: z.array(z.string()).optional().describe("For action: 'read'. File paths relative to repo root."),
1468
- line_range: z.array(z.number()).length(2).optional().describe("For action: 'read'. Optional [start, end] line range (1-indexed)."),
1469
- number: z.number().optional().describe("For action: 'issues' or 'pr'. Issue or PR number to fetch details and comments."),
1470
- search: z.string().optional().describe("For action: 'issues'. Search query to find issues."),
1471
- labels: z.array(z.string()).optional().describe("For action: 'issues'. Filter issues by labels."),
1472
- state: z.enum(["open", "closed", "all"]).optional().describe("For action: 'issues'. Filter issues by state."),
1473
- limit: z.number().optional().describe("For action: 'issues'. Max number of issues to list (default 5).")
1474
- }, async (args) => {
1475
- try {
1476
- return await withSecurity(config, "browser_github", async (session, liveConfig) => {
1477
- return await browserGithub(session, args, liveConfig);
1478
- });
1479
- }
1480
- catch (e) {
1481
- return wrapError(e);
1482
- }
1483
- });
1484
1512
  // ── 43. browser_office ──────────────────────────────────────────────────────
1485
- server.tool("browser_office", `Read and extract structured content from office documents (Excel, Word, PDF, CSV). Note: PDF/Word extraction requires local binary dependencies (e.g., poppler) and output is truncated by max_chars/max_rows limits. Two modes:
1486
- - **url** — fetch a document URL using the browser's live session cookies (works for authenticated downloads, SharePoint, Google Drive exports, etc). Automatically detects format from URL or MIME type.
1487
- - **file** — parse a file that was already downloaded to the local downloads directory (bare filename only, e.g. 'report.xlsx').
1488
-
1489
- Supported formats: .xlsx/.xls (returns per-sheet JSON tables), .docx (returns extracted text), .pdf (returns page text + metadata), .csv (returns row objects).
1490
-
1491
- Use 'list' action first if you don't know what files are in the downloads directory.
1492
-
1513
+ server.tool("browser_office", `Read and extract structured content from office documents (Excel, Word, PDF, CSV). Note: PDF/Word extraction requires local binary dependencies (e.g., poppler) and output is truncated by max_chars/max_rows limits. Two modes:
1514
+ - **url** — fetch a document URL using the browser's live session cookies (works for authenticated downloads, SharePoint, Google Drive exports, etc). Automatically detects format from URL or MIME type.
1515
+ - **file** — parse a file that was already downloaded to the local downloads directory (bare filename only, e.g. 'report.xlsx').
1516
+
1517
+ Supported formats: .xlsx/.xls (returns per-sheet JSON tables), .docx (returns extracted text), .pdf (returns page text + metadata), .csv (returns row objects).
1518
+
1519
+ Use 'list' action first if you don't know what files are in the downloads directory.
1520
+
1493
1521
  DO NOT use browser_scrape or browser_map for binary documents — they cannot read inside Excel, Word, or PDF files.`, {
1494
1522
  action: z.enum(["read", "list"]).describe("'read' — parse a document and return its content. 'list' — show all files in the downloads directory."),
1495
1523
  url: z.string().optional().describe("URL of the document to fetch and parse in-browser (using active session cookies). Supports .pdf, .xlsx, .xls, .docx, .csv. Use for authenticated downloads, SharePoint links, Google Drive exports."),
@@ -1542,6 +1570,7 @@ DO NOT use browser_scrape or browser_map for binary documents — they cannot re
1542
1570
  accuracy: z.number().optional(),
1543
1571
  }).optional().describe("Override geolocation coordinates"),
1544
1572
  timezoneId: z.string().optional().describe("Override browser timezone (e.g. 'America/New_York')"),
1573
+ colorScheme: z.enum(["light", "dark", "no-preference"]).optional().describe("Emulate prefers-color-scheme (dark/light mode)."),
1545
1574
  userAgent: z.string().optional().describe("Override User Agent string"),
1546
1575
  app: z.string().optional().describe("Electron app name configured in config.json under 'electron'."),
1547
1576
  }, async (args) => {
@@ -1579,7 +1608,7 @@ DO NOT use browser_scrape or browser_map for binary documents — they cannot re
1579
1608
  return {
1580
1609
  messages: [{
1581
1610
  role: "user",
1582
- content: { type: "text", text: "No active WeaveTab session found. Open a browser tab and navigate to a page first." }
1611
+ content: { type: "text", text: "No active Weavetab session found. Open a browser tab and navigate to a page first." }
1583
1612
  }]
1584
1613
  };
1585
1614
  }
@@ -1588,22 +1617,22 @@ DO NOT use browser_scrape or browser_map for binary documents — they cannot re
1588
1617
  let pageContextStr = "";
1589
1618
  try {
1590
1619
  const { result: metrics } = await session.Runtime.evaluate({
1591
- expression: `
1592
- new Promise(resolve => {
1593
- const getMetrics = () => resolve(JSON.stringify({
1594
- readyState: document.readyState,
1595
- w: window.innerWidth,
1596
- h: window.innerHeight,
1597
- scrollX: Math.round(window.scrollX),
1598
- scrollY: Math.round(window.scrollY)
1599
- }));
1600
- if (document.readyState === 'complete') {
1601
- getMetrics();
1602
- } else {
1603
- setTimeout(getMetrics, 1500); // Max wait if slow
1604
- window.addEventListener('load', getMetrics);
1605
- }
1606
- })
1620
+ expression: `
1621
+ new Promise(resolve => {
1622
+ const getMetrics = () => resolve(JSON.stringify({
1623
+ readyState: document.readyState,
1624
+ w: window.innerWidth,
1625
+ h: window.innerHeight,
1626
+ scrollX: Math.round(window.scrollX),
1627
+ scrollY: Math.round(window.scrollY)
1628
+ }));
1629
+ if (document.readyState === 'complete') {
1630
+ getMetrics();
1631
+ } else {
1632
+ setTimeout(getMetrics, 1500); // Max wait if slow
1633
+ window.addEventListener('load', getMetrics);
1634
+ }
1635
+ })
1607
1636
  `,
1608
1637
  awaitPromise: true,
1609
1638
  returnByValue: true
@@ -1650,7 +1679,7 @@ DO NOT use browser_scrape or browser_map for binary documents — they cannot re
1650
1679
  catch { /* non-fatal */ }
1651
1680
  // 4. Assemble the prompt text
1652
1681
  const promptText = [
1653
- `# 🌐 WeaveTab Context: ${title ?? "(unknown)"}`,
1682
+ `# 🌐 Weavetab Context: ${title ?? "(unknown)"}`,
1654
1683
  `**URL:** \`${url ?? "(unknown)"}\``,
1655
1684
  pageContextStr,
1656
1685
  frameworkHint,
@@ -1729,11 +1758,11 @@ DO NOT use browser_scrape or browser_map for binary documents — they cannot re
1729
1758
  }
1730
1759
  const existingTool = server._registeredTools?.[ov.name];
1731
1760
  const existingDesc = existingTool?.description || "";
1732
- const existingSchema = existingTool?.parameters?.shape || {};
1761
+ const existingSchema = existingTool?.inputSchema?.shape || existingTool?.parameters?.shape || {};
1733
1762
  if (existingTool) {
1734
1763
  delete server._registeredTools[ov.name];
1735
1764
  }
1736
- server.tool(ov.name, ov.description || existingDesc || `Tool overridden by ${plugin.name}`, ov.schema || existingSchema, async (args) => {
1765
+ server.tool(ov.name, ov.description || existingDesc || `Tool overridden by ${plugin.name}`, ov.schema ? toPluginInputShape(ov.schema) : existingSchema, async (args) => {
1737
1766
  try {
1738
1767
  return await withSecurity(config, ov.name, async (session, liveConfig) => {
1739
1768
  const wrappedOrig = existingTool
@@ -1766,12 +1795,12 @@ DO NOT use browser_scrape or browser_map for binary documents — they cannot re
1766
1795
  process.stderr.write(` \x1b[33m[!] Plugin ${plugin.name} registered tool "${td.name}" which replaced an existing tool.\x1b[0m\n`);
1767
1796
  delete server._registeredTools[td.name];
1768
1797
  }
1769
- server.tool(td.name, td.description, td.schema || td.parameters || {}, async (args) => {
1798
+ server.tool(td.name, td.description, toPluginInputShape(td.schema || td.parameters), async (args) => {
1770
1799
  try {
1771
1800
  return await withSecurity(config, td.name, async (session, liveConfig) => {
1772
1801
  const result = await td.handler(args, session, liveConfig);
1773
1802
  return result;
1774
- }, isOfficial);
1803
+ }, isOfficial, undefined, td.requiresSession !== false);
1775
1804
  }
1776
1805
  catch (e) {
1777
1806
  return wrapError(e);
@@ -1791,11 +1820,11 @@ DO NOT use browser_scrape or browser_map for binary documents — they cannot re
1791
1820
  process.stderr.write(` \x1b[33m[!] Plugin ${plugin.name} dynamic tool "${tool.name}" replaced an existing tool.\x1b[0m\n`);
1792
1821
  delete server._registeredTools[tool.name];
1793
1822
  }
1794
- server.tool(tool.name, tool.description || "", tool.schema || tool.parameters || {}, async (args) => {
1823
+ server.tool(tool.name, tool.description || "", toPluginInputShape(tool.schema || tool.parameters), async (args) => {
1795
1824
  return await withSecurity(config, tool.name, async (session, liveConfig) => {
1796
1825
  const result = await tool.handler(args, session, liveConfig);
1797
1826
  return result;
1798
- }, isOfficial);
1827
+ }, isOfficial, undefined, tool.requiresSession !== false);
1799
1828
  });
1800
1829
  };
1801
1830
  fullPluginCtx.mcp.overrideTool = (toolName, overrideHandler) => {