List, group, deduplicate, bulk-close, activate, and update browser tabs with natural language — from any MCP client.
A browser extension (Chrome and Firefox, one source tree built with WXT) proxies a fixed set of browser.tabs, browser.tabGroups, and browser.scripting operations. A Node.js MCP host handles all the logic (filtering, grouping, dedup) and talks to your AI client over stdio. Everything runs locally on loopback. Tools that only filter or reshape data from existing browser operations ship without any extension change; adding a genuinely new browser operation requires a new handler in the extension's dispatch() (and sometimes a new manifest permission) plus a rebuild/reload.
Works with any Chromium browser, and Firefox:
The extension is built with WXT, which generates a Chrome MV3 build and a Firefox MV2 build from one source tree — tab groups (Chrome-only) are feature-detected and disabled cleanly on Firefox. Safari is planned.
- Node.js v18+
- Local port
9876available
Pick your client below. They all run the same server — just different config locations.
Claude Code
claude mcp add 2manytabs-mcp -- npx -y 2manytabs-mcp-hostClaude Desktop
Add to your config file:
| OS | Path |
|---|---|
| macOS | ~/Library/Application Support/Claude/claude_desktop_config.json |
| Windows | %APPDATA%\Claude\claude_desktop_config.json |
| Linux | ~/.config/claude/claude_desktop_config.json |
{
"mcpServers": {
"2manytabs-mcp": {
"command": "npx",
"args": ["-y", "2manytabs-mcp-host"]
}
}
}Restart Claude Desktop after saving.
VS Code / GitHub Copilot Chat
Create .vscode/mcp.json in your workspace (or add to your user settings.json under "mcp"):
{
"mcpServers": {
"2manytabs-mcp": {
"command": "npx",
"args": ["-y", "2manytabs-mcp-host"]
}
}
}See the VS Code MCP docs for details.
Cursor
Open Settings → MCP and add a new server, or create/edit .cursor/mcp.json:
{
"mcpServers": {
"2manytabs-mcp": {
"command": "npx",
"args": ["-y", "2manytabs-mcp-host"]
}
}
}Windsurf
Open Settings → Cascade → MCP, or create/edit mcp_config.json:
{
"mcpServers": {
"2manytabs-mcp": {
"command": "npx",
"args": ["-y", "2manytabs-mcp-host"]
}
}
}See the Windsurf MCP guide for details.
Continue
Add to .continue/config.json (or create .continue/mcpServers/2manytabs-mcp.json):
{
"mcpServers": {
"2manytabs-mcp": {
"command": "npx",
"args": ["-y", "2manytabs-mcp-host"]
}
}
}See the Continue MCP docs for details.
Cline
Open the MCP Servers panel in Cline and add a new server:
- Command:
npx - Args:
-y 2manytabs-mcp-host
Or edit the Cline MCP config JSON directly with the same block used above. See the Cline MCP docs for details.
Hermes Agent
hermes mcp add 2manytabs-mcp --command "npx -y 2manytabs-mcp-host"Restart your session (/reset or start a new hermes invocation), then verify:
hermes mcp list
hermes mcp test 2manytabs-mcpConfig lives at ~/.hermes/config.yaml under the mcp section.
These clients support MCP but we haven't tested them directly. The same JSON config block should work — just drop it into the client's MCP config file:
| Client | Notes |
|---|---|
| LibreChat | MCP agent/tool server support documented |
| ChatGPT | MCP connectors exist; local stdio flow unverified |
| Sourcegraph Cody | MCP via OpenCTX; setup syntax unverified |
| Genkit | genkitx-mcp plugin can consume MCP servers |
| Zed | Tool support is experimental — prompts/resources only in some builds |
If your client speaks MCP over stdio, it will work. Point it at npx -y 2manytabs-mcp-host and you're set.
First build it — the extension is a WXT project, so it needs a build step (the native host does not):
cd extension
npm install
npm run build # → .output/chrome-mv3/ (Chrome, Brave, Edge, Opera, Comet)
npm run build:firefox # → .output/firefox-mv2/ (Firefox)Chrome / Chromium
- Go to
chrome://extensions/(oredge://extensions/,brave://extensions/, etc.) - Enable Developer mode (toggle in the top-right)
- Click Load unpacked
- Select
extension/.output/chrome-mv3/ - Click the extension icon in the toolbar — the popup should show Connected once the host is running
Firefox
- Go to
about:debugging#/runtime/this-firefox - Click Load Temporary Add-on…
- Select
extension/.output/firefox-mv2/manifest.json - Click the extension icon in the toolbar — the popup should show Connected once the host is running
Temporary add-ons are unloaded when Firefox restarts; reload as needed during development. Tab-group tools are unsupported on Firefox (no tabGroups API) and fail with a clear error instead of breaking other tools.
Run from extension/:
npm run dev/npm run dev:firefox— launches Chrome/Firefox with the extension loaded and hot-reloads on savenpm run build/npm run build:firefox— production build to.output/<target>/npm run zip/npm run zip:firefox— packages a distributable.zipfor each target
Just talk to your AI client:
- "Show me all my open tabs."
- "Find and close duplicate tabs."
- "Close everything matching 'youtube'."
- "Do a dry run of closing all tabs from reddit.com."
- "Group my tabs by domain and show the breakdown."
The host exposes ten tools:
Read-only. Returns a domain histogram with proportional bars and a per-domain listing with titles, pinned (📌), and audible (🔊) flags.
| Param | Type | Default | Description |
|---|---|---|---|
query |
string | — | Substring filter on title or URL |
group_by |
"domain" · "window" · "none" |
"domain" |
How to group results |
duplicates_only |
boolean | false |
Show only duplicate URLs |
Closes tabs. Exactly one selection mode required:
| Param | Type | Description |
|---|---|---|
tab_ids |
number[] | Close specific tab IDs |
match |
string | Close tabs matching this substring |
duplicates |
boolean | Close all duplicates (keeps first occurrence) |
dry_run |
boolean | Preview what would close without closing |
Opens new tabs in the browser.
| Param | Type | Description |
|---|---|---|
urls |
string[] | Array of URLs to open. Required. If no protocol is provided, https:// is prepended automatically. |
Read-only. Lists all existing native Chrome Tab Groups in the current browser session, including their IDs, titles, colors, collapsed states, and parent window IDs.
| Param | Type | Description |
|---|---|---|
title_query |
string | Case-insensitive substring filter for group titles. |
Groups open Chrome tabs into native Chrome Tab Groups. Can create a new group or add tabs to an existing group.
| Param | Type | Description |
|---|---|---|
tab_ids |
number[] | Array of tab IDs to add to the group. Required. |
group_id |
number | Add to an existing group ID. If omitted, a new group is created. |
title |
string | Title to set on the group (new or existing). |
color |
"grey" · "blue" · "red" · "yellow" · "green" · "pink" · "purple" · "cyan" · "orange" |
Color to set on the group (new or existing). |
Removes one or more open Chrome tabs from their current native Tab Groups.
| Param | Type | Description |
|---|---|---|
tab_ids |
number[] | Array of tab IDs to remove from any groups. Required. |
Updates properties of an existing native Chrome Tab Group.
| Param | Type | Description |
|---|---|---|
group_id |
number | The numeric ID of the tab group to update. Required. Obtain from list_tab_groups or list_tabs. |
title |
string | New title for the group. |
color |
"grey" · "blue" · "red" · "yellow" · "green" · "pink" · "purple" · "cyan" · "orange" |
New color for the group. |
collapsed |
boolean | Set true to collapse the group, false to expand it. |
Brings a specific browser tab to the foreground, activating it and focusing its containing window.
| Param | Type | Description |
|---|---|---|
tab_id |
number | The numeric ID of the tab to activate. Required. |
Modifies properties of an open browser tab: navigate it to a new URL, pin/unpin it, or mute/unmute its audio.
| Param | Type | Description |
|---|---|---|
tab_id |
number | The numeric ID of the tab to update. Required. |
url |
string | A new URL to navigate the tab to. If no protocol is provided, https:// is prepended. |
pinned |
boolean | Set true to pin the tab, false to unpin it. |
muted |
boolean | Set true to mute the tab, false to unmute it. |
Read-only. Extracts the plain body text of a loaded browser tab via chrome.scripting. Useful for summarization or classification. Fails on restricted internal browser pages (e.g., chrome://, edge://, or extension pages) or if the tab is not loaded.
| Param | Type | Description |
|---|---|---|
tab_id |
number | The numeric ID of the tab whose body text to extract. Required. |
AI Client ◄──stdio──► MCP Host ──WebSocket :9876──► Browser Extension (Chrome / Firefox)
│
▼
Follower Hosts
- One extension source tree (
extension/, built with WXT) produces a Chrome MV3 build and a Firefox MV2 build. The extension proxies a fixed set ofbrowser.tabs,browser.tabGroups, andbrowser.scriptingoperations (extension/lib/tab-ops.js);browser.tabGroupshas no Firefox equivalent and is feature-detected, failing only the affected tools with a clear error. All selection, filtering, and reshaping logic lives in the host. Tools that only filter or reshape data from an existing browser operation ship with no extension change; adding a genuinely new browser operation requires a newcaseindispatch()(and sometimes a new manifest permission inwxt.config.ts) plus a rebuild/reload. - Multiple AI clients can share one browser. Hosts self-organize: one binds port 9876 (owner), the rest proxy through it (followers). On owner death, followers re-elect automatically.
- The extension reconnects via
browser.alarmsevery 30s (MV3 idle behavior). native-host/is browser-agnostic and shared unchanged by both builds — the WXT build step only affectsextension/; installing or configuring the MCP host is unaffected.
- Loopback only — the server binds to
127.0.0.1, not0.0.0.0. - Origin validation — only
chrome-extension://ormoz-extension://origins can connect. Web pages attempting loopback attacks get rejected with a4003close code. - No cloud, no daemon — everything stays on your machine.
PRs welcome. Safari support is the biggest gap right now:
For a local dev setup: run bash install.sh for the MCP host, and see Extension development for the browser side.
MIT — see LICENSE.