@browserbridge/bbx 1.8.0 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/PRIVACY.md +29 -3
  2. package/README.md +10 -3
  3. package/SECURITY.md +12 -0
  4. package/package.json +7 -1
  5. package/packages/agent-client/src/cli-args.js +70 -6
  6. package/packages/agent-client/src/cli-batch.js +127 -59
  7. package/packages/agent-client/src/cli-helpers.js +8 -0
  8. package/packages/agent-client/src/cli-output.js +8 -1
  9. package/packages/agent-client/src/cli-setup-commands.js +3 -1
  10. package/packages/agent-client/src/cli.js +254 -26
  11. package/packages/agent-client/src/client.js +156 -3
  12. package/packages/agent-client/src/command-registry.js +14 -4
  13. package/packages/agent-client/src/config.js +195 -0
  14. package/packages/agent-client/src/mcp-config.js +8 -8
  15. package/packages/agent-client/src/npm-self-update.js +305 -0
  16. package/packages/agent-client/src/remotes.js +4 -2
  17. package/packages/agent-client/src/runtime.js +99 -7
  18. package/packages/agent-client/src/types.ts +38 -0
  19. package/packages/mcp-server/src/guidance.js +37 -6
  20. package/packages/mcp-server/src/handlers-capture.js +29 -2
  21. package/packages/mcp-server/src/handlers-dom.js +54 -3
  22. package/packages/mcp-server/src/handlers-page.js +333 -60
  23. package/packages/mcp-server/src/handlers-utils.js +141 -151
  24. package/packages/mcp-server/src/handlers.js +17 -4
  25. package/packages/mcp-server/src/server.js +269 -51
  26. package/packages/mcp-server/src/toolset.js +48 -0
  27. package/packages/native-host/src/artifact-store.js +353 -0
  28. package/packages/native-host/src/config.js +9 -1
  29. package/packages/native-host/src/daemon-logger.js +3 -1
  30. package/packages/native-host/src/daemon.js +457 -27
  31. package/packages/native-host/src/dom-baseline-owners.js +210 -0
  32. package/packages/native-host/src/native-host.js +38 -29
  33. package/packages/protocol/src/budget-presets.js +182 -0
  34. package/packages/protocol/src/capability-values.js +2 -0
  35. package/packages/protocol/src/defaults.js +28 -0
  36. package/packages/protocol/src/errors.js +48 -5
  37. package/packages/protocol/src/har.js +34 -0
  38. package/packages/protocol/src/incidental-sanitizer.js +197 -0
  39. package/packages/protocol/src/index.js +4 -0
  40. package/packages/protocol/src/json-lines.js +3 -2
  41. package/packages/protocol/src/protocol.js +316 -6
  42. package/packages/protocol/src/recovery-telemetry.js +302 -0
  43. package/packages/protocol/src/registry.js +226 -6
  44. package/packages/protocol/src/summary.js +116 -25
  45. package/packages/protocol/src/types.ts +490 -4
  46. package/skills/browser-bridge/SKILL.md +26 -8
  47. package/skills/browser-bridge/references/protocol.md +125 -15
  48. package/skills/browser-bridge/references/token-efficiency.md +17 -7
@@ -11,14 +11,17 @@ import * as z from 'zod/v4';
11
11
 
12
12
  import {
13
13
  handleAccessTool,
14
+ handleArtifactTool,
14
15
  handleCaptureTool,
15
16
  handleBatchTool,
16
17
  handleDomTool,
17
18
  handleHealthTool,
18
19
  handleInputTool,
20
+ handleInterceptTool,
19
21
  handleLogTool,
20
22
  handleNavigationTool,
21
23
  handlePageTool,
24
+ handleSensitiveReadTool,
22
25
  handlePatchTool,
23
26
  handleRawCallTool,
24
27
  handleSetupTool,
@@ -27,12 +30,15 @@ import {
27
30
  handleStylesLayoutTool,
28
31
  handleTabsTool,
29
32
  handleInvestigateTool,
33
+ createToolResult,
30
34
  MAX_BATCH_CALLS,
31
35
  } from './handlers.js';
32
36
  import {
37
+ ARTIFACT_CHUNK_BYTES,
33
38
  BUDGET_PRESETS,
34
39
  DEFAULT_CONSOLE_LIMIT,
35
40
  DEFAULT_EVAL_TIMEOUT_MS,
41
+ DEFAULT_HAR_LIMIT,
36
42
  DEFAULT_LOG_TAIL_LIMIT,
37
43
  DEFAULT_MAX_DEPTH,
38
44
  DEFAULT_MAX_HTML_LENGTH,
@@ -41,10 +47,12 @@ import {
41
47
  DEFAULT_TEXT_BUDGET,
42
48
  DEFAULT_NAV_TIMEOUT_MS,
43
49
  DEFAULT_WAIT_TIMEOUT_MS,
50
+ MAX_HAR_ENTRIES,
44
51
  getMethodsByMaxComplexity,
45
52
  } from '../../protocol/src/index.js';
46
53
  import { applyWindowsTcpTransportDefaults } from '../../native-host/src/config.js';
47
- import { MCP_SERVER_INSTRUCTIONS } from './guidance.js';
54
+ import { getMcpServerInstructions } from './guidance.js';
55
+ import { isInitiallyEnabledTool, LOADABLE_TOOLSET_TOOLS } from './toolset.js';
48
56
 
49
57
  export const BUDGET_PRESET_DESCRIPTION = `Budget preset: "quick", "normal", or "deep" (defaults: query ${BUDGET_PRESETS.normal.maxNodes} nodes / depth ${BUDGET_PRESETS.normal.maxDepth} / text ${BUDGET_PRESETS.normal.textBudget}). Numeric fields override the preset when both are provided.`;
50
58
  export const TAB_ID_DESCRIPTION =
@@ -78,24 +86,52 @@ const INVESTIGATE_SUBAGENT_BRIDGE_METHODS = Object.freeze(
78
86
  )
79
87
  );
80
88
 
81
- const INVESTIGATE_DELEGATION_HINT = Object.freeze({
82
- recommended: true,
83
- costTier: 'low',
84
- preferredAgentProfile: {
85
- modelClass: 'small',
86
- reasoningEffort: 'low',
87
- },
88
- preferredTools: ['browser_dom', 'browser_page', 'browser_styles_layout', 'browser_batch'],
89
- escalationTools: ['browser_capture'],
90
- preferredBridgeMethods: INVESTIGATE_SUBAGENT_BRIDGE_METHODS,
91
- escalationTriggers: [
92
- 'Structured DOM or page reads are insufficient.',
93
- 'Visual confirmation is required.',
94
- 'Debugger-backed evidence is required.',
95
- ],
96
- });
89
+ const INVESTIGATE_PREFERRED_TOOLS = Object.freeze(['browser_call', 'browser_batch']);
97
90
 
98
91
  /**
92
+ * Build the subagent delegation hint for browser_investigate using tools that
93
+ * are available before any on-demand expansion.
94
+ */
95
+ function createInvestigateDelegationHint() {
96
+ return Object.freeze({
97
+ recommended: true,
98
+ costTier: 'low',
99
+ preferredAgentProfile: {
100
+ modelClass: 'small',
101
+ reasoningEffort: 'low',
102
+ },
103
+ preferredTools: INVESTIGATE_PREFERRED_TOOLS,
104
+ escalationTools: Object.freeze(['browser_call']),
105
+ preferredBridgeMethods: INVESTIGATE_SUBAGENT_BRIDGE_METHODS,
106
+ escalationTriggers: [
107
+ 'Structured DOM or page reads are insufficient.',
108
+ 'Visual confirmation is required.',
109
+ 'Debugger-backed evidence is required.',
110
+ ],
111
+ });
112
+ }
113
+
114
+ /**
115
+ * Build the browser_investigate description for the progressive tool surface.
116
+ *
117
+ * @returns {string}
118
+ */
119
+ function getInvestigateToolDescription() {
120
+ return (
121
+ 'Investigate a page to answer a question or verify a condition. ' +
122
+ 'Pass a natural-language objective and an optional scope (quick/normal/deep). ' +
123
+ 'DELEGATION HINT: Prefer delegating this to a smaller, low-cost subagent ' +
124
+ 'that starts with structured reads via browser_call and browser_batch. ' +
125
+ 'Escalate through browser_call only ' +
126
+ 'when structured reads are insufficient. ' +
127
+ 'If subagent delegation is not available, a deterministic heuristic fallback ' +
128
+ 'runs a scripted inspection sequence and returns a best-effort summary.'
129
+ );
130
+ }
131
+
132
+ /**
133
+ * Create the MCP server with a compact initial surface and on-demand typed tools.
134
+ *
99
135
  * @returns {McpServer}
100
136
  */
101
137
  export function createBridgeMcpServer() {
@@ -105,11 +141,33 @@ export function createBridgeMcpServer() {
105
141
  version: MCP_SERVER_VERSION,
106
142
  },
107
143
  {
108
- instructions: MCP_SERVER_INSTRUCTIONS,
144
+ instructions: getMcpServerInstructions(),
109
145
  }
110
146
  );
111
147
 
112
- server.registerTool(
148
+ /** @type {Map<string, ReturnType<typeof server.registerTool>>} */
149
+ const registrations = new Map();
150
+
151
+ /**
152
+ * Register a tool, then hide it when it is not part of the initial surface.
153
+ *
154
+ * Registering first keeps every call site checked against the SDK's own
155
+ * generic signature instead of a hand-written passthrough type. Disabling
156
+ * happens before `connect()`, so an excluded tool never reaches `tools/list`
157
+ * but can be enabled later by browser_toolset.
158
+ *
159
+ * @type {typeof server.registerTool}
160
+ */
161
+ const registerTool = (name, config, handler) => {
162
+ const registration = server.registerTool(name, config, handler);
163
+ registrations.set(name, registration);
164
+ if (!isInitiallyEnabledTool(name)) {
165
+ registration.disable();
166
+ }
167
+ return registration;
168
+ };
169
+
170
+ registerTool(
113
171
  'browser_status',
114
172
  {
115
173
  title: 'Browser Bridge Status',
@@ -125,7 +183,7 @@ export function createBridgeMcpServer() {
125
183
  handleStatusTool
126
184
  );
127
185
 
128
- server.registerTool(
186
+ registerTool(
129
187
  'browser_setup',
130
188
  {
131
189
  title: 'Browser Bridge Setup Status',
@@ -140,7 +198,7 @@ export function createBridgeMcpServer() {
140
198
  handleSetupTool
141
199
  );
142
200
 
143
- server.registerTool(
201
+ registerTool(
144
202
  'browser_logs',
145
203
  {
146
204
  title: 'Browser Bridge Logs',
@@ -162,7 +220,7 @@ export function createBridgeMcpServer() {
162
220
  handleLogTool
163
221
  );
164
222
 
165
- server.registerTool(
223
+ registerTool(
166
224
  'browser_health',
167
225
  {
168
226
  title: 'Browser Bridge Health',
@@ -178,7 +236,7 @@ export function createBridgeMcpServer() {
178
236
  handleHealthTool
179
237
  );
180
238
 
181
- server.registerTool(
239
+ registerTool(
182
240
  'browser_tabs',
183
241
  {
184
242
  title: 'Browser Tabs',
@@ -205,7 +263,7 @@ export function createBridgeMcpServer() {
205
263
  handleTabsTool
206
264
  );
207
265
 
208
- server.registerTool(
266
+ registerTool(
209
267
  'browser_dom',
210
268
  {
211
269
  title: 'Browser DOM',
@@ -215,6 +273,10 @@ export function createBridgeMcpServer() {
215
273
  action: z
216
274
  .enum([
217
275
  'query',
276
+ 'baseline_create',
277
+ 'baseline_compare',
278
+ 'baseline_describe',
279
+ 'baseline_release',
218
280
  'describe',
219
281
  'text',
220
282
  'attributes',
@@ -237,6 +299,14 @@ export function createBridgeMcpServer() {
237
299
  .describe(
238
300
  'CSS selector (used if no elementRef; accessibility_tree requires exactly one match)'
239
301
  ),
302
+ baselineId: z.string().optional().describe('Opaque semantic DOM baseline identifier'),
303
+ maxChanges: z
304
+ .number()
305
+ .int()
306
+ .positive()
307
+ .max(200)
308
+ .optional()
309
+ .describe('Maximum returned change examples; exact counts are always retained'),
240
310
  elementRef: z
241
311
  .string()
242
312
  .optional()
@@ -322,7 +392,7 @@ export function createBridgeMcpServer() {
322
392
  handleDomTool
323
393
  );
324
394
 
325
- server.registerTool(
395
+ registerTool(
326
396
  'browser_styles_layout',
327
397
  {
328
398
  title: 'Browser Styles And Layout',
@@ -361,12 +431,31 @@ export function createBridgeMcpServer() {
361
431
  handleStylesLayoutTool
362
432
  );
363
433
 
364
- server.registerTool(
434
+ registerTool(
435
+ 'browser_sensitive_read',
436
+ {
437
+ title: 'Sensitive Browser Read',
438
+ description:
439
+ 'High-risk exact-value read for one local or session storage key. This operation is logged as Sensitive access, is never batched or retried, and returns the whole value or fails atomically.',
440
+ inputSchema: {
441
+ source: z.enum(['local_storage', 'session_storage']).describe('Exact storage source'),
442
+ key: z
443
+ .string()
444
+ .max(2_048)
445
+ .describe('Exact storage key, including an empty key if intended'),
446
+ tabId: z.number().int().positive().optional().describe(TAB_ID_DESCRIPTION),
447
+ destinationId: z.string().optional().describe(DESTINATION_ID_DESCRIPTION),
448
+ },
449
+ },
450
+ handleSensitiveReadTool
451
+ );
452
+
453
+ registerTool(
365
454
  'browser_page',
366
455
  {
367
456
  title: 'Browser Page State',
368
457
  description:
369
- 'Read page-level data, wait for load/URL conditions, or explicitly inspect/handle JavaScript dialogs. For element-level reads, use browser_dom. evaluate, performance, handle_dialog, and source=cdp network capture are debugger-backed.',
458
+ 'Read page-level data, export captured network evidence as HAR 1.2, wait for load/URL conditions, or explicitly inspect/handle JavaScript dialogs. The performance action returns a raw CDP Performance.getMetrics point sample: Chrome defines names and units, which vary, while BBX sets no navigation observation window and does not measure LCP, CLS, or INP. For element-level reads, use browser_dom. evaluate, performance, handle_dialog, and source=cdp network capture are debugger-backed.',
370
459
  inputSchema: {
371
460
  action: z
372
461
  .enum([
@@ -379,9 +468,12 @@ export function createBridgeMcpServer() {
379
468
  'text',
380
469
  'extract_content',
381
470
  'network',
471
+ 'har',
382
472
  'performance',
383
473
  ])
384
- .describe('Page operation to perform'),
474
+ .describe(
475
+ 'Page operation to perform; performance reads raw Chrome/CDP counters, not LCP, CLS, or INP'
476
+ ),
385
477
  tabId: z.number().int().positive().optional().describe(TAB_ID_DESCRIPTION),
386
478
  destinationId: z.string().optional().describe(DESTINATION_ID_DESCRIPTION),
387
479
  budgetPreset: z
@@ -434,7 +526,9 @@ export function createBridgeMcpServer() {
434
526
  .int()
435
527
  .positive()
436
528
  .optional()
437
- .describe(`Maximum entries to return (default: ${DEFAULT_CONSOLE_LIMIT})`),
529
+ .describe(
530
+ `Maximum entries to return (console default: ${DEFAULT_CONSOLE_LIMIT}; HAR default: ${DEFAULT_HAR_LIMIT}, max: ${MAX_HAR_ENTRIES})`
531
+ ),
438
532
  type: z
439
533
  .enum(['local', 'session'])
440
534
  .optional()
@@ -468,7 +562,11 @@ export function createBridgeMcpServer() {
468
562
  .positive()
469
563
  .optional()
470
564
  .describe('Maximum semantic extraction settlement wait (default: 2000)'),
471
- urlPattern: z.string().optional().describe('Filter network entries by URL pattern'),
565
+ urlPattern: z
566
+ .string()
567
+ .max(2_048)
568
+ .optional()
569
+ .describe('Bounded URL pattern used to filter network entries or HAR export evidence'),
472
570
  source: z
473
571
  .enum(['fetch-xhr', 'cdp'])
474
572
  .optional()
@@ -477,6 +575,10 @@ export function createBridgeMcpServer() {
477
575
  .enum(['read', 'start', 'clear', 'stop'])
478
576
  .optional()
479
577
  .describe('Explicit CDP capture lifecycle action (default: read)'),
578
+ delivery: z
579
+ .enum(['auto', 'inline', 'artifact'])
580
+ .optional()
581
+ .describe('HAR delivery mode: auto chooses inline or a daemon-owned artifact'),
480
582
  waitForLoad: z
481
583
  .boolean()
482
584
  .optional()
@@ -491,7 +593,7 @@ export function createBridgeMcpServer() {
491
593
  handlePageTool
492
594
  );
493
595
 
494
- server.registerTool(
596
+ registerTool(
495
597
  'browser_navigation',
496
598
  {
497
599
  title: 'Browser Navigation',
@@ -535,7 +637,7 @@ export function createBridgeMcpServer() {
535
637
  handleNavigationTool
536
638
  );
537
639
 
538
- server.registerTool(
640
+ registerTool(
539
641
  'browser_input',
540
642
  {
541
643
  title: 'Browser Input',
@@ -626,7 +728,7 @@ export function createBridgeMcpServer() {
626
728
  .int()
627
729
  .nonnegative()
628
730
  .optional()
629
- .describe('Hover duration in ms (default: 0)'),
731
+ .describe('Delay after hover dispatch in ms; does not end hover (default: 0)'),
630
732
  sourceElementRef: z.string().optional().describe('Drag source element (for drag action)'),
631
733
  sourceSelector: z
632
734
  .string()
@@ -647,7 +749,7 @@ export function createBridgeMcpServer() {
647
749
  handleInputTool
648
750
  );
649
751
 
650
- server.registerTool(
752
+ registerTool(
651
753
  'browser_patch',
652
754
  {
653
755
  title: 'Browser Patch',
@@ -674,7 +776,14 @@ export function createBridgeMcpServer() {
674
776
  .describe('CSS property: value pairs (for apply_styles)'),
675
777
  important: z.boolean().optional().describe('Add !important flag (default: false)'),
676
778
  operation: z
677
- .enum(['setAttribute', 'removeAttribute', 'addClass', 'removeClass', 'setTextContent'])
779
+ .enum([
780
+ 'setAttribute',
781
+ 'removeAttribute',
782
+ 'addClass',
783
+ 'removeClass',
784
+ 'toggleClass',
785
+ 'setTextContent',
786
+ ])
678
787
  .optional()
679
788
  .describe('DOM mutation type'),
680
789
  value: z.unknown().optional().describe('Value for the DOM operation'),
@@ -691,7 +800,7 @@ export function createBridgeMcpServer() {
691
800
  handlePatchTool
692
801
  );
693
802
 
694
- server.registerTool(
803
+ registerTool(
695
804
  'browser_capture',
696
805
  {
697
806
  title: 'Browser Capture',
@@ -728,6 +837,11 @@ export function createBridgeMcpServer() {
728
837
  .positive()
729
838
  .optional()
730
839
  .describe('CDP node id for cdp_box_model/cdp_computed_styles'),
840
+ computedStyles: z
841
+ .array(z.string().min(1).max(128))
842
+ .max(100)
843
+ .optional()
844
+ .describe('CSS property names to include for cdp_dom_snapshot'),
731
845
  rect: z
732
846
  .object({
733
847
  x: z.number().nonnegative().describe('Region left edge (viewport pixels)'),
@@ -748,12 +862,101 @@ export function createBridgeMcpServer() {
748
862
  .max(100)
749
863
  .optional()
750
864
  .describe('JPEG/WebP quality from 0 to 100; ignored for PNG'),
865
+ delivery: z
866
+ .enum(['auto', 'inline', 'artifact'])
867
+ .optional()
868
+ .describe('Delivery mode: auto chooses inline or a daemon-owned artifact'),
869
+ scale: z
870
+ .number()
871
+ .min(0.1)
872
+ .max(4)
873
+ .optional()
874
+ .describe('Screenshot capture scale (0.1 to 4)'),
751
875
  },
752
876
  },
753
877
  handleCaptureTool
754
878
  );
755
879
 
756
- server.registerTool(
880
+ registerTool(
881
+ 'browser_artifact',
882
+ {
883
+ title: 'Browser Artifact',
884
+ description:
885
+ 'Read (chunked base64) or delete a daemon-owned artifact produced by artifact delivery (screenshots, HAR exports). Artifacts are scoped to this MCP session and expire quickly. Page reads with offset until nextOffset is null, then verify the reassembled bytes against sha256.',
886
+ inputSchema: {
887
+ action: z.enum(['read', 'delete']).describe('Artifact operation to perform'),
888
+ artifactId: z
889
+ .string()
890
+ .min(1)
891
+ .describe('Artifact identifier returned by a capture or HAR export'),
892
+ offset: z
893
+ .number()
894
+ .int()
895
+ .nonnegative()
896
+ .optional()
897
+ .describe('Byte offset to read from (default: 0)'),
898
+ limit: z
899
+ .number()
900
+ .int()
901
+ .positive()
902
+ .max(ARTIFACT_CHUNK_BYTES)
903
+ .optional()
904
+ .describe(`Maximum bytes to read in this chunk (default/max: ${ARTIFACT_CHUNK_BYTES})`),
905
+ destinationId: z.string().optional().describe(DESTINATION_ID_DESCRIPTION),
906
+ budgetPreset: z
907
+ .enum(['quick', 'normal', 'deep'])
908
+ .optional()
909
+ .describe(BUDGET_PRESET_DESCRIPTION),
910
+ },
911
+ },
912
+ handleArtifactTool
913
+ );
914
+
915
+ registerTool(
916
+ 'browser_intercept',
917
+ {
918
+ title: 'Browser Network Intercept',
919
+ description:
920
+ 'Manage CDP Fetch request interception rules: add (block, fulfill, or continue matching requests), list, remove, or clear. Debugger-backed; rules persist until removed or cleared.',
921
+ inputSchema: {
922
+ action: z
923
+ .enum(['add', 'list', 'remove', 'clear'])
924
+ .describe('Intercept operation to perform'),
925
+ tabId: z.number().int().positive().optional().describe(TAB_ID_DESCRIPTION),
926
+ destinationId: z.string().optional().describe(DESTINATION_ID_DESCRIPTION),
927
+ budgetPreset: z
928
+ .enum(['quick', 'normal', 'deep'])
929
+ .optional()
930
+ .describe(BUDGET_PRESET_DESCRIPTION),
931
+ urlPattern: z
932
+ .string()
933
+ .min(1)
934
+ .max(2_048)
935
+ .optional()
936
+ .describe('URL substring to match (required for add)'),
937
+ ruleAction: z
938
+ .enum(['block', 'fulfill', 'continue'])
939
+ .optional()
940
+ .describe('How to handle matched requests (default: continue)'),
941
+ statusCode: z
942
+ .number()
943
+ .int()
944
+ .min(100)
945
+ .max(599)
946
+ .optional()
947
+ .describe('HTTP status for fulfill responses'),
948
+ body: z.string().optional().describe('Response body for fulfill'),
949
+ headers: z
950
+ .record(z.string(), z.string())
951
+ .optional()
952
+ .describe('Response headers for fulfill'),
953
+ ruleId: z.string().min(1).optional().describe('Rule ID (required for remove)'),
954
+ },
955
+ },
956
+ handleInterceptTool
957
+ );
958
+
959
+ registerTool(
757
960
  'browser_batch',
758
961
  {
759
962
  title: 'Browser Bridge Batch',
@@ -790,7 +993,7 @@ export function createBridgeMcpServer() {
790
993
  handleBatchTool
791
994
  );
792
995
 
793
- server.registerTool(
996
+ registerTool(
794
997
  'browser_call',
795
998
  {
796
999
  title: 'Raw Browser Bridge Call',
@@ -813,7 +1016,7 @@ export function createBridgeMcpServer() {
813
1016
  handleRawCallTool
814
1017
  );
815
1018
 
816
- server.registerTool(
1019
+ registerTool(
817
1020
  'browser_skill',
818
1021
  {
819
1022
  title: 'Browser Bridge Runtime Context',
@@ -826,7 +1029,7 @@ export function createBridgeMcpServer() {
826
1029
  handleSkillTool
827
1030
  );
828
1031
 
829
- server.registerTool(
1032
+ registerTool(
830
1033
  'browser_access',
831
1034
  {
832
1035
  title: 'Request Browser Bridge Access',
@@ -839,19 +1042,11 @@ export function createBridgeMcpServer() {
839
1042
  handleAccessTool
840
1043
  );
841
1044
 
842
- server.registerTool(
1045
+ registerTool(
843
1046
  'browser_investigate',
844
1047
  {
845
1048
  title: 'Browser Investigate',
846
- description:
847
- 'Investigate a page to answer a question or verify a condition. ' +
848
- 'Pass a natural-language objective and an optional scope (quick/normal/deep). ' +
849
- 'DELEGATION HINT: Prefer delegating this to a smaller, low-cost subagent ' +
850
- 'that starts with structured reads via browser_dom, browser_page, ' +
851
- 'browser_styles_layout, and browser_batch. Escalate to browser_capture only ' +
852
- 'when structured reads are insufficient. ' +
853
- 'If subagent delegation is not available, a deterministic heuristic fallback ' +
854
- 'runs a scripted inspection sequence and returns a best-effort summary.',
1049
+ description: getInvestigateToolDescription(),
855
1050
  annotations: {
856
1051
  readOnlyHint: true,
857
1052
  destructiveHint: false,
@@ -859,7 +1054,7 @@ export function createBridgeMcpServer() {
859
1054
  openWorldHint: true,
860
1055
  },
861
1056
  _meta: {
862
- delegationHint: INVESTIGATE_DELEGATION_HINT,
1057
+ delegationHint: createInvestigateDelegationHint(),
863
1058
  },
864
1059
  inputSchema: {
865
1060
  objective: z
@@ -884,6 +1079,29 @@ export function createBridgeMcpServer() {
884
1079
  handleInvestigateTool
885
1080
  );
886
1081
 
1082
+ registerTool(
1083
+ 'browser_toolset',
1084
+ {
1085
+ title: 'Load Browser Bridge Tool',
1086
+ description:
1087
+ 'Load one specialized typed Browser Bridge tool by exact name. The tool appears in tools/list automatically and remains enabled for this MCP session.',
1088
+ inputSchema: {
1089
+ tool: z
1090
+ .enum(LOADABLE_TOOLSET_TOOLS)
1091
+ .describe('Exact specialized Browser Bridge tool to load.'),
1092
+ },
1093
+ },
1094
+ ({ tool }) => {
1095
+ const registration = registrations.get(tool);
1096
+ const newlyEnabled = Boolean(registration && !registration.enabled);
1097
+ if (newlyEnabled) registration?.enable();
1098
+ return createToolResult(newlyEnabled ? `Loaded ${tool}.` : `${tool} is already loaded.`, {
1099
+ tool,
1100
+ newlyEnabled,
1101
+ });
1102
+ }
1103
+ );
1104
+
887
1105
  return server;
888
1106
  }
889
1107
 
@@ -0,0 +1,48 @@
1
+ // @ts-check
2
+
3
+ /**
4
+ * Browser Bridge starts with a compact fixed tool surface. Specialized typed
5
+ * tools remain registered but disabled until browser_toolset enables one by
6
+ * exact name. browser_call always keeps the full bridge protocol reachable.
7
+ */
8
+
9
+ /** @type {readonly string[]} */
10
+ export const INITIAL_TOOLSET_TOOLS = Object.freeze([
11
+ 'browser_access',
12
+ 'browser_batch',
13
+ 'browser_call',
14
+ 'browser_health',
15
+ 'browser_status',
16
+ 'browser_toolset',
17
+ ]);
18
+
19
+ /** @typedef {'browser_dom' | 'browser_styles_layout' | 'browser_page' | 'browser_logs' | 'browser_input' | 'browser_navigation' | 'browser_tabs' | 'browser_capture' | 'browser_artifact' | 'browser_patch' | 'browser_intercept' | 'browser_investigate' | 'browser_sensitive_read' | 'browser_setup' | 'browser_skill'} LoadableToolName */
20
+
21
+ /** @type {readonly LoadableToolName[]} */
22
+ export const LOADABLE_TOOLSET_TOOLS = Object.freeze([
23
+ 'browser_dom',
24
+ 'browser_styles_layout',
25
+ 'browser_page',
26
+ 'browser_logs',
27
+ 'browser_input',
28
+ 'browser_navigation',
29
+ 'browser_tabs',
30
+ 'browser_capture',
31
+ 'browser_artifact',
32
+ 'browser_patch',
33
+ 'browser_intercept',
34
+ 'browser_investigate',
35
+ 'browser_sensitive_read',
36
+ 'browser_setup',
37
+ 'browser_skill',
38
+ ]);
39
+
40
+ const INITIAL_TOOLSET = new Set(INITIAL_TOOLSET_TOOLS);
41
+
42
+ /**
43
+ * @param {string} toolName
44
+ * @returns {boolean}
45
+ */
46
+ export function isInitiallyEnabledTool(toolName) {
47
+ return INITIAL_TOOLSET.has(toolName);
48
+ }