poltertab 1.6.0 → 1.7.0-beta.1

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 CHANGED
@@ -84,11 +84,12 @@ Running from a clone instead of npm? Point the client at the file directly:
84
84
 
85
85
  ## Supported MCP Tools
86
86
 
87
- The server exposes 23 tools to the LLM (all prefixed with `browser_`). Every
87
+ The server exposes 27 tools to the LLM (all prefixed with `browser_`). Every
88
88
  page-facing tool also takes optional `tabId` and `session` to target a specific
89
- tab — omit both and PolterTab uses the last tab it navigated. The first
90
- `browser_navigate` of a session opens its own tab rather than commandeering the
91
- one you are looking at.
89
+ tab — omit both and PolterTab uses the agent's current tab: the last one it
90
+ navigated, opened or switched to. The first `browser_navigate` (and the first
91
+ `browser_navigate` of each new session) opens its own tab rather than
92
+ commandeering the one you are looking at.
92
93
 
93
94
  ### Page actions
94
95
 
@@ -192,11 +193,26 @@ next use.
192
193
 
193
194
  | Action | Parameters | Description |
194
195
  | -------- | ----------- | ------------- |
195
- | `browser_session_create` | `name` (required), `url` | Create a session, or bind one to the active tab |
196
+ | `browser_session_create` | `name` (required), `url`, `tabId` | Create a session, adopt an open tab, or bind one to the active tab |
196
197
  | `browser_session_switch` | `name` (required) | Make a session active, recovering its tab if closed |
197
198
  | `browser_session_list` | — | All sessions, each `alive`, `recoverable`, or `expired` |
198
199
  | `browser_session_close` | `name` (required) | Close a session and its tab |
199
- | `browser_session_context` | — | Active session's tab, URL and title |
200
+ | `browser_session_context` | — | The current tab's id, URL, title and session, or `closedTabId` |
201
+
202
+ ### Tabs
203
+
204
+ One agent can drive many tabs. Each agent (and each secondary server) has its
205
+ own **current tab**; page-facing tools without `tabId`/`session` act there. An
206
+ explicit `tabId` acts on that tab without changing the current one. If the
207
+ current tab is closed, untargeted calls fail with a message naming the closed
208
+ tab instead of quietly acting on another one.
209
+
210
+ | Action | Parameters | Description |
211
+ | -------- | ----------- | ------------- |
212
+ | `browser_tab_new` | `url`, `session`, `background` | Open a tab and make it current |
213
+ | `browser_tab_list` | `all` | PolterTab's tabs (or every tab), marking the current one |
214
+ | `browser_tab_switch` | `tabId` or `session`, `focus` | Make another tab current |
215
+ | `browser_tab_close` | `tabId` or `session`, `force` | Close a tab (default: current). Tabs not opened by PolterTab (including ones adopted via `browser_session_create({tabId})`) return `needs_user_permission` — ask the user, then retry with `force: true`. A tab that does not exist in Chrome returns `tab_not_found`. |
200
216
 
201
217
  ### Site memory
202
218
 
@@ -224,8 +240,9 @@ exists inside a web component still resolves.
224
240
  The first MCP server to bind port `7822` becomes the **primary** and owns the
225
241
  connection to the extension. Later servers find the port taken and switch
226
242
  themselves into **secondary** mode, proxying their commands through the primary,
227
- so concurrent agents share one browser with no extra configuration. Each
228
- secondary auto-injects a per-agent `session` so agents get their own tabs. Up to
243
+ so concurrent agents share one browser with no extra configuration. The
244
+ primary tags each secondary's commands with its id, and the extension keeps a
245
+ separate current tab per agent, so agents get their own tabs. Up to
229
246
  5 secondaries are accepted, and if the primary exits one of them promotes itself
230
247
  to replace it. The extension popup lists whichever agents are connected.
231
248
 
@@ -301,7 +301,11 @@ function setupPrimaryWss(wss) {
301
301
  }
302
302
 
303
303
  if (msg.type === "proxy_command") {
304
- const { id, action, params } = msg;
304
+ const { id, action } = msg;
305
+ // Stamp which agent is asking, from the socket rather than the
306
+ // message, so the extension keeps this Secondary's current tab apart
307
+ // from the Primary's and every other Secondary's.
308
+ const params = { ...(msg.params || {}), _client: ws.nodeId };
305
309
  if (params && params.tabId) {
306
310
  ws.lastTabId = params.tabId;
307
311
  broadcastSessionState();
@@ -528,6 +532,20 @@ function awaitReply(envelope, { timedOut, sendFailed }) {
528
532
  });
529
533
  }
530
534
 
535
+ // Build the envelope that goes to the extension. Strips reserved keys (_client,
536
+ // id, action) from caller-supplied params so a tool argument named "_client"
537
+ // cannot spoof the routing identity, and stamps activeNodeId as _client when
538
+ // the caller is a promoted Secondary that still holds a node UUID.
539
+ function _buildEnvelope(action, params, activeNodeId) {
540
+ const { _client: _c, id: _i, action: _a, ...safeParams } = params || {};
541
+ return {
542
+ id: crypto.randomUUID(),
543
+ action,
544
+ ...safeParams,
545
+ ...(activeNodeId ? { _client: activeNodeId } : {}),
546
+ };
547
+ }
548
+
531
549
  // Helper to send command to the extension
532
550
  async function sendCommand(action, params) {
533
551
  const isConnected = await waitForConnection(5000);
@@ -537,10 +555,11 @@ async function sendCommand(action, params) {
537
555
  throw new Error("Secondary node not connected to Primary MCP server.");
538
556
  }
539
557
 
540
- // Auto-inject a unique session to prevent collisions over shared tabs
541
- if (!params.session) {
542
- params.session = "agent_" + nodeId;
543
- }
558
+ // No session is injected any more. It used to be ("agent_<nodeId>"), which
559
+ // overrode every browser_tab_switch this agent made, and an unknown session
560
+ // fell through to the Primary's active tab anyway. The Primary now stamps
561
+ // this node's id as _client and the extension keeps a current tab per
562
+ // client, so a Secondary's first navigate opens its own tab.
544
563
 
545
564
  return awaitReply(
546
565
  { type: "proxy_command", id: crypto.randomUUID(), action, params },
@@ -559,7 +578,7 @@ async function sendCommand(action, params) {
559
578
  }
560
579
 
561
580
  return awaitReply(
562
- { id: crypto.randomUUID(), action, ...params },
581
+ _buildEnvelope(action, params, nodeId),
563
582
  {
564
583
  timedOut: `Command '${action}' timed out after ${COMMAND_TIMEOUT_MS / 1000} seconds.`,
565
584
  sendFailed: (e) => `Failed to send command over WebSocket: ${e.message}`,
@@ -590,4 +609,5 @@ module.exports = {
590
609
  noteActiveTab,
591
610
  isSecondary: () => isSecondary,
592
611
  extensionVersion: () => extensionVersion,
612
+ _buildEnvelope,
593
613
  };
@@ -10,12 +10,13 @@
10
10
  const TARGET = {
11
11
  tabId: {
12
12
  type: "number",
13
- description: "Target a specific tab. Omit to use the last tab navigated.",
13
+ description:
14
+ "Act on this tab (an id from browser_tab_list) without making it current. Omit to use this agent's current tab: the last one navigated, opened or switched to.",
14
15
  },
15
16
  session: {
16
17
  type: "string",
17
18
  description:
18
- "Named session (tab) to run against. Created on first use by browser_navigate, so a loop can keep one tab instead of opening hundreds.",
19
+ "Named session (tab) to run against; it becomes the current tab. browser_navigate with a new name opens it its own tab, so a loop can keep one tab instead of opening hundreds. Other tools error on a name that does not exist yet.",
19
20
  },
20
21
  };
21
22
 
@@ -395,12 +396,17 @@ const BROWSER_TOOLS = [
395
396
  },
396
397
  {
397
398
  name: "browser_session_create",
398
- description: "Create or track a named browser session (tab)",
399
+ description:
400
+ "Create or track a named browser session (tab): opens `url` in a new tab, adopts `tabId`, or with neither binds the tab the user is looking at. It becomes the current tab.",
399
401
  inputSchema: {
400
402
  type: "object",
401
403
  properties: {
402
404
  name: { type: "string" },
403
405
  url: { type: "string" },
406
+ tabId: {
407
+ type: "number",
408
+ description: "Adopt this already-open tab instead of opening one",
409
+ },
404
410
  },
405
411
  required: ["name"],
406
412
  },
@@ -438,13 +444,82 @@ const BROWSER_TOOLS = [
438
444
  },
439
445
  {
440
446
  name: "browser_session_context",
441
- description: "Get context info for the current active session",
447
+ description:
448
+ "Get this agent's current tab: its id, URL, title and session name, or closedTabId if the tab it was on has been closed",
442
449
  inputSchema: {
443
450
  type: "object",
444
451
  properties: {},
445
452
  required: [],
446
453
  },
447
454
  },
455
+ // One agent, many tabs. Each agent (Primary or Secondary) has its own
456
+ // current tab; every page-facing tool without tabId/session acts there.
457
+ {
458
+ name: "browser_tab_new",
459
+ description:
460
+ "Open a new tab and make it this agent's current tab. Later calls without tabId/session act on it. Returns its tabId.",
461
+ inputSchema: {
462
+ type: "object",
463
+ properties: {
464
+ url: { type: "string", description: "Page to open; blank tab if omitted" },
465
+ session: {
466
+ type: "string",
467
+ description: "Also name the tab as a session, to come back to it by name",
468
+ },
469
+ background: {
470
+ type: "boolean",
471
+ description: "Open without stealing focus from the user's tab",
472
+ },
473
+ },
474
+ required: [],
475
+ },
476
+ },
477
+ {
478
+ name: "browser_tab_list",
479
+ description:
480
+ "List the tabs this agent can work with: every tab PolterTab opened or bound to a session, marking which one is current. all: true lists every open tab in the browser (ids, URLs and titles only).",
481
+ inputSchema: {
482
+ type: "object",
483
+ properties: {
484
+ all: { type: "boolean", description: "Include the user's other tabs" },
485
+ },
486
+ required: [],
487
+ },
488
+ },
489
+ {
490
+ name: "browser_tab_switch",
491
+ description:
492
+ "Make another tab this agent's current tab, by tabId or session name. Does not bring it to the front unless focus: true.",
493
+ inputSchema: {
494
+ type: "object",
495
+ properties: {
496
+ tabId: { type: "number" },
497
+ session: { type: "string" },
498
+ focus: {
499
+ type: "boolean",
500
+ description: "Also show the tab to the user",
501
+ },
502
+ },
503
+ required: [],
504
+ },
505
+ },
506
+ {
507
+ name: "browser_tab_close",
508
+ description:
509
+ "Close a tab by tabId or session name (default: the current tab). Tabs not opened by PolterTab — including ones adopted via browser_session_create({tabId}) — require the user's permission: the tool returns code needs_user_permission with the tab's id, title and url; ask the user, then retry with force: true. A tabId that does not exist in Chrome returns tab_not_found. Closing the current tab leaves no current tab; untargeted calls then error until you switch or navigate.",
510
+ inputSchema: {
511
+ type: "object",
512
+ properties: {
513
+ tabId: { type: "number" },
514
+ session: { type: "string" },
515
+ force: {
516
+ type: "boolean",
517
+ description: "Close a tab the user opened. Only when the user asked.",
518
+ },
519
+ },
520
+ required: [],
521
+ },
522
+ },
448
523
  {
449
524
  name: "browser_get_site_memory",
450
525
  description:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "poltertab",
3
- "version": "1.6.0",
3
+ "version": "1.7.0-beta.1",
4
4
  "description": "Let any AI assistant drive and scrape your existing Chrome profile — no headless browser, no complex setup.",
5
5
  "bin": {
6
6
  "poltertab": "./bin/poltertab.js"
@@ -197,14 +197,21 @@ skip the discovery entirely next time.
197
197
 
198
198
  ## Multiple tabs
199
199
 
200
- `browser_session_create` gives a named tab you can return to. Every page-facing
201
- tool takes `session` (or `tabId`).
202
-
203
- Omit both and the first `browser_navigate` opens its own tab — it does not
204
- commandeer the tab the user is looking at — and later calls reuse it. That is
205
- fine for single-track work and ambiguous the moment two flows interleave, so
206
- name your sessions when running more than one. Close what you opened with
207
- `browser_session_close`.
200
+ You have a **current tab**; every page-facing tool without `tabId`/`session`
201
+ acts on it. The first `browser_navigate` opens its own tab — it does not
202
+ commandeer the tab the user is looking at — and later calls reuse it.
203
+
204
+ For several tabs: `browser_tab_new` opens one and makes it current,
205
+ `browser_tab_list` shows them with their ids, `browser_tab_switch` changes the
206
+ current one, and `browser_tab_close` closes one. Pass `tabId` to act on a tab
207
+ once without switching to it, or name tabs with `session` and pass that. If a
208
+ call says your tab was closed, list and switch — do not assume another tab is
209
+ the same page. Close what you opened.
210
+
211
+ `browser_tab_close` refuses tabs that PolterTab did not open — including tabs
212
+ adopted via `browser_session_create({tabId})` — and returns `needs_user_permission`
213
+ with the tab's id, title and url. Ask the user for permission, then retry with
214
+ `force: true`. Closing a tabId that Chrome does not have returns `tab_not_found`.
208
215
 
209
216
  ## Pitfalls
210
217