Skip to main content
Glama

localmcp

A local, read-optimized browser for AI agents. Your agent reads any page — including the ones you're signed in to — as focused, token-budgeted markdown. Runs a real Chromium on your machine. No cloud relay, no API key, no account.

claude mcp add localmcp -- npx -y @network101/localmcp   # Claude Code
npx @network101/localmcp init                            # everything else: prints config for your clients

npm · MIT · Node ≥ 20 · Website


Why

Option

Problem for agents

Built-in web fetch (e.g. Claude Code's WebFetch)

No cookies, no JavaScript, refuses localhost, and large pages come back as a small-model summary rather than the page.

Cloud scrapers (Firecrawl, Jina, Browserbase)

Your Stripe dashboard, internal wiki, and localhost:3000 go through someone else's servers — or can't be reached at all.

Full automation MCPs (e.g. Playwright MCP)

Great for driving a browser. For reading, one page snapshot is 13k–87k tokens of accessibility tree (measured).

CLI browser tools

Need a shell. Claude Desktop and other sandboxed clients don't have one.

localmcp is the reading-first option: the page itself — no summarizer in the middle — focused and under a token budget, read by a browser on your machine. Five MCP tools, or three shell commands. See the benchmark.

Related MCP server: cleanfetch

What's new in 0.2

  • Real authenticated browsing. npx @network101/localmcp login <url> opens a visible browser on a private profile. Sign in once; the agent reuses the session. Or attach to your own Chrome over CDP.

  • Focus + budget. browse({ url, focus: "rate limits", maxTokens: 1500 }) ranks sections by relevance and returns only what fits — and tells the agent which sections it left out.

  • Structured extraction with MCP sampling. extract returns JSON-LD, meta tags, and tables as row objects. Pass a schema and your client's own model fills it — no extra API key.

  • Current MCP spec. Tool annotations, outputSchema + structuredContent, server instructions, progress notifications, and diff snapshots as resources.

  • Safety by default. Domain allow/deny policy, read-only mode, dangerous schemes blocked, and every page wrapped in an untrusted-content fence against prompt injection.

  • Metadata-first reading (0.2.1). If a site publishes markdown for agents (a text/markdown alternate link, or Accept: text/markdown), localmcp reads that instead of the rendered HTML. Stripe's API reference drops from 428k tokens of HTML to the publisher's own markdown. Every result opens with a page card (type, site, author, published/updated dates, canonical URL, source) read from JSON-LD, OpenGraph, <meta> and front matter, and links points out a site's /llms.txt.

See CHANGELOG.md for the full list and migration notes.

How a page becomes agent-readable

  1. Ask the publisher. Use a declared <link rel="alternate" type="text/markdown"> on the same origin, or request the page with Accept: text/markdown. Many docs platforms (Stripe, Vercel, Cloudflare, Anthropic, GitHub) serve clean markdown this way. It's used only from the page's own origin and only if it matches the rendered page's title; otherwise the rendered page wins and the result says so.

  2. Read the metadata. Use JSON-LD (including @graph), OpenGraph, <meta>, the canonical link and markdown front matter to build a one-line page card covering what the page is, who wrote it, how fresh it is, and where the text came from.

  3. Read the structure. If there's no publisher markdown, render the page, strip chrome (nav, footer, banners, hidden and aria-hidden nodes), keep semantic landmarks, headings and tables, and convert to GFM.

  4. Rank and budget. focus ranks sections; maxTokens caps the result and lists omitted headings.

  5. Fence it. Wrap the result in <untrusted-page-content> with typed structuredContent (source, card).

Example card:

> TechArticle · Vercel · updated 2026-09-13 · source: publisher markdown (declared text/markdown alternate)

Turn publisher markdown off with "distill": { "publisherMarkdown": false }. Cross-origin alternates are always ignored, so a page can't point the agent at another host.

Tools

Tool

What it does

Annotations

browse

Page → clean markdown. focus, maxTokens, diff, elements, waitFor.

read-only

extract

JSON-LD, meta/OpenGraph, tables as rows, headings; optional schema filled via sampling.

read-only

links

De-duplicated absolute links; filter by sameOrigin or match.

read-only

screenshot

Viewport, full page, or one selector; png or jpeg.

read-only

interact

click fill select press check uncheck hover scroll wait, then returns the resulting page.

destructive

Read-only annotations let clients auto-approve reads while still confirming interact.

From a shell

The same tools and policy, with no tool schema in your agent's context:

npx @network101/localmcp read https://docs.stripe.com/api --focus "pagination" --max-tokens 1500
npx @network101/localmcp extract https://example.com/pricing --schema '{"plans":[{"name":"string"}]}' --json
npx @network101/localmcp links https://docs.example.com --same-origin --match /api/

--json prints structuredContent; otherwise you get the same fenced markdown the MCP tools return. Exit code 1 means a policy block or a page error.

browse

browse({ url: "https://docs.stripe.com/api", focus: "pagination", maxTokens: 1500 })
# Pagination | Stripe API Reference
https://docs.stripe.com/api/pagination · 1,204 tokens · 98% smaller than raw HTML · truncated to budget
> Focus: pagination

<untrusted-page-content source="https://docs.stripe.com/api/pagination">
…the relevant sections, in page order…
</untrusted-page-content>

_Omitted sections — call again with `focus` or a larger `maxTokens` to read them: Errors · Idempotent requests · …_
  • diff: true — first call saves a baseline; later calls return only a unified diff of what changed (the old watch tool; watch still works as an alias).

  • elements: true — appends visible buttons/links/inputs with CSS selectors verified unique in the live DOM, ready for interact.

  • waitFor: "#app table" — wait for a late-rendering SPA element before reading.

extract

extract({ url: "https://example.com/pricing", schema: { plans: [{ name: "string", price: "string" }] } })

structuredContent always contains title, meta, jsonLd[], tables[] ({ headers, rows: [{ header: value }] }), and headings[]. If the client supports sampling, data holds the schema filled by the client's model and method is "sampling"; otherwise the schema is returned as a hint next to the content (method: "dom").

links({ url: "https://docs.example.com", sameOrigin: true, match: "/api/" })

interact

interact({
  url: "http://localhost:3000/signup",
  actions: [
    { type: "fill", selector: "input[name='email']", value: "test@example.com" },
    { type: "press", selector: "input[name='email']", value: "Enter" },
    { type: "wait", selector: ".welcome" }
  ]
})

Password values are never echoed back. If an action navigates to a host your policy denies, the result is withheld.

Signed-in pages

Option A: dedicated profile (recommended)

npx @network101/localmcp login https://dashboard.stripe.com

A browser window opens on ~/.localmcp/profile. Sign in to whatever your agent should read, then close the window. With the default browser.profile: "auto", localmcp uses that profile from then on. Chromium locks a profile to one process. If two clients run localmcp at once, the second one falls back to an ephemeral session and says so in its output.

Option B: your own Chrome

Start Chrome with --remote-debugging-port=9222, then:

{ "browser": { "cdpEndpoint": "http://localhost:9222" } }

localmcp opens its own tabs in your existing session and never closes your browser.

Either way the agent can read anything those sessions can. So while a signed-in session is in use, interact is off by default (policy.allowInteract: "auto"): the agent reads as you but can't click or type as you until you set allowInteract: true. Pair this with policy.allow (below).

Browsers

Browser

How

Status

Chromium (bundled)

default

✓

Google Chrome, Microsoft Edge

"channel": "chrome" / "msedge"

✓

Firefox

"engine": "firefox"

✓

WebKit (Safari's engine)

"engine": "webkit"

✓

Your running Chrome, Edge, Brave, Arc, Vivaldi, Opera

"cdpEndpoint"

✓

Your running Firefox

WebDriver BiDi attach

Roadmap

Your running Safari

safaridriver

Roadmap

Tabs in your everyday browser, no flags

extension bridge

Roadmap

Firefox and WebKit need a one-time npx playwright install firefox webkit. Sign in per engine with npx @network101/localmcp login <url> --browser firefox.

Configuration

npx @network101/localmcp init writes .localmcp.json. Global defaults can live in ~/.config/localmcp/config.json; project config wins.

{
  "browser": {
    "timeout": 30000,
    "headless": true,
    "engine": "chromium",         // "chromium" | "firefox" | "webkit"
    "channel": null,              // chromium only: "chrome" | "msedge" | …
    "profile": "auto",            // "auto" | "persistent" | "ephemeral"
    "cdpEndpoint": null           // e.g. "http://localhost:9222"
  },
  "distill": {
    "maxTokens": 4000,            // default budget for browse/extract/interact
    "includeLinks": true,
    "includeImages": false,
    "publisherMarkdown": true     // prefer markdown the site serves for agents
  },
  "policy": {
    "allow": [],                  // host globs; empty = any. e.g. ["*.stripe.com", "localhost:*"]
    "deny": [],                   // checked first
    "allowInteract": "auto",      // "auto": off while signed in · true · false (read-only)
    "allowFileUrls": false
  },
  "limits": {
    "maxSessionsPerDay": 100,     // local circuit breaker against runaway agents
    "maxTokensPerDay": 1000000
  }
}

Host globs: example.com (exact host, any port), *.example.com (subdomains, not the apex), localhost:3000, localhost:*.

Security model

  • Nothing is relayed. Pages are fetched and distilled by a browser on your machine, then go only to the model your agent already uses. No telemetry, no account.

  • Policy at the boundary. Every URL is checked before a browser is touched, and again after redirects or actions that change origin. Deny rules also apply to every request a page makes — images, iframes, scripts, fetches — including when attached to your own browser. Only http(s) is allowed by default; file:, javascript:, chrome:, data: are refused.

  • Prompt-injection fence. Page content comes back inside <untrusted-page-content> tags, and the server's instructions tell the model to treat it as data. Pages that try to close the fence early are neutralised. This reduces injection risk; it doesn't eliminate it. Keep interact confirmations on in your client.

  • Circuit breaker. Daily session and token caps (local SQLite at ~/.localmcp/usage.db). npx @network101/localmcp usage shows totals. LOCALMCP_NO_LIMIT=1 overrides.

MCP protocol surface

Feature

Support

tools with title, annotations, outputSchema, structuredContent

✓

isError tool results

✓

Server instructions

✓

notifications/progress (when the client sends a progressToken)

✓

sampling/createMessage (used by extract when the client supports it)

✓

resources — diff snapshots at localmcp://snapshot/{url}

✓

Transport

stdio

CLI

localmcp                 Start the MCP server on stdio
localmcp init            Create .localmcp.json and print setup for your MCP clients
localmcp login [url]     Sign in once on the persistent profile
localmcp usage           Today's and this week's usage
localmcp --version

Token benchmark

npm run bench measures named public pages four ways — rendered HTML, a Playwright MCP browser_snapshot, browse with no budget, and browse with the 4,000-token default — and writes benchmarks/results.md. On 2026-10-01:

Page

Rendered HTML

Playwright MCP snapshot

localmcp (full)

localmcp (default)

Stripe API reference

428,457

30,724

482

482

GitHub REST: Issues

330,083

87,350

20,228

4,002

Next.js docs

159,921

19,721

799

799

Wikipedia: Model Context Protocol

109,268

20,288

6,387

3,991

Python: json module

29,700

25,381

8,624

3,996

Hacker News front page

8,539

12,207

4,023

3,945

Tokens ≈ characters ÷ 4 in every column. Savings range from 2× (a page that is already mostly text) to hundreds of times (a site serving its own markdown); focus and maxTokens cap any page at the budget you choose. Tool schemas: localmcp 1,352 tokens, Playwright MCP 5,072 — Claude Code, Cursor and Codex load schemas on demand, so this mainly matters for clients that don't.

Development

npm install
npx playwright install chromium
npm test          # vitest — unit, browser, and end-to-end MCP protocol tests
npm run build

Name

localmcp was previously published as browsermcpai (and headlessdev before that). The old config files, state directory and env override keep working; see the changelog.

It is not affiliated with Browser MCP (browsermcp.io), a Chrome-extension project, nor with the local-mcp and @localmcp/* packages on npm, which give ChatGPT and Claude shell and file access to your machine. localmcp is the opposite kind of tool: a scoped, read-only browser.

License

MIT

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    B
    maintenance
    Agent-native headless browser for AI agents. Converts web pages to a Semantic Object Model (SOM) instead of raw HTML — 17x average token reduction across real-world sites (up to 117x on complex pages). Native MCP server with fetch_page, extract_text, extract_links, and full browser automation. No API key required.
    41 npm
    Apache 2.0
  • A
    license
    A
    quality
    C
    maintenance
    Enables AI agents to read web pages reliably, returning clean markdown content, hyperlinks, and metadata without navigation or ad noise.
    3
    9 npm
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables AI agents to read, screenshot, or convert web pages to PDF using a real headless browser, turning any URL into clean Markdown, a visual image, or a print-ready document.
    82 npm
    MIT