@browserbridge/bbx 1.9.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.
@@ -11,11 +11,13 @@ 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,
@@ -28,9 +30,11 @@ import {
28
30
  handleStylesLayoutTool,
29
31
  handleTabsTool,
30
32
  handleInvestigateTool,
33
+ createToolResult,
31
34
  MAX_BATCH_CALLS,
32
35
  } from './handlers.js';
33
36
  import {
37
+ ARTIFACT_CHUNK_BYTES,
34
38
  BUDGET_PRESETS,
35
39
  DEFAULT_CONSOLE_LIMIT,
36
40
  DEFAULT_EVAL_TIMEOUT_MS,
@@ -47,7 +51,8 @@ import {
47
51
  getMethodsByMaxComplexity,
48
52
  } from '../../protocol/src/index.js';
49
53
  import { applyWindowsTcpTransportDefaults } from '../../native-host/src/config.js';
50
- import { MCP_SERVER_INSTRUCTIONS } from './guidance.js';
54
+ import { getMcpServerInstructions } from './guidance.js';
55
+ import { isInitiallyEnabledTool, LOADABLE_TOOLSET_TOOLS } from './toolset.js';
51
56
 
52
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.`;
53
58
  export const TAB_ID_DESCRIPTION =
@@ -81,24 +86,52 @@ const INVESTIGATE_SUBAGENT_BRIDGE_METHODS = Object.freeze(
81
86
  )
82
87
  );
83
88
 
84
- const INVESTIGATE_DELEGATION_HINT = Object.freeze({
85
- recommended: true,
86
- costTier: 'low',
87
- preferredAgentProfile: {
88
- modelClass: 'small',
89
- reasoningEffort: 'low',
90
- },
91
- preferredTools: ['browser_dom', 'browser_page', 'browser_styles_layout', 'browser_batch'],
92
- escalationTools: ['browser_capture'],
93
- preferredBridgeMethods: INVESTIGATE_SUBAGENT_BRIDGE_METHODS,
94
- escalationTriggers: [
95
- 'Structured DOM or page reads are insufficient.',
96
- 'Visual confirmation is required.',
97
- 'Debugger-backed evidence is required.',
98
- ],
99
- });
89
+ const INVESTIGATE_PREFERRED_TOOLS = Object.freeze(['browser_call', 'browser_batch']);
100
90
 
101
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
+ *
102
135
  * @returns {McpServer}
103
136
  */
104
137
  export function createBridgeMcpServer() {
@@ -108,11 +141,33 @@ export function createBridgeMcpServer() {
108
141
  version: MCP_SERVER_VERSION,
109
142
  },
110
143
  {
111
- instructions: MCP_SERVER_INSTRUCTIONS,
144
+ instructions: getMcpServerInstructions(),
112
145
  }
113
146
  );
114
147
 
115
- 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(
116
171
  'browser_status',
117
172
  {
118
173
  title: 'Browser Bridge Status',
@@ -128,7 +183,7 @@ export function createBridgeMcpServer() {
128
183
  handleStatusTool
129
184
  );
130
185
 
131
- server.registerTool(
186
+ registerTool(
132
187
  'browser_setup',
133
188
  {
134
189
  title: 'Browser Bridge Setup Status',
@@ -143,7 +198,7 @@ export function createBridgeMcpServer() {
143
198
  handleSetupTool
144
199
  );
145
200
 
146
- server.registerTool(
201
+ registerTool(
147
202
  'browser_logs',
148
203
  {
149
204
  title: 'Browser Bridge Logs',
@@ -165,7 +220,7 @@ export function createBridgeMcpServer() {
165
220
  handleLogTool
166
221
  );
167
222
 
168
- server.registerTool(
223
+ registerTool(
169
224
  'browser_health',
170
225
  {
171
226
  title: 'Browser Bridge Health',
@@ -181,7 +236,7 @@ export function createBridgeMcpServer() {
181
236
  handleHealthTool
182
237
  );
183
238
 
184
- server.registerTool(
239
+ registerTool(
185
240
  'browser_tabs',
186
241
  {
187
242
  title: 'Browser Tabs',
@@ -208,7 +263,7 @@ export function createBridgeMcpServer() {
208
263
  handleTabsTool
209
264
  );
210
265
 
211
- server.registerTool(
266
+ registerTool(
212
267
  'browser_dom',
213
268
  {
214
269
  title: 'Browser DOM',
@@ -337,7 +392,7 @@ export function createBridgeMcpServer() {
337
392
  handleDomTool
338
393
  );
339
394
 
340
- server.registerTool(
395
+ registerTool(
341
396
  'browser_styles_layout',
342
397
  {
343
398
  title: 'Browser Styles And Layout',
@@ -376,7 +431,7 @@ export function createBridgeMcpServer() {
376
431
  handleStylesLayoutTool
377
432
  );
378
433
 
379
- server.registerTool(
434
+ registerTool(
380
435
  'browser_sensitive_read',
381
436
  {
382
437
  title: 'Sensitive Browser Read',
@@ -395,7 +450,7 @@ export function createBridgeMcpServer() {
395
450
  handleSensitiveReadTool
396
451
  );
397
452
 
398
- server.registerTool(
453
+ registerTool(
399
454
  'browser_page',
400
455
  {
401
456
  title: 'Browser Page State',
@@ -538,7 +593,7 @@ export function createBridgeMcpServer() {
538
593
  handlePageTool
539
594
  );
540
595
 
541
- server.registerTool(
596
+ registerTool(
542
597
  'browser_navigation',
543
598
  {
544
599
  title: 'Browser Navigation',
@@ -582,7 +637,7 @@ export function createBridgeMcpServer() {
582
637
  handleNavigationTool
583
638
  );
584
639
 
585
- server.registerTool(
640
+ registerTool(
586
641
  'browser_input',
587
642
  {
588
643
  title: 'Browser Input',
@@ -694,7 +749,7 @@ export function createBridgeMcpServer() {
694
749
  handleInputTool
695
750
  );
696
751
 
697
- server.registerTool(
752
+ registerTool(
698
753
  'browser_patch',
699
754
  {
700
755
  title: 'Browser Patch',
@@ -745,7 +800,7 @@ export function createBridgeMcpServer() {
745
800
  handlePatchTool
746
801
  );
747
802
 
748
- server.registerTool(
803
+ registerTool(
749
804
  'browser_capture',
750
805
  {
751
806
  title: 'Browser Capture',
@@ -822,7 +877,86 @@ export function createBridgeMcpServer() {
822
877
  handleCaptureTool
823
878
  );
824
879
 
825
- 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(
826
960
  'browser_batch',
827
961
  {
828
962
  title: 'Browser Bridge Batch',
@@ -859,7 +993,7 @@ export function createBridgeMcpServer() {
859
993
  handleBatchTool
860
994
  );
861
995
 
862
- server.registerTool(
996
+ registerTool(
863
997
  'browser_call',
864
998
  {
865
999
  title: 'Raw Browser Bridge Call',
@@ -882,7 +1016,7 @@ export function createBridgeMcpServer() {
882
1016
  handleRawCallTool
883
1017
  );
884
1018
 
885
- server.registerTool(
1019
+ registerTool(
886
1020
  'browser_skill',
887
1021
  {
888
1022
  title: 'Browser Bridge Runtime Context',
@@ -895,7 +1029,7 @@ export function createBridgeMcpServer() {
895
1029
  handleSkillTool
896
1030
  );
897
1031
 
898
- server.registerTool(
1032
+ registerTool(
899
1033
  'browser_access',
900
1034
  {
901
1035
  title: 'Request Browser Bridge Access',
@@ -908,19 +1042,11 @@ export function createBridgeMcpServer() {
908
1042
  handleAccessTool
909
1043
  );
910
1044
 
911
- server.registerTool(
1045
+ registerTool(
912
1046
  'browser_investigate',
913
1047
  {
914
1048
  title: 'Browser Investigate',
915
- description:
916
- 'Investigate a page to answer a question or verify a condition. ' +
917
- 'Pass a natural-language objective and an optional scope (quick/normal/deep). ' +
918
- 'DELEGATION HINT: Prefer delegating this to a smaller, low-cost subagent ' +
919
- 'that starts with structured reads via browser_dom, browser_page, ' +
920
- 'browser_styles_layout, and browser_batch. Escalate to browser_capture only ' +
921
- 'when structured reads are insufficient. ' +
922
- 'If subagent delegation is not available, a deterministic heuristic fallback ' +
923
- 'runs a scripted inspection sequence and returns a best-effort summary.',
1049
+ description: getInvestigateToolDescription(),
924
1050
  annotations: {
925
1051
  readOnlyHint: true,
926
1052
  destructiveHint: false,
@@ -928,7 +1054,7 @@ export function createBridgeMcpServer() {
928
1054
  openWorldHint: true,
929
1055
  },
930
1056
  _meta: {
931
- delegationHint: INVESTIGATE_DELEGATION_HINT,
1057
+ delegationHint: createInvestigateDelegationHint(),
932
1058
  },
933
1059
  inputSchema: {
934
1060
  objective: z
@@ -953,6 +1079,29 @@ export function createBridgeMcpServer() {
953
1079
  handleInvestigateTool
954
1080
  );
955
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
+
956
1105
  return server;
957
1106
  }
958
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
+ }