Chrome Control Bridge for AI Coding Agents
AI coding agents are getting very good at working with code.
They can read a repository, modify files, run tests, execute commands, and reason about the results.
But there's a point where the terminal isn't enough.
Eventually, the agent needs to open the application it just built.
It needs to:
navigate to a URL
inspect the page
find an element
click it
type into a form
take a screenshot
inspect console output
inspect network activity
verify that something actually happened
That's where I started building Nimvarya.
Nimvarya is a standalone Chrome-control bridge for terminal AI agents.
It gives agents such as Claude Code, Codex CLI, Gemini CLI, and Cursor a shared MCP interface for controlling a real Chrome tab.
The problem
Browser automation isn't particularly difficult.
What's difficult is making browser control work nicely with AI coding agents.
A typical agent already has a terminal:
AI agent
│
├── read files
├── edit files
├── run commands
└── run tests
Now add a browser:
AI agent
│
├── filesystem
├── terminal
├── tests
│
└── browser
│
├── navigate
├── inspect
├── click
├── type
└── screenshot
The browser becomes another tool available to the agent.
The interesting question is:
How do we connect the two without making the browser implementation part of the agent itself?
That led me to a simple architecture.
Three pieces
Nimvarya is intentionally split into three components:
AI Agent
│
│ MCP / stdio
▼
┌──────────────┐
│ MCP Server │
└──────┬───────┘
│
│ WebSocket
▼
┌──────────────┐
│ Relay │
└──────┬───────┘
│
│ WebSocket
▼
┌──────────────┐
│ Chrome │
│ Extension │
└──────┬───────┘
│
▼
Real Chrome
The project consists of:
A Chrome MV3 extension
A local WebSocket relay
An MCP server
The extension executes browser actions and captures page events.
The relay routes messages between the extension and controllers.
The MCP server exposes browser operations to the AI agent as individual tools.
That separation ended up being important.
Why MCP?
AI coding agents increasingly understand tools through protocols rather than bespoke integrations.
Nimvarya uses MCP as the interface exposed to the agent.
Instead of giving the model one enormous:
browser(action, arguments...)
command, Nimvarya exposes individual page actions.
Conceptually:
navigate
read
find
click
type
screenshot
...
The project currently exposes twenty page actions as discrete MCP tools.
That matters because tool descriptions become part of the agent's decision-making environment.
A tool like:
click
is much easier for an agent to reason about than a generic:
executeBrowserCommand
with a large enum of possible operations.
The browser becomes a collection of explicit capabilities.
The interesting part: the Chrome tab doesn't need to be focused
One of the things I particularly wanted was the ability to control a Chrome tab without making it the user's active tab.
The extension executes actions in a deliberately unfocused sandbox tab.
That makes the architecture much more useful for developer workflows.
You can have:
Your normal Chrome tabs
+
AI-controlled Chrome tab
rather than having an AI agent constantly steal focus from whatever you're doing.
This is a deceptively important property.
If an agent is running a long browser workflow, the browser should behave more like infrastructure than like someone physically operating your mouse.
The relay is deliberately boring
The relay is a local WebSocket server.
By default it binds to:
127.0.0.1:8766
and routes frames between the browser extension and controllers. The port can be overridden with NIMVARYA_PORT.
There is something nice about keeping this layer simple.
The relay doesn't need to understand browser semantics.
It doesn't need to know what a click means.
It doesn't need to know what an MCP tool is.
Its job is essentially:
receive
↓
route
↓
forward
Keeping responsibilities separated makes the system easier to reason about.
The "never throw" contract
One of the more important design decisions in Nimvarya is the error model.
AI agents don't behave like traditional application clients.
A conventional API might respond with:
HTTP 500
and let the caller decide what to do.
For an agent, a transport-level failure can be much more disruptive.
If the browser operation fails because:
the selector doesn't exist
the relay has a problem
the result is too large
the requested operation can't be completed
the goal is to return a structured result rather than turning the whole tool invocation into an opaque transport exception.
The philosophy is:
Browser problem
↓
Structured information
↓
Agent can reason about it
↓
Agent decides what to do next
rather than:
Browser problem
↓
Unhandled exception
↓
Tool call collapses
This becomes especially useful when the agent can recover on its own.
Screenshots have a special problem
Screenshots are useful to an AI agent.
They're also potentially huge.
That creates an awkward failure mode:
captureTab
↓
large screenshot
↓
tool result too large
↓
failure
Instead of treating that as a hard failure, Nimvarya has a self-healing screenshot path.
If a captureTab result is too large, it is automatically re-captured through a bounded downscale ladder.
The idea is simple:
Capture
↓
Too large?
├── No → return
│
└── Yes
↓
resize
↓
capture
↓
acceptable?
├── Yes → return
└── No → continue
This is one of those details that isn't particularly exciting in a feature list, but becomes extremely important once an agent is actually using the system.
TypeScript as the source of truth
Another design decision I wanted was to minimize protocol drift.
The supported browser actions live in one place:
src/protocol/actions.ts
PAGE_ACTIONS acts as the single source of truth.
Consumers type themselves against:
Record<PageAction, ...>
so adding or removing an action can become a TypeScript compiler error instead of a subtle runtime mismatch.
That's a small example of something I like about TypeScript:
use the type system to make architectural contracts executable.
Testing the contract
Nimvarya also takes a fairly strict approach to its own internals.
Every non-types.ts file under src/ has a colocated unit test, and exported symbols are expected to have an importer within the package.
The package has its own:
package.json
tsconfig.json
eslint.config.mjs
vitest.config.ts
lockfile
and can be verified independently with:
npm run verify
which runs typechecking, linting, and tests.
This is deliberate because infrastructure code tends to fail in the seams between components.
The protocol, relay, extension, and MCP server all need to agree.
Using Nimvarya
The current setup is intentionally simple.
Clone the repository and install dependencies:
cd tools/nimvarya
npm install
npm run verify
Start the relay:
npm run relay
Build the Chrome extension:
npm run build:extension
Then load the generated extension directory into Chrome as an unpacked extension.
Finally, the MCP server can be registered with the agent.
For example, Claude Code can use a configuration along the lines of:
{
"nimvarya": {
"type": "stdio",
"command": "node",
"args": ["tools/nimvarya/bin/mcp.mjs"],
"env": {}
}
}
Once connected, the agent gets access to the browser through the MCP surface.
Why build another browser tool?
There are already excellent browser automation tools.
That's not really the point.
Nimvarya is trying to solve a narrower problem:
How do we make a real Chrome browser available as a reusable capability for terminal AI agents?
The distinction matters.
I don't want browser control to become permanently coupled to one agent.
I don't want the browser to become coupled to one automation framework.
And I don't want every agent to implement its own completely different browser integration.
Instead:
Claude Code
│
Codex CLI
│
Gemini CLI
│
Cursor
│
▼
┌──────────┐
│ Nimvarya │
└────┬─────┘
│
▼
Chrome
One browser-control surface.
Multiple agents.
Where this gets interesting
The immediate use case is straightforward:
"Open my application and interact with it."
But the interesting possibilities are broader.
An agent could:
write code
↓
start application
↓
open Chrome
↓
inspect UI
↓
interact with application
↓
observe result
↓
identify problem
↓
modify code
↓
test again
That creates a much tighter feedback loop between code and the running application.
Instead of an agent only reasoning about source code, it can reason about the software as a user experiences it.
That feels like an important direction for coding agents.
What's next?
Nimvarya is still early.
Only Claude Code has been live-verified so far; configuration paths for Gemini CLI, Codex CLI, and Cursor are documented but not yet exercised in the same way. The project also maintains dated live-Chrome verification traces for documented tool behavior.
That's one reason I'm publishing this now.
I don't think the architecture should be considered finished.
I'd rather get it into the hands of people building agents and browser tooling and find out where the abstraction is wrong.
Questions I'm particularly interested in:
What browser capabilities are missing?
Which operations should be primitives?
What information does an agent actually need from the browser?
Where should browser state live?
How should multiple agents share a browser?
What should the protocol look like long-term?
What does a truly agent-agnostic browser interface look like?
Those are more interesting questions than simply adding another click() function.
Try it
Nimvarya is open source and MIT licensed.
The project is currently distributed inside the repository rather than through a package registry.
If you're building AI coding agents, browser automation, MCP tooling, or developer infrastructure, I'd love to see what you think.
GitHub: https://github.com/Basiliskin/nimvarya
Nimvarya — a standalone Chrome-control bridge for your favourite terminal AI.
