poltertab 1.6.0-beta.2 → 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 +25 -8
- package/mcp-server/bridge.js +26 -6
- package/mcp-server/tools.js +79 -4
- package/package.json +1 -1
- package/skills/browser-navigation-strategy/SKILL.md +15 -8
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
|
|
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
|
|
90
|
-
|
|
91
|
-
|
|
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` | — |
|
|
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.
|
|
228
|
-
|
|
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
|
|
package/mcp-server/bridge.js
CHANGED
|
@@ -301,7 +301,11 @@ function setupPrimaryWss(wss) {
|
|
|
301
301
|
}
|
|
302
302
|
|
|
303
303
|
if (msg.type === "proxy_command") {
|
|
304
|
-
const { id, action
|
|
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
|
-
//
|
|
541
|
-
|
|
542
|
-
|
|
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
|
-
|
|
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
|
};
|
package/mcp-server/tools.js
CHANGED
|
@@ -10,12 +10,13 @@
|
|
|
10
10
|
const TARGET = {
|
|
11
11
|
tabId: {
|
|
12
12
|
type: "number",
|
|
13
|
-
description:
|
|
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.
|
|
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:
|
|
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:
|
|
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
|
@@ -197,14 +197,21 @@ skip the discovery entirely next time.
|
|
|
197
197
|
|
|
198
198
|
## Multiple tabs
|
|
199
199
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
`
|
|
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
|
|