@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.
- package/README.md +356 -0
- package/dist/bin/thinkrun-mcp.d.ts +3 -0
- package/dist/bin/thinkrun-mcp.d.ts.map +1 -0
- package/dist/bin/thinkrun-mcp.js +305 -0
- package/dist/bin/thinkrun-mcp.js.map +1 -0
- package/dist/src/client.d.ts +422 -0
- package/dist/src/client.d.ts.map +1 -0
- package/dist/src/client.js +2 -0
- package/dist/src/client.js.map +1 -0
- package/dist/src/cloud-client.d.ts +69 -0
- package/dist/src/cloud-client.d.ts.map +1 -0
- package/dist/src/cloud-client.js +291 -0
- package/dist/src/cloud-client.js.map +1 -0
- package/dist/src/extension-proxy-client.d.ts +139 -0
- package/dist/src/extension-proxy-client.d.ts.map +1 -0
- package/dist/src/extension-proxy-client.js +451 -0
- package/dist/src/extension-proxy-client.js.map +1 -0
- package/dist/src/index.d.ts +11 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +6 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/local-client.d.ts +120 -0
- package/dist/src/local-client.d.ts.map +1 -0
- package/dist/src/local-client.js +947 -0
- package/dist/src/local-client.js.map +1 -0
- package/dist/src/local-locks.d.ts +26 -0
- package/dist/src/local-locks.d.ts.map +1 -0
- package/dist/src/local-locks.js +315 -0
- package/dist/src/local-locks.js.map +1 -0
- package/dist/src/package-version.d.ts +2 -0
- package/dist/src/package-version.d.ts.map +1 -0
- package/dist/src/package-version.js +13 -0
- package/dist/src/package-version.js.map +1 -0
- package/dist/src/page-cache-http.d.ts +15 -0
- package/dist/src/page-cache-http.d.ts.map +1 -0
- package/dist/src/page-cache-http.js +44 -0
- package/dist/src/page-cache-http.js.map +1 -0
- package/dist/src/server.d.ts +135 -0
- package/dist/src/server.d.ts.map +1 -0
- package/dist/src/server.js +1454 -0
- package/dist/src/server.js.map +1 -0
- package/dist/src/test-utils/lock-fixtures.d.ts +18 -0
- package/dist/src/test-utils/lock-fixtures.d.ts.map +1 -0
- package/dist/src/test-utils/lock-fixtures.js +33 -0
- package/dist/src/test-utils/lock-fixtures.js.map +1 -0
- package/dist/src/unwrap-evaluate-payload.d.ts +8 -0
- package/dist/src/unwrap-evaluate-payload.d.ts.map +1 -0
- package/dist/src/unwrap-evaluate-payload.js +19 -0
- package/dist/src/unwrap-evaluate-payload.js.map +1 -0
- 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 @@
|
|
|
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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"}
|