# 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:

```text
AI agent
   │
   ├── read files
   ├── edit files
   ├── run commands
   └── run tests
```

Now add a browser:

```text
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:

```text
                    AI Agent
                       │
                       │ MCP / stdio
                       ▼
                ┌──────────────┐
                │  MCP Server  │
                └──────┬───────┘
                       │
                       │ WebSocket
                       ▼
                ┌──────────────┐
                │    Relay     │
                └──────┬───────┘
                       │
                       │ WebSocket
                       ▼
                ┌──────────────┐
                │ Chrome       │
                │ Extension    │
                └──────┬───────┘
                       │
                       ▼
                  Real Chrome
```

The project consists of:

1.  A Chrome MV3 extension
    
2.  A local WebSocket relay
    
3.  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:

```text
browser(action, arguments...)
```

command, Nimvarya exposes individual page actions.

Conceptually:

```text
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:

```text
click
```

is much easier for an agent to reason about than a generic:

```text
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:

```text
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:

```text
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:

```text
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:

```text
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:

```text
Browser problem
      ↓
Structured information
      ↓
Agent can reason about it
      ↓
Agent decides what to do next
```

rather than:

```text
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:

```text
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:

```text
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:

```text
src/protocol/actions.ts
```

`PAGE_ACTIONS` acts as the single source of truth.

Consumers type themselves against:

```typescript
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:

```text
package.json
tsconfig.json
eslint.config.mjs
vitest.config.ts
lockfile
```

and can be verified independently with:

```bash
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:

```bash
cd tools/nimvarya
npm install
npm run verify
```

Start the relay:

```bash
npm run relay
```

Build the Chrome extension:

```bash
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:

```json
{
  "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:

```text
             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:

```text
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.**
