|
| 1 | +# AGENTS.md — MCP Comet |
| 2 | + |
| 3 | +MCP Comet is a TypeScript MCP (Model Context Protocol) server that automates the Perplexity Comet browser via Chrome DevTools Protocol (CDP). It exposes 13 tools over stdio for prompting, polling, screenshots, tab management, source extraction, and mode switching. |
| 4 | + |
| 5 | +## Commands |
| 6 | + |
| 7 | +```bash |
| 8 | +npm run build # tsc → dist/ |
| 9 | +npm test # vitest run |
| 10 | +npm run test:watch # vitest in watch mode |
| 11 | +npm run test:ci # vitest run --coverage (CI uses this) |
| 12 | +npm run lint # biome check . |
| 13 | +npm run format # biome write . (auto-fix) |
| 14 | +npm run typecheck # tsc --noEmit |
| 15 | +npm start # node dist/index.js (stdio MCP server) |
| 16 | +``` |
| 17 | + |
| 18 | +CI pipeline (`.github/workflows/ci.yml`): `npm ci → build → lint → vitest run --coverage` on Node 22. |
| 19 | + |
| 20 | +**Before committing**: run `npm run lint && npm test`. |
| 21 | + |
| 22 | +## Architecture |
| 23 | + |
| 24 | +Four layers, top to bottom: |
| 25 | + |
| 26 | +``` |
| 27 | +MCP Tools (server.ts) → UI Automation (src/ui/) → CDP Transport (src/cdp/) → Comet Browser |
| 28 | +``` |
| 29 | + |
| 30 | +- **server.ts** — Single file defining all 13 tools via `McpServer.tool()`. Contains `startServer()`, tool definitions, Zod schemas, and all handler logic. This is the main file to edit when adding/modifying tools. |
| 31 | +- **src/ui/** — Functions that return JavaScript strings (evaluated in the browser via `Runtime.evaluate`). Each `build*Script()` function returns a self-contained IIFE string. **Do not** pass complex objects — everything must serialize to a JS expression. |
| 32 | +- **src/cdp/client.ts** — `CDPClient` singleton (`CDPClient.getInstance()`) managing WebSocket connections, auto-reconnect with exponential backoff, and an operation queue (`enqueue()`) to serialize concurrent CDP calls. |
| 33 | +- **src/selectors/** — Version-keyed CSS selector sets (`SelectorSet`). `v145.ts` is the current set. New Comet/Chrome versions get a new `v{version}.ts` file registered in `index.ts`. Unknown versions fall back to the latest known set. |
| 34 | + |
| 35 | +### Key flow: `comet_ask` (fire-and-forget) + `comet_wait` (blocking poll) |
| 36 | + |
| 37 | +`comet_ask` types the prompt via `execCommand('insertText')` and submits immediately — it does NOT wait for a response. `comet_wait` (or `comet_poll`) must be called separately to retrieve results. This is a deliberate design: decoupled prompt submission from response polling. |
| 38 | + |
| 39 | +### Auto-connect |
| 40 | + |
| 41 | +Every tool handler calls `ensureConnected()` first, which lazily launches/connects to Comet if no session exists. Connection health is verified by evaluating `1+1` via CDP with a timeout. |
| 42 | + |
| 43 | +## Code Style |
| 44 | + |
| 45 | +- **Formatter**: Biome (2-space indent, single quotes, no semicolons, trailing commas, 100 char line width) |
| 46 | +- **Module system**: ESM only (`"type": "module"`, `Node16` module resolution, `.js` extensions in imports) |
| 47 | +- **Imports**: Always use `.js` extension in import paths (TypeScript ESM requirement) |
| 48 | +- **Strict mode**: TypeScript strict enabled |
| 49 | +- **Lint rules**: `noExplicitAny: error` in source (off in tests), `noConsole: warn` in source (off in tests, suppress with `// biome-ignore lint/suspicious/noConsole: ...`) |
| 50 | +- **Zod for schemas**: Tool parameters defined as Zod raw shapes; `buildInputSchema()` converts to JSON schema for the exported registry |
| 51 | + |
| 52 | +## Testing |
| 53 | + |
| 54 | +Vitest with v8 coverage. Coverage thresholds: 75% statements/lines, 70% branches, 80% functions. |
| 55 | + |
| 56 | +### Test structure |
| 57 | + |
| 58 | +- **`tests/unit/`** — Mocked CDP, isolated component tests. UI script tests validate that `build*Script()` functions produce valid JS containing expected patterns. |
| 59 | +- **`tests/integration/tools/`** — End-to-end tool handler tests using the **harness pattern** (`tests/integration/tools/harness.ts`). |
| 60 | + |
| 61 | +### Integration test harness |
| 62 | + |
| 63 | +The harness (`harness.ts`) is critical to understand: |
| 64 | + |
| 65 | +1. Mocks `McpServer` to capture handler functions during `startServer()` into `capturedHandlers` |
| 66 | +2. Mocks `CDPClient.getInstance()` to return a controllable `mocks` object |
| 67 | +3. Mocks `loadConfig` and `detectCometVersion` |
| 68 | +4. Tests call `registerHandlers()` in `beforeAll`, then `getHandler('tool_name')` to get the handler |
| 69 | + |
| 70 | +When writing new tool tests: |
| 71 | +```ts |
| 72 | +import { getHandler, mocks, registerHandlers, resetHarness } from './harness.js' |
| 73 | + |
| 74 | +beforeAll(async () => { await registerHandlers() }) |
| 75 | +beforeEach(() => { resetHarness() }) |
| 76 | +// Override mocks as needed per test, then call the handler |
| 77 | +``` |
| 78 | + |
| 79 | +### Unit test patterns |
| 80 | + |
| 81 | +- **CDPClient tests**: Mock `chrome-remote-interface` and `globalThis.fetch` for HTTP endpoints (`/json/version`, `/json/list`) |
| 82 | +- **UI script tests**: Call `build*Script()` functions and assert on the returned string content (no browser needed) |
| 83 | +- Reset singleton between tests: `CDPClient.resetInstance()` |
| 84 | + |
| 85 | +## Gotchas and Non-Obvious Patterns |
| 86 | + |
| 87 | +- **UI scripts are strings, not functions**: Everything in `src/ui/` returns a JS string that gets evaluated remotely via `Runtime.evaluate`. You cannot pass closures or use Node APIs inside these scripts. All context must be embedded in the string via string interpolation. |
| 88 | + |
| 89 | +- **Prompt injection uses `execCommand('insertText')`**: Comet uses a Lexical editor that ignores standard `value` assignment. Prompts are JSON.stringify'd before injection to prevent XSS/injection attacks. |
| 90 | + |
| 91 | +- **Singleton CDPClient**: `CDPClient.getInstance()` returns one instance. Tests must call `CDPClient.resetInstance()` to clear state between test runs. |
| 92 | + |
| 93 | +- **Operation queue**: `CDPClient.enqueue()` serializes all operations. Nested calls within an `enqueue` block are fine, but two concurrent external calls will be sequenced. |
| 94 | + |
| 95 | +- **Auto-reconnect**: `withAutoReconnect()` wraps operations with retry logic. Health checks evaluate `1+1` and reconnect on failure. The reconnect itself is deduplicated via `reconnectPromise`. |
| 96 | + |
| 97 | +- **Tab categorization**: Tabs are classified by URL patterns. `perplexity.ai` with `sidecar` in URL → Sidecar. `chrome://` tabs must never be closed (crashes Comet). Only `agentBrowsing` tabs are closed during cleanup. |
| 98 | + |
| 99 | +- **`comet_mode` requires navigation to home**: Mode switching only works on a new chat page. The tool navigates to `https://www.perplexity.ai` before attempting the slash-command typeahead. |
| 100 | + |
| 101 | +- **Collapsed citations**: Sources with empty URLs and text containing `+` (e.g., "wsj+3") are detected as collapsed. `comet_get_sources` does a two-pass extraction: first pass collects what's visible, second pass clicks collapsed items (via `buildExpandCollapsedCitationsScript`) and re-extracts, then merges by deduplicating on URL. |
| 102 | + |
| 103 | +- **No `src/server.test.ts`**: Server logic is tested via the integration test harness in `tests/integration/tools/`, not via a unit test file. |
| 104 | + |
| 105 | +- **`index.ts` is the stdio entry point**: It imports and calls `startServer()` from `server.ts`. The `cli.ts` file is the `mcp-comet` binary with subcommands (`start`, `call`, `detect`). |
| 106 | + |
| 107 | +- **Logger writes to stderr**: All logging goes to stderr to avoid corrupting MCP stdio JSON-RPC messages on stdout. |
| 108 | + |
| 109 | +- **Release**: Triggered by pushing `v*` tags. Publishes to npm as `@onestepat4time/mcp-comet`. Uses `release-please` for changelog/version management. |
| 110 | + |
| 111 | +## Adding a New Comet Version |
| 112 | + |
| 113 | +When Comet updates its Chrome version and CSS selectors change: |
| 114 | + |
| 115 | +1. Run `mcp-comet detect` to get the Chrome major version |
| 116 | +2. Inspect Comet DOM with DevTools |
| 117 | +3. Create `src/selectors/v{version}.ts` implementing `SelectorSet` interface |
| 118 | +4. Register in `src/selectors/index.ts` selector map |
| 119 | +5. Add unit tests in `tests/unit/selectors/` |
| 120 | +6. Update `docs/comet-compatibility.md` |
| 121 | + |
| 122 | +## Commit Conventions |
| 123 | + |
| 124 | +Conventional commit prefixes: `feat:`, `fix:`, `docs:`, `test:`, `refactor:`, `chore:`. CI requires passing lint + tests before merge. |
0 commit comments