@diegosouzacdv/jev-browser-mcp 0.1.2 → 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.
@@ -6,7 +6,7 @@ import { fileURLToPath } from "node:url";
6
6
  const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../..");
7
7
  const CONFIG_PATH = path.join(PACKAGE_ROOT, "config", "ui-testing.json");
8
8
  const CONFIG_KEYS = {
9
- root: ["version", "browser", "jev"],
9
+ root: ["version", "browser", "jev", "jev_browser_mcp"],
10
10
  browser: [
11
11
  "mode",
12
12
  "harness_browser",
@@ -16,6 +16,30 @@ const CONFIG_KEYS = {
16
16
  "max_flow_steps",
17
17
  "max_text_entry_chars",
18
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
+ "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
+ ],
19
43
  jev: [
20
44
  "provider_url",
21
45
  "credential_env",
@@ -27,6 +51,11 @@ const CONFIG_KEYS = {
27
51
  "max_action_description_chars",
28
52
  "max_response_bytes",
29
53
  ],
54
+ nodeJev: [
55
+ "max_diagnostic_items",
56
+ "max_diagnostic_chars",
57
+ "max_accessibility_violations",
58
+ ],
30
59
  };
31
60
 
32
61
  export class JevBrowserError extends Error {
@@ -57,6 +86,45 @@ function positiveNumber(value, name, integer = false) {
57
86
  return parsed;
58
87
  }
59
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
+ }
127
+
60
128
  function validProviderUrl(value) {
61
129
  let parsed;
62
130
  try {
@@ -71,17 +139,9 @@ function validProviderUrl(value) {
71
139
  }
72
140
 
73
141
  function profileDirectory(template, channel, override) {
74
- let value = override?.trim() || template.replace("{browser}", channel);
75
- if (value.startsWith("~/") || value.startsWith("~\\")) {
76
- value = path.join(homedir(), value.slice(2));
77
- }
142
+ const value = expandHomeDirectory(override?.trim() || template.replace("{browser}", channel));
78
143
  if (!path.isAbsolute(value)) throw new JevBrowserError("JEV_BROWSER_PROFILE must be an absolute path");
79
- const resolved = path.resolve(value);
80
- const insidePackage = path.relative(PACKAGE_ROOT, resolved);
81
- if (insidePackage === "" || (!insidePackage.startsWith(`..${path.sep}`) && insidePackage !== ".." && !path.isAbsolute(insidePackage))) {
82
- throw new JevBrowserError("browser profile must be outside the installed package directory");
83
- }
84
- return resolved;
144
+ return requireOutsidePackage(path.resolve(value), "browser profile");
85
145
  }
86
146
 
87
147
  export function loadSettings({ env = process.env, configPath = CONFIG_PATH } = {}) {
@@ -94,6 +154,11 @@ export function loadSettings({ env = process.env, configPath = CONFIG_PATH } = {
94
154
  requireKeys(document, CONFIG_KEYS.root, "root");
95
155
  requireKeys(document.browser, CONFIG_KEYS.browser, "browser");
96
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;
97
162
  if (document.version !== 1) throw new JevBrowserError("config/ui-testing.json has an unsupported version");
98
163
 
99
164
  if (!new Set(["harness", "computer"]).has(document.browser.mode)) {
@@ -125,15 +190,37 @@ export function loadSettings({ env = process.env, configPath = CONFIG_PATH } = {
125
190
 
126
191
  const configuredProviderUrl = validProviderUrl(document.jev.provider_url);
127
192
  const providerUrl = validProviderUrl(env.JEV_PROVIDER_URL?.trim() || configuredProviderUrl);
128
- const credentialEnv = String(document.jev.credential_env || "").trim();
129
- if (!/^[A-Z][A-Z0-9_]*$/.test(credentialEnv)) {
130
- throw new JevBrowserError("config/ui-testing.json jev.credential_env must be an environment variable name");
131
- }
193
+ const credentialEnv = environmentName(document.jev.credential_env, "jev.credential_env");
132
194
  const configuredModel = String(document.jev.model || "").trim();
133
195
  if (!configuredModel) throw new JevBrowserError("config/ui-testing.json jev.model is required");
134
196
  const model = (env.JEV_MODEL?.trim() || configuredModel).trim();
135
197
  if (!model) throw new JevBrowserError("Jev model must not be empty");
136
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
+ );
223
+
137
224
  return Object.freeze({
138
225
  env,
139
226
  browser: Object.freeze({
@@ -142,6 +229,29 @@ export function loadSettings({ env = process.env, configPath = CONFIG_PATH } = {
142
229
  profileDir: profileDirectory(profileTemplate, channel, env.JEV_BROWSER_PROFILE),
143
230
  maxFlowSteps: positiveNumber(document.browser.max_flow_steps, "browser.max_flow_steps", true),
144
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),
145
255
  }),
146
256
  jev: Object.freeze({
147
257
  providerUrl,
@@ -153,6 +263,9 @@ export function loadSettings({ env = process.env, configPath = CONFIG_PATH } = {
153
263
  maxActionCount: positiveNumber(document.jev.max_action_count, "jev.max_action_count", true),
154
264
  maxActionDescriptionChars: positiveNumber(document.jev.max_action_description_chars, "jev.max_action_description_chars", true),
155
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),
156
269
  }),
157
270
  });
158
271
  }