@browserbridge/bbx 1.10.0 → 1.11.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.
- package/README.md +1 -1
- package/SECURITY.md +14 -2
- package/package.json +8 -10
- package/packages/agent-client/src/cli-proxy-remote.js +7 -4
- package/packages/agent-client/src/command-registry.js +1 -1
- package/packages/agent-client/src/npm-self-update.js +9 -2
- package/packages/agent-client/src/runtime.js +12 -3
- package/packages/agent-client/src/types.ts +2 -0
- package/packages/mcp-server/src/bin.js +1 -1
- package/packages/mcp-server/src/guidance.js +12 -6
- package/packages/mcp-server/src/handlers-utils.js +21 -4
- package/packages/mcp-server/src/handlers.js +10 -4
- package/packages/mcp-server/src/server.js +77 -62
- package/packages/mcp-server/src/toolset.js +25 -0
- package/packages/native-host/bin/postinstall.js +7 -5
- package/packages/native-host/src/auth-token.js +63 -1
- package/packages/native-host/src/daemon.js +148 -45
- package/packages/native-host/src/install-manifest.js +3 -9
- package/packages/native-host/src/native-host.js +3 -3
- package/packages/protocol/src/defaults.js +6 -0
- package/packages/protocol/src/protocol.js +72 -3
- package/packages/protocol/src/types.ts +4 -0
- package/skills/browser-bridge/SKILL.md +1 -1
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
|
|
5
|
-
import { McpServer } from '@modelcontextprotocol/
|
|
6
|
-
import {
|
|
7
|
-
// zod is required at runtime by @modelcontextprotocol/
|
|
5
|
+
import { McpServer } from '@modelcontextprotocol/server';
|
|
6
|
+
import { serveStdio } from '@modelcontextprotocol/server/stdio';
|
|
7
|
+
// zod is required at runtime by @modelcontextprotocol/server for tool parameter schema
|
|
8
8
|
// declarations (z.object, z.string, etc.). It is not used for request/response
|
|
9
9
|
// validation - that is handled by the protocol package.
|
|
10
10
|
import * as z from 'zod/v4';
|
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
handleInvestigateTool,
|
|
33
33
|
createToolResult,
|
|
34
34
|
MAX_BATCH_CALLS,
|
|
35
|
+
runWithMcpRequestEra,
|
|
35
36
|
} from './handlers.js';
|
|
36
37
|
import {
|
|
37
38
|
ARTIFACT_CHUNK_BYTES,
|
|
@@ -52,7 +53,7 @@ import {
|
|
|
52
53
|
} from '../../protocol/src/index.js';
|
|
53
54
|
import { applyWindowsTcpTransportDefaults } from '../../native-host/src/config.js';
|
|
54
55
|
import { getMcpServerInstructions } from './guidance.js';
|
|
55
|
-
import {
|
|
56
|
+
import { isToolEnabledForEra, LOADABLE_TOOLSET_TOOLS } from './toolset.js';
|
|
56
57
|
|
|
57
58
|
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.`;
|
|
58
59
|
export const TAB_ID_DESCRIPTION =
|
|
@@ -130,18 +131,20 @@ function getInvestigateToolDescription() {
|
|
|
130
131
|
}
|
|
131
132
|
|
|
132
133
|
/**
|
|
133
|
-
* Create the MCP server
|
|
134
|
+
* Create the MCP server for one negotiated protocol era.
|
|
134
135
|
*
|
|
136
|
+
* @param {{ era?: 'legacy' | 'modern' }} [options]
|
|
135
137
|
* @returns {McpServer}
|
|
136
138
|
*/
|
|
137
|
-
export function createBridgeMcpServer() {
|
|
139
|
+
export function createBridgeMcpServer(options = {}) {
|
|
140
|
+
const { era = 'legacy' } = options;
|
|
138
141
|
const server = new McpServer(
|
|
139
142
|
{
|
|
140
143
|
name: 'browser-bridge',
|
|
141
144
|
version: MCP_SERVER_VERSION,
|
|
142
145
|
},
|
|
143
146
|
{
|
|
144
|
-
instructions: getMcpServerInstructions(),
|
|
147
|
+
instructions: getMcpServerInstructions(era),
|
|
145
148
|
}
|
|
146
149
|
);
|
|
147
150
|
|
|
@@ -149,19 +152,31 @@ export function createBridgeMcpServer() {
|
|
|
149
152
|
const registrations = new Map();
|
|
150
153
|
|
|
151
154
|
/**
|
|
152
|
-
* Register a tool, then hide it when it is not part of the
|
|
155
|
+
* Register a tool, then hide it when it is not part of the era's compact surface.
|
|
153
156
|
*
|
|
154
157
|
* Registering first keeps every call site checked against the SDK's own
|
|
155
158
|
* 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.
|
|
159
|
+
* happens before `connect()`, so an excluded tool never reaches `tools/list`.
|
|
158
160
|
*
|
|
159
|
-
* @
|
|
161
|
+
* @template {z.ZodObject} InputSchema
|
|
162
|
+
* @param {string} name
|
|
163
|
+
* @param {{
|
|
164
|
+
* title?: string,
|
|
165
|
+
* description?: string,
|
|
166
|
+
* inputSchema: InputSchema,
|
|
167
|
+
* annotations?: import('@modelcontextprotocol/server').ToolAnnotations,
|
|
168
|
+
* _meta?: Record<string, unknown>,
|
|
169
|
+
* }} config
|
|
170
|
+
* @param {import('@modelcontextprotocol/server').AnyToolHandler<InputSchema>} handler
|
|
171
|
+
* @returns {ReturnType<typeof server.registerTool>}
|
|
160
172
|
*/
|
|
161
173
|
const registerTool = (name, config, handler) => {
|
|
162
|
-
const
|
|
174
|
+
const wrappedHandler = /** @type {typeof handler} */ (
|
|
175
|
+
(args, context) => runWithMcpRequestEra(era, () => handler(args, context))
|
|
176
|
+
);
|
|
177
|
+
const registration = server.registerTool(name, config, wrappedHandler);
|
|
163
178
|
registrations.set(name, registration);
|
|
164
|
-
if (!
|
|
179
|
+
if (!isToolEnabledForEra(name, era)) {
|
|
165
180
|
registration.disable();
|
|
166
181
|
}
|
|
167
182
|
return registration;
|
|
@@ -173,12 +188,12 @@ export function createBridgeMcpServer() {
|
|
|
173
188
|
title: 'Browser Bridge Status',
|
|
174
189
|
description:
|
|
175
190
|
'Check bridge readiness: daemon connectivity, extension state, and window access. Omit destinationId to inspect local and configured remote destinations. If access is not enabled, ask the user to click Enable in the extension popup or side panel, then retry.',
|
|
176
|
-
inputSchema: {
|
|
191
|
+
inputSchema: z.object({
|
|
177
192
|
destinationId: z
|
|
178
193
|
.string()
|
|
179
194
|
.optional()
|
|
180
195
|
.describe('Inspect one destination instead of aggregating all configured destinations.'),
|
|
181
|
-
},
|
|
196
|
+
}),
|
|
182
197
|
},
|
|
183
198
|
handleStatusTool
|
|
184
199
|
);
|
|
@@ -188,12 +203,12 @@ export function createBridgeMcpServer() {
|
|
|
188
203
|
{
|
|
189
204
|
title: 'Browser Bridge Setup Status',
|
|
190
205
|
description: 'Check MCP and CLI skill installation status for agent integration.',
|
|
191
|
-
inputSchema: {
|
|
206
|
+
inputSchema: z.object({
|
|
192
207
|
global: z
|
|
193
208
|
.boolean()
|
|
194
209
|
.optional()
|
|
195
210
|
.describe('Check global (true) or local (false) config (default: true)'),
|
|
196
|
-
},
|
|
211
|
+
}),
|
|
197
212
|
},
|
|
198
213
|
handleSetupTool
|
|
199
214
|
);
|
|
@@ -203,7 +218,7 @@ export function createBridgeMcpServer() {
|
|
|
203
218
|
{
|
|
204
219
|
title: 'Browser Bridge Logs',
|
|
205
220
|
description: 'Tail recent bridge request logs for debugging connection or routing issues.',
|
|
206
|
-
inputSchema: {
|
|
221
|
+
inputSchema: z.object({
|
|
207
222
|
limit: z
|
|
208
223
|
.number()
|
|
209
224
|
.int()
|
|
@@ -215,7 +230,7 @@ export function createBridgeMcpServer() {
|
|
|
215
230
|
.optional()
|
|
216
231
|
.describe(BUDGET_PRESET_DESCRIPTION),
|
|
217
232
|
destinationId: z.string().optional().describe(DESTINATION_ID_DESCRIPTION),
|
|
218
|
-
},
|
|
233
|
+
}),
|
|
219
234
|
},
|
|
220
235
|
handleLogTool
|
|
221
236
|
);
|
|
@@ -225,13 +240,13 @@ export function createBridgeMcpServer() {
|
|
|
225
240
|
{
|
|
226
241
|
title: 'Browser Bridge Health',
|
|
227
242
|
description: 'Ping the bridge to verify daemon and extension connectivity.',
|
|
228
|
-
inputSchema: {
|
|
243
|
+
inputSchema: z.object({
|
|
229
244
|
destinationId: z.string().optional().describe(DESTINATION_ID_DESCRIPTION),
|
|
230
245
|
intent: z
|
|
231
246
|
.enum(['inspect', 'interact', 'capture', 'navigate', 'debugger', 'general'])
|
|
232
247
|
.optional()
|
|
233
248
|
.describe('Reported operation family shown in the access prompt (defaults to general).'),
|
|
234
|
-
},
|
|
249
|
+
}),
|
|
235
250
|
},
|
|
236
251
|
handleHealthTool
|
|
237
252
|
);
|
|
@@ -242,7 +257,7 @@ export function createBridgeMcpServer() {
|
|
|
242
257
|
title: 'Browser Tabs',
|
|
243
258
|
description:
|
|
244
259
|
'List, create, close, or activate browser tabs. List without destinationId aggregates configured destinations; other actions default to local. Only create a page when the user explicitly requests it.',
|
|
245
|
-
inputSchema: {
|
|
260
|
+
inputSchema: z.object({
|
|
246
261
|
action: z
|
|
247
262
|
.enum(['list', 'create', 'close', 'activate'])
|
|
248
263
|
.describe('"list" (preferred), "create" (only when needed), "close", or "activate"'),
|
|
@@ -258,7 +273,7 @@ export function createBridgeMcpServer() {
|
|
|
258
273
|
.string()
|
|
259
274
|
.optional()
|
|
260
275
|
.describe('Target one destination; list without it aggregates configured destinations.'),
|
|
261
|
-
},
|
|
276
|
+
}),
|
|
262
277
|
},
|
|
263
278
|
handleTabsTool
|
|
264
279
|
);
|
|
@@ -269,7 +284,7 @@ export function createBridgeMcpServer() {
|
|
|
269
284
|
title: 'Browser DOM',
|
|
270
285
|
description:
|
|
271
286
|
'Query, describe, read, search, or wait for DOM elements. Reuse elementRef from prior results. For full-page text, use browser_page action "text". accessibility_tree is debugger-backed and depth-limited - use query/find first.',
|
|
272
|
-
inputSchema: {
|
|
287
|
+
inputSchema: z.object({
|
|
273
288
|
action: z
|
|
274
289
|
.enum([
|
|
275
290
|
'query',
|
|
@@ -387,7 +402,7 @@ export function createBridgeMcpServer() {
|
|
|
387
402
|
.positive()
|
|
388
403
|
.optional()
|
|
389
404
|
.describe(`Max HTML chars to return (default: ${DEFAULT_MAX_HTML_LENGTH})`),
|
|
390
|
-
},
|
|
405
|
+
}),
|
|
391
406
|
},
|
|
392
407
|
handleDomTool
|
|
393
408
|
);
|
|
@@ -398,7 +413,7 @@ export function createBridgeMcpServer() {
|
|
|
398
413
|
title: 'Browser Styles And Layout',
|
|
399
414
|
description:
|
|
400
415
|
'Read computed styles, matched CSS rules, box model, or hit-test a viewport point. Reuse elementRef from prior queries. For DOM structure, use browser_dom.',
|
|
401
|
-
inputSchema: {
|
|
416
|
+
inputSchema: z.object({
|
|
402
417
|
action: z
|
|
403
418
|
.enum(['computed', 'matched_rules', 'box_model', 'hit_test'])
|
|
404
419
|
.describe('Style/layout operation to perform'),
|
|
@@ -426,7 +441,7 @@ export function createBridgeMcpServer() {
|
|
|
426
441
|
.nonnegative()
|
|
427
442
|
.optional()
|
|
428
443
|
.describe('Y coordinate for hit_test (viewport relative)'),
|
|
429
|
-
},
|
|
444
|
+
}),
|
|
430
445
|
},
|
|
431
446
|
handleStylesLayoutTool
|
|
432
447
|
);
|
|
@@ -437,7 +452,7 @@ export function createBridgeMcpServer() {
|
|
|
437
452
|
title: 'Sensitive Browser Read',
|
|
438
453
|
description:
|
|
439
454
|
'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: {
|
|
455
|
+
inputSchema: z.object({
|
|
441
456
|
source: z.enum(['local_storage', 'session_storage']).describe('Exact storage source'),
|
|
442
457
|
key: z
|
|
443
458
|
.string()
|
|
@@ -445,7 +460,7 @@ export function createBridgeMcpServer() {
|
|
|
445
460
|
.describe('Exact storage key, including an empty key if intended'),
|
|
446
461
|
tabId: z.number().int().positive().optional().describe(TAB_ID_DESCRIPTION),
|
|
447
462
|
destinationId: z.string().optional().describe(DESTINATION_ID_DESCRIPTION),
|
|
448
|
-
},
|
|
463
|
+
}),
|
|
449
464
|
},
|
|
450
465
|
handleSensitiveReadTool
|
|
451
466
|
);
|
|
@@ -456,7 +471,7 @@ export function createBridgeMcpServer() {
|
|
|
456
471
|
title: 'Browser Page State',
|
|
457
472
|
description:
|
|
458
473
|
'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.',
|
|
459
|
-
inputSchema: {
|
|
474
|
+
inputSchema: z.object({
|
|
460
475
|
action: z
|
|
461
476
|
.enum([
|
|
462
477
|
'state',
|
|
@@ -588,7 +603,7 @@ export function createBridgeMcpServer() {
|
|
|
588
603
|
.enum(['exact', 'contains', 'regex'])
|
|
589
604
|
.optional()
|
|
590
605
|
.describe('URL match mode for wait_for_load (default: exact)'),
|
|
591
|
-
},
|
|
606
|
+
}),
|
|
592
607
|
},
|
|
593
608
|
handlePageTool
|
|
594
609
|
);
|
|
@@ -599,7 +614,7 @@ export function createBridgeMcpServer() {
|
|
|
599
614
|
title: 'Browser Navigation',
|
|
600
615
|
description:
|
|
601
616
|
'Navigate to a URL, reload, go back/forward, scroll, or resize the viewport. resize is debugger-backed - use only for exact viewport overrides.',
|
|
602
|
-
inputSchema: {
|
|
617
|
+
inputSchema: z.object({
|
|
603
618
|
action: z
|
|
604
619
|
.enum(['navigate', 'reload', 'go_back', 'go_forward', 'scroll', 'resize'])
|
|
605
620
|
.describe('Navigation operation to perform'),
|
|
@@ -632,7 +647,7 @@ export function createBridgeMcpServer() {
|
|
|
632
647
|
.optional()
|
|
633
648
|
.describe('Viewport device scale factor (for resize)'),
|
|
634
649
|
reset: z.boolean().optional().describe('Reset viewport to original size (for resize)'),
|
|
635
|
-
},
|
|
650
|
+
}),
|
|
636
651
|
},
|
|
637
652
|
handleNavigationTool
|
|
638
653
|
);
|
|
@@ -643,7 +658,7 @@ export function createBridgeMcpServer() {
|
|
|
643
658
|
title: 'Browser Input',
|
|
644
659
|
description:
|
|
645
660
|
'Dispatch browser input. Targeted click, focus, type, fill, press_key, set_checked, select_option, hover, and drag actions perform actionability checks and report resolution/execution metadata. cdp_press_key and scroll_into_view use separate contracts. Reuse elementRef values and verify application state after mutations.',
|
|
646
|
-
inputSchema: {
|
|
661
|
+
inputSchema: z.object({
|
|
647
662
|
action: z
|
|
648
663
|
.enum([
|
|
649
664
|
'click',
|
|
@@ -744,7 +759,7 @@ export function createBridgeMcpServer() {
|
|
|
744
759
|
.describe('Drag destination selector (alternative to destinationElementRef)'),
|
|
745
760
|
offsetX: z.number().optional().describe('Drag drop offset X (default: 0)'),
|
|
746
761
|
offsetY: z.number().optional().describe('Drag drop offset Y (default: 0)'),
|
|
747
|
-
},
|
|
762
|
+
}),
|
|
748
763
|
},
|
|
749
764
|
handleInputTool
|
|
750
765
|
);
|
|
@@ -755,7 +770,7 @@ export function createBridgeMcpServer() {
|
|
|
755
770
|
title: 'Browser Patch',
|
|
756
771
|
description:
|
|
757
772
|
'Apply or rollback document-local style and DOM patches for live prototyping before editing source. commit_baseline keeps current changes but discards rollback history. Set verify=true to get computed results inline.',
|
|
758
|
-
inputSchema: {
|
|
773
|
+
inputSchema: z.object({
|
|
759
774
|
action: z
|
|
760
775
|
.enum(['apply_styles', 'apply_dom', 'list', 'rollback', 'commit_baseline'])
|
|
761
776
|
.describe('Patch operation to perform'),
|
|
@@ -795,7 +810,7 @@ export function createBridgeMcpServer() {
|
|
|
795
810
|
.describe(
|
|
796
811
|
'Return computed result inline after applying, eliminating a verification round-trip'
|
|
797
812
|
),
|
|
798
|
-
},
|
|
813
|
+
}),
|
|
799
814
|
},
|
|
800
815
|
handlePatchTool
|
|
801
816
|
);
|
|
@@ -806,7 +821,7 @@ export function createBridgeMcpServer() {
|
|
|
806
821
|
title: 'Browser Capture',
|
|
807
822
|
description:
|
|
808
823
|
'Capture screenshots or CDP snapshots. Debugger-backed and token-expensive - use only when structured reads (browser_dom, browser_styles_layout) are insufficient. Prefer element, then tight region; full_page only for document-level context.',
|
|
809
|
-
inputSchema: {
|
|
824
|
+
inputSchema: z.object({
|
|
810
825
|
action: z
|
|
811
826
|
.enum([
|
|
812
827
|
'element',
|
|
@@ -872,7 +887,7 @@ export function createBridgeMcpServer() {
|
|
|
872
887
|
.max(4)
|
|
873
888
|
.optional()
|
|
874
889
|
.describe('Screenshot capture scale (0.1 to 4)'),
|
|
875
|
-
},
|
|
890
|
+
}),
|
|
876
891
|
},
|
|
877
892
|
handleCaptureTool
|
|
878
893
|
);
|
|
@@ -882,8 +897,8 @@ export function createBridgeMcpServer() {
|
|
|
882
897
|
{
|
|
883
898
|
title: 'Browser Artifact',
|
|
884
899
|
description:
|
|
885
|
-
'Read (chunked base64) or delete a daemon-owned artifact produced by artifact delivery (screenshots, HAR exports). Artifacts are scoped to
|
|
886
|
-
inputSchema: {
|
|
900
|
+
'Read (chunked base64) or delete a daemon-owned artifact produced by artifact delivery (screenshots, HAR exports). Artifacts are scoped to the current local MCP process and expire quickly. Page reads with offset until nextOffset is null, then verify the reassembled bytes against sha256.',
|
|
901
|
+
inputSchema: z.object({
|
|
887
902
|
action: z.enum(['read', 'delete']).describe('Artifact operation to perform'),
|
|
888
903
|
artifactId: z
|
|
889
904
|
.string()
|
|
@@ -907,7 +922,7 @@ export function createBridgeMcpServer() {
|
|
|
907
922
|
.enum(['quick', 'normal', 'deep'])
|
|
908
923
|
.optional()
|
|
909
924
|
.describe(BUDGET_PRESET_DESCRIPTION),
|
|
910
|
-
},
|
|
925
|
+
}),
|
|
911
926
|
},
|
|
912
927
|
handleArtifactTool
|
|
913
928
|
);
|
|
@@ -918,7 +933,7 @@ export function createBridgeMcpServer() {
|
|
|
918
933
|
title: 'Browser Network Intercept',
|
|
919
934
|
description:
|
|
920
935
|
'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: {
|
|
936
|
+
inputSchema: z.object({
|
|
922
937
|
action: z
|
|
923
938
|
.enum(['add', 'list', 'remove', 'clear'])
|
|
924
939
|
.describe('Intercept operation to perform'),
|
|
@@ -951,7 +966,7 @@ export function createBridgeMcpServer() {
|
|
|
951
966
|
.optional()
|
|
952
967
|
.describe('Response headers for fulfill'),
|
|
953
968
|
ruleId: z.string().min(1).optional().describe('Rule ID (required for remove)'),
|
|
954
|
-
},
|
|
969
|
+
}),
|
|
955
970
|
},
|
|
956
971
|
handleInterceptTool
|
|
957
972
|
);
|
|
@@ -968,7 +983,7 @@ export function createBridgeMcpServer() {
|
|
|
968
983
|
idempotentHint: true,
|
|
969
984
|
openWorldHint: true,
|
|
970
985
|
},
|
|
971
|
-
inputSchema: {
|
|
986
|
+
inputSchema: z.object({
|
|
972
987
|
calls: z
|
|
973
988
|
.array(
|
|
974
989
|
z.object({
|
|
@@ -988,7 +1003,7 @@ export function createBridgeMcpServer() {
|
|
|
988
1003
|
.min(1)
|
|
989
1004
|
.max(MAX_BATCH_CALLS)
|
|
990
1005
|
.describe('Calls to execute in parallel'),
|
|
991
|
-
},
|
|
1006
|
+
}),
|
|
992
1007
|
},
|
|
993
1008
|
handleBatchTool
|
|
994
1009
|
);
|
|
@@ -999,7 +1014,7 @@ export function createBridgeMcpServer() {
|
|
|
999
1014
|
title: 'Raw Browser Bridge Call',
|
|
1000
1015
|
description:
|
|
1001
1016
|
'Primary Browser Bridge tool for permission-ask hosts: call any bridge method directly by name so the user can approve one BBX MCP tool instead of each specialized tool separately.',
|
|
1002
|
-
inputSchema: {
|
|
1017
|
+
inputSchema: z.object({
|
|
1003
1018
|
method: z.string().describe('Bridge method name (e.g., "dom.query", "input.click")'),
|
|
1004
1019
|
params: z
|
|
1005
1020
|
.record(z.string(), z.unknown())
|
|
@@ -1011,7 +1026,7 @@ export function createBridgeMcpServer() {
|
|
|
1011
1026
|
.enum(['quick', 'normal', 'deep'])
|
|
1012
1027
|
.optional()
|
|
1013
1028
|
.describe(BUDGET_PRESET_DESCRIPTION),
|
|
1014
|
-
},
|
|
1029
|
+
}),
|
|
1015
1030
|
},
|
|
1016
1031
|
handleRawCallTool
|
|
1017
1032
|
);
|
|
@@ -1022,9 +1037,9 @@ export function createBridgeMcpServer() {
|
|
|
1022
1037
|
title: 'Browser Bridge Runtime Context',
|
|
1023
1038
|
description:
|
|
1024
1039
|
'Return runtime context: budget presets, method groups, and active limits. Call to discover defaults before inspecting a page.',
|
|
1025
|
-
inputSchema: {
|
|
1040
|
+
inputSchema: z.object({
|
|
1026
1041
|
destinationId: z.string().optional().describe(DESTINATION_ID_DESCRIPTION),
|
|
1027
|
-
},
|
|
1042
|
+
}),
|
|
1028
1043
|
},
|
|
1029
1044
|
handleSkillTool
|
|
1030
1045
|
);
|
|
@@ -1035,9 +1050,9 @@ export function createBridgeMcpServer() {
|
|
|
1035
1050
|
title: 'Request Browser Bridge Access',
|
|
1036
1051
|
description:
|
|
1037
1052
|
'Request window access for Browser Bridge. Surfaces an Enable prompt in the extension popup or side panel. Use once per window; if access is already pending, ask the user to enable that window instead of requesting again.',
|
|
1038
|
-
inputSchema: {
|
|
1053
|
+
inputSchema: z.object({
|
|
1039
1054
|
destinationId: z.string().optional().describe(DESTINATION_ID_DESCRIPTION),
|
|
1040
|
-
},
|
|
1055
|
+
}),
|
|
1041
1056
|
},
|
|
1042
1057
|
handleAccessTool
|
|
1043
1058
|
);
|
|
@@ -1056,7 +1071,7 @@ export function createBridgeMcpServer() {
|
|
|
1056
1071
|
_meta: {
|
|
1057
1072
|
delegationHint: createInvestigateDelegationHint(),
|
|
1058
1073
|
},
|
|
1059
|
-
inputSchema: {
|
|
1074
|
+
inputSchema: z.object({
|
|
1060
1075
|
objective: z
|
|
1061
1076
|
.string()
|
|
1062
1077
|
.describe('What to find, verify, or extract from the current page (natural language).'),
|
|
@@ -1074,7 +1089,7 @@ export function createBridgeMcpServer() {
|
|
|
1074
1089
|
.string()
|
|
1075
1090
|
.optional()
|
|
1076
1091
|
.describe('Optional CSS selector to scope the investigation to a subtree.'),
|
|
1077
|
-
},
|
|
1092
|
+
}),
|
|
1078
1093
|
},
|
|
1079
1094
|
handleInvestigateTool
|
|
1080
1095
|
);
|
|
@@ -1084,12 +1099,12 @@ export function createBridgeMcpServer() {
|
|
|
1084
1099
|
{
|
|
1085
1100
|
title: 'Load Browser Bridge Tool',
|
|
1086
1101
|
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
|
|
1088
|
-
inputSchema: {
|
|
1102
|
+
'Load one specialized typed Browser Bridge tool by exact name. The tool appears in tools/list automatically and remains enabled for this legacy MCP connection.',
|
|
1103
|
+
inputSchema: z.object({
|
|
1089
1104
|
tool: z
|
|
1090
1105
|
.enum(LOADABLE_TOOLSET_TOOLS)
|
|
1091
1106
|
.describe('Exact specialized Browser Bridge tool to load.'),
|
|
1092
|
-
},
|
|
1107
|
+
}),
|
|
1093
1108
|
},
|
|
1094
1109
|
({ tool }) => {
|
|
1095
1110
|
const registration = registrations.get(tool);
|
|
@@ -1106,11 +1121,11 @@ export function createBridgeMcpServer() {
|
|
|
1106
1121
|
}
|
|
1107
1122
|
|
|
1108
1123
|
/**
|
|
1109
|
-
* @
|
|
1124
|
+
* @param {{ serve?: typeof serveStdio }} [options]
|
|
1125
|
+
* @returns {void}
|
|
1110
1126
|
*/
|
|
1111
|
-
export
|
|
1127
|
+
export function startBridgeMcpServer(options = {}) {
|
|
1128
|
+
const { serve = serveStdio } = options;
|
|
1112
1129
|
applyWindowsTcpTransportDefaults();
|
|
1113
|
-
|
|
1114
|
-
const transport = new StdioServerTransport();
|
|
1115
|
-
await server.connect(transport);
|
|
1130
|
+
serve(({ era }) => createBridgeMcpServer({ era }));
|
|
1116
1131
|
}
|
|
@@ -16,6 +16,21 @@ export const INITIAL_TOOLSET_TOOLS = Object.freeze([
|
|
|
16
16
|
'browser_toolset',
|
|
17
17
|
]);
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Modern MCP tool lists cannot change as a side effect of another request.
|
|
21
|
+
* Keep the compact surface static and use browser_call for full protocol reach.
|
|
22
|
+
*
|
|
23
|
+
* @type {readonly string[]}
|
|
24
|
+
*/
|
|
25
|
+
export const MODERN_TOOLSET_TOOLS = Object.freeze([
|
|
26
|
+
'browser_access',
|
|
27
|
+
'browser_batch',
|
|
28
|
+
'browser_call',
|
|
29
|
+
'browser_health',
|
|
30
|
+
'browser_skill',
|
|
31
|
+
'browser_status',
|
|
32
|
+
]);
|
|
33
|
+
|
|
19
34
|
/** @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
35
|
|
|
21
36
|
/** @type {readonly LoadableToolName[]} */
|
|
@@ -38,6 +53,7 @@ export const LOADABLE_TOOLSET_TOOLS = Object.freeze([
|
|
|
38
53
|
]);
|
|
39
54
|
|
|
40
55
|
const INITIAL_TOOLSET = new Set(INITIAL_TOOLSET_TOOLS);
|
|
56
|
+
const MODERN_TOOLSET = new Set(MODERN_TOOLSET_TOOLS);
|
|
41
57
|
|
|
42
58
|
/**
|
|
43
59
|
* @param {string} toolName
|
|
@@ -46,3 +62,12 @@ const INITIAL_TOOLSET = new Set(INITIAL_TOOLSET_TOOLS);
|
|
|
46
62
|
export function isInitiallyEnabledTool(toolName) {
|
|
47
63
|
return INITIAL_TOOLSET.has(toolName);
|
|
48
64
|
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @param {string} toolName
|
|
68
|
+
* @param {'legacy' | 'modern'} era
|
|
69
|
+
* @returns {boolean}
|
|
70
|
+
*/
|
|
71
|
+
export function isToolEnabledForEra(toolName, era) {
|
|
72
|
+
return era === 'modern' ? MODERN_TOOLSET.has(toolName) : INITIAL_TOOLSET.has(toolName);
|
|
73
|
+
}
|
|
@@ -17,15 +17,17 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
|
17
17
|
const repoRoot = path.resolve(__dirname, '../../..');
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
20
|
+
* Host setup is appropriate for a global CLI install, not when this package is
|
|
21
|
+
* an ordinary project dependency. npm exec is also always non-mutating. The
|
|
22
|
+
* explicit force switch is reserved for controlled packaging/tests.
|
|
23
23
|
*
|
|
24
24
|
* @param {NodeJS.ProcessEnv} [env=process.env]
|
|
25
25
|
* @returns {boolean}
|
|
26
26
|
*/
|
|
27
|
-
function shouldSkipPostinstall(env = process.env) {
|
|
28
|
-
|
|
27
|
+
export function shouldSkipPostinstall(env = process.env) {
|
|
28
|
+
const globalInstall = env.npm_config_global === 'true' || env.npm_config_global === '1';
|
|
29
|
+
const explicitlyEnabled = env.BBX_POSTINSTALL_FORCE === '1';
|
|
30
|
+
return env.npm_command === 'exec' || (!globalInstall && !explicitlyEnabled);
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
/**
|
|
@@ -9,6 +9,7 @@ import { getBridgeDir } from './config.js';
|
|
|
9
9
|
export const BRIDGE_AUTH_TOKEN_ENV = 'BBX_AUTH_TOKEN';
|
|
10
10
|
export const BRIDGE_AUTH_TOKEN_FILE_ENV = 'BBX_AUTH_TOKEN_FILE';
|
|
11
11
|
const TOKEN_FILENAME = 'daemon.auth';
|
|
12
|
+
const EXTENSION_TOKEN_FILENAME = 'daemon.extension.auth';
|
|
12
13
|
const TOKEN_BYTES = 32;
|
|
13
14
|
const TOKEN_PATTERN = /^[A-Za-z0-9_-]{32,256}$/u;
|
|
14
15
|
const UUID_TOKEN_PATTERN =
|
|
@@ -22,6 +23,17 @@ export function getBridgeAuthTokenPath(env = process.env) {
|
|
|
22
23
|
return path.join(getBridgeDir(env), TOKEN_FILENAME);
|
|
23
24
|
}
|
|
24
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Private credential used only by the Chrome native host when registering the
|
|
28
|
+
* extension role. Remote agent configurations never receive this token.
|
|
29
|
+
*
|
|
30
|
+
* @param {NodeJS.ProcessEnv} [env=process.env]
|
|
31
|
+
* @returns {string}
|
|
32
|
+
*/
|
|
33
|
+
export function getBridgeExtensionAuthTokenPath(env = process.env) {
|
|
34
|
+
return path.join(getBridgeDir(env), EXTENSION_TOKEN_FILENAME);
|
|
35
|
+
}
|
|
36
|
+
|
|
25
37
|
/**
|
|
26
38
|
* @param {unknown} value
|
|
27
39
|
* @returns {string | null}
|
|
@@ -91,6 +103,25 @@ export async function readBridgeAuthToken(options = {}) {
|
|
|
91
103
|
}
|
|
92
104
|
const tokenPath = options.tokenPath ?? getBridgeAuthTokenPath();
|
|
93
105
|
const readFile = options.readFile ?? fs.promises.readFile.bind(fs.promises);
|
|
106
|
+
return readStoredBridgeAuthToken(tokenPath, readFile);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* @param {{ tokenPath?: string, readFile?: typeof fs.promises.readFile }} [options={}]
|
|
111
|
+
* @returns {Promise<string | null>}
|
|
112
|
+
*/
|
|
113
|
+
export async function readBridgeExtensionAuthToken(options = {}) {
|
|
114
|
+
const tokenPath = options.tokenPath ?? getBridgeExtensionAuthTokenPath();
|
|
115
|
+
const readFile = options.readFile ?? fs.promises.readFile.bind(fs.promises);
|
|
116
|
+
return readStoredBridgeAuthToken(tokenPath, readFile);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* @param {string} tokenPath
|
|
121
|
+
* @param {typeof fs.promises.readFile} readFile
|
|
122
|
+
* @returns {Promise<string | null>}
|
|
123
|
+
*/
|
|
124
|
+
async function readStoredBridgeAuthToken(tokenPath, readFile) {
|
|
94
125
|
try {
|
|
95
126
|
return normalizeBridgeAuthToken(await readFile(tokenPath, 'utf8'));
|
|
96
127
|
} catch (error) {
|
|
@@ -136,12 +167,43 @@ export async function writeBridgeAuthToken(token, options = {}) {
|
|
|
136
167
|
*/
|
|
137
168
|
export async function ensureBridgeAuthToken(options = {}) {
|
|
138
169
|
const tokenPath = options.tokenPath ?? getBridgeAuthTokenPath();
|
|
170
|
+
return ensureStoredBridgeAuthToken(tokenPath, options);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* @param {{
|
|
175
|
+
* tokenPath?: string,
|
|
176
|
+
* readFile?: typeof fs.promises.readFile,
|
|
177
|
+
* writeFile?: typeof fs.promises.writeFile,
|
|
178
|
+
* mkdir?: typeof fs.promises.mkdir,
|
|
179
|
+
* chmod?: typeof fs.promises.chmod,
|
|
180
|
+
* randomBytesFn?: typeof randomBytes
|
|
181
|
+
* }} [options={}]
|
|
182
|
+
* @returns {Promise<string>}
|
|
183
|
+
*/
|
|
184
|
+
export async function ensureBridgeExtensionAuthToken(options = {}) {
|
|
185
|
+
const tokenPath = options.tokenPath ?? getBridgeExtensionAuthTokenPath();
|
|
186
|
+
return ensureStoredBridgeAuthToken(tokenPath, options);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* @param {string} tokenPath
|
|
191
|
+
* @param {{
|
|
192
|
+
* readFile?: typeof fs.promises.readFile,
|
|
193
|
+
* writeFile?: typeof fs.promises.writeFile,
|
|
194
|
+
* mkdir?: typeof fs.promises.mkdir,
|
|
195
|
+
* chmod?: typeof fs.promises.chmod,
|
|
196
|
+
* randomBytesFn?: typeof randomBytes
|
|
197
|
+
* }} options
|
|
198
|
+
* @returns {Promise<string>}
|
|
199
|
+
*/
|
|
200
|
+
async function ensureStoredBridgeAuthToken(tokenPath, options) {
|
|
139
201
|
const readFile = options.readFile ?? fs.promises.readFile.bind(fs.promises);
|
|
140
202
|
const writeFile = options.writeFile ?? fs.promises.writeFile.bind(fs.promises);
|
|
141
203
|
const mkdir = options.mkdir ?? fs.promises.mkdir.bind(fs.promises);
|
|
142
204
|
const chmod = options.chmod ?? fs.promises.chmod.bind(fs.promises);
|
|
143
205
|
const randomBytesFn = options.randomBytesFn ?? randomBytes;
|
|
144
|
-
const existing = await
|
|
206
|
+
const existing = await readStoredBridgeAuthToken(tokenPath, readFile);
|
|
145
207
|
if (existing) {
|
|
146
208
|
return existing;
|
|
147
209
|
}
|