@browserbridge/bbx 1.2.0 → 1.4.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.
- package/README.md +8 -5
- package/package.json +2 -2
- package/packages/agent-client/src/cli.js +56 -31
- package/packages/agent-client/src/client.js +81 -65
- package/packages/agent-client/src/command-registry.js +4 -15
- package/packages/agent-client/src/detect.js +3 -3
- package/packages/agent-client/src/install.js +3 -7
- package/packages/agent-client/src/mcp-config.js +20 -5
- package/packages/agent-client/src/runtime.js +7 -41
- package/packages/agent-client/src/setup-status.js +3 -13
- package/packages/agent-client/src/types.ts +139 -0
- package/packages/mcp-server/src/guidance.js +241 -0
- package/packages/mcp-server/src/handlers-capture.js +91 -16
- package/packages/mcp-server/src/handlers-dom.js +59 -4
- package/packages/mcp-server/src/handlers-navigation.js +22 -2
- package/packages/mcp-server/src/handlers-page.js +6 -11
- package/packages/mcp-server/src/handlers-utils.js +69 -1
- package/packages/mcp-server/src/server.js +111 -28
- package/packages/native-host/bin/postinstall.js +42 -21
- package/packages/native-host/src/auth-token.js +92 -0
- package/packages/native-host/src/daemon-process.js +1 -2
- package/packages/native-host/src/daemon.js +199 -30
- package/packages/native-host/src/framing.js +13 -0
- package/packages/native-host/src/native-host.js +25 -7
- package/packages/protocol/src/defaults.js +3 -0
- package/packages/protocol/src/json-lines.js +29 -1
- package/packages/protocol/src/protocol.js +43 -0
- package/packages/protocol/src/registry.js +3 -9
- package/packages/protocol/src/types.ts +574 -0
- package/skills/browser-bridge/SKILL.md +21 -5
- package/skills/browser-bridge/agents/openai.yaml +1 -1
- package/skills/browser-bridge/references/interaction.md +6 -6
- package/skills/browser-bridge/references/protocol.md +57 -54
- package/skills/browser-bridge/references/ui-workflows.md +1 -1
- package/packages/protocol/src/types.js +0 -626
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
applyTreeBudgetPreset,
|
|
7
7
|
dispatchToolAction,
|
|
8
8
|
inferBudgetFromSelector,
|
|
9
|
+
summarizeToolError,
|
|
9
10
|
} from './handlers-utils.js';
|
|
10
11
|
|
|
11
12
|
/** @typedef {import('../../protocol/src/types.js').BridgeMethod} BridgeMethod */
|
|
@@ -93,6 +94,15 @@ export const DOM_ACTIONS = {
|
|
|
93
94
|
* @returns {Promise<ToolResult>}
|
|
94
95
|
*/
|
|
95
96
|
export async function handleDomTool(args) {
|
|
97
|
+
if (args.action === 'wait' && !hasText(args.selector) && !hasText(args.text)) {
|
|
98
|
+
return summarizeToolError('selector or text is required for dom.wait_for.');
|
|
99
|
+
}
|
|
100
|
+
if (args.action === 'find_text' && !hasText(args.text)) {
|
|
101
|
+
return summarizeToolError('text is required for dom.find_by_text.');
|
|
102
|
+
}
|
|
103
|
+
if (args.action === 'find_role' && !hasText(args.role)) {
|
|
104
|
+
return summarizeToolError('role is required for dom.find_by_role.');
|
|
105
|
+
}
|
|
96
106
|
if (args.action === 'query' || args.action === 'accessibility_tree') {
|
|
97
107
|
const inferred = inferBudgetFromSelector(args);
|
|
98
108
|
const withBudget = inferred ? { ...args, budgetPreset: args.budgetPreset ?? inferred } : args;
|
|
@@ -136,6 +146,15 @@ export const STYLES_LAYOUT_ACTIONS = {
|
|
|
136
146
|
* @returns {Promise<ToolResult>}
|
|
137
147
|
*/
|
|
138
148
|
export async function handleStylesLayoutTool(args) {
|
|
149
|
+
if (
|
|
150
|
+
args.action === 'hit_test' &&
|
|
151
|
+
(typeof args.x !== 'number' ||
|
|
152
|
+
!Number.isFinite(args.x) ||
|
|
153
|
+
typeof args.y !== 'number' ||
|
|
154
|
+
!Number.isFinite(args.y))
|
|
155
|
+
) {
|
|
156
|
+
return summarizeToolError('x and y are required for layout.hit_test.');
|
|
157
|
+
}
|
|
139
158
|
return dispatchToolAction(STYLES_LAYOUT_ACTIONS, args, 'styles/layout');
|
|
140
159
|
}
|
|
141
160
|
|
|
@@ -148,6 +167,7 @@ export const PATCH_ACTIONS = {
|
|
|
148
167
|
target: { elementRef: r },
|
|
149
168
|
declarations: a.declarations,
|
|
150
169
|
important: a.important,
|
|
170
|
+
patchId: a.patchId,
|
|
151
171
|
verify: a.verify,
|
|
152
172
|
}),
|
|
153
173
|
},
|
|
@@ -160,16 +180,22 @@ export const PATCH_ACTIONS = {
|
|
|
160
180
|
const opMap = {
|
|
161
181
|
setAttribute: 'set_attribute',
|
|
162
182
|
removeAttribute: 'remove_attribute',
|
|
163
|
-
addClass: '
|
|
164
|
-
removeClass: '
|
|
183
|
+
addClass: 'add_class',
|
|
184
|
+
removeClass: 'remove_class',
|
|
165
185
|
setTextContent: 'set_text',
|
|
166
186
|
setProperty: 'set_attribute',
|
|
167
187
|
};
|
|
188
|
+
const normalizedOperation = opMap[operation] || operation;
|
|
189
|
+
const value =
|
|
190
|
+
normalizedOperation === 'add_class' || normalizedOperation === 'remove_class'
|
|
191
|
+
? (a.value ?? a.name)
|
|
192
|
+
: a.value;
|
|
168
193
|
return {
|
|
169
194
|
target: { elementRef: r },
|
|
170
|
-
operation:
|
|
171
|
-
value
|
|
195
|
+
operation: normalizedOperation,
|
|
196
|
+
value,
|
|
172
197
|
name: a.name,
|
|
198
|
+
patchId: a.patchId,
|
|
173
199
|
verify: a.verify,
|
|
174
200
|
};
|
|
175
201
|
},
|
|
@@ -192,5 +218,34 @@ export const PATCH_ACTIONS = {
|
|
|
192
218
|
* @returns {Promise<ToolResult>}
|
|
193
219
|
*/
|
|
194
220
|
export async function handlePatchTool(args) {
|
|
221
|
+
if (args.action === 'apply_styles' && !hasStringRecord(args.declarations)) {
|
|
222
|
+
return summarizeToolError('declarations are required for patch.apply_styles.');
|
|
223
|
+
}
|
|
224
|
+
if (args.action === 'apply_dom' && !hasText(args.operation)) {
|
|
225
|
+
return summarizeToolError('operation is required for patch.apply_dom.');
|
|
226
|
+
}
|
|
227
|
+
if (args.action === 'rollback' && !hasText(args.patchId)) {
|
|
228
|
+
return summarizeToolError('patchId is required for patch.rollback.');
|
|
229
|
+
}
|
|
195
230
|
return dispatchToolAction(PATCH_ACTIONS, args, 'patch');
|
|
196
231
|
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* @param {unknown} value
|
|
235
|
+
* @returns {boolean}
|
|
236
|
+
*/
|
|
237
|
+
function hasText(value) {
|
|
238
|
+
return typeof value === 'string' && value.trim().length > 0;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* @param {unknown} value
|
|
243
|
+
* @returns {value is Record<string, string>}
|
|
244
|
+
*/
|
|
245
|
+
function hasStringRecord(value) {
|
|
246
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
const entries = Object.entries(/** @type {Record<string, unknown>} */ (value));
|
|
250
|
+
return entries.length > 0 && entries.every(([key, val]) => key.trim() && typeof val === 'string');
|
|
251
|
+
}
|
|
@@ -61,17 +61,37 @@ export const NAVIGATION_ACTIONS = {
|
|
|
61
61
|
},
|
|
62
62
|
resize: {
|
|
63
63
|
method: 'viewport.resize',
|
|
64
|
-
params: (a) => ({
|
|
64
|
+
params: (a) => ({
|
|
65
|
+
width: a.width,
|
|
66
|
+
height: a.height,
|
|
67
|
+
deviceScaleFactor: a.deviceScaleFactor,
|
|
68
|
+
reset: a.reset,
|
|
69
|
+
}),
|
|
65
70
|
},
|
|
66
71
|
};
|
|
67
72
|
|
|
68
73
|
/**
|
|
69
|
-
* @param {{ action: string, url?: string, waitForLoad?: boolean, timeoutMs?: number, top?: number, left?: number, behavior?: string, relative?: boolean, width?: number, height?: number, reset?: boolean, tabId?: number, budgetPreset?: 'quick' | 'normal' | 'deep' }} args
|
|
74
|
+
* @param {{ action: string, url?: string, waitForLoad?: boolean, timeoutMs?: number, top?: number, left?: number, behavior?: string, relative?: boolean, width?: number, height?: number, deviceScaleFactor?: number, reset?: boolean, tabId?: number, budgetPreset?: 'quick' | 'normal' | 'deep' }} args
|
|
70
75
|
* @returns {Promise<ToolResult>}
|
|
71
76
|
*/
|
|
72
77
|
export async function handleNavigationTool(args) {
|
|
73
78
|
const entry = NAVIGATION_ACTIONS[args.action];
|
|
74
79
|
if (!entry) return summarizeToolError(`Unsupported navigation action "${args.action}".`);
|
|
80
|
+
if (args.action === 'navigate' && (typeof args.url !== 'string' || !args.url.trim())) {
|
|
81
|
+
return summarizeToolError('url is required for navigation.navigate.');
|
|
82
|
+
}
|
|
83
|
+
if (
|
|
84
|
+
args.action === 'resize' &&
|
|
85
|
+
args.reset !== true &&
|
|
86
|
+
(typeof args.width !== 'number' ||
|
|
87
|
+
!Number.isFinite(args.width) ||
|
|
88
|
+
typeof args.height !== 'number' ||
|
|
89
|
+
!Number.isFinite(args.height))
|
|
90
|
+
) {
|
|
91
|
+
return summarizeToolError(
|
|
92
|
+
'width and height are required for viewport.resize unless reset=true.'
|
|
93
|
+
);
|
|
94
|
+
}
|
|
75
95
|
return callBridgeTool(entry.method, entry.params(args), {
|
|
76
96
|
tabId: typeof args.tabId === 'number' ? args.tabId : null,
|
|
77
97
|
tokenBudget: getToolTokenBudget(args),
|
|
@@ -8,12 +8,11 @@ import {
|
|
|
8
8
|
import {
|
|
9
9
|
annotateBridgeSummary,
|
|
10
10
|
applyLimitBudgetPreset,
|
|
11
|
-
|
|
11
|
+
applyPageTextBudgetPreset,
|
|
12
12
|
bridgeMethodNeedsTab,
|
|
13
13
|
callBridgeTool,
|
|
14
14
|
createToolResult,
|
|
15
15
|
getToolTokenBudget,
|
|
16
|
-
requestBridge,
|
|
17
16
|
requestBridgeWithRetry,
|
|
18
17
|
summarizeBatchErrorItem,
|
|
19
18
|
summarizeBatchResponseItem,
|
|
@@ -72,7 +71,7 @@ export const PAGE_ACTIONS = {
|
|
|
72
71
|
export async function handlePageTool(args) {
|
|
73
72
|
let normalizedArgs = args;
|
|
74
73
|
if (args.action === 'text') {
|
|
75
|
-
normalizedArgs =
|
|
74
|
+
normalizedArgs = applyPageTextBudgetPreset(args);
|
|
76
75
|
} else if (args.action === 'console') {
|
|
77
76
|
normalizedArgs = applyLimitBudgetPreset(args, {
|
|
78
77
|
quick: 10,
|
|
@@ -147,14 +146,10 @@ export async function handleBatchTool(args) {
|
|
|
147
146
|
|
|
148
147
|
const startTime = Date.now();
|
|
149
148
|
try {
|
|
150
|
-
const response = await client.
|
|
151
|
-
method,
|
|
152
|
-
params: call.params || {},
|
|
149
|
+
const response = await requestBridgeWithRetry(client, method, call.params || {}, {
|
|
153
150
|
tabId,
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
...(tokenBudget != null ? { token_budget: tokenBudget } : {}),
|
|
157
|
-
},
|
|
151
|
+
source: REQUEST_SOURCE,
|
|
152
|
+
tokenBudget,
|
|
158
153
|
});
|
|
159
154
|
return summarizeBatchResponseItem({
|
|
160
155
|
method,
|
|
@@ -199,7 +194,7 @@ export async function handleRawCallTool(args) {
|
|
|
199
194
|
}
|
|
200
195
|
|
|
201
196
|
return withToolClient(async (client) => {
|
|
202
|
-
const response = await
|
|
197
|
+
const response = await requestBridgeWithRetry(
|
|
203
198
|
client,
|
|
204
199
|
/** @type {BridgeMethod} */ (args.method),
|
|
205
200
|
args.params || {},
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import {
|
|
4
4
|
bridgeMethodNeedsTab,
|
|
5
5
|
DEFAULT_MAX_HTML_LENGTH,
|
|
6
|
+
DEFAULT_PAGE_TEXT_BUDGET,
|
|
6
7
|
estimateJsonPayloadCost,
|
|
7
8
|
getBudgetPreset,
|
|
8
9
|
getErrorRecovery,
|
|
@@ -23,6 +24,52 @@ import { annotateBridgeSummary, summarizeBridgeResponse } from '../../agent-clie
|
|
|
23
24
|
|
|
24
25
|
export const REQUEST_SOURCE = 'mcp';
|
|
25
26
|
|
|
27
|
+
/** @type {ReadonlySet<BridgeMethod>} */
|
|
28
|
+
const RETRY_SAFE_METHODS = new Set([
|
|
29
|
+
'skill.get_runtime_context',
|
|
30
|
+
'setup.get_status',
|
|
31
|
+
'log.tail',
|
|
32
|
+
'health.ping',
|
|
33
|
+
'daemon.metrics',
|
|
34
|
+
'tabs.list',
|
|
35
|
+
'page.get_state',
|
|
36
|
+
'page.get_storage',
|
|
37
|
+
'page.get_text',
|
|
38
|
+
'dom.query',
|
|
39
|
+
'dom.describe',
|
|
40
|
+
'dom.get_text',
|
|
41
|
+
'dom.get_attributes',
|
|
42
|
+
'dom.wait_for',
|
|
43
|
+
'dom.find_by_text',
|
|
44
|
+
'dom.find_by_role',
|
|
45
|
+
'dom.get_html',
|
|
46
|
+
'dom.get_accessibility_tree',
|
|
47
|
+
'layout.get_box_model',
|
|
48
|
+
'layout.hit_test',
|
|
49
|
+
'styles.get_computed',
|
|
50
|
+
'styles.get_matched_rules',
|
|
51
|
+
'screenshot.capture_region',
|
|
52
|
+
'screenshot.capture_element',
|
|
53
|
+
'screenshot.capture_full_page',
|
|
54
|
+
'performance.get_metrics',
|
|
55
|
+
'cdp.get_document',
|
|
56
|
+
'cdp.get_dom_snapshot',
|
|
57
|
+
'cdp.get_box_model',
|
|
58
|
+
'cdp.get_computed_styles_for_node',
|
|
59
|
+
]);
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @param {BridgeMethod} method
|
|
63
|
+
* @param {Record<string, unknown>} params
|
|
64
|
+
* @returns {boolean}
|
|
65
|
+
*/
|
|
66
|
+
export function isRetrySafeBridgeMethod(method, params) {
|
|
67
|
+
if (method === 'page.get_console' || method === 'page.get_network') {
|
|
68
|
+
return params.clear !== true;
|
|
69
|
+
}
|
|
70
|
+
return RETRY_SAFE_METHODS.has(method);
|
|
71
|
+
}
|
|
72
|
+
|
|
26
73
|
/**
|
|
27
74
|
* @typedef {{
|
|
28
75
|
* content: Array<{ type: 'text', text: string }>,
|
|
@@ -176,6 +223,27 @@ export function applyTextBudgetPreset(args) {
|
|
|
176
223
|
});
|
|
177
224
|
}
|
|
178
225
|
|
|
226
|
+
/**
|
|
227
|
+
* @template {{ budgetPreset?: unknown, textBudget?: unknown }} T
|
|
228
|
+
* @param {T} args
|
|
229
|
+
* @returns {T}
|
|
230
|
+
*/
|
|
231
|
+
export function applyPageTextBudgetPreset(args) {
|
|
232
|
+
const presetName = getBudgetPresetName(args.budgetPreset);
|
|
233
|
+
if (!presetName) {
|
|
234
|
+
return args;
|
|
235
|
+
}
|
|
236
|
+
const textBudgetByPreset = {
|
|
237
|
+
quick: 2000,
|
|
238
|
+
normal: DEFAULT_PAGE_TEXT_BUDGET,
|
|
239
|
+
deep: DEFAULT_PAGE_TEXT_BUDGET * 2,
|
|
240
|
+
};
|
|
241
|
+
return /** @type {T} */ ({
|
|
242
|
+
...args,
|
|
243
|
+
textBudget: args.textBudget ?? textBudgetByPreset[presetName],
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
179
247
|
/**
|
|
180
248
|
* @template {{ budgetPreset?: unknown, limit?: unknown }} T
|
|
181
249
|
* @param {T} args
|
|
@@ -224,7 +292,7 @@ export function applyHtmlBudgetPreset(args) {
|
|
|
224
292
|
export async function requestBridgeWithRetry(client, method, params, options) {
|
|
225
293
|
const response = await requestBridge(client, method, params, options);
|
|
226
294
|
const recovery = !response.ok && response.error ? getErrorRecovery(response.error.code) : null;
|
|
227
|
-
if (!response.ok && recovery?.retry) {
|
|
295
|
+
if (!response.ok && recovery?.retry && isRetrySafeBridgeMethod(method, params)) {
|
|
228
296
|
const delay = recovery.retryAfterMs ?? 1000;
|
|
229
297
|
process.stderr.write(
|
|
230
298
|
`[bbx-mcp] Retrying ${method} after ${delay}ms (${response.error.code})\n`
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
|
|
3
5
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
4
6
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
5
7
|
// zod is required at runtime by @modelcontextprotocol/sdk for tool parameter schema
|
|
@@ -38,11 +40,27 @@ import {
|
|
|
38
40
|
getMethodsByMaxComplexity,
|
|
39
41
|
} from '../../protocol/src/index.js';
|
|
40
42
|
import { applyWindowsTcpTransportDefaults } from '../../native-host/src/config.js';
|
|
43
|
+
import { MCP_SERVER_INSTRUCTIONS, registerBridgeMcpGuidance } from './guidance.js';
|
|
41
44
|
|
|
42
45
|
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.`;
|
|
43
46
|
export const TAB_ID_DESCRIPTION =
|
|
44
47
|
'Target a specific tab instead of the active tab in the enabled window.';
|
|
45
48
|
|
|
49
|
+
const MCP_SERVER_VERSION = loadPackageVersion();
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @returns {string}
|
|
53
|
+
*/
|
|
54
|
+
function loadPackageVersion() {
|
|
55
|
+
try {
|
|
56
|
+
const raw = fs.readFileSync(new URL('../../../package.json', import.meta.url), 'utf8');
|
|
57
|
+
const parsed = JSON.parse(raw);
|
|
58
|
+
return parsed && typeof parsed.version === 'string' ? parsed.version : '0.0.0';
|
|
59
|
+
} catch {
|
|
60
|
+
return '0.0.0';
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
46
64
|
/** @type {readonly import('../../protocol/src/types.js').BridgeMethod[]} */
|
|
47
65
|
const INVESTIGATE_SUBAGENT_BRIDGE_METHODS = Object.freeze(
|
|
48
66
|
getMethodsByMaxComplexity('low').filter(
|
|
@@ -75,10 +93,15 @@ const INVESTIGATE_DELEGATION_HINT = Object.freeze({
|
|
|
75
93
|
* @returns {McpServer}
|
|
76
94
|
*/
|
|
77
95
|
export function createBridgeMcpServer() {
|
|
78
|
-
const server = new McpServer(
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
96
|
+
const server = new McpServer(
|
|
97
|
+
{
|
|
98
|
+
name: 'browser-bridge',
|
|
99
|
+
version: MCP_SERVER_VERSION,
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
instructions: MCP_SERVER_INSTRUCTIONS,
|
|
103
|
+
}
|
|
104
|
+
);
|
|
82
105
|
|
|
83
106
|
server.registerTool(
|
|
84
107
|
'browser_status',
|
|
@@ -114,6 +137,8 @@ export function createBridgeMcpServer() {
|
|
|
114
137
|
inputSchema: {
|
|
115
138
|
limit: z
|
|
116
139
|
.number()
|
|
140
|
+
.int()
|
|
141
|
+
.positive()
|
|
117
142
|
.optional()
|
|
118
143
|
.describe(`Maximum log entries to return (default: ${DEFAULT_CONSOLE_LIMIT})`),
|
|
119
144
|
budgetPreset: z
|
|
@@ -147,7 +172,7 @@ export function createBridgeMcpServer() {
|
|
|
147
172
|
.describe('"list" (preferred), "create" (only when needed), or "close"'),
|
|
148
173
|
url: z.string().optional().describe('URL for create action'),
|
|
149
174
|
active: z.boolean().optional().describe('Focus the new tab (default: true)'),
|
|
150
|
-
tabId: z.number().optional().describe('Tab ID (required for close)'),
|
|
175
|
+
tabId: z.number().int().positive().optional().describe('Tab ID (required for close)'),
|
|
151
176
|
},
|
|
152
177
|
},
|
|
153
178
|
handleTabsTool
|
|
@@ -173,7 +198,7 @@ export function createBridgeMcpServer() {
|
|
|
173
198
|
'accessibility_tree',
|
|
174
199
|
])
|
|
175
200
|
.describe('DOM operation to perform'),
|
|
176
|
-
tabId: z.number().optional().describe(TAB_ID_DESCRIPTION),
|
|
201
|
+
tabId: z.number().int().positive().optional().describe(TAB_ID_DESCRIPTION),
|
|
177
202
|
budgetPreset: z
|
|
178
203
|
.enum(['quick', 'normal', 'deep'])
|
|
179
204
|
.optional()
|
|
@@ -189,14 +214,20 @@ export function createBridgeMcpServer() {
|
|
|
189
214
|
withinRef: z.string().optional().describe('Scope query to this elementRef subtree'),
|
|
190
215
|
maxNodes: z
|
|
191
216
|
.number()
|
|
217
|
+
.int()
|
|
218
|
+
.positive()
|
|
192
219
|
.optional()
|
|
193
220
|
.describe(`Maximum nodes to return (default: ${DEFAULT_MAX_NODES})`),
|
|
194
221
|
maxDepth: z
|
|
195
222
|
.number()
|
|
223
|
+
.int()
|
|
224
|
+
.positive()
|
|
196
225
|
.optional()
|
|
197
226
|
.describe(`Maximum tree depth (default: ${DEFAULT_MAX_DEPTH})`),
|
|
198
227
|
textBudget: z
|
|
199
228
|
.number()
|
|
229
|
+
.int()
|
|
230
|
+
.positive()
|
|
200
231
|
.optional()
|
|
201
232
|
.describe(`Max chars of text content per node (default: ${DEFAULT_TEXT_BUDGET})`),
|
|
202
233
|
includeBbox: z
|
|
@@ -216,7 +247,12 @@ export function createBridgeMcpServer() {
|
|
|
216
247
|
.boolean()
|
|
217
248
|
.optional()
|
|
218
249
|
.describe('Require exact text match (default: false, substring match)'),
|
|
219
|
-
maxResults: z
|
|
250
|
+
maxResults: z
|
|
251
|
+
.number()
|
|
252
|
+
.int()
|
|
253
|
+
.positive()
|
|
254
|
+
.optional()
|
|
255
|
+
.describe('Maximum search results (default: 10)'),
|
|
220
256
|
role: z.string().optional().describe('ARIA role to search for (for find_role action)'),
|
|
221
257
|
name: z.string().optional().describe('Accessible name to match with role'),
|
|
222
258
|
state: z
|
|
@@ -225,6 +261,8 @@ export function createBridgeMcpServer() {
|
|
|
225
261
|
.describe('Expected element state (for wait action)'),
|
|
226
262
|
timeoutMs: z
|
|
227
263
|
.number()
|
|
264
|
+
.int()
|
|
265
|
+
.positive()
|
|
228
266
|
.optional()
|
|
229
267
|
.describe(`Timeout for wait operations (default: ${DEFAULT_WAIT_TIMEOUT_MS})`),
|
|
230
268
|
outer: z
|
|
@@ -233,6 +271,8 @@ export function createBridgeMcpServer() {
|
|
|
233
271
|
.describe('Return outerHTML instead of innerHTML (default: false)'),
|
|
234
272
|
maxLength: z
|
|
235
273
|
.number()
|
|
274
|
+
.int()
|
|
275
|
+
.positive()
|
|
236
276
|
.optional()
|
|
237
277
|
.describe(`Max HTML chars to return (default: ${DEFAULT_MAX_HTML_LENGTH})`),
|
|
238
278
|
},
|
|
@@ -250,7 +290,7 @@ export function createBridgeMcpServer() {
|
|
|
250
290
|
action: z
|
|
251
291
|
.enum(['computed', 'matched_rules', 'box_model', 'hit_test'])
|
|
252
292
|
.describe('Style/layout operation to perform'),
|
|
253
|
-
tabId: z.number().optional().describe(TAB_ID_DESCRIPTION),
|
|
293
|
+
tabId: z.number().int().positive().optional().describe(TAB_ID_DESCRIPTION),
|
|
254
294
|
budgetPreset: z
|
|
255
295
|
.enum(['quick', 'normal', 'deep'])
|
|
256
296
|
.optional()
|
|
@@ -261,8 +301,16 @@ export function createBridgeMcpServer() {
|
|
|
261
301
|
.array(z.string())
|
|
262
302
|
.optional()
|
|
263
303
|
.describe('Style properties to fetch (omitting returns all - expensive)'),
|
|
264
|
-
x: z
|
|
265
|
-
|
|
304
|
+
x: z
|
|
305
|
+
.number()
|
|
306
|
+
.nonnegative()
|
|
307
|
+
.optional()
|
|
308
|
+
.describe('X coordinate for hit_test (viewport relative)'),
|
|
309
|
+
y: z
|
|
310
|
+
.number()
|
|
311
|
+
.nonnegative()
|
|
312
|
+
.optional()
|
|
313
|
+
.describe('Y coordinate for hit_test (viewport relative)'),
|
|
266
314
|
},
|
|
267
315
|
},
|
|
268
316
|
handleStylesLayoutTool
|
|
@@ -287,7 +335,7 @@ export function createBridgeMcpServer() {
|
|
|
287
335
|
'performance',
|
|
288
336
|
])
|
|
289
337
|
.describe('Page operation to perform'),
|
|
290
|
-
tabId: z.number().optional().describe(TAB_ID_DESCRIPTION),
|
|
338
|
+
tabId: z.number().int().positive().optional().describe(TAB_ID_DESCRIPTION),
|
|
291
339
|
budgetPreset: z
|
|
292
340
|
.enum(['quick', 'normal', 'deep'])
|
|
293
341
|
.optional()
|
|
@@ -299,6 +347,8 @@ export function createBridgeMcpServer() {
|
|
|
299
347
|
awaitPromise: z.boolean().optional().describe('Await returned promises (default: false)'),
|
|
300
348
|
timeoutMs: z
|
|
301
349
|
.number()
|
|
350
|
+
.int()
|
|
351
|
+
.positive()
|
|
302
352
|
.optional()
|
|
303
353
|
.describe(`Timeout for evaluate/wait operations (default: ${DEFAULT_WAIT_TIMEOUT_MS})`),
|
|
304
354
|
returnByValue: z
|
|
@@ -312,6 +362,8 @@ export function createBridgeMcpServer() {
|
|
|
312
362
|
clear: z.boolean().optional().describe('Clear buffer after reading (default: false)'),
|
|
313
363
|
limit: z
|
|
314
364
|
.number()
|
|
365
|
+
.int()
|
|
366
|
+
.positive()
|
|
315
367
|
.optional()
|
|
316
368
|
.describe(`Maximum entries to return (default: ${DEFAULT_CONSOLE_LIMIT})`),
|
|
317
369
|
type: z
|
|
@@ -324,6 +376,8 @@ export function createBridgeMcpServer() {
|
|
|
324
376
|
.describe('Specific storage keys to fetch (omitting returns all)'),
|
|
325
377
|
textBudget: z
|
|
326
378
|
.number()
|
|
379
|
+
.int()
|
|
380
|
+
.positive()
|
|
327
381
|
.optional()
|
|
328
382
|
.describe(`Max chars for page text (default: ${DEFAULT_PAGE_TEXT_BUDGET})`),
|
|
329
383
|
urlPattern: z.string().optional().describe('Filter network entries by URL pattern'),
|
|
@@ -342,14 +396,19 @@ export function createBridgeMcpServer() {
|
|
|
342
396
|
action: z
|
|
343
397
|
.enum(['navigate', 'reload', 'go_back', 'go_forward', 'scroll', 'resize'])
|
|
344
398
|
.describe('Navigation operation to perform'),
|
|
345
|
-
tabId: z.number().optional().describe(TAB_ID_DESCRIPTION),
|
|
399
|
+
tabId: z.number().int().positive().optional().describe(TAB_ID_DESCRIPTION),
|
|
346
400
|
budgetPreset: z
|
|
347
401
|
.enum(['quick', 'normal', 'deep'])
|
|
348
402
|
.optional()
|
|
349
403
|
.describe(BUDGET_PRESET_DESCRIPTION),
|
|
350
404
|
url: z.string().optional().describe('URL to navigate to (for navigate action)'),
|
|
351
405
|
waitForLoad: z.boolean().optional().describe('Wait for load event (default: true)'),
|
|
352
|
-
timeoutMs: z
|
|
406
|
+
timeoutMs: z
|
|
407
|
+
.number()
|
|
408
|
+
.int()
|
|
409
|
+
.positive()
|
|
410
|
+
.optional()
|
|
411
|
+
.describe('Timeout for navigation (default: 30000)'),
|
|
353
412
|
top: z.number().optional().describe('Scroll target Y position (pixels)'),
|
|
354
413
|
left: z.number().optional().describe('Scroll target X position (pixels)'),
|
|
355
414
|
behavior: z.enum(['auto', 'smooth']).optional().describe('Scroll behavior (default: auto)'),
|
|
@@ -357,8 +416,13 @@ export function createBridgeMcpServer() {
|
|
|
357
416
|
.boolean()
|
|
358
417
|
.optional()
|
|
359
418
|
.describe('Scroll relative to current position (default: false)'),
|
|
360
|
-
width: z.number().optional().describe('Viewport width in pixels'),
|
|
361
|
-
height: z.number().optional().describe('Viewport height in pixels'),
|
|
419
|
+
width: z.number().int().positive().optional().describe('Viewport width in pixels'),
|
|
420
|
+
height: z.number().int().positive().optional().describe('Viewport height in pixels'),
|
|
421
|
+
deviceScaleFactor: z
|
|
422
|
+
.number()
|
|
423
|
+
.nonnegative()
|
|
424
|
+
.optional()
|
|
425
|
+
.describe('Viewport device scale factor (for resize)'),
|
|
362
426
|
reset: z.boolean().optional().describe('Reset viewport to original size (for resize)'),
|
|
363
427
|
},
|
|
364
428
|
},
|
|
@@ -386,7 +450,7 @@ export function createBridgeMcpServer() {
|
|
|
386
450
|
'scroll_into_view',
|
|
387
451
|
])
|
|
388
452
|
.describe('Input operation to perform'),
|
|
389
|
-
tabId: z.number().optional().describe(TAB_ID_DESCRIPTION),
|
|
453
|
+
tabId: z.number().int().positive().optional().describe(TAB_ID_DESCRIPTION),
|
|
390
454
|
budgetPreset: z
|
|
391
455
|
.enum(['quick', 'normal', 'deep'])
|
|
392
456
|
.optional()
|
|
@@ -400,7 +464,13 @@ export function createBridgeMcpServer() {
|
|
|
400
464
|
.enum(['left', 'middle', 'right'])
|
|
401
465
|
.optional()
|
|
402
466
|
.describe('Mouse button for click (default: left)'),
|
|
403
|
-
clickCount: z
|
|
467
|
+
clickCount: z
|
|
468
|
+
.number()
|
|
469
|
+
.int()
|
|
470
|
+
.min(1)
|
|
471
|
+
.max(2)
|
|
472
|
+
.optional()
|
|
473
|
+
.describe('Click count (1=single, 2=double)'),
|
|
404
474
|
text: z.string().max(100000).optional().describe('Text to type (for type action)'),
|
|
405
475
|
clear: z.boolean().optional().describe('Clear field before typing (default: false)'),
|
|
406
476
|
submit: z.boolean().optional().describe('Press Enter after typing (default: false)'),
|
|
@@ -423,10 +493,15 @@ export function createBridgeMcpServer() {
|
|
|
423
493
|
.optional()
|
|
424
494
|
.describe('Option labels to select (alternative to values)'),
|
|
425
495
|
indexes: z
|
|
426
|
-
.array(z.number())
|
|
496
|
+
.array(z.number().int().nonnegative())
|
|
427
497
|
.optional()
|
|
428
498
|
.describe('Option indexes to select (alternative to values/labels)'),
|
|
429
|
-
duration: z
|
|
499
|
+
duration: z
|
|
500
|
+
.number()
|
|
501
|
+
.int()
|
|
502
|
+
.nonnegative()
|
|
503
|
+
.optional()
|
|
504
|
+
.describe('Hover duration in ms (default: 100)'),
|
|
430
505
|
sourceElementRef: z.string().optional().describe('Drag source element (for drag action)'),
|
|
431
506
|
sourceSelector: z
|
|
432
507
|
.string()
|
|
@@ -457,7 +532,7 @@ export function createBridgeMcpServer() {
|
|
|
457
532
|
action: z
|
|
458
533
|
.enum(['apply_styles', 'apply_dom', 'list', 'rollback', 'commit_baseline'])
|
|
459
534
|
.describe('Patch operation to perform'),
|
|
460
|
-
tabId: z.number().optional().describe(TAB_ID_DESCRIPTION),
|
|
535
|
+
tabId: z.number().int().positive().optional().describe(TAB_ID_DESCRIPTION),
|
|
461
536
|
budgetPreset: z
|
|
462
537
|
.enum(['quick', 'normal', 'deep'])
|
|
463
538
|
.optional()
|
|
@@ -517,7 +592,7 @@ export function createBridgeMcpServer() {
|
|
|
517
592
|
.describe(
|
|
518
593
|
'element (preferred), region (tight crop), full_page (document-level only), or cdp_* for low-level data'
|
|
519
594
|
),
|
|
520
|
-
tabId: z.number().optional().describe(TAB_ID_DESCRIPTION),
|
|
595
|
+
tabId: z.number().int().positive().optional().describe(TAB_ID_DESCRIPTION),
|
|
521
596
|
budgetPreset: z
|
|
522
597
|
.enum(['quick', 'normal', 'deep'])
|
|
523
598
|
.optional()
|
|
@@ -527,12 +602,18 @@ export function createBridgeMcpServer() {
|
|
|
527
602
|
.optional()
|
|
528
603
|
.describe('Element reference (for element action, preferred)'),
|
|
529
604
|
selector: z.string().optional().describe('CSS selector (used if no elementRef)'),
|
|
605
|
+
nodeId: z
|
|
606
|
+
.number()
|
|
607
|
+
.int()
|
|
608
|
+
.positive()
|
|
609
|
+
.optional()
|
|
610
|
+
.describe('CDP node id for cdp_box_model/cdp_computed_styles'),
|
|
530
611
|
rect: z
|
|
531
612
|
.object({
|
|
532
|
-
x: z.number().describe('Region left edge (viewport pixels)'),
|
|
533
|
-
y: z.number().describe('Region top edge (viewport pixels)'),
|
|
534
|
-
width: z.number().describe('Region width (pixels)'),
|
|
535
|
-
height: z.number().describe('Region height (pixels)'),
|
|
613
|
+
x: z.number().nonnegative().describe('Region left edge (viewport pixels)'),
|
|
614
|
+
y: z.number().nonnegative().describe('Region top edge (viewport pixels)'),
|
|
615
|
+
width: z.number().positive().describe('Region width (pixels)'),
|
|
616
|
+
height: z.number().positive().describe('Region height (pixels)'),
|
|
536
617
|
})
|
|
537
618
|
.optional()
|
|
538
619
|
.describe('Viewport region for region action (keep crop tight)'),
|
|
@@ -556,7 +637,7 @@ export function createBridgeMcpServer() {
|
|
|
556
637
|
.record(z.string(), z.unknown())
|
|
557
638
|
.optional()
|
|
558
639
|
.describe('Method params for this call'),
|
|
559
|
-
tabId: z.number().optional().describe(TAB_ID_DESCRIPTION),
|
|
640
|
+
tabId: z.number().int().positive().optional().describe(TAB_ID_DESCRIPTION),
|
|
560
641
|
budgetPreset: z
|
|
561
642
|
.enum(['quick', 'normal', 'deep'])
|
|
562
643
|
.optional()
|
|
@@ -582,7 +663,7 @@ export function createBridgeMcpServer() {
|
|
|
582
663
|
.record(z.string(), z.unknown())
|
|
583
664
|
.optional()
|
|
584
665
|
.describe('Method parameters as object'),
|
|
585
|
-
tabId: z.number().optional().describe(TAB_ID_DESCRIPTION),
|
|
666
|
+
tabId: z.number().int().positive().optional().describe(TAB_ID_DESCRIPTION),
|
|
586
667
|
},
|
|
587
668
|
},
|
|
588
669
|
handleRawCallTool
|
|
@@ -644,7 +725,7 @@ export function createBridgeMcpServer() {
|
|
|
644
725
|
'"normal" (state + DOM + text, default), ' +
|
|
645
726
|
'"deep" (state + DOM + text + console + network).'
|
|
646
727
|
),
|
|
647
|
-
tabId: z.number().optional().describe(TAB_ID_DESCRIPTION),
|
|
728
|
+
tabId: z.number().int().positive().optional().describe(TAB_ID_DESCRIPTION),
|
|
648
729
|
selector: z
|
|
649
730
|
.string()
|
|
650
731
|
.optional()
|
|
@@ -654,6 +735,8 @@ export function createBridgeMcpServer() {
|
|
|
654
735
|
handleInvestigateTool
|
|
655
736
|
);
|
|
656
737
|
|
|
738
|
+
registerBridgeMcpGuidance(server);
|
|
739
|
+
|
|
657
740
|
return server;
|
|
658
741
|
}
|
|
659
742
|
|