@diegosouzacdv/jev-browser-mcp 0.2.0 → 0.3.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.
@@ -0,0 +1,38 @@
1
+ import { JevBrowserError } from "./config.mjs";
2
+
3
+ export const ACCESSIBILITY_STANDARDS = Object.freeze({
4
+ wcag2aa: Object.freeze(["wcag2a", "wcag2aa", "wcag21a", "wcag21aa"]),
5
+ });
6
+
7
+ export async function runAccessibilityAudit(page, standard, settings) {
8
+ const tags = ACCESSIBILITY_STANDARDS[standard];
9
+ if (!tags) throw new JevBrowserError("audit_accessibility standard must be wcag2aa");
10
+
11
+ let AxeBuilder;
12
+ try {
13
+ ({ default: AxeBuilder } = await import("@axe-core/playwright"));
14
+ } catch {
15
+ throw new JevBrowserError("accessibility auditing requires the installed @axe-core/playwright dependency");
16
+ }
17
+
18
+ let results;
19
+ try {
20
+ results = await new AxeBuilder({ page }).withTags([...tags]).analyze();
21
+ } catch {
22
+ throw new JevBrowserError("axe-core could not complete the accessibility audit");
23
+ }
24
+ const violations = Array.isArray(results?.violations) ? results.violations : [];
25
+ const incomplete = Array.isArray(results?.incomplete) ? results.incomplete : [];
26
+ return {
27
+ standard,
28
+ violations_count: violations.length,
29
+ violations_truncated: violations.length > settings.jev.maxAccessibilityViolations,
30
+ violations: violations.slice(0, settings.jev.maxAccessibilityViolations).map((violation) => ({
31
+ id: String(violation.id || "unknown").slice(0, 100),
32
+ impact: ["minor", "moderate", "serious", "critical"].includes(violation.impact) ? violation.impact : null,
33
+ help: String(violation.help || "").slice(0, settings.jev.maxDiagnosticChars),
34
+ nodes_count: Array.isArray(violation.nodes) ? violation.nodes.length : 0,
35
+ })),
36
+ incomplete_count: incomplete.length,
37
+ };
38
+ }
@@ -4,39 +4,43 @@ import path from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
 
6
6
  const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../..");
7
- const CONFIG_PATH = path.join(PACKAGE_ROOT, "config", "ui-testing.json");
8
- const CONFIG_KEYS = {
9
- root: ["version", "browser", "jev", "jev_browser_mcp"],
10
- browser: [
7
+ const CONFIG_PATH = path.join(PACKAGE_ROOT, "config", "ui-testing.json");
8
+ const CONFIG_KEYS = {
9
+ root: ["version", "browser", "jev", "jev_browser_mcp"],
10
+ browser: [
11
11
  "mode",
12
12
  "harness_browser",
13
13
  "computer_browser",
14
14
  "playwright_mcp_package",
15
- "computer_user_data_dir",
16
- "max_flow_steps",
17
- "max_text_entry_chars",
18
- ],
19
- nodeBrowser: [
20
- "max_action_timeout_seconds",
21
- "max_upload_files",
22
- "max_upload_path_chars",
23
- "max_upload_file_bytes",
24
- "max_upload_total_bytes",
25
- "upload_root_env",
26
- "artifact_directory",
27
- "artifact_directory_env",
28
- "max_frame_snapshots",
29
- "fast_path_single_plan_default",
30
- "screenshot_on_failure_default",
31
- "trace_on_failure_default",
32
- "capture_console_errors_default",
33
- "capture_network_errors_default",
34
- "block_trackers_default",
35
- "snapshot_scope_default",
36
- "tracker_host_suffixes",
37
- "blocked_resource_types",
15
+ "computer_user_data_dir",
16
+ "max_flow_steps",
17
+ "max_text_entry_chars",
38
18
  ],
39
- jev: [
19
+ nodeBrowser: [
20
+ "max_action_timeout_seconds",
21
+ "max_upload_files",
22
+ "max_upload_path_chars",
23
+ "max_upload_file_bytes",
24
+ "max_upload_total_bytes",
25
+ "max_download_files",
26
+ "max_download_file_bytes",
27
+ "max_download_total_bytes",
28
+ "max_download_timeout_seconds",
29
+ "upload_root_env",
30
+ "artifact_directory",
31
+ "artifact_directory_env",
32
+ "max_frame_snapshots",
33
+ "fast_path_single_plan_default",
34
+ "screenshot_on_failure_default",
35
+ "trace_on_failure_default",
36
+ "capture_console_errors_default",
37
+ "capture_network_errors_default",
38
+ "block_trackers_default",
39
+ "snapshot_scope_default",
40
+ "tracker_host_suffixes",
41
+ "blocked_resource_types",
42
+ ],
43
+ jev: [
40
44
  "provider_url",
41
45
  "credential_env",
42
46
  "model",
@@ -44,12 +48,13 @@ const CONFIG_KEYS = {
44
48
  "max_flow_chars",
45
49
  "max_snapshot_chars",
46
50
  "max_action_count",
47
- "max_action_description_chars",
48
- "max_response_bytes",
49
- ],
50
- nodeJev: [
51
- "max_diagnostic_items",
52
- "max_diagnostic_chars",
51
+ "max_action_description_chars",
52
+ "max_response_bytes",
53
+ ],
54
+ nodeJev: [
55
+ "max_diagnostic_items",
56
+ "max_diagnostic_chars",
57
+ "max_accessibility_violations",
53
58
  ],
54
59
  };
55
60
 
@@ -73,52 +78,52 @@ function requireKeys(value, keys, scope) {
73
78
  }
74
79
  }
75
80
 
76
- function positiveNumber(value, name, integer = false) {
81
+ function positiveNumber(value, name, integer = false) {
77
82
  const parsed = Number(value);
78
83
  if (!Number.isFinite(parsed) || parsed <= 0 || (integer && !Number.isInteger(parsed))) {
79
84
  throw new JevBrowserError(`config/ui-testing.json ${name} must be a positive ${integer ? "integer" : "number"}`);
80
85
  }
81
86
  return parsed;
82
- }
83
-
84
- function expandHomeDirectory(value) {
85
- if (value.startsWith("~/") || value.startsWith("~\\")) return path.join(homedir(), value.slice(2));
86
- return value;
87
- }
88
-
89
- function requireOutsidePackage(resolved, name) {
90
- const insidePackage = path.relative(PACKAGE_ROOT, resolved);
91
- if (insidePackage === "" || (!insidePackage.startsWith(`..${path.sep}`) && insidePackage !== ".." && !path.isAbsolute(insidePackage))) {
92
- throw new JevBrowserError(`${name} must be outside the installed package directory`);
93
- }
94
- return resolved;
95
- }
96
-
97
- function configuredDirectory(value, name) {
98
- if (typeof value !== "string" || !value.trim()) throw new JevBrowserError(`config/ui-testing.json ${name} must be a directory path`);
99
- const expanded = expandHomeDirectory(value.trim());
100
- if (!path.isAbsolute(expanded)) throw new JevBrowserError(`config/ui-testing.json ${name} must be absolute or start with ~`);
101
- return path.resolve(expanded);
102
- }
103
-
104
- function environmentName(value, name) {
105
- if (typeof value !== "string" || !/^[A-Z][A-Z0-9_]*$/.test(value)) {
106
- throw new JevBrowserError(`config/ui-testing.json ${name} must be an environment variable name`);
107
- }
108
- return value;
109
- }
110
-
111
- function booleanValue(value, name) {
112
- if (typeof value !== "boolean") throw new JevBrowserError(`config/ui-testing.json ${name} must be a boolean`);
113
- return value;
114
- }
115
-
116
- function stringList(value, name, validator = () => true) {
117
- if (!Array.isArray(value) || value.some((item) => typeof item !== "string" || !item.trim() || !validator(item))) {
118
- throw new JevBrowserError(`config/ui-testing.json ${name} must be an array of valid strings`);
119
- }
120
- return value.map((item) => item.trim());
121
- }
87
+ }
88
+
89
+ function expandHomeDirectory(value) {
90
+ if (value.startsWith("~/") || value.startsWith("~\\")) return path.join(homedir(), value.slice(2));
91
+ return value;
92
+ }
93
+
94
+ function requireOutsidePackage(resolved, name) {
95
+ const insidePackage = path.relative(PACKAGE_ROOT, resolved);
96
+ if (insidePackage === "" || (!insidePackage.startsWith(`..${path.sep}`) && insidePackage !== ".." && !path.isAbsolute(insidePackage))) {
97
+ throw new JevBrowserError(`${name} must be outside the installed package directory`);
98
+ }
99
+ return resolved;
100
+ }
101
+
102
+ function configuredDirectory(value, name) {
103
+ if (typeof value !== "string" || !value.trim()) throw new JevBrowserError(`config/ui-testing.json ${name} must be a directory path`);
104
+ const expanded = expandHomeDirectory(value.trim());
105
+ if (!path.isAbsolute(expanded)) throw new JevBrowserError(`config/ui-testing.json ${name} must be absolute or start with ~`);
106
+ return path.resolve(expanded);
107
+ }
108
+
109
+ function environmentName(value, name) {
110
+ if (typeof value !== "string" || !/^[A-Z][A-Z0-9_]*$/.test(value)) {
111
+ throw new JevBrowserError(`config/ui-testing.json ${name} must be an environment variable name`);
112
+ }
113
+ return value;
114
+ }
115
+
116
+ function booleanValue(value, name) {
117
+ if (typeof value !== "boolean") throw new JevBrowserError(`config/ui-testing.json ${name} must be a boolean`);
118
+ return value;
119
+ }
120
+
121
+ function stringList(value, name, validator = () => true) {
122
+ if (!Array.isArray(value) || value.some((item) => typeof item !== "string" || !item.trim() || !validator(item))) {
123
+ throw new JevBrowserError(`config/ui-testing.json ${name} must be an array of valid strings`);
124
+ }
125
+ return value.map((item) => item.trim());
126
+ }
122
127
 
123
128
  function validProviderUrl(value) {
124
129
  let parsed;
@@ -133,10 +138,10 @@ function validProviderUrl(value) {
133
138
  return parsed.toString();
134
139
  }
135
140
 
136
- function profileDirectory(template, channel, override) {
137
- const value = expandHomeDirectory(override?.trim() || template.replace("{browser}", channel));
138
- if (!path.isAbsolute(value)) throw new JevBrowserError("JEV_BROWSER_PROFILE must be an absolute path");
139
- return requireOutsidePackage(path.resolve(value), "browser profile");
141
+ function profileDirectory(template, channel, override) {
142
+ const value = expandHomeDirectory(override?.trim() || template.replace("{browser}", channel));
143
+ if (!path.isAbsolute(value)) throw new JevBrowserError("JEV_BROWSER_PROFILE must be an absolute path");
144
+ return requireOutsidePackage(path.resolve(value), "browser profile");
140
145
  }
141
146
 
142
147
  export function loadSettings({ env = process.env, configPath = CONFIG_PATH } = {}) {
@@ -146,14 +151,14 @@ export function loadSettings({ env = process.env, configPath = CONFIG_PATH } = {
146
151
  } catch {
147
152
  throw new JevBrowserError("config/ui-testing.json is unreadable or invalid JSON");
148
153
  }
149
- requireKeys(document, CONFIG_KEYS.root, "root");
150
- requireKeys(document.browser, CONFIG_KEYS.browser, "browser");
151
- requireKeys(document.jev, CONFIG_KEYS.jev, "jev");
152
- requireKeys(document.jev_browser_mcp, ["browser", "jev"], "jev_browser_mcp");
153
- requireKeys(document.jev_browser_mcp.browser, CONFIG_KEYS.nodeBrowser, "jev_browser_mcp.browser");
154
- requireKeys(document.jev_browser_mcp.jev, CONFIG_KEYS.nodeJev, "jev_browser_mcp.jev");
155
- const browserOptions = document.jev_browser_mcp.browser;
156
- const jevOptions = document.jev_browser_mcp.jev;
154
+ requireKeys(document, CONFIG_KEYS.root, "root");
155
+ requireKeys(document.browser, CONFIG_KEYS.browser, "browser");
156
+ requireKeys(document.jev, CONFIG_KEYS.jev, "jev");
157
+ requireKeys(document.jev_browser_mcp, ["browser", "jev"], "jev_browser_mcp");
158
+ requireKeys(document.jev_browser_mcp.browser, CONFIG_KEYS.nodeBrowser, "jev_browser_mcp.browser");
159
+ requireKeys(document.jev_browser_mcp.jev, CONFIG_KEYS.nodeJev, "jev_browser_mcp.jev");
160
+ const browserOptions = document.jev_browser_mcp.browser;
161
+ const jevOptions = document.jev_browser_mcp.jev;
157
162
  if (document.version !== 1) throw new JevBrowserError("config/ui-testing.json has an unsupported version");
158
163
 
159
164
  if (!new Set(["harness", "computer"]).has(document.browser.mode)) {
@@ -176,45 +181,45 @@ export function loadSettings({ env = process.env, configPath = CONFIG_PATH } = {
176
181
  if (!new Set(["chrome", "msedge"]).has(channel)) {
177
182
  throw new JevBrowserError("JEV_BROWSER_CHANNEL must be chrome or msedge");
178
183
  }
179
- const profileTemplate = document.browser.computer_user_data_dir;
184
+ const profileTemplate = document.browser.computer_user_data_dir;
180
185
  const profileRemainder = typeof profileTemplate === "string" ? profileTemplate.replace("{browser}", "") : "";
181
186
  if (typeof profileTemplate !== "string" || profileTemplate.split("{browser}").length !== 2
182
187
  || profileRemainder.includes("{") || profileRemainder.includes("}")) {
183
188
  throw new JevBrowserError("config/ui-testing.json browser.computer_user_data_dir must contain {browser}");
184
189
  }
185
190
 
186
- const configuredProviderUrl = validProviderUrl(document.jev.provider_url);
191
+ const configuredProviderUrl = validProviderUrl(document.jev.provider_url);
187
192
  const providerUrl = validProviderUrl(env.JEV_PROVIDER_URL?.trim() || configuredProviderUrl);
188
- const credentialEnv = environmentName(document.jev.credential_env, "jev.credential_env");
193
+ const credentialEnv = environmentName(document.jev.credential_env, "jev.credential_env");
189
194
  const configuredModel = String(document.jev.model || "").trim();
190
195
  if (!configuredModel) throw new JevBrowserError("config/ui-testing.json jev.model is required");
191
- const model = (env.JEV_MODEL?.trim() || configuredModel).trim();
192
- if (!model) throw new JevBrowserError("Jev model must not be empty");
193
-
194
- const artifactEnv = environmentName(browserOptions.artifact_directory_env, "jev_browser_mcp.browser.artifact_directory_env");
195
- const artifactDirectory = requireOutsidePackage(configuredDirectory(
196
- env[artifactEnv]?.trim() || browserOptions.artifact_directory,
197
- "jev_browser_mcp.browser.artifact_directory",
198
- ), "browser artifact directory");
199
- const uploadRootEnv = environmentName(browserOptions.upload_root_env, "jev_browser_mcp.browser.upload_root_env");
200
- const configuredUploadRoot = env[uploadRootEnv]?.trim();
201
- const uploadRoot = configuredUploadRoot
202
- ? configuredDirectory(configuredUploadRoot, `environment ${uploadRootEnv}`)
203
- : null;
204
- const snapshotScope = browserOptions.snapshot_scope_default;
205
- if (!new Set(["body", "main", "dialog"]).has(snapshotScope)) {
206
- throw new JevBrowserError("config/ui-testing.json jev_browser_mcp.browser.snapshot_scope_default must be body, main, or dialog");
207
- }
208
- const trackerHostSuffixes = stringList(
209
- browserOptions.tracker_host_suffixes,
210
- "jev_browser_mcp.browser.tracker_host_suffixes",
211
- (host) => /^[a-z0-9.-]+$/i.test(host) && !host.startsWith(".") && !host.endsWith("."),
212
- );
213
- const blockedResourceTypes = stringList(
214
- browserOptions.blocked_resource_types,
215
- "jev_browser_mcp.browser.blocked_resource_types",
216
- (type) => new Set(["font", "media", "image"]).has(type),
217
- );
196
+ const model = (env.JEV_MODEL?.trim() || configuredModel).trim();
197
+ if (!model) throw new JevBrowserError("Jev model must not be empty");
198
+
199
+ const artifactEnv = environmentName(browserOptions.artifact_directory_env, "jev_browser_mcp.browser.artifact_directory_env");
200
+ const artifactDirectory = requireOutsidePackage(configuredDirectory(
201
+ env[artifactEnv]?.trim() || browserOptions.artifact_directory,
202
+ "jev_browser_mcp.browser.artifact_directory",
203
+ ), "browser artifact directory");
204
+ const uploadRootEnv = environmentName(browserOptions.upload_root_env, "jev_browser_mcp.browser.upload_root_env");
205
+ const configuredUploadRoot = env[uploadRootEnv]?.trim();
206
+ const uploadRoot = configuredUploadRoot
207
+ ? configuredDirectory(configuredUploadRoot, `environment ${uploadRootEnv}`)
208
+ : null;
209
+ const snapshotScope = browserOptions.snapshot_scope_default;
210
+ if (!new Set(["body", "main", "dialog"]).has(snapshotScope)) {
211
+ throw new JevBrowserError("config/ui-testing.json jev_browser_mcp.browser.snapshot_scope_default must be body, main, or dialog");
212
+ }
213
+ const trackerHostSuffixes = stringList(
214
+ browserOptions.tracker_host_suffixes,
215
+ "jev_browser_mcp.browser.tracker_host_suffixes",
216
+ (host) => /^[a-z0-9.-]+$/i.test(host) && !host.startsWith(".") && !host.endsWith("."),
217
+ );
218
+ const blockedResourceTypes = stringList(
219
+ browserOptions.blocked_resource_types,
220
+ "jev_browser_mcp.browser.blocked_resource_types",
221
+ (type) => new Set(["font", "media", "image"]).has(type),
222
+ );
218
223
 
219
224
  return Object.freeze({
220
225
  env,
@@ -222,27 +227,31 @@ export function loadSettings({ env = process.env, configPath = CONFIG_PATH } = {
222
227
  mode,
223
228
  channel,
224
229
  profileDir: profileDirectory(profileTemplate, channel, env.JEV_BROWSER_PROFILE),
225
- maxFlowSteps: positiveNumber(document.browser.max_flow_steps, "browser.max_flow_steps", true),
226
- maxTextEntryChars: positiveNumber(document.browser.max_text_entry_chars, "browser.max_text_entry_chars", true),
227
- actionTimeoutMs: positiveNumber(browserOptions.max_action_timeout_seconds, "jev_browser_mcp.browser.max_action_timeout_seconds") * 1000,
228
- maxUploadFiles: positiveNumber(browserOptions.max_upload_files, "jev_browser_mcp.browser.max_upload_files", true),
229
- maxUploadPathChars: positiveNumber(browserOptions.max_upload_path_chars, "jev_browser_mcp.browser.max_upload_path_chars", true),
230
- maxUploadFileBytes: positiveNumber(browserOptions.max_upload_file_bytes, "jev_browser_mcp.browser.max_upload_file_bytes", true),
231
- maxUploadTotalBytes: positiveNumber(browserOptions.max_upload_total_bytes, "jev_browser_mcp.browser.max_upload_total_bytes", true),
232
- uploadRoot,
233
- artifactDirectory,
234
- maxFrameSnapshots: positiveNumber(browserOptions.max_frame_snapshots, "jev_browser_mcp.browser.max_frame_snapshots", true),
235
- defaults: Object.freeze({
236
- fastPath: booleanValue(browserOptions.fast_path_single_plan_default, "jev_browser_mcp.browser.fast_path_single_plan_default"),
237
- screenshotOnFailure: booleanValue(browserOptions.screenshot_on_failure_default, "jev_browser_mcp.browser.screenshot_on_failure_default"),
238
- traceOnFailure: booleanValue(browserOptions.trace_on_failure_default, "jev_browser_mcp.browser.trace_on_failure_default"),
239
- captureConsoleErrors: booleanValue(browserOptions.capture_console_errors_default, "jev_browser_mcp.browser.capture_console_errors_default"),
240
- captureNetworkErrors: booleanValue(browserOptions.capture_network_errors_default, "jev_browser_mcp.browser.capture_network_errors_default"),
241
- blockTrackers: booleanValue(browserOptions.block_trackers_default, "jev_browser_mcp.browser.block_trackers_default"),
242
- snapshotScope,
243
- }),
244
- trackerHostSuffixes: Object.freeze(trackerHostSuffixes),
245
- blockedResourceTypes: Object.freeze(blockedResourceTypes),
230
+ maxFlowSteps: positiveNumber(document.browser.max_flow_steps, "browser.max_flow_steps", true),
231
+ maxTextEntryChars: positiveNumber(document.browser.max_text_entry_chars, "browser.max_text_entry_chars", true),
232
+ actionTimeoutMs: positiveNumber(browserOptions.max_action_timeout_seconds, "jev_browser_mcp.browser.max_action_timeout_seconds") * 1000,
233
+ maxUploadFiles: positiveNumber(browserOptions.max_upload_files, "jev_browser_mcp.browser.max_upload_files", true),
234
+ maxUploadPathChars: positiveNumber(browserOptions.max_upload_path_chars, "jev_browser_mcp.browser.max_upload_path_chars", true),
235
+ maxUploadFileBytes: positiveNumber(browserOptions.max_upload_file_bytes, "jev_browser_mcp.browser.max_upload_file_bytes", true),
236
+ maxUploadTotalBytes: positiveNumber(browserOptions.max_upload_total_bytes, "jev_browser_mcp.browser.max_upload_total_bytes", true),
237
+ maxDownloadFiles: positiveNumber(browserOptions.max_download_files, "jev_browser_mcp.browser.max_download_files", true),
238
+ maxDownloadFileBytes: positiveNumber(browserOptions.max_download_file_bytes, "jev_browser_mcp.browser.max_download_file_bytes", true),
239
+ maxDownloadTotalBytes: positiveNumber(browserOptions.max_download_total_bytes, "jev_browser_mcp.browser.max_download_total_bytes", true),
240
+ downloadTimeoutMs: positiveNumber(browserOptions.max_download_timeout_seconds, "jev_browser_mcp.browser.max_download_timeout_seconds") * 1000,
241
+ uploadRoot,
242
+ artifactDirectory,
243
+ maxFrameSnapshots: positiveNumber(browserOptions.max_frame_snapshots, "jev_browser_mcp.browser.max_frame_snapshots", true),
244
+ defaults: Object.freeze({
245
+ fastPath: booleanValue(browserOptions.fast_path_single_plan_default, "jev_browser_mcp.browser.fast_path_single_plan_default"),
246
+ screenshotOnFailure: booleanValue(browserOptions.screenshot_on_failure_default, "jev_browser_mcp.browser.screenshot_on_failure_default"),
247
+ traceOnFailure: booleanValue(browserOptions.trace_on_failure_default, "jev_browser_mcp.browser.trace_on_failure_default"),
248
+ captureConsoleErrors: booleanValue(browserOptions.capture_console_errors_default, "jev_browser_mcp.browser.capture_console_errors_default"),
249
+ captureNetworkErrors: booleanValue(browserOptions.capture_network_errors_default, "jev_browser_mcp.browser.capture_network_errors_default"),
250
+ blockTrackers: booleanValue(browserOptions.block_trackers_default, "jev_browser_mcp.browser.block_trackers_default"),
251
+ snapshotScope,
252
+ }),
253
+ trackerHostSuffixes: Object.freeze(trackerHostSuffixes),
254
+ blockedResourceTypes: Object.freeze(blockedResourceTypes),
246
255
  }),
247
256
  jev: Object.freeze({
248
257
  providerUrl,
@@ -253,9 +262,10 @@ export function loadSettings({ env = process.env, configPath = CONFIG_PATH } = {
253
262
  maxSnapshotChars: positiveNumber(document.jev.max_snapshot_chars, "jev.max_snapshot_chars", true),
254
263
  maxActionCount: positiveNumber(document.jev.max_action_count, "jev.max_action_count", true),
255
264
  maxActionDescriptionChars: positiveNumber(document.jev.max_action_description_chars, "jev.max_action_description_chars", true),
256
- maxResponseBytes: positiveNumber(document.jev.max_response_bytes, "jev.max_response_bytes", true),
257
- maxDiagnosticItems: positiveNumber(jevOptions.max_diagnostic_items, "jev_browser_mcp.jev.max_diagnostic_items", true),
258
- maxDiagnosticChars: positiveNumber(jevOptions.max_diagnostic_chars, "jev_browser_mcp.jev.max_diagnostic_chars", true),
265
+ maxResponseBytes: positiveNumber(document.jev.max_response_bytes, "jev.max_response_bytes", true),
266
+ maxDiagnosticItems: positiveNumber(jevOptions.max_diagnostic_items, "jev_browser_mcp.jev.max_diagnostic_items", true),
267
+ maxDiagnosticChars: positiveNumber(jevOptions.max_diagnostic_chars, "jev_browser_mcp.jev.max_diagnostic_chars", true),
268
+ maxAccessibilityViolations: positiveNumber(jevOptions.max_accessibility_violations, "jev_browser_mcp.jev.max_accessibility_violations", true),
259
269
  }),
260
270
  });
261
271
  }