# Nullprint (npm · nullprint-mcp)

Create, launch, check and drive Nullprint multi-profile browser profiles from any MCP agent.

- Trust score: 58/100 (low)
- Change this week: −10
- Registry status: active
- Liveness: live
- Owner verified: no
- Last scored: 2026-10-23

## Components

- npm · `nullprint-mcp`: 58/100 (this document), [markdown](https://verifymcp.io/servers/nullprintai-nullprint-mcp/nullprint-mcp.md), [page](https://verifymcp.io/servers/nullprintai-nullprint-mcp/nullprint-mcp)
- pypi · `nullprint-mcp`: 55/100, [markdown](https://verifymcp.io/servers/nullprintai-nullprint-mcp/nullprint-mcp-2.md), [page](https://verifymcp.io/servers/nullprintai-nullprint-mcp/nullprint-mcp-2)

## Channel facts

- Registry: `npm`
- Package: `nullprint-mcp`
- Version: `0.1.5`
- Transport: `stdio`

## Trust breakdown

How this component scores in each security and reliability category. Every signal is checked automatically from public evidence about the published package, including repeated runs of it in an isolated sandbox, and we only credit what we can confirm. Scores are 0–100 per category. Scoring method: https://verifymcp.io/docs/scoring (what has changed: https://verifymcp.io/docs/scoring/changelog)

Scored 2026-10-23.

- **Supply Chain Security**: 50/100
  - Malware scan not yet available for this package.
  - No known CVEs affecting this package version or its production dependencies.
  - No install/post-install scripts declared.
  - No production dependencies, so there is no dependency health to assess.
- **Provenance & Transparency**: 45/100
  - Source repository is publicly reachable at the declared URL.
  - Provenance check failed: no build-provenance attestation is published.
  - Clear OSI-approved license (MIT).
  - Actively maintained (last published 0 days ago).
  - Disclosure check failed: no security disclosure policy was found in the source repository.
- **Schema Quality & AI Usability**: 71/100
  - AI-judged instruction clarity (excellent).
  - Context-footprint check failed: tool/resource definitions use about 3521 tokens (~185/item across 19 items; 19 tools + 0 resources), over budget; trim descriptions and params.
  - Usage-examples check failed: none of the tools include examples.
- **Stability & Change Management**: 33/100
  - Stability observed for 10 of 30 days with no destabilising changes; credit accrues until the full window elapses.
- **Tool Coverage**: 100/100
  - 100% of tools have a non-trivial description (not blank, and not just the tool's name).
  - 100% of tool parameters carry a description.
- **Tool Safety**: 75/100
  - No prompt-injection markers were found in the server instructions, tool names or descriptions we captured.
  - 0 of 2 tool(s) whose name or description implies an irreversible operation declare an MCP destructiveHint annotation; "profile_delete" implies "delete" and declares no destructiveHint at all, which the MCP spec reads as destructive by default.
  - An AI judge read all 20 captured unit(s) of tool text and found none that tries to manipulate the model reading it.
- **Capabilities**: 100/100
  - Implements a supported MCP spec version (2025-11-25); the latest is 2026-07-28.

## Install

### How do I install the Nullprint MCP server?

Nullprint runs locally as an npm package, launched with npx -y nullprint-mcp. Ready-made configuration for Claude, Cursor, VS Code, Codex and 5 more is on this page, copied from each client's own documentation.

### Claude

```bash
claude mcp add nullprintai-nullprint-mcp -- npx -y nullprint-mcp
```

### Cursor

```json
{
  "mcpServers": {
    "nullprintai-nullprint-mcp": {
      "command": "npx",
      "args": [
        "-y",
        "nullprint-mcp"
      ]
    }
  }
}
```

### VS Code

```json
{
  "servers": {
    "nullprintai-nullprint-mcp": {
      "command": "npx",
      "args": [
        "-y",
        "nullprint-mcp"
      ]
    }
  }
}
```

### Codex

```bash
codex mcp add nullprintai-nullprint-mcp -- npx -y nullprint-mcp
```

### opencode

```json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "nullprintai-nullprint-mcp": {
      "type": "local",
      "command": [
        "npx",
        "-y",
        "nullprint-mcp"
      ],
      "enabled": true
    }
  }
}
```

### OpenClaw

```bash
openclaw mcp add nullprintai-nullprint-mcp --command npx --arg -y --arg nullprint-mcp
```

### Hermes

```yaml
mcp_servers:
  nullprintai-nullprint-mcp:
    command: "npx"
    args: ["-y", "nullprint-mcp"]
```

### Netclaw

```json
{
  "McpServers": {
    "nullprintai-nullprint-mcp": {
      "Transport": "stdio",
      "Command": "npx",
      "Arguments": [
        "-y",
        "nullprint-mcp"
      ]
    }
  }
}
```

### Vellum

```bash
assistant mcp add nullprintai-nullprint-mcp -t stdio -c npx -a -y nullprint-mcp
```

### Other

```json
{
  "mcpServers": {
    "nullprintai-nullprint-mcp": {
      "command": "npx",
      "args": [
        "-y",
        "nullprint-mcp"
      ]
    }
  }
}
```

## Changelog

Every change recorded for this component, newest first. Days that predate change tracking, or that we cannot explain, say so: "we were watching and nothing happened" and "we were not watching" are different claims.

### 2026-10-23 (score 58, −15)

- [security regression] Malware scan: pass → unverified
- [functional regression] Schema quality: 2980 → 3521
- [functional] Package version: 0.1.2 → 0.1.5

### 2026-10-22 (score 73, +5)

- [functional improvement] Stability: unverified → 0.30

### 2026-10-13 (score 68, +29)

- [security improvement] Injection markers: unverified → pass
- [security] First check of Judged manipulation: pass
- [security] Stability: Stability not yet verified: not enough scan history yet (needs a 30-day window).
- [functional improvement] Tool coverage: unverified → 100
- [functional improvement] MCP protocol: unverified → pass
- [functional] First check of Schema quality: fail
- [functional] First check of Schema quality: excellent
- [functional] First check of Schema quality: fail
- [functional] First check of Destructive annotations: 0
- [functional] First check of Tool coverage: 100

### 2026-10-12 (score 39)

First indexed and scored.

## MCP tools (19)

### `profiles_list` (~103 tokens)

List every identity in this installation, with its live status.

Each entry carries: name, os, engine (which browser core backs it), created, proxy (the exit server only — never credentials), status ("running" when a browser is open for it, otherwise "idle"), and meta (the local ledger: group, tags, note, last_launched).

Call this first to find the exact name to pass to profile_start / profile_stop; names are case-sensitive.

### `profile_start` (~347 tokens)

Open a browser session for an identity and return the endpoint to drive it.

The identity keeps its frozen fingerprint, its exit proxy, and its logged-in cookies — starting it does not re-randomize anything. Starting an identity that already has a session open is a no-op that returns the existing one.

Returns the session record: name, opened_at, idle_seconds, url (the page it is on), plus cdp_endpoint — a ws:// DevTools address you can attach your own automation to. It is only published for Chrome-core identities that have external debugging turned on in the app (off by default; Edit -> Advanced -> External debugging, needs a second verification, then reopen the identity). Otherwise it is null, cdp_unavailable gives the daemon's reason code (external_debug_off, external_debug_not_granted, not_supported_for_engine, starting) and cdp_note says what to do — the session commands work either way.

Prefer the daemon's own commands over attaching to cdp_endpoint. A session driven from outside bypasses Nullprint's consistency guards, so an outside tool can set a viewport, locale, timezone, or header that contradicts the identity's frozen fingerprint and make it detectable. Attach at your own risk, and change nothing the identity did not already claim.

Fails with a readable message if the identity does not exist (404), if too many sessions are already warm (429), or if the daemon is not running.

Input parameters:

- `headless` (boolean): run without a visible window. Default false (headed), which is what sites expect and what you want for anything login-shaped.
- `name` (string, required): identity name, exactly as profiles_list reports it

### `profile_stop` (~209 tokens)

Stop an identity's browser, however it was started.

Cookies and login state live in the identity's own storage and survive the stop; whatever was typed into an open form does not. Stop identities you are done with — a running identity holds a warm browser and keeps its login state exposed.

Two things can be running for one identity and this closes either: the warm session profile_start opens (the daemon drives it), and a window the desktop app launched on its own. The session is closed first; if there is none, the identity-level stop is used instead, which also terminates a launched window and is a no-op on an identity that is already idle.

Returns {name, stopped, closed}: `stopped` is false when the identity was already idle, and `closed` lists what was actually shut down.

Fails with a readable message if the identity does not exist, or if the daemon is not running.

Input parameters:

- `name` (string, required): identity name, exactly as profiles_list reports it

### `profile_create` (~376 tokens)

Create a new identity. Its fingerprint is generated and frozen on the spot.

You do not choose the fingerprint — the point of the product is that every identity gets one self-consistent, frozen set of traits that stays byte-identical across restarts. Do not create identities speculatively: they count against the installation's quota.

Returns {name, os, created}. Fails with a readable message if the name is taken (409), if the quota is reached or the installation is unauthorized (403), or if an argument is rejected (422).

Input parameters:

- `engine` (string): which browser core backs the identity. Leave unset to use this installation's default; an unknown value is rejected with the list of accepted ones.
- `force_dark` (boolean): personal display preference, OFF by default. true inverts web pages that have no dark theme of their own (Google Ads and the like) to dark; sites may notice the changed colors, so use it only on iden…
- `group` (string): local ledger: which account, email, or purpose this identity is bound to
- `name` (string, required): the new identity's name. Must be unique — creating a name that already exists fails rather than overwriting it.
- `note` (string): local ledger note, free text
- `proxy` (string): exit proxy URL, e.g. "http://user:pass@host:port" or "socks5://host:port" — any vendor. Omit for a direct connection. Test it with proxy_check first.
- `tags` (array): local ledger tags
- `urls` (array): startup tabs — pages opened automatically when the identity starts

### `profile_settings` (~166 tokens)

Change a display setting of an existing identity. Only the settings you pass are changed; everything else in its launch settings stays as it is.

force_dark: true = force dark web pages (inverts sites that have no dark theme; the identity's color scheme then reads as dark too). false = off, pages render exactly as the sites intend (the default). Sites may notice forced dark colors — reserve it for identities the user manages for themselves. Chrome-engine identities only. Takes effect the next time the identity starts.

Returns {name, launch} with the launch settings now stored.

Input parameters:

- `force_dark` (boolean): true = force dark web pages; false = off (default). Chrome-engine identities only.
- `name` (string, required): identity name, exactly as profiles_list reports it

### `profile_checkup` (~224 tokens)

Run the consistency checkup on an identity — is its fingerprint coherent?

Every trait has one correct value given the identity's OS, browser core, and exit IP, so each item gets a verdict rather than just being displayed. Run it after changing an identity's proxy, and before trusting an identity with anything that matters.

Returns {name, engine, live, summary, checks}. `summary` is "pass", "warn", or "fail" — the worst verdict among the checks. Each entry in `checks` has an id, a label, a verdict ("pass"/"warn"/"fail"/"skip"), and a detail string explaining it. "skip" means the check needs live=true, or does not apply.

Input parameters:

- `live` (boolean): false (default) derives everything from the stored record — fast, no side effects. true additionally STARTS THE BROWSER to measure the real TLS/JA3 fingerprint and User-Agent; takes tens of seconds a…
- `name` (string, required): identity name, exactly as profiles_list reports it

### `proxy_check` (~171 tokens)

Test a proxy URL and report where it actually comes out. No identity needed.

Use it before binding a proxy to an identity with profile_create: a dead or slow exit found here costs nothing, the same exit found halfway through a signup costs the account.

Returns {ok: true, exit_ip, country, latency_ms} when the proxy works, or {ok: false, error} when it does not. A dead proxy is an ANSWER, not a failure — check the `ok` field rather than assuming success. `country` can be null even on a working proxy if the exit IP cannot be geolocated.

Input parameters:

- `url` (string, required): the proxy URL to test, e.g. "http://user:pass@host:port" or "socks5://host:port"

### `profile_delete` (~222 tokens)

Move an identity to the recycle bin. Recoverable — this is not erasure.

The identity's browser storage, cookies and logged-in sessions go to the recycle bin intact and profile_restore brings them back unchanged. It stops counting against the installation's quota while it sits there, and it no longer appears in profiles_list.

A running identity is refused (409) rather than killed under itself — stop it with profile_stop first, then delete. Deleting a name that is already in the recycle bin replaces the older copy, so the older one is lost.

There is deliberately NO tool here for permanent deletion. Emptying the recycle bin destroys logged-in accounts with no way back, so it is a manual action in the desktop app only — a person does it, looking at what they are about to lose. Do not look for a way around this; tell the human instead.

Fails with a readable message if the identity does not exist (404), or if it is still running (409).

Input parameters:

- `name` (string, required): identity name, exactly as profiles_list reports it

### `trash_list` (~106 tokens)

List the identities in the recycle bin — what is still recoverable.

Read-only. Deleted identities do not appear in profiles_list, so without this you could only restore a name you already knew. Pair it with profile_restore.

Each entry carries name, engine, os, created, and deleted_at. Newest deletion first. An empty list means the recycle bin is empty. Nothing here is gone yet — everything listed still has its cookies and login state and comes back intact via profile_restore.

### `profile_restore` (~120 tokens)

Bring an identity back from the recycle bin, exactly as it was.

Its frozen fingerprint, proxy binding, cookies and login state all come back — restoring is not re-creating, so nothing is regenerated and no logged-in account is lost.

Returns {name, engine, created}. Fails with a readable message if nothing by that name is in the recycle bin (404), or if a live identity has taken the name since (409) — rename or delete that one first, then restore.

Input parameters:

- `name` (string, required): the trashed identity's name

### `session_goto` (~121 tokens)

Navigate a session's current page to a URL.

Open the identity's session with profile_start first — this drives an already-open session, it does not open one itself.

Returns {url}: the page's URL after navigation. Fails with a readable message if the session is not open (409 — call profile_start first) or the identity does not exist (404).

Input parameters:

- `name` (string, required): identity name, exactly as profiles_list reports it
- `url` (string, required): the URL to navigate to, e.g. "https://example.com/login"

### `session_snapshot` (~124 tokens)

Take an accessibility-tree snapshot of the session's current page.

This is the primary way to see what is on a page before acting on it — read it to find the roles/names/selectors to pass to session_click_selector or session_fill_selector. Requires a session opened with profile_start first.

Returns {snapshot}: the page's accessibility tree as text. Fails with a readable message if the session is not open (409 — call profile_start first) or the identity does not exist (404).

Input parameters:

- `name` (string, required): identity name, exactly as profiles_list reports it

### `session_click_selector` (~150 tokens)

Click an element on the session's current page, located by a CSS selector.

Requires a session opened with profile_start first. `selector` is a CSS selector (e.g. "button#submit", "a.nav-link") — not an XPath, not a role/name pair (use session_snapshot to find one).

Returns {ok: true}. Fails with a readable message if the session is not open (409 — call profile_start first), the identity does not exist (404), or the selector never matches / the click times out (502).

Input parameters:

- `name` (string, required): identity name, exactly as profiles_list reports it
- `selector` (string, required): CSS selector for the element to click

### `session_fill_selector` (~166 tokens)

Fill a text field on the session's current page, located by a CSS selector.

Uses Playwright's fill(), which drives React/Vue-controlled inputs correctly (setting the DOM value directly does not). Requires a session opened with profile_start first. `selector` is a CSS selector, not an XPath.

Returns {ok: true}. Fails with a readable message if the session is not open (409 — call profile_start first), the identity does not exist (404), or the selector never matches / the fill times out (502).

Input parameters:

- `name` (string, required): identity name, exactly as profiles_list reports it
- `selector` (string, required): CSS selector for the input to fill
- `text` (string, required): the text to put in the field, replacing whatever was there

### `session_press` (~120 tokens)

Press a keyboard key on the session's current page (whatever is focused).

Requires a session opened with profile_start first. Use Playwright key names, e.g. "Enter", "Tab", "Escape", "Control+A".

Returns {ok: true}. Fails with a readable message if the session is not open (409 — call profile_start first) or the identity does not exist (404).

Input parameters:

- `key` (string, required): the key (or chord) to press
- `name` (string, required): identity name, exactly as profiles_list reports it

### `session_eval` (~194 tokens)

Evaluate a JavaScript expression on the session's current page.

Requires a session opened with profile_start first. A page-JS error is data to read, not a tool failure: the daemon returns {"ok": false, "error", "error_type"} instead of raising when the expression throws.

Returns the {ok, result} envelope: {"ok": true, "result": <value>} on success. `result` is whatever the expression evaluates to as JSON — string, number, boolean, object, array, or null — not necessarily a string; do not assume its type. Fails with a readable message if the session is not open (409 — call profile_start first) or the identity does not exist (404).

Input parameters:

- `expr` (string, required): the JavaScript expression to evaluate in the page, e.g. "document.title"
- `name` (string, required): identity name, exactly as profiles_list reports it

### `session_tabs` (~120 tokens)

List the session's open tabs, with the currently active one marked.

Requires a session opened with profile_start first. Use this to find the index to pass to session_switch_tab.

Returns {tabs, active}: `tabs` is a list of {index, url}, `active` is the index of the tab currently in front. Fails with a readable message if the session is not open (409 — call profile_start first) or the identity does not exist (404).

Input parameters:

- `name` (string, required): identity name, exactly as profiles_list reports it

### `session_switch_tab` (~143 tokens)

Bring one of the session's open tabs to the front and make it the active one.

Requires a session opened with profile_start first. Get `index` from session_tabs — indices are only valid as of that snapshot, a tab opened or closed since shifts them.

Returns {index, url} for the now-active tab. Fails with a readable message if the session is not open (409 — call profile_start first), the identity does not exist (404), or the index is out of range (422).

Input parameters:

- `index` (integer, required): the tab's index, from session_tabs
- `name` (string, required): identity name, exactly as profiles_list reports it

### `session_dialog` (~231 tokens)

Answer the JavaScript dialog (alert, confirm, prompt, beforeunload) open on the session's page.

Dialogs are left open, as in a normal browser: while one is open the page is blocked and the other session_* tools fail with 409 "a JavaScript <type> is open on the page (<message>)". Call this to answer it, then retry. A beforeunload that your own session_goto (or click) causes is accepted automatically and never shows up here.

Returns {ok, action, type, message} for the dialog that was answered. Fails with a readable message if no dialog is open (409), the session is not open (409 — call profile_start first) or the identity does not exist (404).

Input parameters:

- `action` (string, required): "accept" (OK / Leave) or "dismiss" (Cancel / Stay)
- `name` (string, required): identity name, exactly as profiles_list reports it
- `prompt_text` (string): for a prompt() dialog, the text to enter before accepting; omit it to accept the prompt's default value

## Diagnostics

Captured diagnostic sections: Provenance, Dependencies. The full working is on the page: https://verifymcp.io/servers/nullprintai-nullprint-mcp/nullprint-mcp#diagnostics

## Score history

- 2026-10-23: 58
- 2026-10-22: 73
- 2026-10-21: 68
- 2026-10-20: 68
- 2026-10-19: 68
- 2026-10-18: 68
- 2026-10-17: 68
- 2026-10-16: 68
- 2026-10-15: 68
- 2026-10-14: 68
- 2026-10-13: 68
- 2026-10-12: 39

## Common questions

### What is the Nullprint MCP server?

Nullprint is an MCP server listed in the public MCP registry as io.github.nullprintai/nullprint-mcp. Create, launch, check and drive Nullprint multi-profile browser profiles from any MCP agent. This page covers its npm package (nullprint-mcp).

### Is the Nullprint MCP server safe to use?

Nullprint scores 58 out of 100 on VerifyMCP. We found no known CVEs affecting it as of 23 October 2026. It declares no install or post-install scripts. That is a record of what we were able to check automatically, not an endorsement. The category breakdown on this page shows every signal behind the number, including the ones we could not confirm.

### What tools does the Nullprint MCP server expose?

Nullprint exposes 19 tools: profiles_list, profile_start, profile_stop, profile_create, profile_settings, and 14 more. Their descriptions and schemas cost roughly 3,413 tokens of context every time the server is loaded.

### Is the Nullprint MCP server still maintained?

Nullprint is still listed as active in the MCP registry. We last reached this channel on 23 October 2026. Those dates come from our own scans of the registry and the channel itself, not from anything the publisher announced.

### What licence is the Nullprint MCP server under?

Nullprint declares the MIT licence, which is OSI-approved. That covers the source only, and says nothing about the cost of any service it calls.

## Links

- npm package: https://www.npmjs.com/package/nullprint-mcp
- Socket report: https://socket.dev/npm/package/nullprint-mcp
- Repository: https://github.com/nullprintai/nullprint-mcp
- Website: https://nullprint.ai/docs/mcp/
- Changelog RSS feed: https://verifymcp.io/servers/nullprintai-nullprint-mcp/nullprint-mcp.xml
- Changelog JSON feed: https://verifymcp.io/servers/nullprintai-nullprint-mcp/nullprint-mcp.json
- HTML version of this page: https://verifymcp.io/servers/nullprintai-nullprint-mcp/nullprint-mcp
