Skip to main content
Glama

Tabdock

Tabdock lets MCP clients use the tools of a live web page. The page registers tools through WebMCP, and a small adapter on it dials out to a relay: a remote MCP server with one stable URL that Claude Code, Claude on the web, desktop and phone, or any MCP client adds once. Several people and clients can attach to one page, and the person at the tab approves each, unless the page admits members as observers by policy or that person has minted a Can watch invite, which admits guests as observers in advance.

Why it is different

A page's tools run inside the tab, with the user's own signed-in session and live state, so an agent can work an app that has no API, and your back end gains no endpoint or credential.

The tab dials out to one relay URL, so the browser opens no port and the connector never changes as pages come and go. Several people and their clients share a page with roles: observers call only read-only tools, drivers call the rest, and writes run one at a time. Consequential calls wait for a human, on the page or, where the page allows it, in the calling member's own client. Use cases shows what that makes possible; the comparison with MCP-B's local relay shows where a simpler tool fits better.

Related MCP server: Claude Chrome MCP

How it works

flowchart TB
  clients["Clients and their people: Claude Code, Claude on web, desktop and phone, any MCP client"]
  relay["Relay: one process, one stable /mcp URL and the /page socket; sign-in, checks, write queue, audit log"]
  adapter["Adapter and widget, loaded by the page; checks every call again"]
  tools["The page's own tools on document.modelContext"]
  operator["Operator: the person at the tab"]
  clients -->|"MCP over Streamable HTTP to /mcp, https when public"| relay
  adapter -->|"dials out a WebSocket to /page"| relay
  adapter -->|"lists and runs"| tools
  operator -->|"approves people, answers prompts"| adapter

The relay never runs a tool: it signs clients in and checks and routes each call, and the adapter checks it again before the page's handler runs. It sees calls in plain text, so you run your own. Page origins are allowlisted, tokens and codes never reach a log, and every call is audited; Security has the rest. Concepts explains the parts, the flows, the three dials of roles, consequence and reach, and the words for rooms, state and records.

For a room

Clients see nine fixed tools: list_pages, pair_page, list_page_tools, call_page_tool, detach_page, get_page_state, wait_for_page_state, get_proposal and withdraw_proposal. M6, which arrives in the release after 0.1.0 (CHANGELOG.md), lets one projected tab hold a class or a review. A room needs a relay with a public URL and TABDOCK_INVITES=1, since guests join by invite; in local mode a time-boxed session is a time box only. pnpm demo:m6 runs a whole lesson on this machine, the classroom explainer traces it, and Run a class says what a rehearsal by hand needs; pnpm dev:map serves the canvas map page to try the map profile.

  • Map and globe pages. A shared tool profile every client drives alike, with a canvas test page to try it on (Map and globe pages).

  • Images and page state. A tool the page declares returns one still PNG, JPEG or WebP, checked again by the relay and shown only behind the untrusted label; a page publishes a small JSON object any attached client reads or waits on without running a tool (Images and page state).

  • Proposals and time-boxed sessions. Where a page's policy.proposals is 'members' or 'all', an observer's write becomes a proposal the operator accepts or dismisses in the widget; under the default, 'off', it still answers role_denied. A session of 30 to 240 minutes narrows the policy and shows one QR at a time, and its end closes every live invite and agent token on the page and every attachment they made, earlier ones included (Lessons and reviews).

  • Session records. The widget keeps who did what, saves it as JSON and Markdown only on the operator's click, and pnpm audit:log --match holds a saved record to the audit log (The session record).

  • Larger rooms and agent tokens. 40 watching seats apart from the people limit, in a compact roster, a members file read again without a restart, and tokens with which Claude Code or a script watches one page once the operator allows it (Larger rooms); a response ceiling and an optional restart snapshot (Settings for rooms, images and state).

  • CI checks. pnpm ci-check walks a map page's every stop against local mode, as CI's ci-check job does with no secret (Check a page from CI), and another repository can copy it (Copy the CI check).

  • Both sides of a class. Run a class takes a teacher from the relay's settings to a saved record, in order, and Join a shared page is the other side, for a guest with a link, a watcher in a session or a program holding an agent token.

Status

Version 0.1.0, M5's code, is prepared but not yet on npm, so today Tabdock runs from a clone; npx @tabdock/relay and npm install @tabdock/adapter work once 0.1.0 is on npm, and M6's rooms, images and state once the release after it is. Every security requirement in SPEC.md section 9 has automated tests. What has been checked, and where:

Path

Checked in the sandbox

Waiting for the owner's own devices

Claude Code in local mode

Claude Code 2.1.289 headless, on MCP 2026-07-28 and 2025-11-25, first-class tools included

The header helper on macOS; on Windows, which shell would run one (the PowerShell --header line serves meanwhile); the confirmation dialog with a person

Other MCP clients

The SDK client matrix and the official conformance suite in CI; confirmation in the client with SDK clients

Nothing

The adapter in a browser

Headless Chromium with the MCP-B polyfill 5.1.0 and 6.0.0

An hour in a background tab, Energy Saver and laptop sleep

Claude on web, desktop and phone; QR scans; invites

Public URL mode against a stand-in identity provider, in a phone-sized browser

All of it: none has run live yet

The container image

Built and smoke-tested

A real host

M6's rooms, images, proposals and records

The sim page, the canvas map page and the demo in headless Chromium, a stand-in provider, a measured class of 40 on a 512 MiB relay, and Claude Code 2.1.296 with images and an agent token, with no model call

Hosted Claude with images, waits and proposals; a members file and a deploy during a lesson on Fly; a real class

The open steps are in docs/checklists/ (M3 to M6). WebMCP is an early draft in a Chrome origin trial, so the demo board uses the MCP-B polyfill.

Quick start

You need Node 22.18 or later, pnpm 10 and Claude Code or another MCP client.

git clone https://github.com/PaulMRamirez/tabdock.git
cd tabdock
pnpm install
pnpm dev

With no auth settings (in the environment or .env) and outside production, the relay runs in local mode: loopback only, one user, and an owner token in a private file outside the checkout. On macOS and Linux pnpm dev prints a claude mcp add-json line whose header helper reads that token whenever Claude Code connects, so neither the command nor Claude Code's settings hold it. Paste it and check with claude mcp list (never claude mcp get, which prints stored headers). Open the board link it printed, click Connect, ask Claude to pair with the code in the widget, and click Allow as driver. Quick start has the details.

Your own page

import { attach } from '@tabdock/adapter';

const notes: string[] = [];

// A WebMCP runtime must already be installed: Chrome's own, or the MCP-B polyfill.
await document.modelContext?.registerTool({
  name: 'add_note',
  title: 'Add note',
  description: 'Add a note to the page and return how many there are.',
  inputSchema: { type: 'object', properties: { text: { type: 'string' } }, required: ['text'] },
  annotations: { readOnlyHint: false },
  execute: async (input) => {
    const text = (input as { text?: unknown } | null)?.text;
    if (typeof text !== 'string') return 'text must be a string';
    return { count: notes.push(text) };
  },
});
await document.modelContext?.registerTool({
  name: 'clear_notes',
  title: 'Clear notes',
  description: 'Remove every note. This cannot be undone.',
  inputSchema: { type: 'object', properties: {} },
  annotations: { readOnlyHint: false, consequentialHint: true },
  execute: async () => ({ removed: notes.splice(0).length }),
});

// The only control handle: keep it private.
const dock = attach({
  relay: 'ws://127.0.0.1:8787/page',
  policy: { consequentialTools: ['clear_notes'] },
});

That URL serves a page on the relay's own machine, and by default the relay admits http and https pages on localhost, 127.0.0.1 and [::1]; only hosted mode takes wss://<host>/page from pages elsewhere, on origins it lists. TypeScript knows no document.modelContext, so the snippet needs the short declaration in Add to your app.

A tool prompts the operator when its consequentialHint is true or policy.consequentialTools names it. Set both: MCP-B 5.x and Chrome 153 drop the hint, and there, on a page whose tools carry annotations, a list that names no tool makes every tool that is not read-only prompt (ADRs 0002 and 0034). A page that wants no prompts says consequential: 'allow'. Until 0.1.0 is on npm, take the adapter from a clone: pnpm release:pack packs installable tarballs into dist/packages, labelled 0.1.0 but built from the clone's checkout, and pnpm --filter @tabdock/adapter build writes the script-tag file. Add to your app has the rest.

Claude on the web, desktop and phone

Hosted Claude reaches a connector from the cloud, so a relay on loopback cannot serve it. Public URL mode puts the relay behind a tunnel with a fixed https name and signs people in through an OAuth provider that meets SPEC.md section 7, plus a confidential client for the QR page at /pair; pnpm dev:public names the six settings. Pages then still attach only from the relay's machine; hosted mode, behind a host edge, takes them from anywhere on listed origins. In Claude, add https://<relay>/mcp under Customize, Connectors, +, Add custom connector and sign in; Claude Code uses claude mcp add --transport http --scope user tabdock https://<relay>/mcp, then claude mcp login tabdock. See Connect clients and Run a relay.

Docs

The guide is for people using Tabdock; its Where to start table gives a reading order for each task, from a first look to running a class.

For contributors: docs/develop.md (every command), SPEC.md (the source of truth), docs/adr/ (the decisions), docs/threat-model.md (each boundary and its tests), docs/mcp-b-comparison.md, docs/tour/ (the build's history, a milestone a page), docs/deploy.md (the reference deployment), docs/release.md, CHANGELOG.md, .env.example (every setting), and the READMEs of packages/adapter, packages/relay, packages/protocol, packages/sim-page, apps/demo, apps/site, tests/e2e, tests/map-page, tests/ci-check and tests/mcpb6. pnpm site:build builds the demo board, the tour and the guide into a static site for GitHub Pages, not yet published; until it is, run the board with pnpm dev.

Licence

Apache-2.0; see LICENSE and NOTICE.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    An MCP server that runs in the browser, letting web pages register custom tools and prompts and expose them to an MCP client over WebSocket. Enables agents to drive UI, call page-scoped APIs, and get human-in-the-loop confirmation.
    8 npm
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables CLI coding agents to interact with your live browser tabs via MCP, using your real sessions and cookies without a sandbox.
    MIT
  • F
    license
    A
    quality
    B
    maintenance
    Lets an AI share the same live browser tab as a person, receiving the user's Alt+click target and enabling the AI to inspect, click, fill, type, navigate, or otherwise operate on that real page. It runs locally and pairs with the browser extension to bridge the user's page to the MCP client.
    13
    -