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