ghost-bridge 0.8.0 → 0.9.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.
package/README.md CHANGED
@@ -16,6 +16,7 @@ Most browser-capable AI tools start a separate browser. Ghost Bridge connects AI
16
16
  - Inspect page structure, text, screenshots, errors, and network traffic
17
17
  - Search and extract script sources, even in production bundles
18
18
  - Click, type, scroll, and submit forms on the current page
19
+ - Bind multiple Chrome tabs as named targets and operate them independently
19
20
  - Share one Chrome transport across multiple MCP clients
20
21
 
21
22
  ## Quick Start
@@ -94,6 +95,9 @@ Typical prompts:
94
95
  | `get_page_content` | Text, HTML, and structured DOM extraction |
95
96
  | `get_interactive_snapshot` | Find clickable and editable elements |
96
97
  | `dispatch_action` | Click, fill, press, scroll, hover, select |
98
+ | `bind_tab` | Bind a Chrome tab as a named target such as `cases` or `app` |
99
+ | `unbind_tab` | Remove a named target binding |
100
+ | `list_targets` | Show named targets and their per-tab session status |
97
101
  | `pin_current_tab` | Keep Ghost Bridge attached to the current tab while you browse elsewhere |
98
102
  | `pin_tab` | Pin a target tab by tab ID, URL fragment, or title fragment |
99
103
  | `unpin_tab` | Return to following the focused tab |
@@ -117,9 +121,19 @@ Recommended flow:
117
121
 
118
122
  Notes:
119
123
 
124
+ - Use `bind_tab` when a workflow spans multiple pages. For example, bind a checklist page as `cases` and a business page as `app`, then call tools with `target: "cases"` or `target: "app"`.
125
+ - All browser tools accept an optional `target` parameter. When named targets are bound, `dispatch_action` requires `target` so refs from one page are not accidentally used on another page.
120
126
  - Use `pin_current_tab` when you are debugging a page and need to switch to other tabs without changing the AI target. Use `unpin_tab` to restore the original follow-focused-tab behavior.
121
127
  - `list_network_requests` and `get_network_detail` automatically summarize `data:` URLs and very long URLs so inline images or oversized query strings do not overwhelm model context
122
128
 
129
+ Multi-page example:
130
+
131
+ ```text
132
+ Bind the current checklist tab as cases.
133
+ Bind the tab whose title contains "Orders" as app.
134
+ Read the next case from target cases, operate target app, then mark the case passed or failed back on target cases.
135
+ ```
136
+
123
137
  ## Configuration
124
138
 
125
139
  | Setting | Default | Notes |
@@ -135,7 +149,7 @@ flowchart LR
135
149
  A["AI Client<br/>Claude / Codex / Cursor"]
136
150
  B["Ghost Bridge MCP Server<br/>server.js"]
137
151
  C["Chrome Extension<br/>background.js"]
138
- D["Browser Tab<br/>Target Context"]
152
+ D["Browser Tabs<br/>Target Sessions"]
139
153
 
140
154
  A <-->|"stdio"| B
141
155
  B <-->|"WebSocket"| C
package/dist/server.js CHANGED
@@ -28549,6 +28549,8 @@ var actualPort = BASE_PORT;
28549
28549
  var isMainInstance = false;
28550
28550
  var pendingRequests = /* @__PURE__ */ new Map();
28551
28551
  var mcpClients = /* @__PURE__ */ new Set();
28552
+ var mcpClientSeq = 0;
28553
+ var LOCAL_CLIENT_ID = "local";
28552
28554
  function log(msg) {
28553
28555
  console.error(`[ghost-bridge] ${msg}`);
28554
28556
  }
@@ -28561,7 +28563,8 @@ function buildIdentityPayload() {
28561
28563
  port: actualPort,
28562
28564
  version: GHOST_BRIDGE_VERSION,
28563
28565
  serverPath: SERVER_ENTRY_PATH,
28564
- startedAt: SERVER_STARTED_AT
28566
+ startedAt: SERVER_STARTED_AT,
28567
+ capabilities: ["heartbeat"]
28565
28568
  };
28566
28569
  }
28567
28570
  function getServiceMismatch(probe) {
@@ -28769,8 +28772,14 @@ async function initWebSocketService() {
28769
28772
  return wss2;
28770
28773
  }
28771
28774
  var wss = await initWebSocketService();
28775
+ var wsPingInterval = null;
28776
+ var WS_PING_INTERVAL_MS = 3e4;
28772
28777
  if (wss) {
28773
28778
  wss.on("connection", (ws, req) => {
28779
+ ws.isAlive = true;
28780
+ ws.on("pong", () => {
28781
+ ws.isAlive = true;
28782
+ });
28774
28783
  const url2 = new URL(req.url || "/", "http://localhost");
28775
28784
  const token = url2.searchParams.get("token") || "";
28776
28785
  const role = url2.searchParams.get("role") || "";
@@ -28786,7 +28795,9 @@ if (wss) {
28786
28795
  }
28787
28796
  log(`\u8FDE\u63A5\u9A8C\u8BC1\u901A\u8FC7 (token: ${token})`);
28788
28797
  if (role === "mcp-client") {
28789
- log("\u{1F4E1} MCP \u5BA2\u6237\u7AEF\u5DF2\u8FDE\u63A5");
28798
+ const clientId = `c${++mcpClientSeq}`;
28799
+ ws.ghostClientId = clientId;
28800
+ log(`\u{1F4E1} MCP \u5BA2\u6237\u7AEF\u5DF2\u8FDE\u63A5 (${clientId})`);
28790
28801
  mcpClients.add(ws);
28791
28802
  ws.send(JSON.stringify(buildIdentityPayload()));
28792
28803
  ws.on("message", (data) => {
@@ -28814,15 +28825,26 @@ if (wss) {
28814
28825
  return;
28815
28826
  }
28816
28827
  if (msg.id) {
28817
- pendingRequests.set(msg.id, { source: ws });
28828
+ const wireId = `${clientId}:${msg.id}`;
28829
+ pendingRequests.set(wireId, { source: ws, originalId: msg.id });
28830
+ chromeConnection.send(JSON.stringify({
28831
+ id: wireId,
28832
+ command: msg.command,
28833
+ params: msg.params,
28834
+ clientId,
28835
+ ...WS_TOKEN ? { token: WS_TOKEN } : {}
28836
+ }));
28837
+ } else {
28838
+ chromeConnection.send(data);
28818
28839
  }
28819
- chromeConnection.send(data);
28820
28840
  } catch {
28821
28841
  }
28822
28842
  });
28823
28843
  ws.on("close", () => {
28824
- log("\u{1F4E1} MCP \u5BA2\u6237\u7AEF\u5DF2\u65AD\u5F00");
28844
+ log(`\u{1F4E1} MCP \u5BA2\u6237\u7AEF\u5DF2\u65AD\u5F00 (${ws.ghostClientId})`);
28825
28845
  mcpClients.delete(ws);
28846
+ askChrome("_clientDisconnected", { clientId: ws.ghostClientId }, { timeoutMs: 3e3 }).catch(() => {
28847
+ });
28826
28848
  });
28827
28849
  } else {
28828
28850
  if (chromeConnection && chromeConnection !== ws && chromeConnection.readyState === import_websocket.default.OPEN) {
@@ -28840,11 +28862,17 @@ if (wss) {
28840
28862
  ws.on("message", (data) => {
28841
28863
  try {
28842
28864
  const msg = JSON.parse(data.toString());
28865
+ if (msg.type === "heartbeat") {
28866
+ ws.send(JSON.stringify({ type: "heartbeat_ack", ts: msg.ts || Date.now() }));
28867
+ return;
28868
+ }
28843
28869
  if (msg.id && pendingRequests.has(msg.id)) {
28844
28870
  const pending = pendingRequests.get(msg.id);
28845
- if (pending.source && pending.source.readyState === import_websocket.default.OPEN) {
28871
+ if (pending.source) {
28846
28872
  pendingRequests.delete(msg.id);
28847
- pending.source.send(data);
28873
+ if (pending.source.readyState === import_websocket.default.OPEN) {
28874
+ pending.source.send(JSON.stringify({ ...msg, id: pending.originalId }));
28875
+ }
28848
28876
  return;
28849
28877
  }
28850
28878
  }
@@ -28852,14 +28880,38 @@ if (wss) {
28852
28880
  }
28853
28881
  handleIncoming(data);
28854
28882
  });
28855
- ws.on("close", () => {
28856
- log("\u{1F310} Chrome \u8FDE\u63A5\u5DF2\u5173\u95ED");
28883
+ ws.on("close", (code, reason) => {
28884
+ const reasonText = reason ? reason.toString() : "";
28885
+ log(`\u{1F310} Chrome \u8FDE\u63A5\u5DF2\u5173\u95ED (code=${code}${reasonText ? ` reason="${reasonText}"` : ""})`);
28886
+ if (chromeConnection !== ws) {
28887
+ log("\u5FFD\u7565\u65E7 Chrome \u8FDE\u63A5\u7684\u5173\u95ED\u4E8B\u4EF6");
28888
+ return;
28889
+ }
28857
28890
  chromeConnection = null;
28858
- activeConnection = null;
28891
+ if (activeConnection === ws) {
28892
+ activeConnection = null;
28893
+ }
28859
28894
  failAllPending("Chrome \u8FDE\u63A5\u65AD\u5F00");
28860
28895
  });
28896
+ ws.on("error", (err) => {
28897
+ log(`\u{1F310} Chrome \u8FDE\u63A5\u9519\u8BEF: ${err.message}`);
28898
+ });
28861
28899
  }
28862
28900
  });
28901
+ wsPingInterval = setInterval(() => {
28902
+ for (const client of wss.clients) {
28903
+ if (client.isAlive === false) {
28904
+ log("\u{1F310} \u5FC3\u8DF3\u8D85\u65F6\uFF0C\u4E3B\u52A8\u65AD\u5F00\u6B7B\u8FDE\u63A5");
28905
+ client.terminate();
28906
+ continue;
28907
+ }
28908
+ client.isAlive = false;
28909
+ try {
28910
+ client.ping();
28911
+ } catch {
28912
+ }
28913
+ }
28914
+ }, WS_PING_INTERVAL_MS);
28863
28915
  } else {
28864
28916
  log(`\u{1F4E1} \u4F5C\u4E3A\u5BA2\u6237\u7AEF\u8FDE\u63A5\u5230\u4E3B\u5B9E\u4F8B (\u7AEF\u53E3 ${actualPort})...`);
28865
28917
  connectToMainInstance();
@@ -28970,7 +29022,7 @@ async function askMainInstance(command, params = {}) {
28970
29022
  async function askChrome(command, params = {}, options = {}) {
28971
29023
  if (!activeConnection) throw new Error("Chrome \u672A\u8FDE\u63A5\uFF0C\u8BF7\u786E\u8BA4\u6D4F\u89C8\u5668\u5F00\u542F\u4E14\u6269\u5C55\u5DF2\u542F\u7528");
28972
29024
  const id = crypto.randomUUID();
28973
- const payload = { id, command, params };
29025
+ const payload = { id, command, params, clientId: LOCAL_CLIENT_ID };
28974
29026
  if (WS_TOKEN) payload.token = WS_TOKEN;
28975
29027
  const timeoutMs = options.timeoutMs || RESPONSE_TIMEOUT;
28976
29028
  return new Promise((resolve, reject) => {
@@ -28991,6 +29043,10 @@ async function askChrome(command, params = {}, options = {}) {
28991
29043
  function jsonText(data) {
28992
29044
  return typeof data === "string" ? data : JSON.stringify(data);
28993
29045
  }
29046
+ var TARGET_ARG = {
29047
+ type: "string",
29048
+ description: "\u547D\u540D\u6D4F\u89C8\u5668\u76EE\u6807\uFF0C\u5982 cases/app\u3002\u5148\u7528 bind_tab \u7ED1\u5B9A\uFF1B\u4E0D\u6307\u5B9A\u5219\u4F7F\u7528\u9ED8\u8BA4 focused/pinned \u76EE\u6807\u3002\u7ED1\u5B9A\u4E0E pin \u5747\u6309\u4F1A\u8BDD\u9694\u79BB"
29049
+ };
28994
29050
  function buildSnippet(source, line, column, { beautifyEnabled = true, contextLines = 20 } = {}) {
28995
29051
  const result = {};
28996
29052
  if (!source) {
@@ -29038,6 +29094,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
29038
29094
  inputSchema: {
29039
29095
  type: "object",
29040
29096
  properties: {
29097
+ target: TARGET_ARG,
29041
29098
  selector: {
29042
29099
  type: "string",
29043
29100
  description: "CSS \u9009\u62E9\u5668\uFF0C\u9650\u5B9A\u5206\u6790\u8303\u56F4\u3002\u4E0D\u6307\u5B9A\u5219\u5206\u6790\u6574\u4E2A\u9875\u9762"
@@ -29063,6 +29120,36 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
29063
29120
  description: "\u5217\u51FA\u5F53\u524D Chrome \u6807\u7B7E\u9875\uFF0C\u5E76\u8FD4\u56DE Ghost Bridge \u5F53\u524D\u76EE\u6807\u6A21\u5F0F\u4E0E\u76EE\u6807\u6807\u7B7E\u9875\u3002",
29064
29121
  inputSchema: { type: "object", properties: {} }
29065
29122
  },
29123
+ {
29124
+ name: "bind_tab",
29125
+ description: "\u6309 tabId\u3001URL \u7247\u6BB5\u6216\u6807\u9898\u7247\u6BB5\u628A Chrome \u6807\u7B7E\u9875\u7ED1\u5B9A\u4E3A\u547D\u540D target\uFF0C\u4F8B\u5982 cases \u6216 app\u3002target \u547D\u540D\u7A7A\u95F4\u6309 MCP \u4F1A\u8BDD\u9694\u79BB\uFF1A\u591A\u4E2A Agent / \u540C\u4E00 Agent \u7684\u591A\u4E2A\u4F1A\u8BDD\u5404\u81EA\u7ED1\u5B9A\u7684 target\uFF08\u542B\u540C\u540D\uFF09\u4E92\u4E0D\u5E72\u6270\u3002",
29126
+ inputSchema: {
29127
+ type: "object",
29128
+ properties: {
29129
+ name: { type: "string", description: "target \u540D\u79F0\uFF0C\u5982 cases/app\u3002\u53EA\u80FD\u5305\u542B\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u4E0B\u5212\u7EBF\u548C\u8FDE\u5B57\u7B26" },
29130
+ tabId: { type: "number", description: "Chrome \u6807\u7B7E\u9875 ID\uFF0C\u4F18\u5148\u4F7F\u7528" },
29131
+ urlContains: { type: "string", description: "URL \u7247\u6BB5\u3002\u672A\u63D0\u4F9B tabId \u65F6\u6309\u5B83\u5339\u914D" },
29132
+ titleContains: { type: "string", description: "\u6807\u9898\u7247\u6BB5\u3002\u53EF\u4E0E urlContains \u540C\u65F6\u4F7F\u7528" }
29133
+ },
29134
+ required: ["name"]
29135
+ }
29136
+ },
29137
+ {
29138
+ name: "unbind_tab",
29139
+ description: "\u89E3\u7ED1\u4E00\u4E2A\u547D\u540D target\u3002\u82E5\u8BE5 tab \u6CA1\u6709\u5176\u4ED6 target \u5F15\u7528\uFF0C\u4F1A\u91CA\u653E\u5BF9\u5E94 debugger session\u3002",
29140
+ inputSchema: {
29141
+ type: "object",
29142
+ properties: {
29143
+ name: { type: "string", description: "\u8981\u89E3\u7ED1\u7684 target \u540D\u79F0" }
29144
+ },
29145
+ required: ["name"]
29146
+ }
29147
+ },
29148
+ {
29149
+ name: "list_targets",
29150
+ description: "\u67E5\u770B\u5F53\u524D\u5DF2\u7ED1\u5B9A\u7684\u547D\u540D targets\uFF0C\u4EE5\u53CA\u9ED8\u8BA4 focused/pinned \u76EE\u6807\u72B6\u6001\u3002",
29151
+ inputSchema: { type: "object", properties: {} }
29152
+ },
29066
29153
  {
29067
29154
  name: "get_target_tab",
29068
29155
  description: "\u67E5\u770B Ghost Bridge \u5F53\u524D\u64CD\u4F5C\u76EE\u6807\u3002\u76EE\u6807\u6A21\u5F0F\u4E3A focused \u65F6\u8DDF\u968F\u5F53\u524D\u805A\u7126\u6807\u7B7E\u9875\uFF1Bpinned \u65F6\u9501\u5B9A\u6307\u5B9A\u6807\u7B7E\u9875\u3002",
@@ -29075,7 +29162,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
29075
29162
  },
29076
29163
  {
29077
29164
  name: "pin_tab",
29078
- description: "\u6309 tabId\u3001URL \u7247\u6BB5\u6216\u6807\u9898\u7247\u6BB5\u9501\u5B9A Chrome \u6807\u7B7E\u9875\u3002",
29165
+ description: "\u6309 tabId\u3001URL \u7247\u6BB5\u6216\u6807\u9898\u7247\u6BB5\u9501\u5B9A Chrome \u6807\u7B7E\u9875\u3002pin \u72B6\u6001\u6309 MCP \u4F1A\u8BDD\u9694\u79BB\uFF1A\u591A\u4E2A Agent / \u591A\u4E2A\u4F1A\u8BDD\u5404\u81EA pin \u7684\u9875\u9762\u4E92\u4E0D\u8986\u76D6\u3002",
29079
29166
  inputSchema: {
29080
29167
  type: "object",
29081
29168
  properties: {
@@ -29096,6 +29183,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
29096
29183
  inputSchema: {
29097
29184
  type: "object",
29098
29185
  properties: {
29186
+ target: TARGET_ARG,
29099
29187
  severity: {
29100
29188
  type: "string",
29101
29189
  enum: ["error", "warn", "info", "all"],
@@ -29114,6 +29202,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
29114
29202
  inputSchema: {
29115
29203
  type: "object",
29116
29204
  properties: {
29205
+ target: TARGET_ARG,
29117
29206
  scriptUrlContains: { type: "string" },
29118
29207
  line: { type: "number" },
29119
29208
  column: { type: "number" },
@@ -29128,6 +29217,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
29128
29217
  inputSchema: {
29129
29218
  type: "object",
29130
29219
  properties: {
29220
+ target: TARGET_ARG,
29131
29221
  durationMs: { type: "number", description: "\u9ED8\u8BA4 1500ms" }
29132
29222
  }
29133
29223
  }
@@ -29138,6 +29228,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
29138
29228
  inputSchema: {
29139
29229
  type: "object",
29140
29230
  properties: {
29231
+ target: TARGET_ARG,
29141
29232
  query: { type: "string" },
29142
29233
  scriptUrlContains: { type: "string" },
29143
29234
  maxMatches: { type: "number" }
@@ -29148,14 +29239,14 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
29148
29239
  {
29149
29240
  name: "symbolic_hints",
29150
29241
  description: "\u6536\u96C6\u9875\u9762\u7684\u8D44\u6E90\u3001\u5168\u5C40\u7B26\u53F7\u4E0E UA/URL \u7EBF\u7D22\uFF0C\u5E2E\u52A9\u63A8\u65AD\u7248\u672C\u4E0E\u6A21\u5757\u5F52\u5C5E",
29151
- inputSchema: { type: "object", properties: {} }
29242
+ inputSchema: { type: "object", properties: { target: TARGET_ARG } }
29152
29243
  },
29153
29244
  {
29154
29245
  name: "eval_script",
29155
29246
  description: "\u5728\u76EE\u6807\u6807\u7B7E\u9875\u6267\u884C\u53EA\u8BFB JS \u8868\u8FBE\u5F0F\uFF08\u8C28\u614E\u4F7F\u7528\uFF09",
29156
29247
  inputSchema: {
29157
29248
  type: "object",
29158
- properties: { code: { type: "string" } },
29249
+ properties: { target: TARGET_ARG, code: { type: "string" } },
29159
29250
  required: ["code"]
29160
29251
  }
29161
29252
  },
@@ -29165,6 +29256,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
29165
29256
  inputSchema: {
29166
29257
  type: "object",
29167
29258
  properties: {
29259
+ target: TARGET_ARG,
29168
29260
  filter: { type: "string", description: "URL \u5173\u952E\u8BCD\u8FC7\u6EE4" },
29169
29261
  method: { type: "string", description: "\u8BF7\u6C42\u65B9\u6CD5\uFF1AGET/POST/PUT/DELETE \u7B49" },
29170
29262
  status: { type: "string", description: "\u72B6\u6001\uFF1Asuccess/error/failed/pending" },
@@ -29184,6 +29276,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
29184
29276
  inputSchema: {
29185
29277
  type: "object",
29186
29278
  properties: {
29279
+ target: TARGET_ARG,
29187
29280
  requestId: { type: "string", description: "\u8BF7\u6C42 ID\uFF08\u4ECE list_network_requests \u83B7\u53D6\uFF09" },
29188
29281
  includeBody: { type: "boolean", description: "\u662F\u5426\u5305\u542B\u54CD\u5E94\u4F53\uFF0C\u9ED8\u8BA4 false" }
29189
29282
  },
@@ -29193,7 +29286,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
29193
29286
  {
29194
29287
  name: "clear_network_requests",
29195
29288
  description: "\u6E05\u7A7A\u5DF2\u6355\u83B7\u7684\u7F51\u7EDC\u8BF7\u6C42\u8BB0\u5F55",
29196
- inputSchema: { type: "object", properties: {} }
29289
+ inputSchema: { type: "object", properties: { target: TARGET_ARG } }
29197
29290
  },
29198
29291
  {
29199
29292
  name: "perf_metrics",
@@ -29201,6 +29294,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
29201
29294
  inputSchema: {
29202
29295
  type: "object",
29203
29296
  properties: {
29297
+ target: TARGET_ARG,
29204
29298
  includeTimings: {
29205
29299
  type: "boolean",
29206
29300
  description: "\u662F\u5426\u5305\u542B Navigation Timing \u548C Web Vitals\uFF0C\u9ED8\u8BA4 true"
@@ -29218,6 +29312,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
29218
29312
  inputSchema: {
29219
29313
  type: "object",
29220
29314
  properties: {
29315
+ target: TARGET_ARG,
29221
29316
  format: {
29222
29317
  type: "string",
29223
29318
  enum: ["png", "jpeg"],
@@ -29250,6 +29345,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
29250
29345
  inputSchema: {
29251
29346
  type: "object",
29252
29347
  properties: {
29348
+ target: TARGET_ARG,
29253
29349
  mode: {
29254
29350
  type: "string",
29255
29351
  enum: ["text", "html", "structured"],
@@ -29276,6 +29372,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
29276
29372
  inputSchema: {
29277
29373
  type: "object",
29278
29374
  properties: {
29375
+ target: TARGET_ARG,
29279
29376
  selector: {
29280
29377
  type: "string",
29281
29378
  description: "CSS \u9009\u62E9\u5668\uFF0C\u9650\u5B9A\u626B\u63CF\u8303\u56F4\u3002\u4E0D\u6307\u5B9A\u5219\u626B\u63CF\u6574\u4E2A\u9875\u9762"
@@ -29297,6 +29394,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
29297
29394
  inputSchema: {
29298
29395
  type: "object",
29299
29396
  properties: {
29397
+ target: TARGET_ARG,
29300
29398
  ref: {
29301
29399
  type: "string",
29302
29400
  description: "\u76EE\u6807\u5143\u7D20\u7684 ref \u6807\u8BC6\uFF0C\u5982 'e1'\u3001'e5'\uFF08\u4ECE get_interactive_snapshot \u83B7\u53D6\uFF09"
@@ -29337,8 +29435,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
29337
29435
  const args = request.params.arguments || {};
29338
29436
  try {
29339
29437
  if (name === "inspect_page") {
29340
- const { selector, includeInteractive = true, maxElements = 30 } = args;
29438
+ const { target, selector, includeInteractive = true, maxElements = 30 } = args;
29341
29439
  const snapshot = await askChrome("inspectPageSnapshot", {
29440
+ target,
29342
29441
  selector,
29343
29442
  includeInteractive,
29344
29443
  maxElements
@@ -29421,6 +29520,20 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
29421
29520
  const res = await askChrome("listTabs");
29422
29521
  return { content: [{ type: "text", text: jsonText(res) }] };
29423
29522
  }
29523
+ if (name === "bind_tab") {
29524
+ const { name: targetName, tabId, urlContains, titleContains } = args;
29525
+ const res = await askChrome("bindTab", { name: targetName, tabId, urlContains, titleContains }, { timeoutMs: 1e4 });
29526
+ return { content: [{ type: "text", text: jsonText(res) }] };
29527
+ }
29528
+ if (name === "unbind_tab") {
29529
+ const { name: targetName } = args;
29530
+ const res = await askChrome("unbindTab", { name: targetName }, { timeoutMs: 1e4 });
29531
+ return { content: [{ type: "text", text: jsonText(res) }] };
29532
+ }
29533
+ if (name === "list_targets") {
29534
+ const res = await askChrome("listTargets");
29535
+ return { content: [{ type: "text", text: jsonText(res) }] };
29536
+ }
29424
29537
  if (name === "get_target_tab") {
29425
29538
  const res = await askChrome("getTargetTab");
29426
29539
  return { content: [{ type: "text", text: jsonText(res) }] };
@@ -29439,12 +29552,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
29439
29552
  return { content: [{ type: "text", text: jsonText(res) }] };
29440
29553
  }
29441
29554
  if (name === "get_last_error") {
29442
- const { severity = "error", limit = 20 } = args;
29443
- const data = await askChrome("getLastError", { severity, limit });
29555
+ const { target, severity = "error", limit = 20 } = args;
29556
+ const data = await askChrome("getLastError", { target, severity, limit });
29444
29557
  return { content: [{ type: "text", text: jsonText(data) }] };
29445
29558
  }
29446
29559
  if (name === "get_script_source") {
29447
29560
  const {
29561
+ target,
29448
29562
  scriptUrlContains,
29449
29563
  line,
29450
29564
  column,
@@ -29453,6 +29567,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
29453
29567
  } = args;
29454
29568
  const res = await askChrome("getScriptSource", {
29455
29569
  scriptUrlContains,
29570
+ target,
29456
29571
  line,
29457
29572
  column
29458
29573
  });
@@ -29479,45 +29594,46 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
29479
29594
  };
29480
29595
  }
29481
29596
  if (name === "coverage_snapshot") {
29597
+ const { target } = args;
29482
29598
  const durationMs = args.durationMs || 1500;
29483
- const res = await askChrome("coverageSnapshot", { durationMs }, { timeoutMs: durationMs + 4e3 });
29599
+ const res = await askChrome("coverageSnapshot", { target, durationMs }, { timeoutMs: durationMs + 4e3 });
29484
29600
  return { content: [{ type: "text", text: jsonText(res) }] };
29485
29601
  }
29486
29602
  if (name === "find_by_string") {
29487
- const { query, scriptUrlContains, maxMatches = 5 } = args;
29488
- const res = await askChrome("findByString", { query, scriptUrlContains, maxMatches });
29603
+ const { target, query, scriptUrlContains, maxMatches = 5 } = args;
29604
+ const res = await askChrome("findByString", { target, query, scriptUrlContains, maxMatches });
29489
29605
  return { content: [{ type: "text", text: jsonText(res) }] };
29490
29606
  }
29491
29607
  if (name === "symbolic_hints") {
29492
- const res = await askChrome("symbolicHints");
29608
+ const res = await askChrome("symbolicHints", { target: args.target });
29493
29609
  return { content: [{ type: "text", text: jsonText(res) }] };
29494
29610
  }
29495
29611
  if (name === "eval_script") {
29496
- const res = await askChrome("eval", { code: args.code });
29612
+ const res = await askChrome("eval", { target: args.target, code: args.code });
29497
29613
  return { content: [{ type: "text", text: jsonText(res) }] };
29498
29614
  }
29499
29615
  if (name === "list_network_requests") {
29500
- const { filter, method, status, resourceType, limit, priorityMode = "debug" } = args;
29501
- const res = await askChrome("listNetworkRequests", { filter, method, status, resourceType, limit, priorityMode });
29616
+ const { target, filter, method, status, resourceType, limit, priorityMode = "debug" } = args;
29617
+ const res = await askChrome("listNetworkRequests", { target, filter, method, status, resourceType, limit, priorityMode });
29502
29618
  return { content: [{ type: "text", text: jsonText(res) }] };
29503
29619
  }
29504
29620
  if (name === "get_network_detail") {
29505
- const { requestId, includeBody } = args;
29506
- const res = await askChrome("getNetworkDetail", { requestId, includeBody });
29621
+ const { target, requestId, includeBody } = args;
29622
+ const res = await askChrome("getNetworkDetail", { target, requestId, includeBody });
29507
29623
  return { content: [{ type: "text", text: jsonText(res) }] };
29508
29624
  }
29509
29625
  if (name === "clear_network_requests") {
29510
- const res = await askChrome("clearNetworkRequests");
29626
+ const res = await askChrome("clearNetworkRequests", { target: args.target });
29511
29627
  return { content: [{ type: "text", text: jsonText(res) }] };
29512
29628
  }
29513
29629
  if (name === "perf_metrics") {
29514
- const { includeTimings, includeResources } = args;
29515
- const res = await askChrome("perfMetrics", { includeTimings, includeResources });
29630
+ const { target, includeTimings, includeResources } = args;
29631
+ const res = await askChrome("perfMetrics", { target, includeTimings, includeResources });
29516
29632
  return { content: [{ type: "text", text: jsonText(res) }] };
29517
29633
  }
29518
29634
  if (name === "capture_screenshot") {
29519
- const { format, quality, fullPage, clip } = args;
29520
- const res = await askChrome("captureScreenshot", { format, quality, fullPage, clip }, { timeoutMs: 15e3 });
29635
+ const { target, format, quality, fullPage, clip } = args;
29636
+ const res = await askChrome("captureScreenshot", { target, format, quality, fullPage, clip }, { timeoutMs: 15e3 });
29521
29637
  const contents = [];
29522
29638
  if (res.imageData) {
29523
29639
  contents.push({
@@ -29541,7 +29657,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
29541
29657
  return { content: contents };
29542
29658
  }
29543
29659
  if (name === "get_page_content") {
29544
- const { mode = "text", selector, maxLength = 5e4, includeMetadata = true } = args;
29660
+ const { target, mode = "text", selector, maxLength = 5e4, includeMetadata = true } = args;
29545
29661
  const validModes = ["text", "html", "structured"];
29546
29662
  if (mode && !validModes.includes(mode)) {
29547
29663
  return {
@@ -29551,17 +29667,17 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
29551
29667
  }]
29552
29668
  };
29553
29669
  }
29554
- const res = await askChrome("getPageContent", { mode, selector, maxLength, includeMetadata });
29670
+ const res = await askChrome("getPageContent", { target, mode, selector, maxLength, includeMetadata });
29555
29671
  return { content: [{ type: "text", text: jsonText(res) }] };
29556
29672
  }
29557
29673
  if (name === "get_interactive_snapshot") {
29558
- const { selector, includeText, maxElements } = args;
29559
- const res = await askChrome("getInteractiveSnapshot", { selector, includeText, maxElements });
29674
+ const { target, selector, includeText, maxElements } = args;
29675
+ const res = await askChrome("getInteractiveSnapshot", { target, selector, includeText, maxElements });
29560
29676
  return { content: [{ type: "text", text: jsonText(res) }] };
29561
29677
  }
29562
29678
  if (name === "dispatch_action") {
29563
- const { ref, action, value, key, deltaX, deltaY, waitMs } = args;
29564
- const res = await askChrome("dispatchAction", { ref, action, value, key, deltaX, deltaY, waitMs }, { timeoutMs: 1e4 });
29679
+ const { target, ref, action, value, key, deltaX, deltaY, waitMs } = args;
29680
+ const res = await askChrome("dispatchAction", { target, ref, action, value, key, deltaX, deltaY, waitMs }, { timeoutMs: 1e4 });
29565
29681
  return { content: [{ type: "text", text: jsonText(res) }] };
29566
29682
  }
29567
29683
  return { content: [{ type: "text", text: `\u672A\u77E5\u5DE5\u5177\uFF1A${name}` }] };
@@ -29612,6 +29728,10 @@ function cleanup() {
29612
29728
  } catch (e) {
29613
29729
  log(`\u6E05\u7406\u7AEF\u53E3\u4FE1\u606F\u6587\u4EF6\u5931\u8D25: ${e.message}`);
29614
29730
  }
29731
+ if (wsPingInterval) {
29732
+ clearInterval(wsPingInterval);
29733
+ wsPingInterval = null;
29734
+ }
29615
29735
  if (wss) {
29616
29736
  wss.close(() => {
29617
29737
  log("\u{1F50C} WebSocket \u670D\u52A1\u5668\u5DF2\u5173\u95ED");