Skip to content
Conduit

mcp server

localmcp

A local, scoped, read-first browser for AI agents (MCP + CLI). The page itself, not a summary.

NETWORK101/localmcpcommunitylistedunreviewed

Set up

Runs on your computer. Pick your agent and operating system.

Before you start

  • Node.js 20 or newer
~/Library/Application Support/Claude/claude_desktop_config.json
{
  "mcpServers": {
    "localmcp": {
      "command": "npx",
      "args": [
        "-y",
        "@localmcp_serv/localmcp"
      ]
    }
  }
}

same format Cursor, GitHub Copilot CLI, Junie, JetBrains AI Assistant, Augment (Auggie CLI), Factory Droid, Kiro, Devin CLI +12

Values in angle brackets are placeholders. Restart Claude Desktop after editing its config.Claude Desktop docs

README

synced Oct 27, 2026

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 @localmcp_serv/localmcp   # Claude Code
npx @localmcp_serv/localmcp init                            # everything else: prints config for your clients

npm · MIT · Node ≥ 20 · Website


Why

OptionProblem 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 toolsNeed 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.

What's new in 0.2

  • Real authenticated browsing. npx @localmcp_serv/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

ToolWhat it doesAnnotations
browsePage → clean markdown. focus, maxTokens, diff, elements, waitFor.read-only
extractJSON-LD, meta/OpenGraph, tables as rows, headings; optional schema filled via sampling.read-only
linksDe-duplicated absolute links; filter by sameOrigin or match.read-only
screenshotViewport, full page, or one selector; png or jpeg.read-only
interactclick 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 @localmcp_serv/localmcp read https://docs.stripe.com/api --focus "pagination" --max-tokens 1500
npx @localmcp_serv/localmcp extract https://example.com/pricing --schema '{"plans":[{"name":"string"}]}' --json
npx @localmcp_serv/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

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 @localmcp_serv/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

BrowserHowStatus
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 FirefoxWebDriver BiDi attachRoadmap
Your running SafarisafaridriverRoadmap
Tabs in your everyday browser, no flagsextension bridgeRoadmap

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

Configuration

npx @localmcp_serv/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 @localmcp_serv/localmcp usage shows totals. LOCALMCP_NO_LIMIT=1 overrides.

MCP protocol surface

FeatureSupport
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}✓
Transportstdio

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:

PageRendered HTMLPlaywright MCP snapshotlocalmcp (full)localmcp (default)
Stripe API reference428,45730,724482482
GitHub REST: Issues330,08387,35020,2284,002
Next.js docs159,92119,721799799
Wikipedia: Model Context Protocol109,26820,2886,3873,991
Python: json module29,70025,3818,6243,996
Hacker News front page8,53912,2074,0233,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

↑↓ navigate openesc close