sessionmcp 0.2.0

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 (3) hide show
  1. package/README.md +253 -0
  2. package/index.js +476 -0
  3. package/package.json +38 -0
package/README.md ADDED
@@ -0,0 +1,253 @@
1
+ # SessionMCP Relay
2
+
3
+ > Universal MCP browser bridge — connect Claude Code, Cursor, VS Code Copilot, Windsurf, and Cline to your **existing** Chrome session.
4
+ > No new browser window. Your logins, your cookies, your tabs.
5
+
6
+ ---
7
+
8
+ ## How It Works
9
+
10
+ SessionMCP has two parts that work together:
11
+
12
+ ```
13
+ AI Tool (Claude Code / Cursor / etc.)
14
+ ↓ MCP stdio (JSON-RPC 2.0)
15
+ relay/index.js ←→ ws://127.0.0.1:7337
16
+ ↕ WebSocket + token auth
17
+ Chrome Extension (SessionMCP)
18
+ ↕ chrome.debugger API
19
+ Your Chrome Browser
20
+ ```
21
+
22
+ 1. **Chrome Extension** — installs into Chrome, holds your session
23
+ 2. **Relay** (this folder) — runs locally, bridges MCP ↔ extension via WebSocket
24
+
25
+ ---
26
+
27
+ ## Prerequisites
28
+
29
+ - **Node.js 18+** — [nodejs.org](https://nodejs.org)
30
+ - **Chrome** with the **SessionMCP extension** installed and connected (badge shows ✓)
31
+
32
+ ---
33
+
34
+ ## Installation
35
+
36
+ ### Option A — npm (recommended, global)
37
+
38
+ ```bash
39
+ npm install -g sessionmcp
40
+ ```
41
+
42
+ ### Option B — run directly from this folder
43
+
44
+ ```bash
45
+ cd relay
46
+ npm install
47
+ node index.js
48
+ ```
49
+
50
+ ---
51
+
52
+ ## Get Your Token
53
+
54
+ 1. Click the **SessionMCP** icon in Chrome's toolbar
55
+ 2. Click **Setup Wizard**
56
+ 3. Copy your token from Step 2
57
+
58
+ The token is generated once on first install and persists. It authenticates the relay to your extension.
59
+
60
+ ---
61
+
62
+ ## Setup by AI Tool
63
+
64
+ ### Claude Code
65
+
66
+ Config file: `~/.claude/claude_mcp_config.json`
67
+
68
+ ```json
69
+ {
70
+ "mcpServers": {
71
+ "sessionmcp": {
72
+ "command": "sessionmcp",
73
+ "args": [],
74
+ "env": {
75
+ "SESSIONMCP_TOKEN": "YOUR_TOKEN_HERE"
76
+ }
77
+ }
78
+ }
79
+ }
80
+ ```
81
+
82
+ Or if running directly from source:
83
+
84
+ ```json
85
+ {
86
+ "mcpServers": {
87
+ "sessionmcp": {
88
+ "command": "node",
89
+ "args": ["/absolute/path/to/sessionmcp/relay/index.js"],
90
+ "env": {
91
+ "SESSIONMCP_TOKEN": "YOUR_TOKEN_HERE"
92
+ }
93
+ }
94
+ }
95
+ }
96
+ ```
97
+
98
+ Restart Claude Code. Verify with: `claude mcp list`
99
+
100
+ ---
101
+
102
+ ### Cursor
103
+
104
+ Config file: `~/.cursor/mcp.json`
105
+
106
+ ```json
107
+ {
108
+ "mcpServers": {
109
+ "sessionmcp": {
110
+ "command": "sessionmcp",
111
+ "args": [],
112
+ "env": {
113
+ "SESSIONMCP_TOKEN": "YOUR_TOKEN_HERE"
114
+ }
115
+ }
116
+ }
117
+ }
118
+ ```
119
+
120
+ Restart Cursor. The MCP server appears in **Settings → MCP**.
121
+
122
+ ---
123
+
124
+ ### Windsurf (Codeium)
125
+
126
+ Config file: `~/.codeium/windsurf/mcp_config.json`
127
+
128
+ ```json
129
+ {
130
+ "mcpServers": {
131
+ "sessionmcp": {
132
+ "command": "sessionmcp",
133
+ "args": [],
134
+ "env": {
135
+ "SESSIONMCP_TOKEN": "YOUR_TOKEN_HERE"
136
+ }
137
+ }
138
+ }
139
+ }
140
+ ```
141
+
142
+ Restart Windsurf. Check **Cascade → MCP** panel to confirm connected.
143
+
144
+ ---
145
+
146
+ ### Cline / RooCode (VS Code extension)
147
+
148
+ 1. Open VS Code
149
+ 2. Go to Cline extension sidebar → **MCP Servers** tab
150
+ 3. Click **Add Server** and use:
151
+
152
+ ```json
153
+ {
154
+ "sessionmcp": {
155
+ "command": "sessionmcp",
156
+ "args": [],
157
+ "env": {
158
+ "SESSIONMCP_TOKEN": "YOUR_TOKEN_HERE"
159
+ }
160
+ }
161
+ }
162
+ ```
163
+
164
+ Or edit `~/.vscode/cline_mcp_settings.json` directly (path varies by OS).
165
+
166
+ ---
167
+
168
+ ### VS Code Copilot (GitHub Copilot)
169
+
170
+ Config file: `.vscode/mcp.json` in your workspace, or user settings.
171
+
172
+ ```json
173
+ {
174
+ "servers": {
175
+ "sessionmcp": {
176
+ "type": "stdio",
177
+ "command": "sessionmcp",
178
+ "args": [],
179
+ "env": {
180
+ "SESSIONMCP_TOKEN": "YOUR_TOKEN_HERE"
181
+ }
182
+ }
183
+ }
184
+ }
185
+ ```
186
+
187
+ Enable via **Command Palette → MCP: List Servers**.
188
+
189
+ ---
190
+
191
+ ## Available Tools
192
+
193
+ | Tool | Description |
194
+ |---|---|
195
+ | `tabs_context` | List all open Chrome tabs with tabId, title, URL, active state |
196
+ | `navigate` | Navigate a tab to a URL and wait for load |
197
+ | `screenshot` | Take a screenshot of a tab — returns base64 PNG |
198
+ | `click` | Click at x,y coordinates on a tab |
199
+ | `type_text` | Type text into the focused element |
200
+ | `evaluate` | Execute JavaScript in a tab and return the result |
201
+ | `get_page_text` | Get the full visible text content of a page |
202
+ | `scroll` | Scroll a tab by x/y pixels |
203
+ | `get_console_logs` | Retrieve console messages from a tab |
204
+
205
+ ---
206
+
207
+ ## Known Limitations
208
+
209
+ | Limitation | Details |
210
+ |---|---|
211
+ | `chrome://` pages | Chrome blocks debugger access to its own internal pages (`chrome://extensions`, `chrome://settings`, etc.). screenshot, evaluate, click and all CDP tools will return an error on these URLs. Use a regular web page tab instead. |
212
+ | `file://` pages | Local HTML files opened via `file://` also block debugger access by default. |
213
+ | Extension pages | `chrome-extension://` URLs are blocked the same way. |
214
+ | PDFs opened in Chrome | Chrome's built-in PDF viewer blocks CDP commands. |
215
+ | Multiple relay instances | Running two AI tool terminals simultaneously causes port 7337 conflict — second relay loses WebSocket connection. Fix coming in v0.2.0. |
216
+ | Multiple Chrome windows | `tabs_context` returns one active tab per window. If you have 2+ Chrome windows open, it's ambiguous which tab the user is actually looking at. The focused/foreground window is not indicated. Fix coming in v0.2.0 — `tabs_context` will include a `windowFocused` field. |
217
+
218
+ ---
219
+
220
+ ## Troubleshooting
221
+
222
+ **Extension badge shows ✗ (disconnected)**
223
+ - Is the relay running? Start it: `sessionmcp` or `node index.js`
224
+ - Check the port isn't blocked: `ws://127.0.0.1:7337`
225
+ - Reload the extension in `chrome://extensions`
226
+
227
+ **"Tool not found" in AI tool**
228
+ - Verify token in `env.SESSIONMCP_TOKEN` matches the extension popup
229
+ - Restart your AI tool after adding the MCP config
230
+
231
+ **Screenshot returns empty**
232
+ - The debugger permission requires user confirmation on some Chrome versions
233
+ - Click "Allow" if Chrome prompts for debugger access
234
+
235
+ **Windows path issues**
236
+ - Use forward slashes or escaped backslashes in JSON config paths
237
+ - Prefer the global `npm install -g sessionmcp` to avoid path problems
238
+
239
+ ---
240
+
241
+ ## Security
242
+
243
+ - Token is 32-byte random (44-char base64url), generated once on install
244
+ - Relay only accepts WebSocket connections with a valid token header
245
+ - All traffic stays on `127.0.0.1` — nothing leaves your machine
246
+ - No cloud, no telemetry, no accounts
247
+
248
+ ---
249
+
250
+ ## License
251
+
252
+ MIT — © 2026 [Limitless GMTech Solutions, Lda](https://www.limitless-gmtech.com/)
253
+ Built by [Ginquel Moreira](https://www.linkedin.com/in/ginquel-moreira/)
package/index.js ADDED
@@ -0,0 +1,476 @@
1
+ #!/usr/bin/env node
2
+ // Copyright (c) 2026 Limitless GMTech Solutions, Lda. All rights reserved.
3
+ // Proprietary and confidential. Unauthorized copying or use is prohibited.
4
+ // See LICENSE file for full terms.
5
+ 'use strict';
6
+
7
+ const { WebSocketServer, WebSocket } = require('ws');
8
+ const readline = require('readline');
9
+
10
+ const PORT = parseInt(process.env.SESSIONMCP_PORT || '7337', 10);
11
+ const TOKEN = process.env.SESSIONMCP_TOKEN || '';
12
+
13
+ // ── STATE ───────────────────────────────────────────────────────────────────
14
+ let isPrimary = false;
15
+
16
+ // Primary-only
17
+ let extensionSocket = null;
18
+ const relayPeers = new Set();
19
+
20
+ // Secondary-only
21
+ let primarySocket = null;
22
+
23
+ // Shared: in-flight requests waiting for a response
24
+ const pendingRequests = new Map(); // id → { resolve, reject, timer }
25
+ let nextId = 1;
26
+
27
+ // ── PRIMARY: WS SERVER ──────────────────────────────────────────────────────
28
+ function startServer() {
29
+ const wss = new WebSocketServer({ port: PORT, host: '127.0.0.1' });
30
+
31
+ wss.once('listening', () => {
32
+ isPrimary = true;
33
+ process.stderr.write(`[SessionMCP Relay] PRIMARY — listening on ws://127.0.0.1:${PORT}\n`);
34
+ });
35
+
36
+ wss.once('error', (err) => {
37
+ wss.close();
38
+ if (err.code === 'EADDRINUSE') {
39
+ process.stderr.write(`[SessionMCP Relay] SECONDARY — port ${PORT} in use, connecting to primary\n`);
40
+ connectToPrimary();
41
+ } else {
42
+ process.stderr.write(`[SessionMCP Relay] WS server error: ${err.message}\n`);
43
+ }
44
+ });
45
+
46
+ wss.on('connection', (socket, req) => {
47
+ const url = new URL(req.url, `http://127.0.0.1:${PORT}`);
48
+
49
+ // Token auth
50
+ if (TOKEN && url.searchParams.get('token') !== TOKEN) {
51
+ socket.close(4001, 'Invalid token');
52
+ return;
53
+ }
54
+
55
+ if (url.searchParams.get('relay') === '1') {
56
+ handleRelayPeer(socket);
57
+ } else {
58
+ handleExtension(socket);
59
+ }
60
+ });
61
+ }
62
+
63
+ function handleExtension(socket) {
64
+ extensionSocket = socket;
65
+ process.stderr.write('[SessionMCP Relay] Extension connected\n');
66
+
67
+ socket.on('message', (data) => {
68
+ try {
69
+ const msg = JSON.parse(data.toString());
70
+ if (msg.type === 'ping') return;
71
+ resolveRequest(msg);
72
+ } catch (e) {
73
+ process.stderr.write(`[SessionMCP Relay] Bad message from extension: ${e}\n`);
74
+ }
75
+ });
76
+
77
+ socket.on('close', () => {
78
+ if (extensionSocket === socket) extensionSocket = null;
79
+ process.stderr.write('[SessionMCP Relay] Extension disconnected\n');
80
+ });
81
+ }
82
+
83
+ function handleRelayPeer(socket) {
84
+ relayPeers.add(socket);
85
+ process.stderr.write(`[SessionMCP Relay] Secondary relay connected (${relayPeers.size} peers)\n`);
86
+
87
+ socket.on('message', async (data) => {
88
+ try {
89
+ const { id, method, params } = JSON.parse(data.toString());
90
+ try {
91
+ const result = await sendToExtension(method, params);
92
+ socket.send(JSON.stringify({ id, result }));
93
+ } catch (err) {
94
+ socket.send(JSON.stringify({ id, error: err.message }));
95
+ }
96
+ } catch (e) {
97
+ process.stderr.write(`[SessionMCP Relay] Bad message from peer: ${e}\n`);
98
+ }
99
+ });
100
+
101
+ socket.on('close', () => {
102
+ relayPeers.delete(socket);
103
+ process.stderr.write(`[SessionMCP Relay] Secondary relay disconnected (${relayPeers.size} peers)\n`);
104
+ });
105
+ }
106
+
107
+ // ── SECONDARY: CONNECT TO PRIMARY ───────────────────────────────────────────
108
+ function connectToPrimary() {
109
+ const url = `ws://127.0.0.1:${PORT}${TOKEN ? `?token=${TOKEN}&relay=1` : '?relay=1'}`;
110
+
111
+ try {
112
+ primarySocket = new WebSocket(url);
113
+ } catch (e) {
114
+ primarySocket = null;
115
+ scheduleBecomePrimary();
116
+ return;
117
+ }
118
+
119
+ primarySocket.on('open', () => {
120
+ process.stderr.write('[SessionMCP Relay] Connected to primary relay\n');
121
+ });
122
+
123
+ primarySocket.on('message', (data) => {
124
+ try {
125
+ const msg = JSON.parse(data.toString());
126
+ resolveRequest(msg);
127
+ } catch (e) {
128
+ process.stderr.write(`[SessionMCP Relay] Bad message from primary: ${e}\n`);
129
+ }
130
+ });
131
+
132
+ primarySocket.on('close', () => {
133
+ primarySocket = null;
134
+ process.stderr.write('[SessionMCP Relay] Primary relay gone — attempting takeover\n');
135
+ scheduleBecomePrimary();
136
+ });
137
+
138
+ primarySocket.on('error', () => {
139
+ primarySocket = null;
140
+ scheduleBecomePrimary();
141
+ });
142
+ }
143
+
144
+ // Jitter prevents thundering herd when multiple secondaries race to become primary
145
+ function scheduleBecomePrimary() {
146
+ const delay = 200 + Math.floor(Math.random() * 400);
147
+ setTimeout(() => {
148
+ isPrimary = false;
149
+ startServer();
150
+ }, delay);
151
+ }
152
+
153
+ // ── SHARED: REQUEST RESOLUTION ───────────────────────────────────────────────
154
+ function resolveRequest(msg) {
155
+ if (!msg.id || !pendingRequests.has(msg.id)) return;
156
+ const { resolve, reject, timer } = pendingRequests.get(msg.id);
157
+ clearTimeout(timer);
158
+ pendingRequests.delete(msg.id);
159
+ if (msg.error) reject(new Error(msg.error));
160
+ else resolve(msg.result);
161
+ }
162
+
163
+ // ── SHARED: SEND TOOL CALL ───────────────────────────────────────────────────
164
+ function sendToExtension(method, params) {
165
+ return new Promise((resolve, reject) => {
166
+ let target;
167
+
168
+ if (isPrimary) {
169
+ if (!extensionSocket || extensionSocket.readyState !== WebSocket.OPEN) {
170
+ reject(new Error('SessionMCP extension not connected. Is Chrome open with the SessionMCP extension installed?'));
171
+ return;
172
+ }
173
+ target = extensionSocket;
174
+ } else {
175
+ if (!primarySocket || primarySocket.readyState !== WebSocket.OPEN) {
176
+ reject(new Error('SessionMCP not connected to primary relay. Retry in a moment.'));
177
+ return;
178
+ }
179
+ target = primarySocket;
180
+ }
181
+
182
+ const id = nextId++;
183
+ const timer = setTimeout(() => {
184
+ if (pendingRequests.has(id)) {
185
+ pendingRequests.delete(id);
186
+ reject(new Error(`Extension command timed out after 30s`));
187
+ }
188
+ }, 30000);
189
+
190
+ pendingRequests.set(id, { resolve, reject, timer });
191
+ target.send(JSON.stringify({ id, method, params }));
192
+ });
193
+ }
194
+
195
+ // ── MCP STDIO SERVER ─────────────────────────────────────────────────────────
196
+ const TOOLS = [
197
+ {
198
+ name: 'tabs_context',
199
+ description: 'List all open Chrome tabs. Call this first to get tabId values for other tools.',
200
+ inputSchema: { type: 'object', properties: {}, required: [] }
201
+ },
202
+ {
203
+ name: 'navigate',
204
+ description: 'Navigate a Chrome tab to a URL.',
205
+ inputSchema: {
206
+ type: 'object',
207
+ properties: {
208
+ tabId: { type: 'number', description: 'Tab ID from tabs_context' },
209
+ url: { type: 'string', description: 'URL to navigate to' }
210
+ },
211
+ required: ['tabId', 'url']
212
+ }
213
+ },
214
+ {
215
+ name: 'screenshot',
216
+ description: 'Take a screenshot of a Chrome tab. Returns base64 PNG. Optionally clip to a specific region.',
217
+ inputSchema: {
218
+ type: 'object',
219
+ properties: {
220
+ tabId: { type: 'number', description: 'Tab ID from tabs_context' },
221
+ clip: {
222
+ type: 'object',
223
+ description: 'Optional crop region in CSS pixels',
224
+ properties: {
225
+ x: { type: 'number' },
226
+ y: { type: 'number' },
227
+ width: { type: 'number' },
228
+ height: { type: 'number' }
229
+ },
230
+ required: ['x', 'y', 'width', 'height']
231
+ }
232
+ },
233
+ required: ['tabId']
234
+ }
235
+ },
236
+ {
237
+ name: 'click',
238
+ description: 'Click at coordinates in a Chrome tab.',
239
+ inputSchema: {
240
+ type: 'object',
241
+ properties: {
242
+ tabId: { type: 'number' },
243
+ x: { type: 'number', description: 'X coordinate in CSS pixels' },
244
+ y: { type: 'number', description: 'Y coordinate in CSS pixels' }
245
+ },
246
+ required: ['tabId', 'x', 'y']
247
+ }
248
+ },
249
+ {
250
+ name: 'type_text',
251
+ description: 'Type text into the focused element in a Chrome tab.',
252
+ inputSchema: {
253
+ type: 'object',
254
+ properties: {
255
+ tabId: { type: 'number' },
256
+ text: { type: 'string', description: 'Text to type' }
257
+ },
258
+ required: ['tabId', 'text']
259
+ }
260
+ },
261
+ {
262
+ name: 'evaluate',
263
+ description: 'Execute JavaScript in a Chrome tab and return the result.',
264
+ inputSchema: {
265
+ type: 'object',
266
+ properties: {
267
+ tabId: { type: 'number' },
268
+ expression: { type: 'string', description: 'JavaScript expression to evaluate. No return statements.' }
269
+ },
270
+ required: ['tabId', 'expression']
271
+ }
272
+ },
273
+ {
274
+ name: 'get_page_text',
275
+ description: 'Get the visible text content of a Chrome tab.',
276
+ inputSchema: {
277
+ type: 'object',
278
+ properties: {
279
+ tabId: { type: 'number' }
280
+ },
281
+ required: ['tabId']
282
+ }
283
+ },
284
+ {
285
+ name: 'scroll',
286
+ description: 'Scroll a Chrome tab by pixels.',
287
+ inputSchema: {
288
+ type: 'object',
289
+ properties: {
290
+ tabId: { type: 'number' },
291
+ deltaY: { type: 'number', description: 'Pixels to scroll (positive = down, negative = up)' },
292
+ x: { type: 'number', description: 'X coordinate (default: center)' },
293
+ y: { type: 'number', description: 'Y coordinate (default: center)' }
294
+ },
295
+ required: ['tabId', 'deltaY']
296
+ }
297
+ },
298
+ {
299
+ name: 'get_console_logs',
300
+ description: 'Get browser console log messages from a Chrome tab.',
301
+ inputSchema: {
302
+ type: 'object',
303
+ properties: {
304
+ tabId: { type: 'number' },
305
+ limit: { type: 'number', description: 'Max messages to return (default: 50)' }
306
+ },
307
+ required: ['tabId']
308
+ }
309
+ },
310
+ {
311
+ name: 'press_key',
312
+ description: 'Press a keyboard key or shortcut in a Chrome tab. Supports named keys (Enter, Tab, Escape, Backspace, Delete, ArrowUp, ArrowDown, ArrowLeft, ArrowRight, Home, End, PageUp, PageDown, F5, Space) and modifier combos like Ctrl+A, Ctrl+C, Ctrl+V, Ctrl+Z, Shift+Tab.',
313
+ inputSchema: {
314
+ type: 'object',
315
+ properties: {
316
+ tabId: { type: 'number' },
317
+ key: { type: 'string', description: 'Key name or combo, e.g. "Enter", "Tab", "Escape", "Ctrl+A", "Shift+Tab"' }
318
+ },
319
+ required: ['tabId', 'key']
320
+ }
321
+ },
322
+ {
323
+ name: 'click_element',
324
+ description: 'Click a page element by CSS selector. More reliable than click() with coordinates — finds the element, computes its center, and clicks it. Use get_dom_snapshot first to discover selectors.',
325
+ inputSchema: {
326
+ type: 'object',
327
+ properties: {
328
+ tabId: { type: 'number' },
329
+ selector: { type: 'string', description: 'CSS selector, e.g. "#submit-btn", "button[type=submit]", ".nav-link:first-child"' }
330
+ },
331
+ required: ['tabId', 'selector']
332
+ }
333
+ },
334
+ {
335
+ name: 'get_dom_snapshot',
336
+ description: 'Get a structured map of all interactive elements on the page (buttons, inputs, links, selects) with their text, CSS selectors, and x/y coordinates. Call this before click_element to discover what is on the page.',
337
+ inputSchema: {
338
+ type: 'object',
339
+ properties: {
340
+ tabId: { type: 'number' }
341
+ },
342
+ required: ['tabId']
343
+ }
344
+ },
345
+ {
346
+ name: 'wait_for',
347
+ description: 'Wait until a condition is met in a Chrome tab. Polls every 300ms up to timeout. Use selector to wait for an element to appear, selector_hidden for it to disappear, text to wait for text, or network_idle to wait for requests to settle.',
348
+ inputSchema: {
349
+ type: 'object',
350
+ properties: {
351
+ tabId: { type: 'number' },
352
+ selector: { type: 'string', description: 'CSS selector — wait until visible' },
353
+ selector_hidden: { type: 'string', description: 'CSS selector — wait until gone/hidden' },
354
+ text: { type: 'string', description: 'Text — wait until present in body' },
355
+ network_idle: { type: 'boolean', description: 'Wait for network activity to settle (500ms heuristic)' },
356
+ timeout: { type: 'number', description: 'Max wait in ms (default 10000)' }
357
+ },
358
+ required: ['tabId']
359
+ }
360
+ },
361
+ {
362
+ name: 'fill_form',
363
+ description: 'Fill a form field by CSS selector. Works with input, textarea, select, checkbox, and radio. Dispatches native input/change events so React, Angular, and Vue frameworks detect the change.',
364
+ inputSchema: {
365
+ type: 'object',
366
+ properties: {
367
+ tabId: { type: 'number' },
368
+ selector: { type: 'string', description: 'CSS selector for the field, e.g. "#email", "input[name=username]"' },
369
+ value: { description: 'Value to set. String for text/select, true/false for checkbox/radio.' }
370
+ },
371
+ required: ['tabId', 'selector', 'value']
372
+ }
373
+ },
374
+ {
375
+ name: 'network_requests',
376
+ description: 'Return the last N completed network requests (XHR, Fetch, etc.) captured since the debugger attached to this tab.',
377
+ inputSchema: {
378
+ type: 'object',
379
+ properties: {
380
+ tabId: { type: 'number' },
381
+ limit: { type: 'number', description: 'Max requests to return (default 20)' }
382
+ },
383
+ required: ['tabId']
384
+ }
385
+ },
386
+ {
387
+ name: 'handle_dialog',
388
+ description: 'Accept or dismiss a JavaScript dialog (alert, confirm, prompt) in a Chrome tab.',
389
+ inputSchema: {
390
+ type: 'object',
391
+ properties: {
392
+ tabId: { type: 'number' },
393
+ accept: { type: 'boolean', description: 'true to accept/OK, false to dismiss/Cancel (default true)' },
394
+ promptText: { type: 'string', description: 'Text to fill in for prompt dialogs' }
395
+ },
396
+ required: ['tabId']
397
+ }
398
+ },
399
+ {
400
+ name: 'hover',
401
+ description: 'Move the mouse to coordinates in a Chrome tab (triggers hover/tooltip effects).',
402
+ inputSchema: {
403
+ type: 'object',
404
+ properties: {
405
+ tabId: { type: 'number' },
406
+ x: { type: 'number', description: 'X coordinate in CSS pixels' },
407
+ y: { type: 'number', description: 'Y coordinate in CSS pixels' }
408
+ },
409
+ required: ['tabId', 'x', 'y']
410
+ }
411
+ }
412
+ ];
413
+
414
+ async function handleMcpRequest(req) {
415
+ const { id, method, params = {} } = req;
416
+
417
+ if (method === 'initialize') {
418
+ return {
419
+ jsonrpc: '2.0',
420
+ id,
421
+ result: {
422
+ protocolVersion: '2024-11-05',
423
+ capabilities: { tools: {} },
424
+ serverInfo: { name: 'sessionmcp', version: '0.1.0' }
425
+ }
426
+ };
427
+ }
428
+
429
+ if (method === 'tools/list') {
430
+ return { jsonrpc: '2.0', id, result: { tools: TOOLS } };
431
+ }
432
+
433
+ if (method === 'tools/call') {
434
+ const { name, arguments: args = {} } = params;
435
+
436
+ try {
437
+ const result = await sendToExtension(name, args);
438
+ return {
439
+ jsonrpc: '2.0',
440
+ id,
441
+ result: {
442
+ content: [{ type: 'text', text: typeof result === 'string' ? result : JSON.stringify(result, null, 2) }]
443
+ }
444
+ };
445
+ } catch (err) {
446
+ return {
447
+ jsonrpc: '2.0',
448
+ id,
449
+ result: {
450
+ content: [{ type: 'text', text: `Error: ${err.message}` }],
451
+ isError: true
452
+ }
453
+ };
454
+ }
455
+ }
456
+
457
+ return { jsonrpc: '2.0', id, result: {} };
458
+ }
459
+
460
+ const rl = readline.createInterface({ input: process.stdin, crlfDelay: Infinity });
461
+ rl.on('line', async (line) => {
462
+ if (!line.trim()) return;
463
+ try {
464
+ const req = JSON.parse(line);
465
+ if (req.id === undefined || req.id === null) return;
466
+ const response = await handleMcpRequest(req);
467
+ process.stdout.write(JSON.stringify(response) + '\n');
468
+ } catch (e) {
469
+ process.stderr.write(`[SessionMCP Relay] Parse error: ${e}\n`);
470
+ }
471
+ });
472
+
473
+ rl.on('close', () => process.exit(0));
474
+
475
+ // Boot
476
+ startServer();
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "sessionmcp",
3
+ "version": "0.2.0",
4
+ "description": "Universal MCP browser bridge — connect Claude Code, Cursor, VS Code, Windsurf & Cline to your existing Chrome session. No new browser. Real sessions, real cookies.",
5
+ "bin": {
6
+ "sessionmcp": "./index.js"
7
+ },
8
+ "main": "index.js",
9
+ "files": [
10
+ "index.js",
11
+ "README.md"
12
+ ],
13
+ "dependencies": {
14
+ "ws": "^8.18.0"
15
+ },
16
+ "engines": { "node": ">=18" },
17
+ "keywords": [
18
+ "mcp",
19
+ "browser",
20
+ "chrome",
21
+ "claude-code",
22
+ "cursor",
23
+ "windsurf",
24
+ "cline",
25
+ "bridge",
26
+ "model-context-protocol"
27
+ ],
28
+ "author": "Ginquel Moreira <contact@limitless-gmtech.com> (https://limitless-gmtech.com)",
29
+ "license": "MIT",
30
+ "homepage": "https://sessionmcp.limitless-gmtech.com",
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "https://github.com/devGinMoreira/sessionmcp"
34
+ },
35
+ "bugs": {
36
+ "url": "https://github.com/devGinMoreira/sessionmcp/issues"
37
+ }
38
+ }