@diegosouzacdv/jev-browser-mcp 0.3.0 → 0.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 +327 -1771
- package/config/ui-testing.json +15 -3
- package/docs/jev-browser-mcp.md +61 -37
- package/mcp_servers/jev-browser-npm/bin/jev-browser-mcp.cjs +1 -1
- package/mcp_servers/jev-browser-npm/src/config.mjs +49 -8
- package/mcp_servers/jev-browser-npm/src/flow.mjs +548 -228
- package/mcp_servers/jev-browser-npm/src/server.mjs +8 -12
- package/package.json +32 -35
package/config/ui-testing.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 1,
|
|
3
3
|
"browser": {
|
|
4
|
-
"mode": "
|
|
4
|
+
"mode": "computer",
|
|
5
5
|
"harness_browser": "chrome",
|
|
6
6
|
"playwright_mcp_package": "@playwright/mcp@0.0.79",
|
|
7
7
|
"computer_browser": "chrome",
|
|
@@ -22,8 +22,20 @@
|
|
|
22
22
|
},
|
|
23
23
|
"jev_browser_mcp": {
|
|
24
24
|
"browser": {
|
|
25
|
-
"max_action_timeout_seconds": 8,
|
|
26
|
-
"
|
|
25
|
+
"max_action_timeout_seconds": 8,
|
|
26
|
+
"ready_timeout_seconds_default": 15,
|
|
27
|
+
"max_ready_timeout_seconds": 60,
|
|
28
|
+
"ready_network_idle_default": true,
|
|
29
|
+
"ready_stable_ms_default": 400,
|
|
30
|
+
"max_ready_stable_ms": 2000,
|
|
31
|
+
"post_step_ready_timeout_seconds_default": 5,
|
|
32
|
+
"max_step_timeout_seconds": 60,
|
|
33
|
+
"key_delay_ms_default": 30,
|
|
34
|
+
"reuse_page_default": false,
|
|
35
|
+
"capture_network_error_bodies_default": false,
|
|
36
|
+
"max_network_error_body_bytes": 65536,
|
|
37
|
+
"max_network_error_message_chars": 300,
|
|
38
|
+
"max_upload_files": 5,
|
|
27
39
|
"max_upload_path_chars": 4096,
|
|
28
40
|
"max_upload_file_bytes": 10485760,
|
|
29
41
|
"max_upload_total_bytes": 26214400,
|
package/docs/jev-browser-mcp.md
CHANGED
|
@@ -17,10 +17,10 @@ versão em produção, use o número explícito no argumento do pacote:
|
|
|
17
17
|
"mcpServers": {
|
|
18
18
|
"jev-browser": {
|
|
19
19
|
"command": "npx",
|
|
20
|
-
"args": ["--yes", "@diegosouzacdv/jev-browser-mcp@0.
|
|
20
|
+
"args": ["--yes", "@diegosouzacdv/jev-browser-mcp@0.4.0"],
|
|
21
21
|
"env": {
|
|
22
22
|
"OPENROUTER_API_KEY": "${OPENROUTER_API_KEY}",
|
|
23
|
-
"JEV_BROWSER_MODE": "
|
|
23
|
+
"JEV_BROWSER_MODE": "computer"
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
}
|
|
@@ -31,15 +31,18 @@ O formato de interpolação de variáveis varia por harness. Injete a chave por
|
|
|
31
31
|
um secret manager ou pelo ambiente do processo; não grave a chave no arquivo
|
|
32
32
|
de configuração. Para instalar no projeto Node do próprio harness:
|
|
33
33
|
|
|
34
|
-
```sh
|
|
35
|
-
npm install @diegosouzacdv/jev-browser-mcp
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
34
|
+
```sh
|
|
35
|
+
npm install @diegosouzacdv/jev-browser-mcp
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
O padrão `computer` exige Chrome ou Edge instalado. Se optar por `harness`,
|
|
39
|
+
configure `JEV_BROWSER_MODE=harness` antes de instalar o navegador gerenciado
|
|
40
|
+
pelo Playwright com `npx --yes @diegosouzacdv/jev-browser-mcp --install-browser`.
|
|
41
|
+
|
|
42
|
+
O padrão é `computer`: o pacote abre o Chrome/Edge instalado e usa um perfil
|
|
43
|
+
persistente exclusivo em `browser.computer_user_data_dir`. No modo `harness`, a
|
|
44
|
+
instalação baixa uma vez o Chrome/Edge que o Playwright controlará. Personalize
|
|
45
|
+
as escolhas com `JEV_BROWSER_MODE`, `JEV_BROWSER_CHANNEL` e
|
|
43
46
|
`JEV_BROWSER_PROFILE`. O browser permanece aquecido enquanto o processo MCP
|
|
44
47
|
estiver ativo e fecha quando o harness encerra o processo. `JEV_PROVIDER_URL`
|
|
45
48
|
e `JEV_MODEL` podem substituir os valores centrais; a credencial continua no
|
|
@@ -49,11 +52,15 @@ Uma aplicação Node também pode importar `createJevBrowserServer` por
|
|
|
49
52
|
`@diegosouzacdv/jev-browser-mcp/server` e conectar o servidor ao transporte MCP
|
|
50
53
|
que ela já utiliza.
|
|
51
54
|
|
|
52
|
-
O pacote é montado
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
O pacote é montado em uma pasta temporária isolada: o publicador usa o campo
|
|
56
|
+
`files` do `package.json` para copiar somente o código Node, a configuração
|
|
57
|
+
compartilhada e esta documentação, que também vira o `README.md` da raiz do
|
|
58
|
+
pacote. Assim, o npm não inclui o README geral do orquestrador na página do MCP.
|
|
59
|
+
Para gerar e conferir o pacote antes de publicar, execute
|
|
60
|
+
`npm run pack:jev-browser-mcp`; para publicar uma versão já autenticada no npm, execute
|
|
61
|
+
`npm run publish:jev-browser-mcp`. A instalação por npm é a recomendada; use a
|
|
62
|
+
referência GitHub somente quando precisar experimentar uma revisão ainda não
|
|
63
|
+
publicada.
|
|
57
64
|
|
|
58
65
|
### Contrato do pacote
|
|
59
66
|
|
|
@@ -82,12 +89,17 @@ resultado esperado na tela.
|
|
|
82
89
|
|
|
83
90
|
Cada plano pode usar:
|
|
84
91
|
|
|
85
|
-
- `click`, `type`, `hover` e `select_option` com papel/nome acessível exatos;
|
|
86
|
-
- `
|
|
87
|
-
|
|
92
|
+
- `click`, `type`, `hover` e `select_option` com papel/nome acessível exatos;
|
|
93
|
+
- `timeout_seconds` opcional em cada etapa, limitado pela configuração central;
|
|
94
|
+
- `type` com `mode: "keys"` para digitar sequencialmente em campos com máscara,
|
|
95
|
+
`blur: true` para desfocar o campo e `sensitive: false` para permitir que o
|
|
96
|
+
valor apareça nas evidências;
|
|
97
|
+
- `wait_for_text` e `wait_for_condition` (`network_idle`, `hidden` ou
|
|
98
|
+
`text_hidden`);
|
|
88
99
|
- `press_key` com `PageDown`, `PageUp`, `Home`, `End`, `ArrowDown`, `ArrowUp`,
|
|
89
100
|
`Enter`, `Escape` ou `Tab`;
|
|
90
|
-
- `assert_text`, `assert_value`, `assert_visible` e `assert_hidden`;
|
|
101
|
+
- `assert_text`, `assert_value`, `assert_visible` e `assert_hidden`; `assert_text`
|
|
102
|
+
pode receber apenas `role` quando a região, como `alert`, não tem nome acessível;
|
|
91
103
|
- `upload_file` para input rotulado, botão que abre o seletor de arquivo ou
|
|
92
104
|
dropzone;
|
|
93
105
|
- `audit_accessibility` com axe-core para WCAG 2.1 A/AA;
|
|
@@ -235,9 +247,9 @@ do pacote Node ficam no bloco opcional `jev_browser_mcp`, que não altera o
|
|
|
235
247
|
contrato lido pelo servidor Python. Preserve a estrutura completa exigida pelos
|
|
236
248
|
validadores.
|
|
237
249
|
|
|
238
|
-
`browser.mode` aceita `harness` ou `computer`:
|
|
250
|
+
`browser.mode` aceita `harness` ou `computer`; o padrão é `computer`:
|
|
239
251
|
|
|
240
|
-
- `harness` usa Chrome headless e
|
|
252
|
+
- `harness` usa Chrome headless e contexto isolado, adequado a execuções do
|
|
241
253
|
harness e CI; o estado de autenticação é descartado ao final da chamada.
|
|
242
254
|
- `computer` abre o Chrome ou Edge instalado em modo visível e usa o diretório
|
|
243
255
|
persistente `browser.computer_user_data_dir`, separado por navegador. Não
|
|
@@ -246,12 +258,15 @@ validadores.
|
|
|
246
258
|
|
|
247
259
|
`browser.max_flow_steps` limita a soma de passos declarados entre os planos e
|
|
248
260
|
`browser.max_text_entry_chars` limita cada valor digitado. O resultado contém
|
|
249
|
-
`status`, o plano escolhido, as ações executadas, a última captura acessível e
|
|
250
|
-
se o critério esperado apareceu. Quando existem asserções explícitas, `status`
|
|
251
|
-
também pode ser `passed` com todas elas satisfeitas; `assertions_passed` registra
|
|
252
|
-
esse resultado e `expected_outcome_visible` continua descrevendo somente o
|
|
253
|
-
texto global. `incomplete` significa que nenhum critério foi comprovado;
|
|
254
|
-
confiança do Jev não substitui essa verificação.
|
|
261
|
+
`status`, o plano escolhido, as ações executadas, a última captura acessível e
|
|
262
|
+
se o critério esperado apareceu. Quando existem asserções explícitas, `status`
|
|
263
|
+
também pode ser `passed` com todas elas satisfeitas; `assertions_passed` registra
|
|
264
|
+
esse resultado e `expected_outcome_visible` continua descrevendo somente o
|
|
265
|
+
texto global. `incomplete` significa que nenhum critério foi comprovado;
|
|
266
|
+
confiança do Jev não substitui essa verificação. `timings_ms.ready_ms` mede a
|
|
267
|
+
espera da SPA; `warnings` registra capturas vazias durante transições; e
|
|
268
|
+
`failed_step` identifica índice, ação, alvo, timeout e erro resumido quando uma
|
|
269
|
+
etapa falha.
|
|
255
270
|
|
|
256
271
|
`jev_browser_mcp.browser.max_action_timeout_seconds` limita esperas por ações,
|
|
257
272
|
seletores e condições. `JEV_BROWSER_UPLOAD_ROOT` libera uploads somente dentro
|
|
@@ -267,12 +282,18 @@ Os limites de download ficam no mesmo bloco: `max_download_files`,
|
|
|
267
282
|
limita quantas descrições de violações axe entram no resultado; a contagem total
|
|
268
283
|
continua informada mesmo quando a lista é truncada.
|
|
269
284
|
|
|
270
|
-
O `run_browser_flow` aceita `options` com `fast_path`, `snapshot_scope`,
|
|
271
|
-
`block_trackers`, `capture_console_errors`, `capture_network_errors`,
|
|
272
|
-
`
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
285
|
+
O `run_browser_flow` aceita `options` com `fast_path`, `snapshot_scope`,
|
|
286
|
+
`block_trackers`, `capture_console_errors`, `capture_network_errors`,
|
|
287
|
+
`capture_network_error_bodies`, `ready_timeout_seconds`, `ready_network_idle`,
|
|
288
|
+
`ready_stable_ms`, `ready_text`, `reuse_page`, `screenshot_on_failure` e
|
|
289
|
+
`trace_on_failure`. Sem override, os padrões são lidos de `jev_browser_mcp` em
|
|
290
|
+
`config/ui-testing.json`: um plano candidato pula a chamada Decisions;
|
|
291
|
+
captura de console e screenshot de falha ficam ligadas; trace, rede e bloqueio
|
|
292
|
+
de recursos ficam desligados. O browser espera a SPA renderizar uma captura
|
|
293
|
+
acessível estável. `ready_text` pode identificar o conteúdo que marca a
|
|
294
|
+
prontidão. `reuse_page: true` pula a navegação somente quando a página e
|
|
295
|
+
`initial_url` têm a mesma origem. `snapshot_scope` aceita `body`, `main` ou
|
|
296
|
+
`dialog`.
|
|
276
297
|
|
|
277
298
|
`block_trackers: true` bloqueia os domínios e tipos de recurso listados na
|
|
278
299
|
configuração (analytics, Hotjar, fontes externas e mídia). Isso pode alterar o
|
|
@@ -280,9 +301,12 @@ layout ou o comportamento do site, então a opção é desligada por padrão.
|
|
|
280
301
|
|
|
281
302
|
Com `capture_console_errors` e `capture_network_errors`, o retorno traz
|
|
282
303
|
`console_errors` e `network_failures`, limitados em quantidade e tamanho.
|
|
283
|
-
Respostas HTTP 4xx/5xx e requisições que falham são listadas; query strings,
|
|
284
|
-
fragmentos, valores de formulário e nomes de arquivo são removidos ou
|
|
285
|
-
sanitizados.
|
|
304
|
+
Respostas HTTP 4xx/5xx e requisições que falham são listadas; query strings,
|
|
305
|
+
fragmentos, valores de formulário e nomes de arquivo são removidos ou
|
|
306
|
+
sanitizados. `capture_network_error_bodies: true` lê somente respostas JSON
|
|
307
|
+
4xx/5xx da mesma origem, limita o corpo e inclui apenas o campo textual
|
|
308
|
+
`message`, também sanitizado. Em falhas, `screenshot_on_failure` salva
|
|
309
|
+
screenshot local e retorna
|
|
286
310
|
`screenshot_path`. `trace_on_failure: true` também grava um `.zip` compatível
|
|
287
311
|
com o Trace Viewer do Playwright e retorna `trace_path`. O diretório padrão é
|
|
288
312
|
`~/.cache/orquestrador/jev-browser-artifacts`; `JEV_BROWSER_ARTIFACT_DIR` pode
|
|
@@ -10,7 +10,7 @@ Usage:
|
|
|
10
10
|
jev-browser-mcp --help Show this help
|
|
11
11
|
|
|
12
12
|
Configuration is read from config/ui-testing.json. Set OPENROUTER_API_KEY in
|
|
13
|
-
the environment
|
|
13
|
+
the environment. The default mode is computer; use JEV_BROWSER_MODE=harness to select isolated headless mode.
|
|
14
14
|
For upload_file, set JEV_BROWSER_UPLOAD_ROOT to a dedicated fixture directory.
|
|
15
15
|
`;
|
|
16
16
|
|
|
@@ -16,8 +16,20 @@ const CONFIG_KEYS = {
|
|
|
16
16
|
"max_flow_steps",
|
|
17
17
|
"max_text_entry_chars",
|
|
18
18
|
],
|
|
19
|
-
nodeBrowser: [
|
|
20
|
-
"max_action_timeout_seconds",
|
|
19
|
+
nodeBrowser: [
|
|
20
|
+
"max_action_timeout_seconds",
|
|
21
|
+
"ready_timeout_seconds_default",
|
|
22
|
+
"max_ready_timeout_seconds",
|
|
23
|
+
"ready_network_idle_default",
|
|
24
|
+
"ready_stable_ms_default",
|
|
25
|
+
"max_ready_stable_ms",
|
|
26
|
+
"post_step_ready_timeout_seconds_default",
|
|
27
|
+
"max_step_timeout_seconds",
|
|
28
|
+
"key_delay_ms_default",
|
|
29
|
+
"reuse_page_default",
|
|
30
|
+
"capture_network_error_bodies_default",
|
|
31
|
+
"max_network_error_body_bytes",
|
|
32
|
+
"max_network_error_message_chars",
|
|
21
33
|
"max_upload_files",
|
|
22
34
|
"max_upload_path_chars",
|
|
23
35
|
"max_upload_file_bytes",
|
|
@@ -206,7 +218,7 @@ export function loadSettings({ env = process.env, configPath = CONFIG_PATH } = {
|
|
|
206
218
|
const uploadRoot = configuredUploadRoot
|
|
207
219
|
? configuredDirectory(configuredUploadRoot, `environment ${uploadRootEnv}`)
|
|
208
220
|
: null;
|
|
209
|
-
const snapshotScope = browserOptions.snapshot_scope_default;
|
|
221
|
+
const snapshotScope = browserOptions.snapshot_scope_default;
|
|
210
222
|
if (!new Set(["body", "main", "dialog"]).has(snapshotScope)) {
|
|
211
223
|
throw new JevBrowserError("config/ui-testing.json jev_browser_mcp.browser.snapshot_scope_default must be body, main, or dialog");
|
|
212
224
|
}
|
|
@@ -215,11 +227,28 @@ export function loadSettings({ env = process.env, configPath = CONFIG_PATH } = {
|
|
|
215
227
|
"jev_browser_mcp.browser.tracker_host_suffixes",
|
|
216
228
|
(host) => /^[a-z0-9.-]+$/i.test(host) && !host.startsWith(".") && !host.endsWith("."),
|
|
217
229
|
);
|
|
218
|
-
const blockedResourceTypes = stringList(
|
|
230
|
+
const blockedResourceTypes = stringList(
|
|
219
231
|
browserOptions.blocked_resource_types,
|
|
220
232
|
"jev_browser_mcp.browser.blocked_resource_types",
|
|
221
233
|
(type) => new Set(["font", "media", "image"]).has(type),
|
|
222
|
-
);
|
|
234
|
+
);
|
|
235
|
+
const maxReadyTimeoutSeconds = positiveNumber(browserOptions.max_ready_timeout_seconds, "jev_browser_mcp.browser.max_ready_timeout_seconds");
|
|
236
|
+
const readyTimeoutSecondsDefault = positiveNumber(browserOptions.ready_timeout_seconds_default, "jev_browser_mcp.browser.ready_timeout_seconds_default");
|
|
237
|
+
const maxReadyStableMs = positiveNumber(browserOptions.max_ready_stable_ms, "jev_browser_mcp.browser.max_ready_stable_ms", true);
|
|
238
|
+
const readyStableMsDefault = positiveNumber(browserOptions.ready_stable_ms_default, "jev_browser_mcp.browser.ready_stable_ms_default", true);
|
|
239
|
+
if (readyTimeoutSecondsDefault > maxReadyTimeoutSeconds) {
|
|
240
|
+
throw new JevBrowserError("config/ui-testing.json ready timeout default exceeds its configured maximum");
|
|
241
|
+
}
|
|
242
|
+
if (readyStableMsDefault > maxReadyStableMs) {
|
|
243
|
+
throw new JevBrowserError("config/ui-testing.json stable snapshot default exceeds its configured maximum");
|
|
244
|
+
}
|
|
245
|
+
const postStepReadyTimeoutSeconds = positiveNumber(
|
|
246
|
+
browserOptions.post_step_ready_timeout_seconds_default,
|
|
247
|
+
"jev_browser_mcp.browser.post_step_ready_timeout_seconds_default",
|
|
248
|
+
);
|
|
249
|
+
if (postStepReadyTimeoutSeconds > maxReadyTimeoutSeconds) {
|
|
250
|
+
throw new JevBrowserError("config/ui-testing.json post-step readiness timeout exceeds its configured maximum");
|
|
251
|
+
}
|
|
223
252
|
|
|
224
253
|
return Object.freeze({
|
|
225
254
|
env,
|
|
@@ -229,7 +258,14 @@ export function loadSettings({ env = process.env, configPath = CONFIG_PATH } = {
|
|
|
229
258
|
profileDir: profileDirectory(profileTemplate, channel, env.JEV_BROWSER_PROFILE),
|
|
230
259
|
maxFlowSteps: positiveNumber(document.browser.max_flow_steps, "browser.max_flow_steps", true),
|
|
231
260
|
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,
|
|
261
|
+
actionTimeoutMs: positiveNumber(browserOptions.max_action_timeout_seconds, "jev_browser_mcp.browser.max_action_timeout_seconds") * 1000,
|
|
262
|
+
maxReadyTimeoutSeconds,
|
|
263
|
+
maxReadyStableMs,
|
|
264
|
+
postStepReadyTimeoutMs: postStepReadyTimeoutSeconds * 1000,
|
|
265
|
+
maxStepTimeoutSeconds: positiveNumber(browserOptions.max_step_timeout_seconds, "jev_browser_mcp.browser.max_step_timeout_seconds"),
|
|
266
|
+
keyDelayMs: positiveNumber(browserOptions.key_delay_ms_default, "jev_browser_mcp.browser.key_delay_ms_default", true),
|
|
267
|
+
maxNetworkErrorBodyBytes: positiveNumber(browserOptions.max_network_error_body_bytes, "jev_browser_mcp.browser.max_network_error_body_bytes", true),
|
|
268
|
+
maxNetworkErrorMessageChars: positiveNumber(browserOptions.max_network_error_message_chars, "jev_browser_mcp.browser.max_network_error_message_chars", true),
|
|
233
269
|
maxUploadFiles: positiveNumber(browserOptions.max_upload_files, "jev_browser_mcp.browser.max_upload_files", true),
|
|
234
270
|
maxUploadPathChars: positiveNumber(browserOptions.max_upload_path_chars, "jev_browser_mcp.browser.max_upload_path_chars", true),
|
|
235
271
|
maxUploadFileBytes: positiveNumber(browserOptions.max_upload_file_bytes, "jev_browser_mcp.browser.max_upload_file_bytes", true),
|
|
@@ -246,8 +282,13 @@ export function loadSettings({ env = process.env, configPath = CONFIG_PATH } = {
|
|
|
246
282
|
screenshotOnFailure: booleanValue(browserOptions.screenshot_on_failure_default, "jev_browser_mcp.browser.screenshot_on_failure_default"),
|
|
247
283
|
traceOnFailure: booleanValue(browserOptions.trace_on_failure_default, "jev_browser_mcp.browser.trace_on_failure_default"),
|
|
248
284
|
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
|
-
|
|
285
|
+
captureNetworkErrors: booleanValue(browserOptions.capture_network_errors_default, "jev_browser_mcp.browser.capture_network_errors_default"),
|
|
286
|
+
captureNetworkErrorBodies: booleanValue(browserOptions.capture_network_error_bodies_default, "jev_browser_mcp.browser.capture_network_error_bodies_default"),
|
|
287
|
+
blockTrackers: booleanValue(browserOptions.block_trackers_default, "jev_browser_mcp.browser.block_trackers_default"),
|
|
288
|
+
readyNetworkIdle: booleanValue(browserOptions.ready_network_idle_default, "jev_browser_mcp.browser.ready_network_idle_default"),
|
|
289
|
+
reusePage: booleanValue(browserOptions.reuse_page_default, "jev_browser_mcp.browser.reuse_page_default"),
|
|
290
|
+
readyTimeoutSeconds: readyTimeoutSecondsDefault,
|
|
291
|
+
readyStableMs: readyStableMsDefault,
|
|
251
292
|
snapshotScope,
|
|
252
293
|
}),
|
|
253
294
|
trackerHostSuffixes: Object.freeze(trackerHostSuffixes),
|