Skip to content

feat: sync browser_drop, evaluate, and network request tools#122

Open
JustasMonkev wants to merge 5 commits into
mainfrom
codex/issue-116-browser-tool-sync
Open

feat: sync browser_drop, evaluate, and network request tools#122
JustasMonkev wants to merge 5 commits into
mainfrom
codex/issue-116-browser-tool-sync

Conversation

@JustasMonkev

@JustasMonkev JustasMonkev commented Jul 9, 2026

Copy link
Copy Markdown
Owner

Summary

Syncs the browser toolset with upstream playwright-mcp (issue #116):

  • browser_drop — drop files or MIME-typed data onto an element, as if dragged from outside the page. File paths are sandboxed to the client filesystem root / configured output dir.
  • browser_evaluate — now accepts a plain expression as well as a function, awaits returned promises, and reports errors instead of throwing raw.
  • browser_network_requests — numbered list with stable indexes; hides successful static resources by default; adds static, filter, and filename params.
  • browser_network_request (new) — inspect headers or a single body part for one indexed request. Redacts sensitive headers by default, caps inline/total body sizes, saves binary bodies byte-for-byte, and fails closed on compressed/unbounded bodies.

Docs (README, SKILL.md) updated for the new/changed tools.

Testing

  • npm run typecheck — clean
  • npm run test — 394 passed
  • npm run test:mcp — 29 passed, 1 skipped (browser_install); added harness coverage for browser_drop and browser_network_request

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added a new drag-and-drop tool for placing text or files onto page elements.
    • Added detailed network inspection, including numbered request listings and per-request headers/body views.
    • Expanded page evaluation support to handle both JavaScript functions and plain expressions.
  • Bug Fixes

    • Improved handling of dropped data and file paths, including validation and safer file resolution.
    • Refined network request output to better handle filtering, sensitive data redaction, and large or compressed responses.
  • Documentation

    • Updated tool documentation to reflect the new and enhanced browser interaction capabilities.

Bring the browser toolset in line with upstream playwright-mcp:

- browser_drop: drop files or MIME-typed data onto an element, with file
  paths sandboxed to the client root / output dir.
- browser_evaluate: accept a plain expression as well as a function, await
  promises, and surface errors instead of throwing raw.
- browser_network_requests: numbered list, hides successful static
  resources by default, adds static/filter/filename params.
- browser_network_request: inspect headers or body for one indexed
  request, with sensitive-header redaction, size caps, and safe handling
  of compressed/binary bodies.

Adds harness coverage for the two new tools and updates README/SKILL docs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@JustasMonkev, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 50 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0208ca76-4ee8-4808-b190-97da04065ab3

📥 Commits

Reviewing files that changed from the base of the PR and between 603dcb4 and 75ef1eb.

📒 Files selected for processing (4)
  • .claude/run-mcp-direct-harness.mjs
  • README.md
  • src/utils/codegen.ts
  • tests/tools-files.test.ts
📝 Walkthrough

Walkthrough

This PR adds a new browser_drop tool for dropping files or MIME-typed data onto page elements, rewrites browser_evaluate to detect function vs. expression source and evaluate on page or element locators with new result serialization, and expands network tooling with a browser_network_request tool supporting indexed requests, part-based rendering, header redaction, and file output. Documentation, harness, and tests are updated accordingly.

Changes

Browser drop, evaluate rewrite, and network request inspection

Layer / File(s) Summary
browser_evaluate execution rewrite
src/tools/evaluate.ts, tests/tools-evaluate.test.ts
Evaluate now detects function vs. expression source via eval-based wrapping, runs on page or locator based on ref/element, and serializes results with a new stringifyResult fallback helper; tests updated/expanded accordingly.
browser_drop tool
src/tools/files.ts, tests/tools-files.test.ts
New drop tool validates paths/data input, resolves and constrains file paths to client roots, performs the drop via locator, and is covered by a new test suite.
Network request inspection tools
src/tools/network.ts, tests/tools-network.test.ts
browser_network_requests gains static/filter/filename options and numbered output; new browser_network_request tool renders full details or specific parts with header redaction, compressed-body gating, size limits, and file output; tests rewritten with new mock helpers.
Harness and e2e smoke coverage
.claude/run-mcp-direct-harness.mjs, tests/e2e-tools-smoke.test.ts
Harness and smoke tests updated to exercise browser_drop, browser_network_request, and updated browser_evaluate behavior against fixture routes.
Documentation and dependency bump
README.md, SKILL.md, package.json
Tool docs updated for browser_drop, browser_evaluate, and network tools; @types/node bumped.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related issues

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main browser tool sync, including drop, evaluate, and network request updates.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/issue-116-browser-tool-sync

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

JustasMonkev and others added 4 commits July 9, 2026 21:53
…er-tool-sync

# Conflicts:
#	.claude/run-mcp-direct-harness.mjs
Resolve harness conflict by keeping main's fixture-based browser_drop and
browser_network_request tests; drop the duplicate tests this branch added.

Two follow-on fixes so the merged harness passes:
- codegen.formatObject now quotes object keys that are not valid JS
  identifiers (e.g. "text/plain"), so browser_drop generates valid code.
- network harness tests pass { static: true } since the top-level document
  request is a non-fetch resource hidden by default.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.claude/run-mcp-direct-harness.mjs:
- Around line 155-158: The test is using a hard-coded request index and a
too-loose assertion, which can inspect the wrong browser request. In
browser_network_request within run-mcp-direct-harness.mjs, resolve the
/audit-site entry from browser_network_requests first and pass that index into
callTool, then tighten the assertText check so it requires the expected detail
markers from the selected request rather than accepting a generic match.

In `@src/tools/evaluate.ts`:
- Around line 51-63: The normalization logic in evaluate() is still using
page-context eval inside both the locator.evaluate and tab.page.evaluate
branches, which can break CSP-restricted pages for plain expressions like
document.title. Move the function-vs-expression detection outside the page realm
in src/tools/evaluate.ts, then pass either a callable or a simple expression
path into the existing evaluate flow without using eval in the page context. Add
a CSP regression test covering the browser_evaluate path to ensure expressions
work when unsafe-eval is blocked.

In `@src/tools/network.ts`:
- Around line 359-361: The default binary filename built in the network download
flow uses new Date().toISOString() inside the defaultPrefix filename, which
introduces colon characters that break Windows paths when filename is omitted.
Update the filename generation in the relevant helper in network.ts to sanitize
the timestamp before composing defaultFilename, and keep the change localized
around the extension/defaultFilename/outputFile logic so the fallback path
remains valid across platforms.

In `@tests/e2e-tools-smoke.test.ts`:
- Around line 61-65: The async drop handling in browser_drop is not being
awaited, so assertions can run before dropzone.dataset.dropped is updated. Add a
promise or other synchronization in the drop listener that resolves after the
awaited file.text() completes, and have browser_drop wait on it before
returning. Apply the same fix for both file-drop assertion paths in browser_drop
and the later helper used around the second drop assertion, so browser_evaluate
always sees the final dropped value.

In `@tests/tools-evaluate.test.ts`:
- Around line 107-110: The locator mocks in tests/tools-evaluate.test.ts define
normalize more than once, which makes the mock object ambiguous and can break
TypeScript compilation. Clean up the mock object passed to mockTab.refLocator so
it has only one normalize property, and apply the same fix to the second locator
mock mentioned in the review. Use the refLocator setup and locator mock
definitions as the target points for the duplicate removal.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 45fe73ce-044f-4e1a-87b1-266da8e3b439

📥 Commits

Reviewing files that changed from the base of the PR and between 51c90a7 and 603dcb4.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (11)
  • .claude/run-mcp-direct-harness.mjs
  • README.md
  • SKILL.md
  • package.json
  • src/tools/evaluate.ts
  • src/tools/files.ts
  • src/tools/network.ts
  • tests/e2e-tools-smoke.test.ts
  • tests/tools-evaluate.test.ts
  • tests/tools-files.test.ts
  • tests/tools-network.test.ts

Comment thread .claude/run-mcp-direct-harness.mjs Outdated
Comment thread src/tools/evaluate.ts
Comment on lines +51 to +63
evalResult = await locator.evaluate(async (element, source) => {
const value = eval(`(${source})`);
const isFunction = typeof value === 'function';
const result = await (isFunction ? value(element) : value);
return { result, isFunction };
}, params.function);
} else {
evalResult = await tab.page.evaluate(async source => {
const value = eval(`(${source})`);
const isFunction = typeof value === 'function';
const result = await (isFunction ? value() : value);
return { result, isFunction };
}, params.function);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether evaluate tests cover CSP pages and where eval is introduced.
rg -nP '\beval\s*\(' src tests
rg -nP 'Content-Security-Policy|unsafe-eval|CSP' tests src

Repository: JustasMonkev/mcp-accessibility-scanner

Length of output: 320


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## evaluate.ts\n'
cat -n src/tools/evaluate.ts | sed -n '1,220p'

printf '\n## tests mentioning evaluate/browser_evaluate/CSP\n'
rg -n -C 2 'browser_evaluate|evaluate\(|Content-Security-Policy|unsafe-eval|CSP' tests src || true

Repository: JustasMonkev/mcp-accessibility-scanner

Length of output: 14819


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## file inventory\n'
git ls-files | rg '(^|/)(test|tests|__tests__)/|evaluate\.ts$|axe|auditSite|auditKeyboard'

printf '\n## evaluate.ts outline\n'
ast-grep outline src/tools/evaluate.ts --view expanded || true

Repository: JustasMonkev/mcp-accessibility-scanner

Length of output: 1448


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## src/tools/evaluate.ts (focused)\n'
sed -n '1,220p' src/tools/evaluate.ts | cat -n

printf '\n## test files with evaluate/tool coverage\n'
fd -a -t f '.*\.(test|spec)\.(ts|tsx|js|jsx)$' . | sed 's#^\./##' | sort | head -200

printf '\n## search in tests for evaluate-related paths\n'
rg -n -C 2 'evaluate\.ts|browser_evaluate|evalResult|params\.function|locator\.evaluate|page\.evaluate|Content-Security-Policy|unsafe-eval' . || true

Repository: JustasMonkev/mcp-accessibility-scanner

Length of output: 23663


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
p = Path('src/tools/evaluate.ts')
print(p.read_text())
PY

Repository: JustasMonkev/mcp-accessibility-scanner

Length of output: 3490


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '## src/tools/evaluate.ts'
cat -n src/tools/evaluate.ts | sed -n '1,180p'

echo
echo '## test files'
fd -a -t f '.*\.(test|spec)\.(ts|tsx|js|jsx)$' . | sed 's#^\./##' | sort | head -200

echo
echo '## CSP / evaluate references'
rg -n -C 2 'browser_evaluate|params\.function|locator\.evaluate|page\.evaluate|Content-Security-Policy|unsafe-eval|CSP' src tests . || true

Repository: JustasMonkev/mcp-accessibility-scanner

Length of output: 29978


Avoid page-context eval when normalizing browser_evaluate input. CSP-restricted pages can block unsafe-eval, so plain expressions like document.title may fail even though the public page.evaluate call would succeed. Detect function-vs-expression outside the page realm and add a CSP regression test for this path.

🧰 Tools
🪛 ast-grep (0.44.1)

[warning] 51-51: Avoid eval with expressions
Context: eval((${source}))
Note: [CWE-95] Improper Neutralization of Directives in Dynamically Evaluated Code ('Eval Injection').

(detect-eval-with-expression-typescript)


[warning] 58-58: Avoid eval with expressions
Context: eval((${source}))
Note: [CWE-95] Improper Neutralization of Directives in Dynamically Evaluated Code ('Eval Injection').

(detect-eval-with-expression-typescript)

🪛 Biome (2.5.1)

[error] 52-52: eval() exposes to security risks and performance issues.

(lint/security/noGlobalEval)


[error] 59-59: eval() exposes to security risks and performance issues.

(lint/security/noGlobalEval)

🪛 OpenGrep (1.23.0)

[ERROR] 52-52: eval() with dynamic input can execute arbitrary code. Avoid dynamic code evaluation entirely, or use a safe alternative.

(coderabbit.code-injection.eval-js)


[ERROR] 59-59: eval() with dynamic input can execute arbitrary code. Avoid dynamic code evaluation entirely, or use a safe alternative.

(coderabbit.code-injection.eval-js)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/tools/evaluate.ts` around lines 51 - 63, The normalization logic in
evaluate() is still using page-context eval inside both the locator.evaluate and
tab.page.evaluate branches, which can break CSP-restricted pages for plain
expressions like document.title. Move the function-vs-expression detection
outside the page realm in src/tools/evaluate.ts, then pass either a callable or
a simple expression path into the existing evaluate flow without using eval in
the page context. Add a CSP regression test covering the browser_evaluate path
to ensure expressions work when unsafe-eval is blocked.

Source: Linters/SAST tools

Comment thread src/tools/network.ts
Comment on lines +359 to +361
const extension = mime.getExtension(contentType) ?? 'bin';
const defaultFilename = `${defaultPrefix}-${new Date().toISOString()}.${extension}`;
const outputPath = await tab.context.outputFile(filename ?? defaultFilename);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Sanitize the generated binary filename timestamp.

toISOString() includes : characters, so the default binary save path can fail on Windows when no filename is provided.

Proposed fix
-  const defaultFilename = `${defaultPrefix}-${new Date().toISOString()}.${extension}`;
+  const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
+  const defaultFilename = `${defaultPrefix}-${timestamp}.${extension}`;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const extension = mime.getExtension(contentType) ?? 'bin';
const defaultFilename = `${defaultPrefix}-${new Date().toISOString()}.${extension}`;
const outputPath = await tab.context.outputFile(filename ?? defaultFilename);
const extension = mime.getExtension(contentType) ?? 'bin';
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const defaultFilename = `${defaultPrefix}-${timestamp}.${extension}`;
const outputPath = await tab.context.outputFile(filename ?? defaultFilename);
🧰 Tools
🪛 ast-grep (0.44.1)

[warning] 361-361: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.promises.writeFile(outputPath, body)
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename-typescript)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/tools/network.ts` around lines 359 - 361, The default binary filename
built in the network download flow uses new Date().toISOString() inside the
defaultPrefix filename, which introduces colon characters that break Windows
paths when filename is omitted. Update the filename generation in the relevant
helper in network.ts to sanitize the timestamp before composing defaultFilename,
and keep the change localized around the extension/defaultFilename/outputFile
logic so the fallback path remains valid across platforms.

Comment on lines +61 to +65
dropzone.addEventListener('drop', async event => {
event.preventDefault();
const file = event.dataTransfer.files[0];
dropzone.dataset.dropped = file ? await file.text() : event.dataTransfer.getData('text/plain');
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the test file and inspect the relevant regions with line numbers.
wc -l tests/e2e-tools-smoke.test.ts
sed -n '1,140p' tests/e2e-tools-smoke.test.ts
printf '\n--- later section ---\n'
sed -n '250,320p' tests/e2e-tools-smoke.test.ts

# Search for the specific browser_drop / browser_evaluate pattern and any existing sync helpers.
rg -n "browser_drop|browser_evaluate|dropzoneDropComplete|dataset\.dropped|addEventListener\('drop'" tests/e2e-tools-smoke.test.ts

Repository: JustasMonkev/mcp-accessibility-scanner

Length of output: 8099


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the browser_drop / browser_evaluate implementations and inspect the relevant slices.
rg -n "browser_drop|browser_evaluate|drop" src tests -g '!**/node_modules/**'

printf '\n--- browser server backend outline ---\n'
ast-grep outline src/browserServerBackend.ts --view expanded || true

printf '\n--- search in browser server backend ---\n'
rg -n "browser_drop|browser_evaluate|callTool|evaluate|drop" src/browserServerBackend.ts src -g '!**/node_modules/**'

# Read the implementation regions if the file exists.
if [ -f src/browserServerBackend.ts ]; then
  printf '\n--- browser server backend slice 1 ---\n'
  sed -n '1,260p' src/browserServerBackend.ts
  printf '\n--- browser server backend slice 2 ---\n'
  sed -n '260,520p' src/browserServerBackend.ts
fi

Repository: JustasMonkev/mcp-accessibility-scanner

Length of output: 18475


Wait for the async drop handler before asserting. browser_drop finishes before the drop listener’s awaited file.text() resolves, so the following browser_evaluate can read a stale value. Synchronize on a promise from the handler before both file-drop assertions here and at 282-286.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/e2e-tools-smoke.test.ts` around lines 61 - 65, The async drop handling
in browser_drop is not being awaited, so assertions can run before
dropzone.dataset.dropped is updated. Add a promise or other synchronization in
the drop listener that resolves after the awaited file.text() completes, and
have browser_drop wait on it before returning. Apply the same fix for both
file-drop assertion paths in browser_drop and the later helper used around the
second drop assertion, so browser_evaluate always sees the final dropped value.

Comment on lines 107 to 110
mockTab.refLocator.mockResolvedValue({
evaluate: locatorEvaluate,
_resolveSelector: async () => ({ resolvedSelector: 'internal:role=button' }),
normalize: async () => ({ toString: () => `locator('#button')` }),
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Remove the duplicate normalize mock property.

Both locator mock objects define normalize twice, which can fail TypeScript compilation and makes the mock shape ambiguous.

Proposed fix
 mockTab.refLocator.mockResolvedValue({
   evaluate: locatorEvaluate,
-  normalize: async () => ({ toString: () => `locator('`#button`')` }),
   normalize: async () => ({ toString: () => `locator('`#button`')` }),
 });

Apply the same cleanup to the second locator mock.

Also applies to: 131-134

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/tools-evaluate.test.ts` around lines 107 - 110, The locator mocks in
tests/tools-evaluate.test.ts define normalize more than once, which makes the
mock object ambiguous and can break TypeScript compilation. Clean up the mock
object passed to mockTab.refLocator so it has only one normalize property, and
apply the same fix to the second locator mock mentioned in the review. Use the
refLocator setup and locator mock definitions as the target points for the
duplicate removal.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant