@thinkrun/mcp 0.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +356 -0
  2. package/dist/bin/thinkrun-mcp.d.ts +3 -0
  3. package/dist/bin/thinkrun-mcp.d.ts.map +1 -0
  4. package/dist/bin/thinkrun-mcp.js +305 -0
  5. package/dist/bin/thinkrun-mcp.js.map +1 -0
  6. package/dist/src/client.d.ts +422 -0
  7. package/dist/src/client.d.ts.map +1 -0
  8. package/dist/src/client.js +2 -0
  9. package/dist/src/client.js.map +1 -0
  10. package/dist/src/cloud-client.d.ts +69 -0
  11. package/dist/src/cloud-client.d.ts.map +1 -0
  12. package/dist/src/cloud-client.js +291 -0
  13. package/dist/src/cloud-client.js.map +1 -0
  14. package/dist/src/extension-proxy-client.d.ts +139 -0
  15. package/dist/src/extension-proxy-client.d.ts.map +1 -0
  16. package/dist/src/extension-proxy-client.js +451 -0
  17. package/dist/src/extension-proxy-client.js.map +1 -0
  18. package/dist/src/index.d.ts +11 -0
  19. package/dist/src/index.d.ts.map +1 -0
  20. package/dist/src/index.js +6 -0
  21. package/dist/src/index.js.map +1 -0
  22. package/dist/src/local-client.d.ts +120 -0
  23. package/dist/src/local-client.d.ts.map +1 -0
  24. package/dist/src/local-client.js +947 -0
  25. package/dist/src/local-client.js.map +1 -0
  26. package/dist/src/local-locks.d.ts +26 -0
  27. package/dist/src/local-locks.d.ts.map +1 -0
  28. package/dist/src/local-locks.js +315 -0
  29. package/dist/src/local-locks.js.map +1 -0
  30. package/dist/src/package-version.d.ts +2 -0
  31. package/dist/src/package-version.d.ts.map +1 -0
  32. package/dist/src/package-version.js +13 -0
  33. package/dist/src/package-version.js.map +1 -0
  34. package/dist/src/page-cache-http.d.ts +15 -0
  35. package/dist/src/page-cache-http.d.ts.map +1 -0
  36. package/dist/src/page-cache-http.js +44 -0
  37. package/dist/src/page-cache-http.js.map +1 -0
  38. package/dist/src/server.d.ts +135 -0
  39. package/dist/src/server.d.ts.map +1 -0
  40. package/dist/src/server.js +1454 -0
  41. package/dist/src/server.js.map +1 -0
  42. package/dist/src/test-utils/lock-fixtures.d.ts +18 -0
  43. package/dist/src/test-utils/lock-fixtures.d.ts.map +1 -0
  44. package/dist/src/test-utils/lock-fixtures.js +33 -0
  45. package/dist/src/test-utils/lock-fixtures.js.map +1 -0
  46. package/dist/src/unwrap-evaluate-payload.d.ts +8 -0
  47. package/dist/src/unwrap-evaluate-payload.d.ts.map +1 -0
  48. package/dist/src/unwrap-evaluate-payload.js +19 -0
  49. package/dist/src/unwrap-evaluate-payload.js.map +1 -0
  50. package/package.json +65 -0
package/README.md ADDED
@@ -0,0 +1,356 @@
1
+ # @thinkrun/mcp
2
+
3
+ MCP (Model Context Protocol) server for [ThinkRun](https://thinkbrowse.io). Gives AI agents the ability to control **your actual Chrome browser** — with all your cookies and active sessions — or a cloud browser for headless automation.
4
+
5
+ ## Why ThinkRun vs Playwright MCP
6
+
7
+ | Capability | ThinkRun MCP | Playwright MCP |
8
+ |---|---|---|
9
+ | **Uses your real Chrome cookies** | ✅ Local mode — browse as you | ❌ Fresh context, no auth |
10
+ | **Access sites you're logged into** | ✅ LinkedIn, Salesforce, internal tools | ❌ Requires scripted login every time |
11
+ | **Cloud headless sessions** | ✅ Isolated Playwright browser on demand | ❌ Local browser only (no ThinkRun cloud) |
12
+ | **Runtime local ↔ cloud switching** | ✅ `set_mode` tool, no restart | ❌ Single mode per server |
13
+ | **Structured content extraction** | ✅ Reader layer (articles, structured data) | ❌ Raw DOM access only |
14
+ | **Session recording + sharing** | ✅ Full video, screenshots, AI analysis | ❌ |
15
+ | **Persistent sessions** | ✅ Resume across agent invocations | ❌ Single-run only |
16
+ | **Direct browser actions** | ✅ Browser tools + `local_action_*` | ✅ Browser tools |
17
+ | **AI task planning** | ✅ `task_create` / `task_execute` | ❌ |
18
+
19
+ **TL;DR**: Use ThinkRun when you need to browse as yourself (authenticated sites, internal tools). Use Playwright MCP for stateless public-web automation.
20
+
21
+ ## Installation
22
+
23
+ ### Claude Code
24
+
25
+ ```bash
26
+ claude mcp add thinkrun --transport stdio \
27
+ -e THINKRUN_API_KEY=your-key \
28
+ -- npx @thinkrun/mcp
29
+ ```
30
+
31
+ ### Claude Desktop / Cursor / VS Code
32
+
33
+ Add to your MCP config file:
34
+
35
+ | Tool | Config file |
36
+ |------|------------|
37
+ | Claude Desktop | `~/Library/Application Support/Claude/claude_desktop_config.json` |
38
+ | Cursor | `.cursor/mcp.json` in project root |
39
+ | VS Code (Copilot) | `.vscode/mcp.json` in project root |
40
+
41
+ **Auto mode (recommended)** — tries local Chrome first, falls back to cloud:
42
+
43
+ ```json
44
+ {
45
+ "mcpServers": {
46
+ "thinkrun": {
47
+ "command": "npx",
48
+ "args": ["@thinkrun/mcp"],
49
+ "env": {
50
+ "THINKRUN_API_KEY": "your-key"
51
+ }
52
+ }
53
+ }
54
+ }
55
+ ```
56
+
57
+ **Local only** (Chrome extension, no API key needed):
58
+
59
+ ```json
60
+ {
61
+ "mcpServers": {
62
+ "thinkrun": {
63
+ "command": "npx",
64
+ "args": ["@thinkrun/mcp", "--mode", "local"]
65
+ }
66
+ }
67
+ }
68
+ ```
69
+
70
+ **Cloud only**:
71
+
72
+ ```json
73
+ {
74
+ "mcpServers": {
75
+ "thinkrun": {
76
+ "command": "npx",
77
+ "args": ["@thinkrun/mcp", "--mode", "cloud"],
78
+ "env": {
79
+ "THINKRUN_API_KEY": "your-key"
80
+ }
81
+ }
82
+ }
83
+ }
84
+ ```
85
+
86
+ **Security note**: MCP config files (`.cursor/mcp.json`, `.vscode/mcp.json`) are often committed to git. Use environment variable references where supported, or add these files to `.gitignore` to avoid leaking your API key.
87
+
88
+ ### Global install (alternative)
89
+
90
+ ```bash
91
+ npm install -g @thinkrun/mcp
92
+ thinkrun-mcp --mode auto
93
+ ```
94
+
95
+ ## Modes
96
+
97
+ | Mode | Backend | Requires |
98
+ |------|---------|----------|
99
+ | `auto` | Tries local first, falls back to cloud | Either of the below |
100
+ | `local` | Chrome extension + native host | Chrome with ThinkRun extension |
101
+ | `cloud` | ThinkRun REST API (Fly.io) | API key from thinkbrowse.io |
102
+
103
+ **Page cache tools** (`page_cache_html`, `page_cache_text`, `page_cache_screenshot`): stateless `POST /api/cache/*` on the service. In **local** mode, set `THINKRUN_BASE_URL` and `THINKRUN_API_KEY` (or pass `--base-url` / `--api-key` so the native host client can reach the API — the native host itself has no `/api/cache` routes).
104
+
105
+ **Runtime switching**: Use the `set_mode` tool to switch between local and cloud without restarting.
106
+
107
+ ### Local mode setup
108
+
109
+ ```
110
+ MCP Server ──HTTP──> Native Host ──stdio──> Chrome Extension ──> Chrome tabs
111
+ ```
112
+
113
+ 1. Install the [ThinkRun Chrome extension](https://thinkbrowse.io)
114
+ 2. Set up the native host binary (extension guides you through this)
115
+ 3. Verify: `curl http://localhost:$(cat ~/.thinkbrowse/port)/health`
116
+ 4. Add MCP config above
117
+
118
+ ## Available Tools (51 + 1 optional)
119
+
120
+ > 51 tools are always registered. `set_mode` is registered when the server is started with mode-switch support (the default CLI binary enables it).
121
+
122
+ ### Session Lifecycle
123
+ | Tool | Description | Modes |
124
+ |------|-------------|-------|
125
+ | `session_create` | Create a new browser session | all |
126
+ | `session_status` | Get session URL, title, action count | all |
127
+ | `session_delete` | Terminate session, preserve artifacts | all |
128
+ | `session_list` | List all active sessions | all |
129
+ | `session_wait_ready` | Poll until session is ready (use after create in cloud mode) | all |
130
+ | `session_use` | Set default session ID for subsequent tool calls | all |
131
+ | `session_release` | Release the current local session ownership without closing the tab | local |
132
+ | `session_artifacts` | List screenshots and recordings for a session | cloud |
133
+
134
+ ### Mode Switching
135
+ | Tool | Description | Modes |
136
+ |------|-------------|-------|
137
+ | `set_mode` | Switch between local/cloud/auto at runtime without restart | all |
138
+
139
+ ### Stateless Page Cache
140
+ | Tool | Description | Modes |
141
+ |------|-------------|-------|
142
+ | `page_cache_html` | Fetch rendered HTML without creating a session | all |
143
+ | `page_cache_text` | Extract plain text without creating a session | all |
144
+ | `page_cache_screenshot` | Capture a screenshot without creating a session | all |
145
+
146
+ ### Navigation
147
+ | Tool | Description | Modes |
148
+ |------|-------------|-------|
149
+ | `navigate` | Go to a URL, wait for page load | all |
150
+ | `go_back` | Navigate back in browser history | all |
151
+ | `go_forward` | Navigate forward in browser history | all |
152
+ | `wait_for_element` | Wait for a CSS selector to appear | all |
153
+
154
+ ### Interaction
155
+ | Tool | Description | Modes |
156
+ |------|-------------|-------|
157
+ | `click` | Click an element by CSS selector | all |
158
+ | `type_text` | Type text into an element (appends) | all |
159
+ | `fill` | Fill a form field (replaces content) | all |
160
+ | `press_key` | Press a keyboard key (Enter, Tab, Escape, etc.) | all |
161
+ | `scroll` | Scroll the page or a specific element | all |
162
+ | `hover` | Hover over an element (triggers tooltips/menus) | all |
163
+ | `select_option` | Select from a `<select>` dropdown | all |
164
+
165
+ ### Observation
166
+ | Tool | Description | Modes |
167
+ |------|-------------|-------|
168
+ | `snapshot` | Get accessibility tree (lightweight page overview) | all |
169
+ | `screenshot` | Capture page screenshot (png/jpeg/webp, full-page option) | all |
170
+ | `extract` | Extract text, HTML, or structured data from elements | all |
171
+ | `evaluate` | Run JavaScript in the page context | all |
172
+ | `get_url` | Return the current page URL | all |
173
+ | `get_title` | Return the current page title | all |
174
+ | `get_html` | Return current page HTML with explicit truncation metadata when large | all |
175
+ | `sleep` | Pause for a bounded duration in milliseconds | all |
176
+
177
+ ### Tab Management
178
+ | Tool | Description | Modes |
179
+ |------|-------------|-------|
180
+ | `tab_list` | List all open browser tabs | local |
181
+ | `tab_new` | Open a new tab (optionally with URL) | local |
182
+ | `tab_close` | Close a tab by ID | local |
183
+ | `tab_attach` | Attach to an existing local tab and bind it as the default session | local |
184
+ | `tab_switch` | Switch the active local browser tab without rebinding the default session | local |
185
+ | `focus` | Bring the currently bound local browser tab/window to the foreground | local |
186
+ | `window_new` | Open a new local browser window and bind it as the default session | local |
187
+
188
+ ### Dialog Handling
189
+ | Tool | Description | Modes |
190
+ |------|-------------|-------|
191
+ | `get_dialog` | Check for open alert/confirm/prompt dialogs | all |
192
+ | `handle_dialog` | Accept or dismiss a dialog with optional text | all |
193
+
194
+ ### Wait
195
+ | Tool | Description | Modes |
196
+ |------|-------------|-------|
197
+ | `wait_for_text` | Wait for specific text to appear on the page | all |
198
+
199
+ ### Monitoring
200
+ | Tool | Description | Modes |
201
+ |------|-------------|-------|
202
+ | `console_messages` | Get browser console messages (log, warn, error) | all |
203
+ | `network_requests` | Get network requests made by the page | all |
204
+ | `clear_logs` | Clear console and network logs for a clean capture | all |
205
+
206
+ ### Local Actions
207
+ | Tool | Description | Modes |
208
+ |------|-------------|-------|
209
+ | `local_action_run` | Run a direct native-host action against the currently attached local tab | local |
210
+ | `local_action_status` | Check the status of a previously started local action | local |
211
+ | `local_action_cancel` | Cancel a running local action | local |
212
+
213
+ ### Local Diagnostics And Recovery
214
+ | Tool | Description | Modes |
215
+ |------|-------------|-------|
216
+ | `local_diagnostics` | Inspect the current local continuity, lock owner, reclaim audit, and bridge recovery state | local |
217
+ | `local_reset_connection` | Reset the local bridge connection using the bounded circuit-breaker recovery path | local |
218
+
219
+ ### Task Planning
220
+ | Tool | Description | Modes |
221
+ |------|-------------|-------|
222
+ | `task_create` | Create an AI task plan from natural language | cloud |
223
+ | `task_execute` | Execute a task plan | cloud |
224
+ | `task_status` | Check task execution status | cloud |
225
+
226
+ ## Local Session Semantics
227
+
228
+ The new local lifecycle tools intentionally distinguish between **ownership-changing** operations and **focus-only** operations:
229
+
230
+ - `tab_attach` rebinding:
231
+ attaches to an existing local Chrome tab and sets it as the default session for subsequent session-scoped tool calls.
232
+ - `window_new` rebinding:
233
+ opens a fresh local window and sets the resulting tab as the default session.
234
+ - `tab_switch` focus-only:
235
+ changes the active local browser tab, but does **not** steal or rewrite the current default session just because browser focus changed.
236
+ - `focus` focus-only:
237
+ foregrounds the currently bound local tab/window without rebinding or switching the default session.
238
+ - `session_release` clearing:
239
+ releases the current local session ownership and clears the default session when that bound local context is released.
240
+
241
+ Lock safety follows the same high-level contract as the CLI:
242
+
243
+ - contested live local locks fail explicitly
244
+ - same-owner rebinds can succeed idempotently
245
+ - stale locks may be replaced only when the existing owner is no longer live
246
+
247
+ This package also exposes structured local diagnostics and bounded recovery rather than full CLI admin commands:
248
+
249
+ - `local_diagnostics` is the MCP equivalent for continuity/bridge truth that CLI users get from `doctor` and `session debug`
250
+ - `local_reset_connection` is the MCP equivalent for the CLI’s bounded `reset-connection` recovery path
251
+ - install/bootstrap/config flows remain CLI-only
252
+
253
+ This package now exposes the CLI's local native-host execution flow as:
254
+
255
+ - `local_action_run`
256
+ - `local_action_status`
257
+ - `local_action_cancel`
258
+
259
+ The naming is intentional:
260
+
261
+ - `action` means a direct operation against a browser context
262
+ - `task` means a higher-level cloud multi-step planning/execution workflow
263
+
264
+ ## CLI Options
265
+
266
+ ```
267
+ thinkrun-mcp [options]
268
+
269
+ Options:
270
+ --mode <mode> cloud, local, or auto (default: auto)
271
+ --api-key <key> API key for cloud mode (or set THINKRUN_API_KEY)
272
+ --base-url <url> Cloud API URL (default: https://api.thinkbrowse.io)
273
+ --port <port> Override native host port
274
+ --session-id <id> Default session ID (enables session_use)
275
+ -h, --help Show help
276
+ -v, --version Show version
277
+ ```
278
+
279
+ ## Environment Variables
280
+
281
+ | Variable | Description |
282
+ |----------|-------------|
283
+ | `THINKRUN_API_KEY` | API key for cloud mode |
284
+ | `THINKRUN_BASE_URL` | Cloud API base URL |
285
+ | `THINKRUN_BRIDGE_PORT` | Override native host port |
286
+ | `THINKRUN_LOCAL` | Set to `true` to force local mode |
287
+
288
+ ## Port Discovery
289
+
290
+ The native host writes its port to `~/.thinkbrowse/port` on startup. This path stays stable until the native-host cutover moves it to `~/.thinkrun/port`. The MCP server reads this automatically.
291
+
292
+ Priority: `--port` flag > `THINKRUN_BRIDGE_PORT` env > `~/.thinkbrowse/port` > `3012`
293
+
294
+ ## Testing
295
+
296
+ ```bash
297
+ # Unit tests
298
+ bun test
299
+
300
+ # Smoke test — full session lifecycle against cloud
301
+ THINKRUN_API_KEY=your-key bun run scripts/smoke-mcp-parity.ts
302
+
303
+ # Manual local parity smoke — requires live native host + extension bridge
304
+ bun run smoke:local-parity
305
+
306
+ # Manual local parity soak — repeats the same local parity assertions
307
+ THINKRUN_MCP_SOAK_ITERATIONS=50 bun run soak:local-parity
308
+
309
+ # Smoke test — clear_logs only
310
+ bun run test:smoke
311
+ ```
312
+
313
+ ## Programmatic Usage
314
+
315
+ **Basic:**
316
+
317
+ ```typescript
318
+ import { createServer, CloudClient } from '@thinkrun/mcp';
319
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
320
+
321
+ const client = new CloudClient({
322
+ baseUrl: 'https://api.thinkbrowse.io',
323
+ apiKey: process.env.THINKRUN_API_KEY!,
324
+ });
325
+
326
+ const server = createServer(client);
327
+ await server.connect(new StdioServerTransport());
328
+ ```
329
+
330
+ **With runtime mode switching:**
331
+
332
+ ```typescript
333
+ import { createServer, CloudClient } from '@thinkrun/mcp';
334
+ import type { ClientRef } from '@thinkrun/mcp';
335
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
336
+
337
+ const client = new CloudClient({
338
+ baseUrl: 'https://api.thinkbrowse.io',
339
+ apiKey: process.env.THINKRUN_API_KEY!,
340
+ });
341
+
342
+ const clientRef: ClientRef = { current: client };
343
+ const server = createServer(client, {
344
+ clientRef,
345
+ onSetMode: async (mode) => {
346
+ // Return a new client for the requested mode
347
+ return new CloudClient({ baseUrl: '...', apiKey: '...' });
348
+ },
349
+ });
350
+
351
+ await server.connect(new StdioServerTransport());
352
+ ```
353
+
354
+ ## License
355
+
356
+ MIT
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=thinkrun-mcp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"thinkrun-mcp.d.ts","sourceRoot":"","sources":["../../bin/thinkrun-mcp.ts"],"names":[],"mappings":""}
@@ -0,0 +1,305 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * ThinkRun MCP Server CLI
4
+ *
5
+ * Usage:
6
+ * npx @thinkrun/mcp --mode cloud --api-key YOUR_KEY
7
+ * npx @thinkrun/mcp --mode local --port 3012
8
+ * npx @thinkrun/mcp (auto-detect: tries native host, falls back to cloud)
9
+ */
10
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
11
+ import { createServer } from '../src/server.js';
12
+ import { CloudClient } from '../src/cloud-client.js';
13
+ import { LocalClient } from '../src/local-client.js';
14
+ import { readPackageVersion } from '../src/package-version.js';
15
+ import { readFileSync, existsSync } from 'fs';
16
+ import { join } from 'path';
17
+ const DEFAULT_CLOUD_URL = 'https://api.thinkbrowse.io';
18
+ const DEFAULT_PORT = 3012;
19
+ /** Read port from ~/.thinkbrowse/port discovery file. */
20
+ function readDiscoveredPort() {
21
+ try {
22
+ const portFile = join(process.env.HOME || process.env.USERPROFILE || '/tmp', '.thinkbrowse', 'port');
23
+ if (existsSync(portFile)) {
24
+ const port = parseInt(readFileSync(portFile, 'utf-8').trim(), 10);
25
+ if (!isNaN(port) && port > 0 && port <= 65535)
26
+ return port;
27
+ }
28
+ }
29
+ catch {
30
+ // unreadable
31
+ }
32
+ return undefined;
33
+ }
34
+ function nextArg(argv, i, flag) {
35
+ const val = argv[i + 1];
36
+ if (!val || val.startsWith('--') || val.startsWith('-')) {
37
+ console.error(`[thinkrun-mcp] Error: ${flag} requires a value`);
38
+ process.exit(1);
39
+ }
40
+ return val;
41
+ }
42
+ function parsePort(value) {
43
+ const port = parseInt(value, 10);
44
+ if (isNaN(port) || port < 1 || port > 65535)
45
+ return undefined;
46
+ return port;
47
+ }
48
+ function parseArgs() {
49
+ const args = {};
50
+ const argv = process.argv.slice(2);
51
+ for (let i = 0; i < argv.length; i++) {
52
+ switch (argv[i]) {
53
+ case '--mode': {
54
+ const mode = nextArg(argv, i, '--mode');
55
+ if (mode !== 'cloud' && mode !== 'local' && mode !== 'auto') {
56
+ console.error(`[thinkrun-mcp] Error: invalid mode "${mode}". Must be cloud, local, or auto`);
57
+ process.exit(1);
58
+ }
59
+ args.mode = mode;
60
+ i++;
61
+ break;
62
+ }
63
+ case '--api-key':
64
+ args.apiKey = nextArg(argv, i, '--api-key');
65
+ i++;
66
+ break;
67
+ case '--base-url':
68
+ args.baseUrl = nextArg(argv, i, '--base-url');
69
+ i++;
70
+ break;
71
+ case '--port':
72
+ case '--bridge-port': { // backwards compat
73
+ const portStr = nextArg(argv, i, argv[i]);
74
+ const port = parsePort(portStr);
75
+ if (!port) {
76
+ console.error(`[thinkrun-mcp] Error: invalid port "${portStr}". Must be 1-65535`);
77
+ process.exit(1);
78
+ }
79
+ args.port = port;
80
+ args.explicitPort = true;
81
+ i++;
82
+ break;
83
+ }
84
+ case '--session-id':
85
+ args.sessionId = nextArg(argv, i, '--session-id');
86
+ i++;
87
+ break;
88
+ case '--help':
89
+ case '-h':
90
+ printHelp();
91
+ process.exit(0);
92
+ break;
93
+ case '--version':
94
+ case '-v':
95
+ console.error(`@thinkrun/mcp v${readPackageVersion(new URL('../../package.json', import.meta.url))}`);
96
+ process.exit(0);
97
+ break;
98
+ }
99
+ }
100
+ // Environment variable fallbacks
101
+ args.apiKey = args.apiKey || process.env.THINKRUN_API_KEY || process.env.THINKBROWSE_API_KEY;
102
+ args.baseUrl = args.baseUrl || process.env.THINKRUN_BASE_URL || DEFAULT_CLOUD_URL;
103
+ // Port priority: CLI flag > env var > discovery file > default
104
+ if (!args.port) {
105
+ const envPort = parsePort(process.env.THINKRUN_BRIDGE_PORT || '');
106
+ if (envPort) {
107
+ args.port = envPort;
108
+ args.explicitPort = true;
109
+ }
110
+ else {
111
+ args.port = readDiscoveredPort() ?? DEFAULT_PORT;
112
+ }
113
+ }
114
+ args.sessionId = args.sessionId || process.env.SESSION_ID;
115
+ if (process.env.THINKRUN_LOCAL === 'true' && !args.mode) {
116
+ args.mode = 'local';
117
+ }
118
+ return args;
119
+ }
120
+ function printHelp() {
121
+ console.error(`
122
+ ThinkRun MCP Server - Browser automation for AI agents
123
+
124
+ Usage:
125
+ thinkrun-mcp [options]
126
+
127
+ Options:
128
+ --mode <mode> Connection mode: cloud, local, or auto (default: auto)
129
+ --api-key <key> API key for cloud mode (or set THINKRUN_API_KEY)
130
+ --base-url <url> Cloud API base URL (default: ${DEFAULT_CLOUD_URL})
131
+ --port <port> Native host port (overrides discovery file)
132
+ --session-id <id> Default session ID (enables session_use to switch default)
133
+ -h, --help Show this help
134
+ -v, --version Show version
135
+
136
+ Port Discovery:
137
+ The native host writes its port to ~/.thinkbrowse/port on startup.
138
+ This is read automatically — no port configuration needed.
139
+ Priority: --port flag > THINKRUN_BRIDGE_PORT env > ~/.thinkbrowse/port > ${DEFAULT_PORT}
140
+
141
+ Environment Variables:
142
+ THINKRUN_API_KEY API key for cloud mode
143
+ THINKRUN_BASE_URL Cloud API base URL
144
+ THINKRUN_BRIDGE_PORT Override native host port
145
+ THINKRUN_LOCAL Set to "true" to force local mode
146
+
147
+ Modes:
148
+ cloud Connect to ThinkRun cloud API (requires API key)
149
+ local Connect to Chrome extension native host (auto-discovered port)
150
+ auto Try native host first, fall back to cloud
151
+
152
+ Examples:
153
+ # Cloud mode
154
+ thinkrun-mcp --mode cloud --api-key sk-abc123
155
+
156
+ # Local mode (Chrome + extension must be running)
157
+ thinkrun-mcp --mode local
158
+
159
+ # Auto-detect (default)
160
+ thinkrun-mcp
161
+
162
+ Claude Desktop / Claude Code configuration:
163
+ {
164
+ "mcpServers": {
165
+ "thinkrun": {
166
+ "command": "npx",
167
+ "args": ["@thinkrun/mcp", "--mode", "local"]
168
+ }
169
+ }
170
+ }
171
+ `.trim());
172
+ }
173
+ async function detectNativeHost(port) {
174
+ const controller = new AbortController();
175
+ const timer = setTimeout(() => controller.abort(), 2000);
176
+ try {
177
+ const response = await fetch(`http://localhost:${port}/health`, {
178
+ signal: controller.signal,
179
+ });
180
+ return response.ok;
181
+ }
182
+ catch {
183
+ return false;
184
+ }
185
+ finally {
186
+ clearTimeout(timer);
187
+ }
188
+ }
189
+ /** LocalClient needs API URL + key for `/api/cache/*`; native host does not expose those routes. */
190
+ function localClientConfig(args) {
191
+ return {
192
+ port: args.port,
193
+ serviceBaseUrl: args.baseUrl,
194
+ serviceApiKey: args.apiKey,
195
+ };
196
+ }
197
+ async function createClient(args) {
198
+ const mode = args.mode || 'auto';
199
+ if (mode === 'local') {
200
+ console.error(`[thinkrun-mcp] Local mode → native host at localhost:${args.port}`);
201
+ return new LocalClient(localClientConfig(args));
202
+ }
203
+ if (mode === 'cloud') {
204
+ if (!args.apiKey) {
205
+ console.error('[thinkrun-mcp] Error: --api-key or THINKRUN_API_KEY required for cloud mode');
206
+ process.exit(1);
207
+ }
208
+ console.error(`[thinkrun-mcp] Cloud mode → ${args.baseUrl}`);
209
+ return new CloudClient({ baseUrl: args.baseUrl, apiKey: args.apiKey });
210
+ }
211
+ // Auto-detect: try native host first
212
+ console.error('[thinkrun-mcp] Auto-detecting mode...');
213
+ const nativeHostAvailable = await detectNativeHost(args.port);
214
+ if (nativeHostAvailable) {
215
+ console.error(`[thinkrun-mcp] Native host detected → localhost:${args.port}`);
216
+ return new LocalClient(localClientConfig(args));
217
+ }
218
+ if (args.apiKey) {
219
+ console.error(`[thinkrun-mcp] No native host, using cloud → ${args.baseUrl}`);
220
+ return new CloudClient({ baseUrl: args.baseUrl, apiKey: args.apiKey });
221
+ }
222
+ console.error('[thinkrun-mcp] Error: Native host not running and no API key configured.');
223
+ console.error(' Open Chrome, Helium, or another supported Chromium browser with the ThinkRun extension for local mode, or set THINKRUN_API_KEY for cloud mode.');
224
+ process.exit(1);
225
+ }
226
+ async function main() {
227
+ const args = parseArgs();
228
+ const client = await createClient(args);
229
+ const defaultSessionRef = { current: args.sessionId ?? undefined };
230
+ const clientRef = { current: client };
231
+ const onSetMode = async (mode) => {
232
+ // Re-discover port only when startup port was not explicitly set (CLI/env).
233
+ // Explicit overrides stay sticky across mode switches.
234
+ const freshPort = args.explicitPort
235
+ ? args.port ?? DEFAULT_PORT
236
+ : readDiscoveredPort() ?? args.port ?? DEFAULT_PORT;
237
+ // Pre-flight validation — createClient calls process.exit on failure,
238
+ // which is uncatchable. Validate before delegating so set_mode returns
239
+ // a structured error instead of crashing the server.
240
+ if (mode === 'cloud' && !args.apiKey) {
241
+ throw new Error('Cloud mode requires an API key (set THINKRUN_API_KEY)');
242
+ }
243
+ // For local and auto, probe the native host once and resolve the effective mode
244
+ // so createClient doesn't repeat the health check.
245
+ let effectiveMode = mode === 'cloud' ? 'cloud' : 'local';
246
+ if (mode === 'local' || mode === 'auto') {
247
+ const hasNativeHost = await detectNativeHost(freshPort);
248
+ if (mode === 'local' && !hasNativeHost) {
249
+ throw new Error(`Local mode failed: native host not running on port ${freshPort}`);
250
+ }
251
+ if (mode === 'auto') {
252
+ if (hasNativeHost) {
253
+ effectiveMode = 'local';
254
+ }
255
+ else if (args.apiKey) {
256
+ effectiveMode = 'cloud';
257
+ }
258
+ else {
259
+ throw new Error('Auto mode failed: native host not running and no API key configured');
260
+ }
261
+ }
262
+ }
263
+ const oldClient = clientRef.current;
264
+ const newArgs = { ...args, mode: effectiveMode, port: freshPort };
265
+ const newClient = await createClient(newArgs);
266
+ // Best-effort cleanup of old client resources (bounded to 2s)
267
+ if (oldClient && 'close' in oldClient && typeof oldClient.close === 'function') {
268
+ let timer;
269
+ const closePromise = oldClient.close();
270
+ const timeoutPromise = new Promise((_, reject) => {
271
+ timer = setTimeout(() => reject(new Error('close timeout')), 2000);
272
+ });
273
+ try {
274
+ await Promise.race([closePromise, timeoutPromise]);
275
+ }
276
+ catch (err) {
277
+ console.error('[thinkrun-mcp] Warning: failed to close previous client:', err);
278
+ }
279
+ finally {
280
+ if (timer)
281
+ clearTimeout(timer);
282
+ // Ensure close() rejection after timeout doesn't become unhandled
283
+ closePromise.catch(() => { });
284
+ }
285
+ }
286
+ // Clear default session — old session ID is invalid for the new client
287
+ defaultSessionRef.current = undefined;
288
+ console.error(`[thinkrun-mcp] Mode switched to: ${effectiveMode}`);
289
+ return { client: newClient, resolvedMode: effectiveMode };
290
+ };
291
+ const server = createServer(client, {
292
+ defaultSessionId: defaultSessionRef.current,
293
+ defaultSessionRef,
294
+ clientRef,
295
+ onSetMode,
296
+ });
297
+ const transport = new StdioServerTransport();
298
+ await server.connect(transport);
299
+ console.error('[thinkrun-mcp] Server running. Waiting for MCP client connection...');
300
+ }
301
+ main().catch((error) => {
302
+ console.error('[thinkrun-mcp] Fatal error:', error);
303
+ process.exit(1);
304
+ });
305
+ //# sourceMappingURL=thinkrun-mcp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"thinkrun-mcp.js","sourceRoot":"","sources":["../../bin/thinkrun-mcp.ts"],"names":[],"mappings":"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"}