@pyrokine/mcp-chrome 1.1.0 → 1.3.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 (91) hide show
  1. package/README.md +103 -53
  2. package/dist/anti-detection/behavior.d.ts +0 -8
  3. package/dist/anti-detection/behavior.d.ts.map +1 -1
  4. package/dist/anti-detection/behavior.js +0 -16
  5. package/dist/anti-detection/behavior.js.map +1 -1
  6. package/dist/cdp/client.d.ts +0 -2
  7. package/dist/cdp/client.d.ts.map +1 -1
  8. package/dist/cdp/client.js +30 -45
  9. package/dist/cdp/client.js.map +1 -1
  10. package/dist/cdp/launcher.d.ts +1 -8
  11. package/dist/cdp/launcher.d.ts.map +1 -1
  12. package/dist/cdp/launcher.js +4 -20
  13. package/dist/cdp/launcher.js.map +1 -1
  14. package/dist/core/auto-wait.d.ts +2 -2
  15. package/dist/core/auto-wait.d.ts.map +1 -1
  16. package/dist/core/auto-wait.js +1 -1
  17. package/dist/core/auto-wait.js.map +1 -1
  18. package/dist/core/errors.d.ts +10 -13
  19. package/dist/core/errors.d.ts.map +1 -1
  20. package/dist/core/errors.js +19 -25
  21. package/dist/core/errors.js.map +1 -1
  22. package/dist/core/locator.d.ts +6 -7
  23. package/dist/core/locator.d.ts.map +1 -1
  24. package/dist/core/locator.js +77 -31
  25. package/dist/core/locator.js.map +1 -1
  26. package/dist/core/retry.d.ts.map +1 -1
  27. package/dist/core/retry.js +1 -1
  28. package/dist/core/retry.js.map +1 -1
  29. package/dist/core/session.d.ts +32 -33
  30. package/dist/core/session.d.ts.map +1 -1
  31. package/dist/core/session.js +154 -114
  32. package/dist/core/session.js.map +1 -1
  33. package/dist/core/types.d.ts +4 -0
  34. package/dist/core/types.d.ts.map +1 -1
  35. package/dist/core/types.js +6 -0
  36. package/dist/core/types.js.map +1 -1
  37. package/dist/core/unified-session.d.ts +54 -67
  38. package/dist/core/unified-session.d.ts.map +1 -1
  39. package/dist/core/unified-session.js +215 -181
  40. package/dist/core/unified-session.js.map +1 -1
  41. package/dist/extension/bridge.d.ts +0 -19
  42. package/dist/extension/bridge.d.ts.map +1 -1
  43. package/dist/extension/bridge.js +6 -52
  44. package/dist/extension/bridge.js.map +1 -1
  45. package/dist/extension/http-server.d.ts +13 -11
  46. package/dist/extension/http-server.d.ts.map +1 -1
  47. package/dist/extension/http-server.js +101 -95
  48. package/dist/extension/http-server.js.map +1 -1
  49. package/dist/index.js +11 -64
  50. package/dist/index.js.map +1 -1
  51. package/dist/tools/browse.d.ts +3 -80
  52. package/dist/tools/browse.d.ts.map +1 -1
  53. package/dist/tools/browse.js +135 -291
  54. package/dist/tools/browse.js.map +1 -1
  55. package/dist/tools/cookies.d.ts +3 -71
  56. package/dist/tools/cookies.d.ts.map +1 -1
  57. package/dist/tools/cookies.js +75 -157
  58. package/dist/tools/cookies.js.map +1 -1
  59. package/dist/tools/evaluate.d.ts +3 -52
  60. package/dist/tools/evaluate.d.ts.map +1 -1
  61. package/dist/tools/evaluate.js +35 -86
  62. package/dist/tools/evaluate.js.map +1 -1
  63. package/dist/tools/extract.d.ts +3 -226
  64. package/dist/tools/extract.d.ts.map +1 -1
  65. package/dist/tools/extract.js +98 -170
  66. package/dist/tools/extract.js.map +1 -1
  67. package/dist/tools/index.d.ts +9 -9
  68. package/dist/tools/index.d.ts.map +1 -1
  69. package/dist/tools/index.js +9 -9
  70. package/dist/tools/index.js.map +1 -1
  71. package/dist/tools/input.d.ts +3 -258
  72. package/dist/tools/input.d.ts.map +1 -1
  73. package/dist/tools/input.js +56 -143
  74. package/dist/tools/input.js.map +1 -1
  75. package/dist/tools/logs.d.ts +3 -51
  76. package/dist/tools/logs.d.ts.map +1 -1
  77. package/dist/tools/logs.js +47 -108
  78. package/dist/tools/logs.js.map +1 -1
  79. package/dist/tools/manage.d.ts +3 -64
  80. package/dist/tools/manage.d.ts.map +1 -1
  81. package/dist/tools/manage.js +243 -373
  82. package/dist/tools/manage.js.map +1 -1
  83. package/dist/tools/schema.d.ts +16 -182
  84. package/dist/tools/schema.d.ts.map +1 -1
  85. package/dist/tools/schema.js +70 -159
  86. package/dist/tools/schema.js.map +1 -1
  87. package/dist/tools/wait.d.ts +3 -221
  88. package/dist/tools/wait.d.ts.map +1 -1
  89. package/dist/tools/wait.js +74 -145
  90. package/dist/tools/wait.js.map +1 -1
  91. package/package.json +1 -1
@@ -8,232 +8,9 @@
8
8
  * - screenshot: 截图
9
9
  * - state: 页面状态(精简的可交互元素列表)
10
10
  */
11
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
11
12
  /**
12
- * extract 工具定义
13
+ * 注册 extract 工具
13
14
  */
14
- export declare const extractToolDefinition: {
15
- name: string;
16
- description: string;
17
- inputSchema: {
18
- type: "object";
19
- properties: {
20
- type: {
21
- type: string;
22
- enum: string[];
23
- description: string;
24
- };
25
- target: {
26
- description: string;
27
- oneOf: readonly [{
28
- readonly type: "object";
29
- readonly title: "可访问性树定位";
30
- readonly description: "通过 ARIA role 和 name 定位元素";
31
- readonly properties: {
32
- readonly role: {
33
- readonly type: "string";
34
- readonly description: "ARIA role(如 button、link、textbox)";
35
- };
36
- readonly name: {
37
- readonly type: "string";
38
- readonly description: "可访问名称(可选)";
39
- };
40
- };
41
- readonly required: readonly ["role"];
42
- readonly additionalProperties: false;
43
- }, {
44
- readonly type: "object";
45
- readonly title: "文本内容定位";
46
- readonly description: "通过元素文本内容定位";
47
- readonly properties: {
48
- readonly text: {
49
- readonly type: "string";
50
- readonly description: "文本内容";
51
- };
52
- readonly exact: {
53
- readonly type: "boolean";
54
- readonly description: "是否精确匹配(默认 false)";
55
- };
56
- };
57
- readonly required: readonly ["text"];
58
- readonly additionalProperties: false;
59
- }, {
60
- readonly type: "object";
61
- readonly title: "Label 定位";
62
- readonly description: "通过表单 label 文本定位关联的输入元素";
63
- readonly properties: {
64
- readonly label: {
65
- readonly type: "string";
66
- readonly description: "label 文本";
67
- };
68
- readonly exact: {
69
- readonly type: "boolean";
70
- readonly description: "是否精确匹配(默认 false)";
71
- };
72
- };
73
- readonly required: readonly ["label"];
74
- readonly additionalProperties: false;
75
- }, {
76
- readonly type: "object";
77
- readonly title: "Placeholder 定位";
78
- readonly description: "通过输入框的 placeholder 属性定位";
79
- readonly properties: {
80
- readonly placeholder: {
81
- readonly type: "string";
82
- readonly description: "placeholder 文本";
83
- };
84
- readonly exact: {
85
- readonly type: "boolean";
86
- readonly description: "是否精确匹配(默认 false)";
87
- };
88
- };
89
- readonly required: readonly ["placeholder"];
90
- readonly additionalProperties: false;
91
- }, {
92
- readonly type: "object";
93
- readonly title: "Title 属性定位";
94
- readonly description: "通过元素的 title 属性定位";
95
- readonly properties: {
96
- readonly title: {
97
- readonly type: "string";
98
- readonly description: "title 属性值";
99
- };
100
- readonly exact: {
101
- readonly type: "boolean";
102
- readonly description: "是否精确匹配(默认 false)";
103
- };
104
- };
105
- readonly required: readonly ["title"];
106
- readonly additionalProperties: false;
107
- }, {
108
- readonly type: "object";
109
- readonly title: "Alt 属性定位";
110
- readonly description: "通过图片的 alt 属性定位";
111
- readonly properties: {
112
- readonly alt: {
113
- readonly type: "string";
114
- readonly description: "alt 属性值";
115
- };
116
- readonly exact: {
117
- readonly type: "boolean";
118
- readonly description: "是否精确匹配(默认 false)";
119
- };
120
- };
121
- readonly required: readonly ["alt"];
122
- readonly additionalProperties: false;
123
- }, {
124
- readonly type: "object";
125
- readonly title: "TestId 定位";
126
- readonly description: "通过 data-testid 属性定位";
127
- readonly properties: {
128
- readonly testId: {
129
- readonly type: "string";
130
- readonly description: "data-testid 值";
131
- };
132
- };
133
- readonly required: readonly ["testId"];
134
- readonly additionalProperties: false;
135
- }, {
136
- readonly type: "object";
137
- readonly title: "CSS 选择器定位";
138
- readonly description: "通过 CSS 选择器定位";
139
- readonly properties: {
140
- readonly css: {
141
- readonly type: "string";
142
- readonly description: "CSS 选择器";
143
- };
144
- };
145
- readonly required: readonly ["css"];
146
- readonly additionalProperties: false;
147
- }, {
148
- readonly type: "object";
149
- readonly title: "CSS + 文本组合定位";
150
- readonly description: "通过 CSS 选择器 + 文本内容组合定位(先 CSS 筛选,再按文本过滤)";
151
- readonly properties: {
152
- readonly css: {
153
- readonly type: "string";
154
- readonly description: "CSS 选择器";
155
- };
156
- readonly text: {
157
- readonly type: "string";
158
- readonly description: "文本内容";
159
- };
160
- readonly exact: {
161
- readonly type: "boolean";
162
- readonly description: "是否精确匹配(默认 false)";
163
- };
164
- };
165
- readonly required: readonly ["css", "text"];
166
- readonly additionalProperties: false;
167
- }, {
168
- readonly type: "object";
169
- readonly title: "XPath 定位";
170
- readonly description: "通过 XPath 表达式定位";
171
- readonly properties: {
172
- readonly xpath: {
173
- readonly type: "string";
174
- readonly description: "XPath 表达式";
175
- };
176
- };
177
- readonly required: readonly ["xpath"];
178
- readonly additionalProperties: false;
179
- }, {
180
- readonly type: "object";
181
- readonly title: "坐标定位";
182
- readonly description: "通过页面坐标定位";
183
- readonly properties: {
184
- readonly x: {
185
- readonly type: "number";
186
- readonly description: "X 坐标(像素)";
187
- };
188
- readonly y: {
189
- readonly type: "number";
190
- readonly description: "Y 坐标(像素)";
191
- };
192
- };
193
- readonly required: readonly ["x", "y"];
194
- readonly additionalProperties: false;
195
- }];
196
- };
197
- attribute: {
198
- type: string;
199
- description: string;
200
- };
201
- fullPage: {
202
- type: string;
203
- description: string;
204
- };
205
- output: {
206
- type: string;
207
- description: string;
208
- };
209
- tabId: {
210
- type: string;
211
- description: string;
212
- };
213
- timeout: {
214
- type: string;
215
- description: string;
216
- };
217
- frame: {
218
- oneOf: {
219
- type: string;
220
- }[];
221
- description: string;
222
- };
223
- };
224
- required: string[];
225
- };
226
- };
227
- /**
228
- * extract 工具处理器
229
- */
230
- export declare function handleExtract(params: unknown): Promise<{
231
- content: Array<{
232
- type: 'text' | 'image';
233
- text?: string;
234
- data?: string;
235
- mimeType?: string;
236
- }>;
237
- isError?: boolean;
238
- }>;
15
+ export declare function registerExtractTool(server: McpServer): void;
239
16
  //# sourceMappingURL=extract.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"extract.d.ts","sourceRoot":"","sources":["../../src/tools/extract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAQH;;GAEG;AACH,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0CjC,CAAA;AAkBD;;GAEG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC;IAC1D,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC5F,OAAO,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC,CAgOD"}
1
+ {"version":3,"file":"extract.d.ts","sourceRoot":"","sources":["../../src/tools/extract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,yCAAyC,CAAA;AA6ZtE;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAK3D"}
@@ -10,73 +10,32 @@
10
10
  */
11
11
  import { writeFile } from 'fs/promises';
12
12
  import { z } from 'zod';
13
- import { formatErrorResponse, getSession, getUnifiedSession } from '../core/index.js';
14
- import { targetJsonSchema, targetToFindParams, targetZodSchema } from './schema.js';
15
- /**
16
- * extract 工具定义
17
- */
18
- export const extractToolDefinition = {
19
- name: 'extract',
20
- description: '提取页面内容:文本、HTML、属性、截图、状态',
21
- inputSchema: {
22
- type: 'object',
23
- properties: {
24
- type: {
25
- type: 'string',
26
- enum: ['text', 'html', 'attribute', 'screenshot', 'state'],
27
- description: '提取类型',
28
- },
29
- target: {
30
- ...targetJsonSchema,
31
- description: '目标元素(attribute 必填;text/html 可选,省略则提取整个页面;screenshot/state 不需要)',
32
- },
33
- attribute: {
34
- type: 'string',
35
- description: '属性名(attribute)',
36
- },
37
- fullPage: {
38
- type: 'boolean',
39
- description: '是否全页面截图(screenshot)',
40
- },
41
- output: {
42
- type: 'string',
43
- description: '输出文件路径(可选)。若指定,结果写入文件;否则返回内容',
44
- },
45
- tabId: {
46
- type: 'string',
47
- description: '目标 Tab ID(可选,仅 Extension 模式)。不指定则使用当前 attach 的 tab。可操作非当前 attach 的 tab。CDP 模式下忽略此参数',
48
- },
49
- timeout: {
50
- type: 'number',
51
- description: '等待目标元素超时',
52
- },
53
- frame: {
54
- oneOf: [{ type: 'string' }, { type: 'number' }],
55
- description: 'iframe 定位(可选,仅 Extension 模式)。CSS 选择器(如 "iframe#main")或索引(如 0)。不指定则在主框架操作',
56
- },
57
- },
58
- required: ['type'],
59
- },
60
- };
13
+ import { formatErrorResponse, formatResponse, getSession, getUnifiedSession } from '../core/index.js';
14
+ import { targetToFindParams, targetZodSchema } from './schema.js';
61
15
  /**
62
16
  * extract 参数 schema
63
17
  */
64
18
  const extractSchema = z.object({
65
- type: z.enum(['text', 'html', 'attribute', 'screenshot', 'state']),
66
- target: targetZodSchema.optional(),
67
- attribute: z.string().optional(),
68
- fullPage: z.boolean().optional(),
69
- output: z.string().optional(),
70
- tabId: z.string().optional(),
71
- timeout: z.number().optional(),
72
- frame: z.union([z.string(), z.number()]).optional(),
19
+ type: z.enum(['text', 'html', 'attribute', 'screenshot', 'state'])
20
+ .describe('提取类型'),
21
+ target: targetZodSchema.optional().describe('目标元素(attribute 必填;text/html 可选,省略则提取整个页面;screenshot/state 不需要)'),
22
+ attribute: z.string().optional().describe('属性名(attribute)'),
23
+ fullPage: z.boolean().optional().describe('是否全页面截图(screenshot)'),
24
+ scale: z.number().optional().describe('截图缩放比例(screenshot fullPage)。默认 1,设为 0.5 可降低分辨率加速大页面截图'),
25
+ format: z.enum(['png', 'jpeg', 'webp']).optional().describe('截图格式(screenshot)。默认 png,jpeg/webp 体积更小,复杂页面推荐 jpeg 减少超时'),
26
+ quality: z.number().min(0).max(100).optional().describe('截图质量(screenshot,仅 jpeg/webp 有效)。0-100,推荐 80'),
27
+ output: z.string()
28
+ .optional()
29
+ .describe('输出文件路径(可选)。若指定,结果写入文件;否则返回内容'),
30
+ tabId: z.string().optional().describe('目标 Tab ID(可选,仅 Extension 模式)。不指定则使用当前 attach 的 tab。可操作非当前 attach 的 tab。CDP 模式下忽略此参数'),
31
+ timeout: z.number().optional().describe('等待目标元素超时'),
32
+ frame: z.union([z.string(), z.number()]).optional().describe('iframe 定位(可选,仅 Extension 模式)。CSS 选择器(如 "iframe#main")或索引(如 0)。不指定则在主框架操作'),
73
33
  });
74
34
  /**
75
35
  * extract 工具处理器
76
36
  */
77
- export async function handleExtract(params) {
37
+ async function handleExtract(args) {
78
38
  try {
79
- const args = extractSchema.parse(params);
80
39
  const unifiedSession = getUnifiedSession();
81
40
  const useExtension = unifiedSession.isExtensionConnected();
82
41
  const session = getSession();
@@ -94,32 +53,18 @@ export async function handleExtract(params) {
94
53
  : await extractText(session, args.target, args.timeout);
95
54
  if (args.output) {
96
55
  await writeFile(args.output, text, 'utf-8');
97
- return {
98
- content: [
99
- {
100
- type: 'text',
101
- text: JSON.stringify({
102
- success: true,
103
- type: 'text',
104
- output: args.output,
105
- size: text.length,
106
- }),
107
- },
108
- ],
109
- };
56
+ return formatResponse({
57
+ success: true,
58
+ type: 'text',
59
+ output: args.output,
60
+ size: text.length,
61
+ });
110
62
  }
111
- return {
112
- content: [
113
- {
114
- type: 'text',
115
- text: JSON.stringify({
116
- success: true,
117
- type: 'text',
118
- content: text,
119
- }),
120
- },
121
- ],
122
- };
63
+ return formatResponse({
64
+ success: true,
65
+ type: 'text',
66
+ content: text,
67
+ });
123
68
  }
124
69
  case 'html': {
125
70
  const html = useExtension
@@ -127,32 +72,18 @@ export async function handleExtract(params) {
127
72
  : await extractHTML(session, args.target, args.timeout);
128
73
  if (args.output) {
129
74
  await writeFile(args.output, html, 'utf-8');
130
- return {
131
- content: [
132
- {
133
- type: 'text',
134
- text: JSON.stringify({
135
- success: true,
136
- type: 'html',
137
- output: args.output,
138
- size: html.length,
139
- }),
140
- },
141
- ],
142
- };
75
+ return formatResponse({
76
+ success: true,
77
+ type: 'html',
78
+ output: args.output,
79
+ size: html.length,
80
+ });
143
81
  }
144
- return {
145
- content: [
146
- {
147
- type: 'text',
148
- text: JSON.stringify({
149
- success: true,
150
- type: 'html',
151
- content: html,
152
- }),
153
- },
154
- ],
155
- };
82
+ return formatResponse({
83
+ success: true,
84
+ type: 'html',
85
+ content: html,
86
+ });
156
87
  }
157
88
  case 'attribute': {
158
89
  if (!args.target) {
@@ -194,37 +125,28 @@ export async function handleExtract(params) {
194
125
  else {
195
126
  value = await extractAttribute(session, args.target, args.attribute, args.timeout);
196
127
  }
197
- return {
198
- content: [
199
- {
200
- type: 'text',
201
- text: JSON.stringify({
202
- success: true,
203
- type: 'attribute',
204
- attribute: args.attribute,
205
- value,
206
- }),
207
- },
208
- ],
209
- };
128
+ return formatResponse({
129
+ success: true,
130
+ type: 'attribute',
131
+ attribute: args.attribute,
132
+ value,
133
+ });
210
134
  }
211
135
  case 'screenshot': {
212
- const base64 = await unifiedSession.screenshot({ fullPage: args.fullPage ?? false });
136
+ const base64 = await unifiedSession.screenshot({
137
+ fullPage: args.fullPage ?? false,
138
+ scale: args.scale,
139
+ format: args.format,
140
+ quality: args.quality,
141
+ });
213
142
  if (args.output) {
214
143
  // 写入文件
215
144
  await writeFile(args.output, Buffer.from(base64, 'base64'));
216
- return {
217
- content: [
218
- {
219
- type: 'text',
220
- text: JSON.stringify({
221
- success: true,
222
- type: 'screenshot',
223
- output: args.output,
224
- }),
225
- },
226
- ],
227
- };
145
+ return formatResponse({
146
+ success: true,
147
+ type: 'screenshot',
148
+ output: args.output,
149
+ });
228
150
  }
229
151
  // 返回 base64 图片
230
152
  return {
@@ -232,7 +154,7 @@ export async function handleExtract(params) {
232
154
  {
233
155
  type: 'image',
234
156
  data: base64,
235
- mimeType: 'image/png',
157
+ mimeType: `image/${args.format === 'jpeg' ? 'jpeg' : args.format ?? 'png'}`,
236
158
  },
237
159
  ],
238
160
  };
@@ -241,31 +163,17 @@ export async function handleExtract(params) {
241
163
  const state = await unifiedSession.readPage();
242
164
  if (args.output) {
243
165
  await writeFile(args.output, JSON.stringify(state, null, 2), 'utf-8');
244
- return {
245
- content: [
246
- {
247
- type: 'text',
248
- text: JSON.stringify({
249
- success: true,
250
- type: 'state',
251
- output: args.output,
252
- }),
253
- },
254
- ],
255
- };
166
+ return formatResponse({
167
+ success: true,
168
+ type: 'state',
169
+ output: args.output,
170
+ });
256
171
  }
257
- return {
258
- content: [
259
- {
260
- type: 'text',
261
- text: JSON.stringify({
262
- success: true,
263
- type: 'state',
264
- state,
265
- }, null, 2),
266
- },
267
- ],
268
- };
172
+ return formatResponse({
173
+ success: true,
174
+ type: 'state',
175
+ state,
176
+ });
269
177
  }
270
178
  default:
271
179
  return {
@@ -309,10 +217,9 @@ async function extractText(session, target, timeout) {
309
217
  async function extractHTML(session, target, timeout) {
310
218
  if (target) {
311
219
  const locator = session.createLocator(target, timeout !== undefined ? { timeout } : undefined);
312
- const html = await locator.evaluateOn(`function() {
220
+ return await locator.evaluateOn(`function() {
313
221
  return this.outerHTML;
314
222
  }`);
315
- return html;
316
223
  }
317
224
  return session.evaluate('document.documentElement.outerHTML');
318
225
  }
@@ -331,11 +238,13 @@ async function extractAttribute(session, target, attribute, timeout) {
331
238
  * 支持所有 Target 形式(css/xpath/text/role/label 等)
332
239
  */
333
240
  async function extractTextExtension(unifiedSession, target) {
334
- if (!target)
241
+ if (!target) {
335
242
  return unifiedSession.getText();
243
+ }
336
244
  const { selector, text, xpath } = targetToFindParams(target);
337
- if (selector)
245
+ if (selector) {
338
246
  return unifiedSession.getText(selector);
247
+ }
339
248
  // xpath/text 定位:通过 evaluate 在页面上下文中查找
340
249
  if (xpath) {
341
250
  return unifiedSession.evaluate(`(function(xp) { var r = document.evaluate(xp, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null); return r.singleNodeValue ? r.singleNodeValue.textContent || '' : '' })`, undefined, undefined, [xpath]);
@@ -350,11 +259,13 @@ async function extractTextExtension(unifiedSession, target) {
350
259
  * 支持所有 Target 形式(css/xpath/text/role/label 等)
351
260
  */
352
261
  async function extractHtmlExtension(unifiedSession, target, outer = true) {
353
- if (!target)
262
+ if (!target) {
354
263
  return unifiedSession.getHtml(undefined, outer);
264
+ }
355
265
  const { selector, text, xpath } = targetToFindParams(target);
356
- if (selector)
266
+ if (selector) {
357
267
  return unifiedSession.getHtml(selector, outer);
268
+ }
358
269
  const prop = outer ? 'outerHTML' : 'innerHTML';
359
270
  if (xpath) {
360
271
  return unifiedSession.evaluate(`(function(xp, p) { var r = document.evaluate(xp, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null); return r.singleNodeValue ? r.singleNodeValue[p] || '' : '' })`, undefined, undefined, [xpath, prop]);
@@ -368,12 +279,16 @@ async function extractHtmlExtension(unifiedSession, target, outer = true) {
368
279
  * Extension 模式:提取属性
369
280
  */
370
281
  async function extractAttributeExtension(unifiedSession, target, attribute) {
371
- const { selector, text, xpath } = targetToFindParams(target);
282
+ const { selector, text, xpath, nth: nthParam } = targetToFindParams(target);
372
283
  // xpath/text 定位需要先 find 得到 refId,再获取属性
373
284
  if (xpath || text) {
374
285
  const elements = await unifiedSession.find(selector, text, xpath);
375
286
  if (elements.length > 0) {
376
- return unifiedSession.getAttribute(undefined, elements[0].refId, attribute);
287
+ const nth = nthParam ?? 0;
288
+ if (nth >= elements.length) {
289
+ throw new Error(`第 ${nth} 个匹配元素不存在(共 ${elements.length} 个)`);
290
+ }
291
+ return unifiedSession.getAttribute(undefined, elements[nth].refId, attribute);
377
292
  }
378
293
  return null;
379
294
  }
@@ -391,7 +306,8 @@ async function extractAttributeExtension(unifiedSession, target, attribute) {
391
306
  async function waitForTargetExtension(unifiedSession, target, timeout) {
392
307
  const startTime = Date.now();
393
308
  const retryDelay = 100;
394
- const { selector, text, xpath } = targetToFindParams(target);
309
+ const { selector, text, xpath, nth: nthParam } = targetToFindParams(target);
310
+ const nth = nthParam ?? 0;
395
311
  let lastError = null;
396
312
  while (true) {
397
313
  const elapsed = Date.now() - startTime;
@@ -407,12 +323,15 @@ async function waitForTargetExtension(unifiedSession, target, timeout) {
407
323
  try {
408
324
  const remaining = timeout - elapsed;
409
325
  const elements = await unifiedSession.find(selector, text, xpath, remaining);
410
- if (elements.length > 0)
326
+ if (elements.length > nth) {
411
327
  return;
328
+ }
412
329
  }
413
330
  catch (err) {
414
331
  // 暂时性错误(RPC 超时、发送失败、连接断开)可重试,其他确定性错误立即抛出
415
- if (err instanceof Error && /Request timeout|Failed to send|disconnect|未连接|stopped|replaced/i.test(err.message)) {
332
+ if (err instanceof
333
+ Error &&
334
+ /Request timeout|Failed to send|disconnect|未连接|stopped|replaced/i.test(err.message)) {
416
335
  lastError = err;
417
336
  await new Promise(r => setTimeout(r, retryDelay));
418
337
  continue;
@@ -422,4 +341,13 @@ async function waitForTargetExtension(unifiedSession, target, timeout) {
422
341
  await new Promise(r => setTimeout(r, retryDelay));
423
342
  }
424
343
  }
344
+ /**
345
+ * 注册 extract 工具
346
+ */
347
+ export function registerExtractTool(server) {
348
+ server.registerTool('extract', {
349
+ description: '提取页面内容:文本、HTML、属性、截图、状态',
350
+ inputSchema: extractSchema,
351
+ }, (args) => handleExtract(args));
352
+ }
425
353
  //# sourceMappingURL=extract.js.map