neu-browser-mcp
An MCP server that gives an agent a remote browser: create one, get a CDP URL to drive it, hand the user a live view, pick up the files it downloaded, and shut it down.
- Lifecycle only. It does not click, type or navigate. The agent drives the browser over CDP with agent-browser, Playwright, or any CDP client.
- Same tools on any backend. Prompts and skills stay the same when the browser moves between
nap,kernel,browser-useandhyperbrowser. Each backend is a small adapter behind one interface. - Credentials stay on the server. The backend token never enters the agent's context.
- Human handoff and downloads. Backends with a live view give you a URL to watch or take over the browser. Files the browser downloads can be listed, linked, or read.
Tools
Backends
A capability a backend lacks shows up as null or as a missing tool. See Adding a backend.
More backends are planned.
Run it as a server
Requires Node.js 22 or newer. The server speaks MCP over stdio.
{
"mcpServers": {
"browser": {
"command": "npx",
"args": ["-y", "neu-browser-mcp"],
"env": {
"NEU_BROWSER_BACKEND": "nap",
"NAP_BROWSER_URL": "https://browser.example.com",
"NAP_BROWSER_TOKEN": "<token>"
}
}
}
}
NEU_BROWSER_BACKEND picks the backend and defaults to nap. Each backend then reads its own variables.
nap
kernel
browser-use
hyperbrowser
Use it as a library
To add the tools to an MCP server you already run, register them on it:
import { McpServer } from '@modelcontextprotocol/server'
import { createNapProvider, registerBrowserTools } from 'neu-browser-mcp'
const server = new McpServer({ name: 'my-server', version: '1.0.0' })
registerBrowserTools(server, {
provider: createNapProvider({
baseUrl: 'http://browser.internal:3005',
publicBaseUrl: 'https://browser.example.com',
token: async () => lookUpTokenFor(tenant),
}),
scope: { tenant_id: tenant.id },
})
The server can come from either generation of the MCP TypeScript SDK: @modelcontextprotocol/server (v2) or @modelcontextprotocol/sdk (v1).
scope is a set of tags that confines the tools to one tenant. Every browser they create carries the tags, list_browsers returns only browsers that carry them, and a tool given a browser ID refuses it unless that browser carries them. This is what keeps tenants apart when they share the backend's credentials. token can be a string or an async function; a function is called once, on first use.
To pick the backend from the environment, the way the standalone server does, use providerFromEnv(process.env).
Adding a backend
A backend implements BrowserProvider from src/types.ts:
interface BrowserProvider {
readonly name: string
readonly capabilities: ProviderCapabilities
createSession(opts?: CreateSessionOptions): Promise<BrowserSession>
listSessions(opts?: ListSessionsOptions): Promise<BrowserSession[]>
releaseSession(id: string): Promise<void>
readonly files?: BrowserFiles // list, and optionally downloadUrl and read
}
interface ProviderCapabilities {
timeout: { kind: 'absolute' | 'idle'; defaultSeconds: number; maxSeconds: number }
liveView: boolean
httpCdp: boolean
metadata: boolean
}
The required part is the session lifecycle plus a CDP WebSocket URL. Everything else is declared, and the tools adapt:
src/providers/nap.ts is the reference implementation.
Development
npm install
npx tsc --noEmit
npx biome check .
npm test
See CONTRIBUTING.md.