Skip to main content
Glama
maparham

agent-ui-bridge

by maparham

agent-ui-bridge

Let an MCP agent drive a running web app: the page registers named, schema-validated actions, dials a WebSocket relay in your backend, and ten ui_* MCP tools forward to it. A Chrome extension lets the page screenshot or focus its own tab while backgrounded.

Nothing here knows what your app does. Actions are yours; this is the wire.

What is in the box

Piece

Install

npm package agent-ui-bridge (action registry, relay client, confirm gate, React modal, extension client)

npm i github:maparham/agent-ui-bridge

Python package agent-ui-bridge (relay hub, WebSocket pump, the ui_* MCP tools, a probe)

uv add "agent-ui-bridge @ git+https://github.com/maparham/agent-ui-bridge#subdirectory=python"

extension/ Tab Bridge Chrome extension

clone this repo, then load extension/ unpacked (it is not in the npm tarball), see extension/README.md

The extension's page-side client ships in the npm package as agent-ui-bridge/tab-bridge (probeTabBridge, tabBridgeScreenshot, tabBridgeFocus); the React confirm modal is agent-ui-bridge/react.

Related MCP server: OpenCode in Chrome

Frontend: register actions, start the bridge

import { registerAction, registerTabActions, startAgentBridge } from "agent-ui-bridge";

registerAction({
  name: "cart.add",
  description: "Add a product to the cart.",
  kind: "write", // read | write | confirm
  params: {
    type: "object",
    properties: { sku: { type: "string" }, qty: { type: "number" } },
    required: ["sku"],
  },
  handler: async (args) => addToCart(String(args.sku), Number(args.qty ?? 1)),
});

registerTabActions();            // adds tab.title.set, required by ui_set_title
const stop = startAgentBridge({  // returns a stop function
  url: "ws://localhost:8000/ws/agent-ui",
  token: () => myAuthToken(),    // optional; appended as ?token=
  decorateUrl: (u) => u,         // optional last hook before dialing
});

A kind: "confirm" action executes only after a human clicks Approve. Render the dialog once, near the root:

import { AgentConfirmHost } from "agent-ui-bridge/react";

<AgentConfirmHost className="modal" backdropClassName="modal-backdrop" />

No CSS ships with it. Style your two class names plus .modal-head, .confirm-body, .modal-foot, .ghost, .confirm-primary and .agent-confirm-warning.

Backend: one hub, one route, one call

from agent_ui_bridge import BridgeHub, register_ui_tools, serve_tab
from mcp.server import MCPServer

HUB = BridgeHub()
mcp = MCPServer("my-app-ui")

register_ui_tools(
    mcp, HUB,
    screenshot_action="page.screenshot",   # your own read-kind action
    app_name="MyApp",                      # used in the tab-control tool text
    frontend_url=lambda: "http://localhost:5173",
    hosted=lambda: False,                  # True refuses browser tab control
)
# Optional: docs={tool_name: text} overrides any tool description agents read
# (screenshot_doc does the same for ui_screenshot); title_example is the sample
# title quoted in the UNTITLED_TAB error;
# app_url_label names the setting frontend_url comes from in error text.
# The call returns {tool_name: callable} so tests can drive the tools directly.

@app.websocket("/ws/agent-ui")
async def ws_agent_ui(websocket):
    # your own auth first
    await websocket.accept()
    try:
        await serve_tab(HUB, websocket)
    except WebSocketDisconnect:
        pass

The tools: ui_sessions, ui_actions, ui_set_title, ui_invoke, ui_wait, ui_read_state, ui_screenshot, plus ui_open_tab / ui_focus_tab / ui_close_tab (macOS local dev, AppleScript). Every driven tab must be named with ui_set_title first; everything else answers UNTITLED_TAB until it has.

ui_screenshot invokes screenshot_action and expects {image_base64, mime, via, caption?}. caption becomes the text line beside the image.

Probe

python3 -m agent_ui_bridge.probe --url http://localhost:8000/mcp
python3 -m agent_ui_bridge.probe --read-state cart.state
python3 -m agent_ui_bridge.probe --invoke cart.add --args '{"sku":"A1"}'
python3 -m agent_ui_bridge.probe --screenshot /tmp/shot.png

Local development against a consumer app

cd agent-ui-bridge && npm link
cd ../your-app/frontend && npm link agent-ui-bridge
# With a linked copy, dedupe React or the confirm modal's hook throws:
# vite.config.ts -> resolve: { dedupe: ["react", "react-dom"] }

uv pip install -e ../agent-ui-bridge/python

Tests

npm test                          # frontend library (vitest)
node --test extension/*.test.js   # extension
cd python && uv run pytest        # backend library

License

MIT, Mahan Parham.

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    Enables AI agents to control the Google Chrome browser through a Node.js WebSocket bridge and a dedicated browser extension. It provides tools for capturing screenshots, executing JavaScript, managing tabs, and extracting page content via the MCP protocol.
    2
    -
  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables local opencode agents to control a live Chrome browser via MCP tools, including tab management, JavaScript execution, clicking, form filling, page reading, screenshots, and console log retrieval.
    MIT
  • F
    license
    A
    quality
    B
    maintenance
    Enables coding agents to drive a real Playwright-backed browser session, navigating, observing, interacting, and capturing proof on web pages via MCP tools across stdio and Streamable HTTP transports.
    15
    2
    -
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables remote AI agents to control a local, authenticated desktop browser over MCP through an outbound WebSocket bridge and Chrome extension, supporting tab management, navigation, page reading, clicking, typing, scrolling, screenshots, and JavaScript evaluation.
    1
    MIT