@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.
- package/README.md +103 -53
- package/dist/anti-detection/behavior.d.ts +0 -8
- package/dist/anti-detection/behavior.d.ts.map +1 -1
- package/dist/anti-detection/behavior.js +0 -16
- package/dist/anti-detection/behavior.js.map +1 -1
- package/dist/cdp/client.d.ts +0 -2
- package/dist/cdp/client.d.ts.map +1 -1
- package/dist/cdp/client.js +30 -45
- package/dist/cdp/client.js.map +1 -1
- package/dist/cdp/launcher.d.ts +1 -8
- package/dist/cdp/launcher.d.ts.map +1 -1
- package/dist/cdp/launcher.js +4 -20
- package/dist/cdp/launcher.js.map +1 -1
- package/dist/core/auto-wait.d.ts +2 -2
- package/dist/core/auto-wait.d.ts.map +1 -1
- package/dist/core/auto-wait.js +1 -1
- package/dist/core/auto-wait.js.map +1 -1
- package/dist/core/errors.d.ts +10 -13
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +19 -25
- package/dist/core/errors.js.map +1 -1
- package/dist/core/locator.d.ts +6 -7
- package/dist/core/locator.d.ts.map +1 -1
- package/dist/core/locator.js +77 -31
- package/dist/core/locator.js.map +1 -1
- package/dist/core/retry.d.ts.map +1 -1
- package/dist/core/retry.js +1 -1
- package/dist/core/retry.js.map +1 -1
- package/dist/core/session.d.ts +32 -33
- package/dist/core/session.d.ts.map +1 -1
- package/dist/core/session.js +154 -114
- package/dist/core/session.js.map +1 -1
- package/dist/core/types.d.ts +4 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +6 -0
- package/dist/core/types.js.map +1 -1
- package/dist/core/unified-session.d.ts +54 -67
- package/dist/core/unified-session.d.ts.map +1 -1
- package/dist/core/unified-session.js +215 -181
- package/dist/core/unified-session.js.map +1 -1
- package/dist/extension/bridge.d.ts +0 -19
- package/dist/extension/bridge.d.ts.map +1 -1
- package/dist/extension/bridge.js +6 -52
- package/dist/extension/bridge.js.map +1 -1
- package/dist/extension/http-server.d.ts +13 -11
- package/dist/extension/http-server.d.ts.map +1 -1
- package/dist/extension/http-server.js +101 -95
- package/dist/extension/http-server.js.map +1 -1
- package/dist/index.js +11 -64
- package/dist/index.js.map +1 -1
- package/dist/tools/browse.d.ts +3 -80
- package/dist/tools/browse.d.ts.map +1 -1
- package/dist/tools/browse.js +135 -291
- package/dist/tools/browse.js.map +1 -1
- package/dist/tools/cookies.d.ts +3 -71
- package/dist/tools/cookies.d.ts.map +1 -1
- package/dist/tools/cookies.js +75 -157
- package/dist/tools/cookies.js.map +1 -1
- package/dist/tools/evaluate.d.ts +3 -52
- package/dist/tools/evaluate.d.ts.map +1 -1
- package/dist/tools/evaluate.js +35 -86
- package/dist/tools/evaluate.js.map +1 -1
- package/dist/tools/extract.d.ts +3 -226
- package/dist/tools/extract.d.ts.map +1 -1
- package/dist/tools/extract.js +98 -170
- package/dist/tools/extract.js.map +1 -1
- package/dist/tools/index.d.ts +9 -9
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +9 -9
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/input.d.ts +3 -258
- package/dist/tools/input.d.ts.map +1 -1
- package/dist/tools/input.js +56 -143
- package/dist/tools/input.js.map +1 -1
- package/dist/tools/logs.d.ts +3 -51
- package/dist/tools/logs.d.ts.map +1 -1
- package/dist/tools/logs.js +47 -108
- package/dist/tools/logs.js.map +1 -1
- package/dist/tools/manage.d.ts +3 -64
- package/dist/tools/manage.d.ts.map +1 -1
- package/dist/tools/manage.js +243 -373
- package/dist/tools/manage.js.map +1 -1
- package/dist/tools/schema.d.ts +16 -182
- package/dist/tools/schema.d.ts.map +1 -1
- package/dist/tools/schema.js +70 -159
- package/dist/tools/schema.js.map +1 -1
- package/dist/tools/wait.d.ts +3 -221
- package/dist/tools/wait.d.ts.map +1 -1
- package/dist/tools/wait.js +74 -145
- package/dist/tools/wait.js.map +1 -1
- package/package.json +1 -1
package/dist/tools/extract.d.ts
CHANGED
|
@@ -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
|
|
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;
|
|
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"}
|
package/dist/tools/extract.js
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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({
|
|
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
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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: '
|
|
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
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 >
|
|
326
|
+
if (elements.length > nth) {
|
|
411
327
|
return;
|
|
328
|
+
}
|
|
412
329
|
}
|
|
413
330
|
catch (err) {
|
|
414
331
|
// 暂时性错误(RPC 超时、发送失败、连接断开)可重试,其他确定性错误立即抛出
|
|
415
|
-
if (err instanceof
|
|
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
|