npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@sema-agent/connector-browser

v0.2.3

Published

Browser connector: runs the pinned Playwright MCP server and a pinned Chrome for Testing behind the sema-connector launcher, in an isolated, headless session whose login state is a secret handle.

Readme

Browser connector

@sema-agent/connector-browser (CN-027) gives the engine a web browser. It runs the Playwright MCP server (@playwright/mcp) unmodified behind the sema-connector launcher, driving a pinned Chrome for Testing, at the versions and sha256 values its binaries.lock.json pins. The launcher's stages (scope, normalize, status, trace, the withholding of secret values) apply to it as to every connector; see ARCHITECTURE.md. Its bin is sema-connector-browser; its subcommands are the shared CLI kit's (CN-039) around the shared install of a pinned upstream program (CN-027) and this package's auth, and read the configuration directory from CONFIG_LOCAL_DIR like every connector's (INTEGRATION-SERVER.md).

The browser is headless and runs in a fresh, in-memory profile at every start; what it knows of a login is the login state auth stored (see "The login state"), which a session can save back when the person has turned the export of the login state on (CN-055, see "Exporting the login state"). Chrome for Testing is pinned for darwin-arm64 and linux-x64 only.

Setting it up

export CONFIG_LOCAL_DIR=/absolute/path/to/the/engine/configuration
sema-connector-browser install
echo '{}' | sema-connector-browser auth          # a clean profile
sema-connector-browser mount
sema-connector-browser status

On Linux the connector keeps Chromium's own sandbox on (--sandbox, never --no-sandbox), which needs user namespaces: in a container that does not allow them (Docker's default seccomp profile, for one) the first browser_navigate answers Chromium sandboxing failed! and nothing is browsed. Run the connector on a host or in a container that allows user namespaces (KL-18; see KNOWN-LIMITS.md, shipped with this package and, in the repository, docs/KNOWN-LIMITS.md); the connector never turns the sandbox off.

Then restart the engine. To start from a login instead of a clean profile, save a Playwright storage state (for example with Playwright's context.storageState({ path }) after logging in) to a file only you can read, and give auth its path: echo '{"storageStateFile": "/absolute/path/state.json"}' | sema-connector-browser auth. Delete the file afterwards; the state is a secret (it holds the session cookies). To let sessions keep the logins they make, turn the export on with the same line: echo '{"export": "on"}' | sema-connector-browser auth (a clean profile, export on), or {"storageStateFile": "/absolute/path/state.json", "export": "on"}.

The pinned build

binaries.lock.json is the one place that names the build, in the shape "Pinned upstream programs" in ARCHITECTURE.md describes: the key playwright-mcp, version 0.0.83, and five archives.

| Archive | Platform | Source | Unpacked as | |---|---|---|---| | playwright-mcp-0.0.83.tgz | * | https://registry.npmjs.org/@playwright/mcp/-/mcp-{version}.tgz | node_modules/@playwright/mcp | | playwright-1.64.0-alpha-1790635538000.tgz | * | https://registry.npmjs.org/playwright/-/{file} | node_modules/playwright | | playwright-core-1.64.0-alpha-1790635538000.tgz | * | https://registry.npmjs.org/playwright-core/-/{file} | node_modules/playwright-core | | chrome-mac-arm64.zip | darwin-arm64 | https://cdn.playwright.dev/builds/cft/155.0.8059.12/mac-arm64/{file} | chrome | | chrome-linux64.zip | linux-x64 | https://cdn.playwright.dev/builds/cft/155.0.8059.12/linux64/{file} | chrome |

The three npm tarballs are the whole run-time closure of @playwright/mcp 0.0.83 (its lock file lists no other package), each pinned with the sha256 of the tarball whose sha512 integrity the registry gives. Chrome for Testing 155.0.8059.12 is the build that playwright-core 1.64.0-alpha-1790635538000 names (Playwright's chromium 1247), fetched from the host Playwright itself downloads it from. The program is the Node.js script node_modules/@playwright/mcp/cli.js ({"node": ...}): it runs on the Node.js that runs the connector, and Node.js finds playwright and playwright-core next to it. npm is never run.

install

install is the shared install of a pinned upstream program (see "Pinned upstream programs" in ARCHITECTURE.md): it fetches the four archives of this platform, compares each with its sha256 and checks what it gives before anything is unpacked, then unpacks each below <configuration directory>/connectors/browser/bin/ (0700) and keeps the archives there. About 580 MB on darwin-arm64. When that directory already holds the pinned build, install changes nothing. On a platform the lock does not pin it refuses (binaries.lock.json pins no playwright-mcp build for <platform>).

auth

auth reads exactly one line from standard input (never from an argument), a JSON object with at most the string members storageStateFile and export:

{}
{"storageStateFile": "<absolute path>"}
{"export": "on"}
{"storageStateFile": "<absolute path>", "export": "on"}
  • {} stores a clean profile, {"cookies":[],"origins":[]}. Empty input is refused rather than read as {}: a pipe whose producer failed must not replace a stored login with an empty one.
  • storageStateFile names a Playwright storage state file by its absolute path: at most 4 MiB of UTF-8 JSON holding one object with exactly the keys cookies and origins, each a list. Its text is stored as it is.
  • export is "on" or "off": "on" turns the export of the login state on (see "Exporting the login state"), "off" or no export key turns it off. Every auth states the connector's whole configuration: the login state as above, and the export on or off.
  • Any other key, a key given twice, a value that is not a string, another export value, or a file outside the rule is refused, and a refusal never repeats what was read.

The state is stored through the secrets backend as the handle BROWSER_STORAGE_STATE: a 0600 file in <configuration directory>/connectors/browser/ (0700), a secret (no metadata). The export switch is the handle BROWSER_EXPORT, holding on, stored when the export is on (a handle that is not a secret, {"secret": false}) and removed when it is off. Both writes are one group inside the lock of the connector's directory, as for every connector (see "Secrets" in ARCHITECTURE.md): a connector started while auth runs gets the old state and switch or the new ones. The switch is written first, so an auth that fails leaves the old login state, with the switch as it was or already as asked.

mount and status

mount writes the connector's standalone catalog entry through the shared mount function, and status prints the status the launcher serves, as for every connector. Before auth there is no handle, so the launcher does not start the connector and answers itself (see "Parked session" in ARCHITECTURE.md): status prints {"state":"unauthorized","tools":[]}. After auth (a clean profile included) the status is authorized and lists the 25 tools of playwright-mcp (26 with the export on, see "Exporting the login state"); reading them starts playwright-mcp but not the browser, which starts at the first tool that needs a page.

unmount removes that entry again and list prints the connectors mounted in the configuration directory, through the shared unmount and listMounted (@sema-agent/connectors 0.9.0, CN-065); an entry under this connector's name that is not exactly what mount writes is refused and left alone, and never listed (KNOWN-LIMITS.md, KL-32).

What runs

With no arguments, sema-connector-browser is the connector process the launcher starts. It refuses to start (exit 1, with a line on stderr naming what to do) on the shared start check ("Pinned upstream programs": without CONNECTOR_DIR, without the BROWSER_STORAGE_STATE handle, when its per-launch copy, or that of BROWSER_EXPORT, is not a regular file in CONNECTOR_DIR, when bin/ does not hold the pinned build), when the stored state breaks the rule of "auth", when a stored BROWSER_EXPORT does not hold on, when the export is on and CONNECTOR_DIR is not <configuration directory>/connectors/browser, and when Chrome for Testing is not in bin/chrome. Otherwise it makes the launch's output directory (see "The output directory"), makes it the working directory, and starts node_modules/@playwright/mcp/cli.js on its own Node.js as its child on the same standard input, output and error, so every MCP message passes between the launcher and playwright-mcp untouched (with the export on, its standard input and output pass through the connector's relay instead: see "Exporting the login state"); it ends as playwright-mcp ends, with its exit code or by the signal that ended it, and passes its stop signals on. playwright-mcp closes the browser when its input ends or it gets SIGTERM (measured well under a second, inside the launcher's teardown steps); killed with SIGKILL before that, it leaves Chromium running (KL-22).

playwright-mcp gets the arguments of "Security baseline" and the environment the launcher gave the connector without CONNECTOR_DIR, BROWSER_STORAGE_STATE_FILE, BROWSER_EXPORT_FILE and every PLAYWRIGHT_MCP_* variable (the variable twins of its options), so its options are those arguments whoever starts the connector, and with HOME set to its own home in the connector's directory, <CONNECTOR_DIR>/home (CN-040; "Pinned upstream programs" in ARCHITECTURE.md): what Playwright keeps under its home stays there (KL-25).

Security baseline

The arguments are one table in src/upstream.ts; none comes from the environment, standard input or a file.

| Argument | Why | |---|---| | --headless | no window; the browser is driven only through the tools. | | --isolated | the profile lives in memory and is gone when the browser closes; nothing persists between starts but the stored login state. | | --browser chromium | Chrome for Testing, the pinned build. | | --sandbox | the Chromium sandbox on: playwright-mcp leaves it off on Linux for Chrome for Testing unless asked (KL-18). | | --block-service-workers | a page cannot install a service worker that outlives it or intercepts its requests. | | --no-webmcp | tools a page registers are not offered to the engine: a page cannot add tools. | | --timeout-navigation 60000 | the navigation timeout, stated rather than defaulted. | | --output-max-size 67108864 | the output directory holds at most 64 MiB; older files are evicted. | | --executable-path <bin>/chrome/... | the pinned Chrome for Testing in the bin directory; playwright-mcp never looks for or downloads a browser. | | --output-dir <launch directory>/out | screenshots, snapshots and other files land in the launch's own directory and go with it. | | --storage-state <per-launch copy> | the login state, read when the browser context is created. | | --caps storage | only with the export of the login state on, last: playwright-mcp's storage capability, of whose tools the connector offers only browser_storage_state (see "Exporting the login state"). |

Never given: --no-sandbox, --caps other than --caps storage with the export on (none of the opt-in capabilities vision, pdf, devtools, config, network is ever on), --user-data-dir, --save-session, --secrets, --allow-unrestricted-file-access, --proxy-server, --port and --host (MCP runs on standard input and output only), --init-script, --init-page, --config, --extension, --cdp-endpoint, --allowed-origins and --blocked-origins (playwright-mcp itself says they are not a security boundary and do not apply to redirects; the connector does not offer them as one).

The login state

The state is a secret handle: the launcher reads it before the connector starts and hands the connector a per-launch copy (0600, inside the connector's directory, removed when the launcher ends), whose path the connector gives playwright-mcp with --storage-state. playwright-mcp loads its cookies and local storage into each new browser context. The launcher withholds the state's text as a whole from everything it forwards; a cookie value a page's script can read (one not marked HttpOnly) reaches the engine like any page content, through browser_evaluate and the other tools that read the page. A reply that carries the stored state's text as a whole, the clean profile's {"cookies":[],"origins":[]} included, is withheld like any secret value (answered with connector.internal).

The profile is in memory (--isolated): a login made during a session ends with the browser unless the session saves it with the export on (below). Otherwise, to keep a login, store a new state with auth.

Exporting the login state

The export (CN-055) lets a session keep the logins it makes. It is off until the person turns it on with auth ("export": "on", see "auth"); a stored BROWSER_EXPORT handle is the only way to turn it on, and nothing else ever writes the login state back.

With the export on, playwright-mcp runs with its storage capability (--caps storage), and the connector's standard input and output pass through the connector's relay, which changes three things and passes every other message on as it is (each line whole; a line longer than the launcher's 4 MiB is passed on cut to one byte over it, which the launcher refuses as it refuses any longer line):

  • One storage tool is offered. The capability adds seventeen tools; sixteen of them read, set or load cookies and web storage and answer with their values (browser_cookie_*, browser_localstorage_*, browser_sessionstorage_*, browser_set_storage_state). playwright-mcp has no option to leave a tool out, so the relay withholds them: they are not in tools/list (nor in the status), and a call naming one never reaches playwright-mcp: the relay answers it with playwright-mcp's own answer for a tool it does not have (Tool "<name>" not found, a tool result marked as an error), without starting the browser; a call without an id is dropped. The list in src/upstream.ts is the one place that names them. browser_storage_state is the one tool the export adds: 26 tools in all.
  • The connector names the file. A browser_storage_state call reaches playwright-mcp with its arguments replaced by a file of the connector's choosing, in the launch's output directory and under a name no page can guess, whatever file name the caller gave (playwright-mcp would resolve a relative name against the client's first root, outside the launch). playwright-mcp writes the browser's cookies and local storage there, HttpOnly cookies included, and its answer names the file and holds no value. A call without an id is dropped.
  • A successful save is stored. When playwright-mcp answers that call with success, the connector reads that one file (never through a symbolic link), holds it to the rule of "auth", stores it as BROWSER_STORAGE_STATE through the secrets backend (the lock of the connector's directory, written whole) and removes the file; the answer is passed on as playwright-mcp gave it (a file that cannot be removed is said on the connector's standard error; it goes with the launch's directory). When the file is not there or not a regular file, breaks the rule, is over 4 MiB, or the backend cannot store it, nothing is stored, the file is removed, the answer says The login state was not stored: with the reason (never the content) and the connector says the same on its standard error. When playwright-mcp's own answer is a failure, it is passed on as it is and nothing is stored.

No other file is ever read: a page can put files of names of its own choosing into the output directory (a download lands there, named as the page says), so a state file there named like playwright-mcp's own (storage-state-….json or storage-state.json) is never taken for the saved one. Each save replaces the stored state; the running launch keeps the copy it started with, and the next launch starts from the last state saved. The saved state is everything the browser holds at that moment, the cookies a page set for itself included (KL-26).

With the export off, playwright-mcp has no storage capability and the connector passes its standard input and output straight through, as before.

The output directory

Every launch has an output directory of its own: out (0700) in the launch's directory, the 0700 directory the launcher makes inside the connector's directory for the per-launch copy of the login state and removes when the launch ends. It holds what playwright-mcp writes (screenshots, page snapshots, console logs, downloads, at most 64 MiB) and goes with the launch, so nothing a session wrote outlives it, and two launches at once (two engine sessions, or status while a session runs) never touch each other's files. It is also playwright-mcp's working directory, and playwright-mcp confines the files its tools read and write (browser_file_upload, a screenshot's file name, browser_run_code_unsafe's filename) to its output directory and its workspace, which is the working directory unless the MCP client names a root (then its first root). file: URLs are not opened.

Tools

The connector offers every tool playwright-mcp lists; which of them a caller may use is the launcher's scope stage, and standalone grants tool:* unless the configuration directory carries a local per-tool policy (CN-054, ARCHITECTURE.md, "Stages on the launcher hop"). The 25 tools, and the one the export adds, with what each can reach:

| Tool | Reach | |---|---| | browser_run_code_unsafe | Runs arbitrary JavaScript in the playwright-mcp process (playwright-mcp calls it RCE-equivalent): it can read and write every file the user can, the tokens of the other connectors in the configuration directory included, start processes and connect anywhere. | | browser_evaluate | Runs JavaScript in the page, with the page's access: its DOM, storage and script-readable cookies, requests as the page. | | browser_network_requests, browser_network_request | The page's requests and responses, headers and bodies included (authorization headers a page sends, and whatever a logged-in site puts in a body; the request headers Playwright reports carry no cookies). | | browser_file_upload, browser_drop | Give files to a page: files in the output directory or the workspace (see "The output directory"). | | browser_navigate, browser_navigate_back, browser_tabs | Open any http or https URL, as the logged-in user where the state has a login. | | browser_click, browser_type, browser_press_key, browser_fill_form, browser_select_option, browser_hover, browser_drag, browser_handle_dialog | Act on the page as the user (submit forms, confirm dialogs, buy, post, delete). | | browser_snapshot, browser_find, browser_take_screenshot, browser_console_messages | Read the page (its text, its look, its console); snapshots and screenshots are also written to the output directory. | | browser_resize, browser_emulate_media, browser_wait_for, browser_close | Change the viewport or media features, wait, close the browser. | | browser_storage_state | Only with the export on: replaces the stored login state with everything the browser holds (its cookies, HttpOnly ones included, and local storage); the answer holds no value (see "Exporting the login state"). |

A recommended policy

Mounting this connector grants every tool above by default (tool:*): nothing in this package writes a policy for it. An operator who wants to keep the reach of browser_run_code_unsafe, browser_evaluate, browser_network_request and browser_network_requests off, while still using the browser for everything else, writes connectors/browser/tools.json in the configuration directory by hand (ARCHITECTURE.md, "Stages on the launcher hop"; no CLI subcommand writes this file):

{
  "allow": [
    "browser_navigate", "browser_navigate_back", "browser_tabs",
    "browser_click", "browser_type", "browser_press_key", "browser_fill_form", "browser_select_option",
    "browser_hover", "browser_drag", "browser_handle_dialog",
    "browser_snapshot", "browser_find", "browser_take_screenshot", "browser_console_messages",
    "browser_resize", "browser_emulate_media", "browser_wait_for", "browser_close", "browser_drop"
  ]
}

The example leaves a fifth tool out as well, browser_file_upload: a deployment that needs it adds it back (it reaches only the output directory and the workspace, not arbitrary files), so the list above holds 20 names and a deployment that keeps browser_file_upload lists 21. A tool left out of allow fails tools/call with connector.forbidden_scope and is absent from tools/list and the status resource's tools; nothing about the connector itself changes. With the export on, the example leaves browser_storage_state out too, so a session cannot save its login until the deployment adds it to allow.

Outbound connections

The browser connects to whatever the engine asks it to open: that is what it is for. The standalone form has no egress proxy (M3), and the connector gives playwright-mcp none. The negative-control gate (run-browser-negctl-test) checks what else connects: playwright-mcp itself connects nowhere (every Node.js process runs under the gate's egress probe and, on macOS, a sandbox that refuses every connection but the gate's own loopback page). Chrome for Testing 155 does contact Google on its own when it starts, although Playwright passes switches meant to keep it quiet: the gate's proxied run names accounts.google.com, update.googleapis.com, www.google.com and android.clients.google.com, and Chromium also connects a UDP socket to [2001:4860:4860::8888]:443 (KL-19); the gate fails on a host outside google.com and googleapis.com. The browser follows the system's proxy settings.

playwright-mcp also publishes the running browser for other Playwright tools of the same user: a socket under /tmp/pw-<hash>/browser/ and an entry in Playwright's registry directory, which lies in playwright-mcp's home (<CONNECTOR_DIR>/home/Library/Caches/ms-playwright/b on macOS, <CONNECTOR_DIR>/home/.cache/ms-playwright/b on Linux; CN-040), both removed when the browser closes. A process of the same user can find the socket and attach to the browser there (KL-21).

Known limits

  • No egress policy in the standalone form (KL-17).
  • The Chromium sandbox needs user namespaces on Linux (KL-18): in a container without them Chromium does not start; the connector never turns the sandbox off.
  • Chrome for Testing contacts Google on its own (KL-19).
  • Two platforms: darwin-arm64 and linux-x64 (KL-20).
  • Other processes of the user can attach to the running browser (KL-21).
  • Chromium outlives a playwright-mcp killed with SIGKILL before it closed the browser (KL-22).
  • A saved login state holds everything the browser holds, the cookies a page set included (KL-26).
  • Check, then start: as for every pinned upstream program (see "Pinned upstream programs" in ARCHITECTURE.md): at each start the trees are compared with their archives by their files' names, kinds, sizes, modes and link targets, the program by its content.