agent-board
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@agent-boardShow the project roadmap in a new tab and pin it"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Agent Board
A local tabbed HTML viewer the Cursor agent can drive over MCP.
You keep http://127.0.0.1:4747 open in the browser. The agent opens, updates, reads, and closes tabs there instead of writing throwaway .html files into a workspace.
How it works
A small daemon serves the board on
127.0.0.1:4747and remembers tabs in%LOCALAPPDATA%\agent-board.Cursor talks to a stdio MCP process. That process starts the daemon if needed, then calls the local HTTP API.
The browser page stays connected over a WebSocket, so new pages appear as tabs immediately.
The MCP process can come and go with Cursor. The daemon stays up so the board does not reset when a chat ends.
Related MCP server: bronom
Setup on a new PC
dist/ is not in git, and Cursor only sees the MCP server and skill if they are registered on that machine. After clone:
cd C:\path\to\agent-board
npm install
npm run buildThen two copies outside the repo:
MCP — add this to
%USERPROFILE%\.cursor\mcp.json, with theargspath pointing at this clone:
"agent-board": {
"command": "node",
"args": ["C:\\path\\to\\agent-board\\dist\\index.js"]
}Skill — copy
.cursor\skills\agent-boardto%USERPROFILE%\.cursor\skills\agent-board. A project skill only applies in this repo; the personal copy is what agents use from every other workspace. A junction stays in sync with git:
mklink /J %USERPROFILE%\.cursor\skills\agent-board C:\path\to\agent-board\.cursor\skills\agent-boardReload MCP in Cursor after changing mcp.json. Then open http://127.0.0.1:4747 or ask the agent to present something visually.
npm start runs the daemon in the foreground. Cursor does not need this: the MCP server starts the daemon on first use. npm stop stops a running daemon.
Agent tools
Tool | Purpose |
| Create or replace a page ( |
| Change snippets on an existing page ( |
| Capture a PNG (or JPEG) of a tab's page or a CSS |
| List open tabs ( |
| Page the whole Library in the user's order (default 20, max 50), or search every page with |
| List Library folders as paths ( |
| Open a closed page on the strip |
| Read a page's HTML so it can be revised (open or closed). |
| Read what the user has actually typed, added, or checked off on an interactive page |
| Block until the page fires a named signal ( |
| Write state without focusing. Unfocused open tabs and closed pages show an unread blip. |
| Pin or unpin a tab ( |
| Close one tab, all unpinned tabs, or everything; the pages stay in the Library. Pass |
| Reusable page templates (agent authors them only when asked; the user opens instances from the sidebar) |
Reuse the same key when updating a topic. Pass a full HTML document, or a fragment (it gets a readable dark template). For a small change to an existing page, board_patch with exact oldString/newString edits instead of sending the whole document again. For a large page, board_read with toFile: true, edit the file, then board_patch with htmlPath and expectedRevision.
board_show, board_patch, and board_read also return viewUrl: the tab page on its own (http://127.0.0.2:4747/view/<id>), outside the board's iframe, so a browser tool can click, drag, and run scripts in it.
Images
board_show accepts local image files via assets (path strings, or { path, name }). The daemon copies them next to the tab and the page can reference them as asset:name:
<img src="asset:hero.png" alt="Hero">png, jpg, gif, webp, svg, ico, and avif. 8 MB per file, 16 files / 32 MB per tab. These do not count toward the 2 MB HTML cap. Re-showing a key without assets keeps files already attached. Workspace-relative <img src> and file:// URLs do not work — tab pages are served from http://127.0.0.2 and cannot see the disk.
board_screenshot loads the tab's content page in a headless Chromium browser (Edge, Chrome, or Brave — not the board chrome) and returns an image. Pair it with board_show(..., background: true) so a design loop does not steal window focus. Default viewport is 1280×800; pass selector for one element or fullPage for a tall page.
Interactive pages
Every tab owns a JSON state object that lives in the daemon, not in the browser. The agent can read and write it whether or not the tab is focused, or the browser is even open. Pages get it as window.board, injected before any page script runs:
board.state // current state, readable synchronously on load
board.set({ todos }) // merge top-level keys, saved on a short debounce
board.signal("submitted") // wake board_wait; flushes pending board.set first
board.onChange(render) // agent or another viewer changed something
board.bind(el, "notes") // two-way bind an input, textarea, or checkboxA submit button can declare the same handshake without extra script: data-board-signal="submitted". The agent then calls board_wait with that signal name. board_show clears the last signal on the tab so a new wait does not instantly see the previous submit.
Interactive pages should use this instead of localStorage — all tab pages share one origin, so their localStorage collides, and the agent cannot see it.
board.bind is what makes text fields safe. It saves as you type (250 ms idle, 1 s ceiling), and when a remote change arrives for a field you are currently in, it leaves your caret and half-typed text alone, marks the field board-stale, and reconciles once you move on.
Writes merge at the top level, so the agent updating todos never disturbs the notes you are typing. To make in-progress form input completely off limits, keep it under a draft key — by convention the agent reads it but never writes it.
Conflicts
board_get_state returns a stateRevision. Passing it back as expectedRevision makes the write conditional: if the user changed the page in between, it is refused with 409 and the response carries their current state, so the agent can merge and retry. Agent writes without an expectedRevision are refused on a page that already has state, unless force is set. Writes from the page itself are never blocked — the person looking at the screen wins ties.
board_wait blocks until board.signal("name") (or data-board-signal="name") fires on that tab. It returns the signal plus the current state. Waiting for any state change would wake on every keystroke; the named signal is the handshake. After a successful wait, pass signal.revision as afterSignalRevision to wait for the next one without re-showing the page.
Data
Tabs persist in %LOCALAPPDATA%\agent-board\board.sqlite across daemon and Cursor restarts, including each tab's state object (max 256 KB per tab). Image files live in %LOCALAPPDATA%\agent-board\assets\<tabId>\. Every page lives in the **Library** until you delete it; closing a tab only takes it off the strip. **Ctrl+Z** undoes whichever is newer: the most recent close (reopens the tab), or the most recent delete. A folder delete or any other bulk delete is one undo step. Deleted pages and folders sit in the **Trash** (Library ⋯ menu → Trash) for 7 days: each row has a restore button that puts it back in the Library, and its context menu can delete it for good. After 7 days they are deleted automatically. A previous state.json is imported once and renamed to state.json.bak.
The browser Clear button closes unpinned tabs. Pinned tabs stay until you close or delete them. Shift+click a tab's × deletes the page (the notice has Undo). Ctrl+S downloads the current page as HTML (markup only). Settings and the tab/Library context menus export a .board.json pack that includes state, images, Library folder and position, and the templates behind any template pages (Export all includes every template; a folder's menu exports just that folder); Import (or a drop on Settings, the tab strip, or the Library) restores those files.
The sidebar has Library and Templates. The Library is a tree of folders and pages in your own order: drag rows to reorder or file them, drag a page onto the strip to open it there, or drag a tab into the Library to file it and close it. Open pages have an accent bar, the current tab's row is filled, and pinned pages get a faint warm tint. Hovering a tab or Library row shows its full title, id, created and updated times, and folder. Templates are reusable pages with a form. The agent creates a template when you ask; you open copies from the list. A few built-ins (Embed, Markdown note, Todo list) sit in a collapsible Built-in group under your own; opening one first adds a copy to your templates and the page uses that copy, so app updates never change your pages. Right-click a built-in to add it without opening a page. Updating a template refreshes every page created from it. A linked page's HTML cannot be edited — only the template can. If a template change breaks that page's data, the board blocks the page until the agent fixes the data.
Embedding a site
A page whose <head> has <meta name="agent-board-embed" content="URL"> is shown by pointing the tab iframe straight at that URL, instead of nesting it inside the tab page. The Embed template does this. Only http: and https: URLs outside the board's own origin count; anything else falls back to rendering the page's HTML. The stored HTML stays a small wrapper, so board_read and search see the URL but not the site's content.
A direct frame keeps the site on the same site as the board chrome (127.0.0.1), so logins that use SameSite=Lax cookies (ComfyUI-Login, for example) keep working. Use 127.0.0.1, not localhost: the browser treats them as different sites. Board shortcuts (Ctrl+S, Ctrl+D, …) don't reach the board while focus is inside the embedded site.
Hiding a tab from the agent
Right-click a tab or Library row and choose Hide from agent, or tick Hide from agent in a template's Open/Edit form. Hidden tabs show an eye icon. To the agent they don't exist: they're left out of board_list, board_library, search, activeId, bulk board_close, and template instance counts, and every per-tab tool returns "tab not found". A board_wait already running on the tab ends as if the tab had closed. board_show with a hidden tab's key creates a separate tab instead of overwriting it. Only the board UI can change the flag; the MCP marks its requests with an x-agent-board-client: agent header and cannot flip it.
This is a guardrail on the board's tools, not a sandbox. An agent with a shell or browser could still call the HTTP API without the header, or open the embedded URL itself.
Port: 4747 (override with AGENT_BOARD_PORT). Bound to localhost only.
Tab pages load in an iframe from http://127.0.0.2:4747 so they can use localStorage without accessing the board chrome or API. Refresh the board after upgrading so the new iframe sandbox takes effect.
This server cannot be deployed
Maintenance
Related MCP Connectors
Agent-native website builder: create, edit, and host websites via MCP
Stealth web browser for agents: search, fetch, click, download and type in persistent MCP sessions.
Create, edit, preview, publish, and manage web pages from MCP-capable AI clients.
Hosted real Google Chrome MCP with per-user persistent state. Navigate, click, type, screenshot.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceEnables MCP clients to control a real local browser window for web automation tasks such as clicking, typing, scrolling, and taking screenshots.7 npm-

bronomofficial
AlicenseNot gradedqualityCmaintenanceExposes a persistent, visible multi-tab Electron browser to AI clients through MCP, enabling tab management, navigation, interaction, screenshots, and JavaScript evaluation.1MIT- FlicenseNot gradedqualityAmaintenanceEnables AI agents to control a persistent local browser with live tabs, navigation, interaction, inspection, and state management through MCP.6-
- AlicenseNot gradedqualityBmaintenanceEnables local control of existing Chrome/Chromium browser tabs through MCP, including tab management, navigation, content reading, screenshots, and page interaction.Apache 2.0