# Tabvana MCP Bridge

A dev-only Node.js daemon that exposes the Tabvana Chrome extension's IndexedDB as an
[MCP](https://modelcontextprotocol.io) tool server. This lets AI coding assistants like
**Claude Code** and **Gemini CLI** query your browser tabs, search history, tags, and
semantic embeddings directly.

> **Dev-only.** The bridge relies on a WebSocket injected into the extension's service worker
> only when the extension is built in development mode (`import.meta.env.DEV === true`).
> Production builds have zero overhead.

---

## Quick start

### 1. Start the bridge daemon

From the **repo root**:

```bash
npm run mcp-bridge
# or: cd mcp-bridge && npm start
```

Expected output:

```
[Tabvana Bridge] WS   ws://127.0.0.1:7878        ← extension connects here
[Tabvana Bridge] MCP  http://127.0.0.1:7878/mcp  ← LLM client connects here
[Tabvana Bridge] Waiting for extension connection...
```

Use `PORT=7879 npm run mcp-bridge` to run on a different port.

### 2. Load the dev extension build

```bash
# From repo root — builds once and watches for changes
npm run dev
```

Then in Chrome:

1. Navigate to `chrome://extensions`
2. Enable **Developer mode**
3. Click **Load unpacked** → select the `dist/` folder

### 3. Wake the service worker

Open the Tabvana main window once (click the extension icon or use `Ctrl+Shift+A`).
The service worker starts and connects to the bridge:

```
[Tabvana Bridge] Extension connected
```

The bridge is now ready to serve MCP tool calls.

---

## Configure Claude Code

```bash
claude mcp add tabvana --transport http http://127.0.0.1:7878/mcp
```

Verify the tools are visible:

```bash
claude mcp list
```

---

## Configure Gemini CLI

Add to `~/.gemini/settings.json`:

```json
{
  "mcpServers": {
    "tabvana": {
      "url": "http://127.0.0.1:7878/mcp"
    }
  }
}
```

---

## Tool reference

All tools are **read-only** — no mutations, no background enqueues.

| Tool | Params | Description |
|------|--------|-------------|
| `search_pages` | `query: string`, `limit?: number` (default 20) | Full-text search over titles, URLs, and tags |
| `get_page` | `url: string` | Fetch one page by normalised URL |
| `list_tags` | — | All tags with page counts, sorted descending |
| `get_pages_by_tag` | `tag: string`, `limit?: number` (default 100) | Pages carrying a specific tag |
| `find_similar` | `url: string`, `limit?: number` (default 10) | Semantically similar pages via embedding cosine similarity |
| `recent_pages` | `limit?: number` (default 20) | Most recently visited pages |
| `get_stats` | — | `{ pageCount, tagCount, embeddedCount }` |

### Compact page shape

Returned page objects drop `embedding`, `accessTimes`, the `*Desc` sort fields, and the
URL-graph arrays to keep LLM context clean:

```json
{
  "url": "https://example.com/article",
  "originalUrl": "https://example.com/article?utm_source=x",
  "title": "An Article",
  "visible": "closed",
  "humanTags": ["reading-list"],
  "autoTags": [],
  "lastActiveAt": 1715000000000,
  "embeddingStatus": "embedded"
}
```

### `find_similar` notes

- Returns an error (not an exception) if the target page has no embedding yet.
- Does **not** trigger embedding generation. Run the Tabvana UI and wait for background
  embedding to complete first.
- Similarity is computed over all pages with embeddings via batched cosine similarity in
  the service worker; may take a few seconds for large databases.

---

## Troubleshooting

| Symptom | Fix |
|---------|-----|
| `Extension not connected` error from tools | Reload the extension or open the Tabvana window to wake the service worker |
| `Address already in use` on port 7878 | `PORT=7879 npm run mcp-bridge` and update your MCP client config |
| Tools return empty results | Check that the extension is connected (`curl http://127.0.0.1:7878/health`) |
| `find_similar` returns "no embedding" | Wait for the background embedding sweep to finish; watch the Tabvana UI progress indicator |

Health check endpoint: `GET http://127.0.0.1:7878/health` → `{"ok":true,"extensionConnected":true/false}`
