@mearl/mcp-server 2.0.1 → 2.1.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 +5 -2
- package/dist/index.js +163 -61
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -9,18 +9,21 @@
|
|
|
9
9
|
- 📝 **browser_get_logs**: 获取控制台日志
|
|
10
10
|
- 🎭 **browser_set_mock**: 动态设置 Mock 数据
|
|
11
11
|
- 🌐 **browser_list**: 统一列出普通浏览器和托管浏览器,返回默认目标、类型及连接状态
|
|
12
|
+
- 📴 **browser_release**: 释放 debugger/CDP 控制并保留浏览器与全部标签页
|
|
12
13
|
- 🚀 **browser_launch / browser_close**: 启动或关闭独立 Profile 的托管 Chrome
|
|
13
14
|
- 🌐 **browser_page_snapshot**: 首次获取页面结构和可交互元素
|
|
14
15
|
- ⚡ **browser_page_click/type/hover/scroll/press/upload**: 执行页面动作,默认内置观察,返回通知、可交互控件和焦点等高置信度信号
|
|
15
16
|
|
|
16
17
|
## 浏览器交互工作流
|
|
17
18
|
|
|
18
|
-
首次进入页面时调用 `browser_page_snapshot` 建立结构基线。后续点击、输入、悬停、滚动、按键和上传直接调用 `browser_page_click/type/hover/scroll/press/upload`,它们默认内置观察,动作与异步同步在一次调用内完成;同页结果读取 `effects.notifications`、`effects.interactives` 和 `effects.focus
|
|
19
|
+
首次进入页面时调用 `browser_page_snapshot` 建立结构基线。后续点击、输入、悬停、滚动、按键和上传直接调用 `browser_page_click/type/hover/scroll/press/upload`,它们默认内置观察,动作与异步同步在一次调用内完成;同页结果读取 `effects.notifications`、`effects.interactives` 和 `effects.focus`。新增或变化的可交互节点会尽量携带可直接用于后续动作的 `node.ref`,解析不到 ref 时回退使用 `node.selector`。导航结果读取新 URL 和就绪状态。通常仅在导航后、`fullSnapshotRecommended: true` 或需要视觉判断时重新 snapshot 或截图;滚动后若下一步需要读取新视口内容,可按需获取 viewport 快照。`browser_page_eval` 默认裸执行,仅在表达式会修改页面且需要变化信号时显式传 `observe` 对象。
|
|
19
20
|
|
|
20
|
-
长列表重新感知时优先使用 `browser_page_snapshot(mode: "viewport")
|
|
21
|
+
长列表重新感知时优先使用 `browser_page_snapshot(mode: "viewport")`;已知 CSS 区域时使用 `rootSelector`,已有 ref 时使用 `rootRef`,只查找特定文案或角色时使用 `query`;仅需要当前视口内的控件时使用 `mode: "interactive"`,避免整页 AX Tree 占用上下文。视口内缺少 AX 控件语义时,`interactive` 会自动回退到 viewport,并返回 `fallbackMode: "viewport"`。重复文本点击可用 `scope` 限定子树,滚动子节点所在容器可用 `containerPolicy: "nearest"`。
|
|
21
22
|
|
|
22
23
|
页面动作传 `observe: false` 可关闭内置观察仅执行裸动作;不需要在每次动作后固定追加 wait、snapshot 或 screenshot。
|
|
23
24
|
|
|
25
|
+
`browser_page_click.clickMode` 默认 `auto`:可见桌面页使用可信 mouse,移动模拟页使用可信 touch;目标页隐藏时不会切换标签或还原窗口,而是使用 DOM fallback,并返回 `dispatchMode: "dom"`、`fallbackReason: "page-hidden"`。可用 `dom` / `mouse` / `touch` 覆盖自动策略;可信输入返回实际 `pointerType`。
|
|
26
|
+
|
|
24
27
|
## 安装
|
|
25
28
|
|
|
26
29
|
**方式一:使用 npx(推荐,保持最新)**
|
package/dist/index.js
CHANGED
|
@@ -22,7 +22,10 @@ import { tmpdir } from 'os';
|
|
|
22
22
|
import { join, dirname } from 'path';
|
|
23
23
|
import { spawn } from 'child_process';
|
|
24
24
|
import { fileURLToPath } from 'url';
|
|
25
|
+
import { createRequire } from 'module';
|
|
25
26
|
import { RESPONSE_SIZE_SMALL, RESPONSE_SIZE_MEDIUM, RESPONSE_SIZE_LARGE, RESPONSE_SIZE_MAX, } from './constants.js';
|
|
27
|
+
const require = createRequire(import.meta.url);
|
|
28
|
+
const MCP_SERVER_VERSION = require('../package.json').version;
|
|
26
29
|
// 日志输出到 stderr(不影响 stdio 通信)
|
|
27
30
|
const LOG_FILE = join(tmpdir(), 'mearl-mcp-server.log');
|
|
28
31
|
const log = (...args) => {
|
|
@@ -245,35 +248,32 @@ function formatResponseWithSizeCheck(result, toolName, args) {
|
|
|
245
248
|
},
|
|
246
249
|
];
|
|
247
250
|
}
|
|
248
|
-
// 策略 5: 超超大响应(>1MB 压缩)-
|
|
249
|
-
const truncateSize = RESPONSE_SIZE_MAX;
|
|
250
|
-
const truncatedStr = compactStr.substring(0, truncateSize);
|
|
251
|
+
// 策略 5: 超超大响应(>1MB 压缩)- 返回可解析的诊断信息
|
|
251
252
|
const suggestions = getOptimizationSuggestions(toolName, args, result, stats);
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
stats.largestFields
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
}
|
|
253
|
+
const diagnostic = {
|
|
254
|
+
error: {
|
|
255
|
+
code: 'RESPONSE_TOO_LARGE',
|
|
256
|
+
message: 'The result exceeded the MCP response limit. Narrow the request and retry.',
|
|
257
|
+
originalSizeBytes: compactSize,
|
|
258
|
+
limitBytes: RESPONSE_SIZE_MAX,
|
|
259
|
+
},
|
|
260
|
+
meta: stats.meta,
|
|
261
|
+
largestFields: stats.largestFields?.map(field => ({
|
|
262
|
+
...field,
|
|
263
|
+
percentageOfTotal: Math.round((field.size / compactSize) * 100),
|
|
264
|
+
})),
|
|
265
|
+
suggestions: suggestions.length > 0
|
|
266
|
+
? suggestions
|
|
267
|
+
: [
|
|
268
|
+
'Significantly reduce the data requested',
|
|
269
|
+
'Use "fields" to extract only essential data',
|
|
270
|
+
'Add filters to narrow down results',
|
|
271
|
+
],
|
|
272
|
+
};
|
|
273
273
|
return [
|
|
274
274
|
{
|
|
275
275
|
type: 'text',
|
|
276
|
-
text:
|
|
276
|
+
text: JSON.stringify(diagnostic, null, 2),
|
|
277
277
|
},
|
|
278
278
|
];
|
|
279
279
|
}
|
|
@@ -317,12 +317,12 @@ async function main() {
|
|
|
317
317
|
// 创建 MCP Server
|
|
318
318
|
const server = new Server({
|
|
319
319
|
name: 'mearl',
|
|
320
|
-
version:
|
|
320
|
+
version: MCP_SERVER_VERSION,
|
|
321
321
|
}, {
|
|
322
322
|
capabilities: {
|
|
323
323
|
tools: {},
|
|
324
324
|
},
|
|
325
|
-
instructions: '浏览器交互默认工作流:首次进入页面时使用 browser_page_snapshot 建立页面基线;后续点击、输入、悬停、滚动、按键和上传直接使用 browser_page_click/type/hover/scroll/press/upload,它们默认内置观察,动作与异步稳定判断在一次调用内完成,返回 { action, observation };传 observe:false 可关闭观察仅执行裸动作。观察结果不替代完整页面理解:observation.mode 为 delta 时确认 scope="main-document",再读取 effects.notifications、effects.interactives 和 effects.focus;notifications 中 change=transient 表示已经消失的 Toast、状态或错误提示。为 navigation 时先检查 ready,ready=true 表示文档、相关业务请求和可见骨架状态已经稳定,可在新页面重建 snapshot
|
|
325
|
+
instructions: '浏览器交互默认工作流:首次进入页面时使用 browser_page_snapshot 建立页面基线;后续点击、输入、悬停、滚动、按键和上传直接使用 browser_page_click/type/hover/scroll/press/upload,它们默认内置观察,动作与异步稳定判断在一次调用内完成,返回 { action, observation };传 observe:false 可关闭观察仅执行裸动作。观察结果不替代完整页面理解:observation.mode 为 delta 时确认 scope="main-document",再读取 effects.notifications、effects.interactives 和 effects.focus;interactives/focus 节点有 ref 时优先直接用于后续动作,没有 ref 时使用 selector;notifications 中 change=transient 表示已经消失的 Toast、状态或错误提示。为 navigation 时先检查 ready,ready=true 表示文档、相关业务请求和可见骨架状态已经稳定,可在新页面重建 snapshot。其他情况下通常仅当 fullSnapshotRecommended 为 true 时根据 snapshotReasons 回退,并选择最小范围:已有区域 ref 用 rootRef,已有 CSS 根节点用 rootSelector,只需查找特定目标时加 query,长列表用 mode="viewport",只需当前视口内的控件用 mode="interactive";滚动动作的 moved 只证明位置变化,下一步需要读取新视口内容时可按需获取 viewport 快照。视口内缺少 AX 控件语义时 interactive 会在同一次调用中回退到 viewport,并返回 fallbackMode。重复文本点击用 scope 限定区域,ref 指向滚动容器子节点时用 containerPolicy="nearest"。page_eval 默认裸执行且应作为结构化能力无法表达时的补充,仅在表达式修改页面并需要变化信号时显式传 observe 对象。视觉任务再使用 screenshot。不要在每次动作后固定调用 browser_page_wait、snapshot 或 screenshot。',
|
|
326
326
|
});
|
|
327
327
|
// 页面交互工具共享的 observe 参数 schema:默认开启内置观察,传 false 关闭
|
|
328
328
|
const OBSERVE_INPUT_SCHEMA = {
|
|
@@ -452,6 +452,11 @@ async function main() {
|
|
|
452
452
|
description: '返回条数上限,默认 20',
|
|
453
453
|
default: 20,
|
|
454
454
|
},
|
|
455
|
+
includeRaw: {
|
|
456
|
+
type: 'boolean',
|
|
457
|
+
description: '是否返回完整 tracking URL、_raw 原始载荷和 _context 上下文;默认 false,仅返回解析后的精简字段',
|
|
458
|
+
default: false,
|
|
459
|
+
},
|
|
455
460
|
},
|
|
456
461
|
},
|
|
457
462
|
},
|
|
@@ -461,6 +466,10 @@ async function main() {
|
|
|
461
466
|
inputSchema: {
|
|
462
467
|
type: 'object',
|
|
463
468
|
properties: {
|
|
469
|
+
tabId: {
|
|
470
|
+
type: 'number',
|
|
471
|
+
description: '目标标签页 ID,从 browser_tab_list 获取;不传时使用当前活动标签页',
|
|
472
|
+
},
|
|
464
473
|
level: {
|
|
465
474
|
type: 'string',
|
|
466
475
|
enum: ['log', 'warn', 'error', 'info', 'all'],
|
|
@@ -489,6 +498,10 @@ async function main() {
|
|
|
489
498
|
inputSchema: {
|
|
490
499
|
type: 'object',
|
|
491
500
|
properties: {
|
|
501
|
+
tabId: {
|
|
502
|
+
type: 'number',
|
|
503
|
+
description: '目标标签页 ID,从 browser_tab_list 获取;不传时使用当前活动标签页',
|
|
504
|
+
},
|
|
492
505
|
apiName: {
|
|
493
506
|
type: 'string',
|
|
494
507
|
description: 'API 名称或正则表达式模式。例如: "mtop.cart.query" 精确匹配, ".*\\.cart\\..*" 匹配所有购物车接口',
|
|
@@ -542,6 +555,10 @@ async function main() {
|
|
|
542
555
|
inputSchema: {
|
|
543
556
|
type: 'object',
|
|
544
557
|
properties: {
|
|
558
|
+
tabId: {
|
|
559
|
+
type: 'number',
|
|
560
|
+
description: '目标标签页 ID,从 browser_tab_list 获取;不传时使用当前活动标签页',
|
|
561
|
+
},
|
|
545
562
|
apiName: {
|
|
546
563
|
type: 'string',
|
|
547
564
|
description: '过滤指定接口,支持正则表达式。例如: "mtop.cart.query" 或 ".*\\.cart\\..*"。不传则返回所有 Mock',
|
|
@@ -959,6 +976,14 @@ async function main() {
|
|
|
959
976
|
required: ['browser'],
|
|
960
977
|
},
|
|
961
978
|
},
|
|
979
|
+
{
|
|
980
|
+
name: 'browser_release',
|
|
981
|
+
description: '释放 Agent 对当前目标浏览器的 debugger/CDP 控制,停止持续采集和会话级临时效果,但保留浏览器与全部标签页。后续浏览器操作仍可自动重新建立控制。',
|
|
982
|
+
inputSchema: {
|
|
983
|
+
type: 'object',
|
|
984
|
+
properties: {},
|
|
985
|
+
},
|
|
986
|
+
},
|
|
962
987
|
{
|
|
963
988
|
name: 'browser_tab_open',
|
|
964
989
|
description: '打开或按 URL 规则复用标签页。默认新建并等待加载完成;reuse=prefer 可优先复用,reuse=require 可限制为只复用。响应的 opened/reused 可用于判断流程结束时是否应关闭该 Tab。',
|
|
@@ -1076,7 +1101,7 @@ async function main() {
|
|
|
1076
1101
|
},
|
|
1077
1102
|
{
|
|
1078
1103
|
name: 'browser_page_click',
|
|
1079
|
-
description: '点击页面元素,默认内置观察:同一次调用内等待相关 DOM、XHR/Fetch/MTop 请求或导航稳定,返回 { action, observation };传 observe:false 仅执行裸点击。定位方式三选一:selector(CSS 或 @eN snapshot ref)、text(按可见文本匹配 accessibility name
|
|
1104
|
+
description: '点击页面元素,默认内置观察:同一次调用内等待相关 DOM、XHR/Fetch/MTop 请求或导航稳定,返回 { action, observation };传 observe:false 仅执行裸点击。定位方式三选一:selector(CSS 或 @eN snapshot ref)、text(按可见文本匹配 accessibility name,可用 scope 限定子树)、point(视口坐标)。clickMode 默认 auto:可见页按设备模拟状态使用可信 mouse/touch,隐藏页不切换标签或还原窗口并自动使用 DOM fallback;checkbox/radio 使用带状态校验的 DOM 路径。可用 dom/mouse/touch 强制派发方式。响应返回实际 dispatchMode,可信输入还返回 pointerType。',
|
|
1080
1105
|
inputSchema: {
|
|
1081
1106
|
type: 'object',
|
|
1082
1107
|
properties: {
|
|
@@ -1086,7 +1111,7 @@ async function main() {
|
|
|
1086
1111
|
},
|
|
1087
1112
|
selector: {
|
|
1088
1113
|
type: 'string',
|
|
1089
|
-
description: '原生 CSS 选择器(如 "[aria-label=\'提交\']"、"#login-btn")或
|
|
1114
|
+
description: '原生 CSS 选择器(如 "[aria-label=\'提交\']"、"#login-btn")或 page_snapshot / 页面动作 observation 返回的 ref(如 "@e3",必须带 "@" 前缀)。注意字段名是 "selector" 而非 "ref"。不支持 :has-text/:visible 等 Playwright 扩展语法。与 text/point 互斥。',
|
|
1090
1115
|
},
|
|
1091
1116
|
text: {
|
|
1092
1117
|
type: 'string',
|
|
@@ -1110,11 +1135,15 @@ async function main() {
|
|
|
1110
1135
|
description: '配合 text 使用,默认 true 精确匹配;false 时按子串匹配',
|
|
1111
1136
|
default: true,
|
|
1112
1137
|
},
|
|
1113
|
-
|
|
1138
|
+
scope: {
|
|
1139
|
+
type: 'string',
|
|
1140
|
+
description: '配合 text 使用,把 AX 文本匹配限制在指定 CSS 元素或 snapshot ref(如 "@e12")子树内',
|
|
1141
|
+
},
|
|
1142
|
+
clickMode: {
|
|
1114
1143
|
type: 'string',
|
|
1115
|
-
enum: ['
|
|
1116
|
-
description: '
|
|
1117
|
-
default: '
|
|
1144
|
+
enum: ['auto', 'dom', 'mouse', 'touch'],
|
|
1145
|
+
description: '点击模式。auto 在可见页按设备模拟状态选择可信 mouse/touch,在隐藏页使用 DOM fallback,checkbox/radio 使用带状态校验的 DOM 路径;dom/mouse/touch 强制指定派发方式',
|
|
1146
|
+
default: 'auto',
|
|
1118
1147
|
},
|
|
1119
1148
|
observe: OBSERVE_INPUT_SCHEMA,
|
|
1120
1149
|
},
|
|
@@ -1133,7 +1162,7 @@ async function main() {
|
|
|
1133
1162
|
},
|
|
1134
1163
|
selector: {
|
|
1135
1164
|
type: 'string',
|
|
1136
|
-
description: '原生 CSS
|
|
1165
|
+
description: '原生 CSS 选择器,或 page_snapshot / 页面动作 observation 返回的 ref(如 "@e6",必须带 "@" 前缀)。字段名是 "selector"。不支持 :has-text/:visible 等 Playwright 扩展。',
|
|
1137
1166
|
},
|
|
1138
1167
|
text: {
|
|
1139
1168
|
type: 'string',
|
|
@@ -1151,7 +1180,7 @@ async function main() {
|
|
|
1151
1180
|
},
|
|
1152
1181
|
{
|
|
1153
1182
|
name: 'browser_page_scroll',
|
|
1154
|
-
description: '
|
|
1183
|
+
description: '滚动页面或指定滚动容器,默认内置观察(懒加载内容等变化),返回 { action, observation };传 observe:false 仅执行裸滚动。支持 up、down、top、bottom 四个方向;containerPolicy="nearest" 可从目标子节点解析最近可滚动祖先。动作结果包含前后位置、是否移动和边界状态。',
|
|
1155
1184
|
inputSchema: {
|
|
1156
1185
|
type: 'object',
|
|
1157
1186
|
properties: {
|
|
@@ -1169,6 +1198,15 @@ async function main() {
|
|
|
1169
1198
|
description: '滚动距离(像素),仅 up/down 方向有效。默认 600',
|
|
1170
1199
|
default: 600,
|
|
1171
1200
|
},
|
|
1201
|
+
selector: {
|
|
1202
|
+
type: 'string',
|
|
1203
|
+
description: '目标滚动容器的原生 CSS 选择器,或 page_snapshot / 页面动作 observation 返回的 ref(如 "@e6",必须带 "@" 前缀);不传时滚动页面。',
|
|
1204
|
+
},
|
|
1205
|
+
containerPolicy: {
|
|
1206
|
+
type: 'string',
|
|
1207
|
+
enum: ['self', 'nearest'],
|
|
1208
|
+
description: '默认 self,要求 selector 本身可滚动;nearest 会在其不可滚动时使用最近的可滚动祖先',
|
|
1209
|
+
},
|
|
1172
1210
|
observe: OBSERVE_INPUT_SCHEMA,
|
|
1173
1211
|
},
|
|
1174
1212
|
required: ['tabId', 'direction'],
|
|
@@ -1186,7 +1224,7 @@ async function main() {
|
|
|
1186
1224
|
},
|
|
1187
1225
|
selector: {
|
|
1188
1226
|
type: 'string',
|
|
1189
|
-
description: '原生 CSS
|
|
1227
|
+
description: '原生 CSS 选择器,或 page_snapshot / 页面动作 observation 返回的 ref(如 "@e1",必须带 "@" 前缀)。字段名是 "selector"。不支持 :has-text/:visible 等 Playwright 扩展。',
|
|
1190
1228
|
},
|
|
1191
1229
|
observe: OBSERVE_INPUT_SCHEMA,
|
|
1192
1230
|
},
|
|
@@ -1195,7 +1233,7 @@ async function main() {
|
|
|
1195
1233
|
},
|
|
1196
1234
|
{
|
|
1197
1235
|
name: 'browser_page_eval',
|
|
1198
|
-
description: '
|
|
1236
|
+
description: '在页面上下文中执行 JavaScript 表达式并返回结果,作为结构化页面能力无法表达时的补充。默认裸执行;仅在表达式会修改页面且需要变化信号时显式传 observe 对象。',
|
|
1199
1237
|
inputSchema: {
|
|
1200
1238
|
type: 'object',
|
|
1201
1239
|
properties: {
|
|
@@ -1216,13 +1254,18 @@ async function main() {
|
|
|
1216
1254
|
description: '是否等待表达式返回的 Promise,默认 true',
|
|
1217
1255
|
default: true,
|
|
1218
1256
|
},
|
|
1257
|
+
observe: {
|
|
1258
|
+
...OBSERVE_INPUT_SCHEMA,
|
|
1259
|
+
type: 'object',
|
|
1260
|
+
description: '仅在表达式会修改页面且需要观察结果时显式传入;不传时轻量执行并直接返回结果',
|
|
1261
|
+
},
|
|
1219
1262
|
},
|
|
1220
1263
|
required: ['tabId', 'expression'],
|
|
1221
1264
|
},
|
|
1222
1265
|
},
|
|
1223
1266
|
{
|
|
1224
1267
|
name: 'browser_page_snapshot',
|
|
1225
|
-
description: '获取页面无障碍树,返回角色、名称、值、状态和 @ref。默认 mode="full" 建立完整基线;长列表可用 mode="viewport"
|
|
1268
|
+
description: '获取页面无障碍树,返回角色、名称、值、状态和 @ref。默认 mode="full" 建立完整基线;长列表可用 mode="viewport",只看当前视口内的表单控件可用 mode="interactive",已知 CSS 区域可用 rootSelector,已有 ref 可用 rootRef + ancestorDepth 获取局部上下文,只查找特定文本/角色时用 query 服务端裁剪。视口内缺少 AX 控件语义时 interactive 会自动回退到 viewport,并返回 fallbackMode="viewport"。后续交互依赖页面交互工具内置的观察结果,仅在建议回退、导航或 ref 失效时再次调用。',
|
|
1226
1269
|
inputSchema: {
|
|
1227
1270
|
type: 'object',
|
|
1228
1271
|
properties: {
|
|
@@ -1238,11 +1281,39 @@ async function main() {
|
|
|
1238
1281
|
mode: {
|
|
1239
1282
|
type: 'string',
|
|
1240
1283
|
enum: ['full', 'interactive', 'viewport'],
|
|
1241
|
-
description: '输出范围:full 为完整树;interactive
|
|
1284
|
+
description: '输出范围:full 为完整树;interactive 只保留当前视口内的语义控件,视口内无 AX 控件语义时自动回退到 viewport;viewport 只保留当前可视区域。默认 full',
|
|
1242
1285
|
},
|
|
1243
1286
|
rootSelector: {
|
|
1244
1287
|
type: 'string',
|
|
1245
|
-
description: '只获取指定 CSS 元素的 AX 子树,可与 mode
|
|
1288
|
+
description: '只获取指定 CSS 元素的 AX 子树,可与 mode 组合;与 rootRef 互斥',
|
|
1289
|
+
},
|
|
1290
|
+
rootRef: {
|
|
1291
|
+
type: 'string',
|
|
1292
|
+
description: '以已有 snapshot ref(如 "@e8")为根获取局部 AX 子树',
|
|
1293
|
+
},
|
|
1294
|
+
ancestorDepth: {
|
|
1295
|
+
type: 'number',
|
|
1296
|
+
description: 'rootRef 向上扩展的 DOM 祖先层数,默认 0,最大 20',
|
|
1297
|
+
minimum: 0,
|
|
1298
|
+
maximum: 20,
|
|
1299
|
+
},
|
|
1300
|
+
query: {
|
|
1301
|
+
type: 'object',
|
|
1302
|
+
description: '服务端过滤 AX 树,只保留按 accessibility name / role 命中的节点及祖先路径',
|
|
1303
|
+
properties: {
|
|
1304
|
+
text: {
|
|
1305
|
+
type: 'string',
|
|
1306
|
+
description: '要匹配的 accessibility name',
|
|
1307
|
+
},
|
|
1308
|
+
role: {
|
|
1309
|
+
type: 'string',
|
|
1310
|
+
description: '要匹配的 AX role',
|
|
1311
|
+
},
|
|
1312
|
+
exact: {
|
|
1313
|
+
type: 'boolean',
|
|
1314
|
+
description: 'text 默认精确匹配;false 时按子串匹配',
|
|
1315
|
+
},
|
|
1316
|
+
},
|
|
1246
1317
|
},
|
|
1247
1318
|
maxNodes: {
|
|
1248
1319
|
type: 'number',
|
|
@@ -1318,7 +1389,7 @@ async function main() {
|
|
|
1318
1389
|
},
|
|
1319
1390
|
{
|
|
1320
1391
|
name: 'browser_page_navigate',
|
|
1321
|
-
description: '
|
|
1392
|
+
description: '在指定标签页中导航到 URL、设置 refresh:true 重新加载,或用 history:"back"|"forward" 沿浏览历史移动。三种模式互斥,返回最终 URL 和标题。',
|
|
1322
1393
|
inputSchema: {
|
|
1323
1394
|
type: 'object',
|
|
1324
1395
|
properties: {
|
|
@@ -1328,13 +1399,18 @@ async function main() {
|
|
|
1328
1399
|
},
|
|
1329
1400
|
url: {
|
|
1330
1401
|
type: 'string',
|
|
1331
|
-
description: '要导航到的 URL
|
|
1402
|
+
description: '要导航到的 URL;与 refresh/history 互斥',
|
|
1332
1403
|
},
|
|
1333
1404
|
refresh: {
|
|
1334
1405
|
type: 'boolean',
|
|
1335
|
-
description: '为 true
|
|
1406
|
+
description: '为 true 时直接重新加载当前页面;与 url/history 互斥',
|
|
1336
1407
|
default: false,
|
|
1337
1408
|
},
|
|
1409
|
+
history: {
|
|
1410
|
+
type: 'string',
|
|
1411
|
+
enum: ['back', 'forward'],
|
|
1412
|
+
description: '沿当前标签页浏览历史后退或前进;与 url/refresh 互斥',
|
|
1413
|
+
},
|
|
1338
1414
|
},
|
|
1339
1415
|
required: ['tabId'],
|
|
1340
1416
|
},
|
|
@@ -1351,7 +1427,7 @@ async function main() {
|
|
|
1351
1427
|
},
|
|
1352
1428
|
selector: {
|
|
1353
1429
|
type: 'string',
|
|
1354
|
-
description: '原生 CSS
|
|
1430
|
+
description: '原生 CSS 选择器,或 page_snapshot / 页面动作 observation 返回的 ref(如 "@e5",必须带 "@" 前缀),用于定位 <input type="file"> 元素。字段名是 "selector"。',
|
|
1355
1431
|
},
|
|
1356
1432
|
filePaths: {
|
|
1357
1433
|
type: 'array',
|
|
@@ -1521,18 +1597,26 @@ async function main() {
|
|
|
1521
1597
|
break;
|
|
1522
1598
|
}
|
|
1523
1599
|
case 'browser_get_events': {
|
|
1524
|
-
const { tabId, source = 'all', event_type, filter, since, limit = 20 } = args;
|
|
1525
|
-
result = await client.getEvents({
|
|
1600
|
+
const { tabId, source = 'all', event_type, filter, since, limit = 20, includeRaw = false, } = args;
|
|
1601
|
+
result = await client.getEvents({
|
|
1602
|
+
tabId,
|
|
1603
|
+
source,
|
|
1604
|
+
event_type,
|
|
1605
|
+
filter,
|
|
1606
|
+
since,
|
|
1607
|
+
limit,
|
|
1608
|
+
includeRaw,
|
|
1609
|
+
});
|
|
1526
1610
|
break;
|
|
1527
1611
|
}
|
|
1528
1612
|
case 'browser_get_logs': {
|
|
1529
|
-
const { level = 'all', since, limit = 100, filter } = args;
|
|
1530
|
-
result = await client.getLogs({ level, since, limit, filter });
|
|
1613
|
+
const { tabId, level = 'all', since, limit = 100, filter } = args;
|
|
1614
|
+
result = await client.getLogs({ tabId, level, since, limit, filter });
|
|
1531
1615
|
break;
|
|
1532
1616
|
}
|
|
1533
1617
|
case 'browser_set_mock': {
|
|
1534
1618
|
const { payloadFile } = args;
|
|
1535
|
-
let { apiName, mockData, fields, enabled, duration, conditions } = args;
|
|
1619
|
+
let { tabId, apiName, mockData, fields, enabled, duration, conditions } = args;
|
|
1536
1620
|
enabled = enabled ?? true;
|
|
1537
1621
|
// 如果提供了 payloadFile,从文件读取 mock 数据
|
|
1538
1622
|
if (payloadFile) {
|
|
@@ -1541,7 +1625,9 @@ async function main() {
|
|
|
1541
1625
|
}
|
|
1542
1626
|
const fileContent = readFileSync(payloadFile, 'utf-8');
|
|
1543
1627
|
const fileData = JSON.parse(fileContent);
|
|
1544
|
-
// 文件内容可以包含 apiName、mockData、fields、enabled、duration、conditions 等参数
|
|
1628
|
+
// 文件内容可以包含 tabId、apiName、mockData、fields、enabled、duration、conditions 等参数
|
|
1629
|
+
if (fileData.tabId !== undefined)
|
|
1630
|
+
tabId = fileData.tabId;
|
|
1545
1631
|
if (fileData.apiName)
|
|
1546
1632
|
apiName = fileData.apiName;
|
|
1547
1633
|
if (fileData.mockData !== undefined)
|
|
@@ -1556,6 +1642,7 @@ async function main() {
|
|
|
1556
1642
|
conditions = fileData.conditions;
|
|
1557
1643
|
}
|
|
1558
1644
|
result = await client.setMock({
|
|
1645
|
+
tabId,
|
|
1559
1646
|
apiName,
|
|
1560
1647
|
mockData,
|
|
1561
1648
|
fields,
|
|
@@ -1566,8 +1653,8 @@ async function main() {
|
|
|
1566
1653
|
break;
|
|
1567
1654
|
}
|
|
1568
1655
|
case 'browser_get_mocks': {
|
|
1569
|
-
const { apiName } = args;
|
|
1570
|
-
result = await client.getMocks({ apiName });
|
|
1656
|
+
const { tabId, apiName } = args;
|
|
1657
|
+
result = await client.getMocks({ tabId, apiName });
|
|
1571
1658
|
break;
|
|
1572
1659
|
}
|
|
1573
1660
|
case 'browser_get_api_schema': {
|
|
@@ -1659,6 +1746,10 @@ async function main() {
|
|
|
1659
1746
|
result = await client.browserClose(args);
|
|
1660
1747
|
break;
|
|
1661
1748
|
}
|
|
1749
|
+
case 'browser_release': {
|
|
1750
|
+
result = await client.browserRelease();
|
|
1751
|
+
break;
|
|
1752
|
+
}
|
|
1662
1753
|
case 'browser_tab_open': {
|
|
1663
1754
|
const { url, active = false, closeExisting = false, reuse = 'never', match, restoreFocusOnClose = false, emulation, group, } = args;
|
|
1664
1755
|
result = await client.tabOpen({
|
|
@@ -1683,7 +1774,7 @@ async function main() {
|
|
|
1683
1774
|
break;
|
|
1684
1775
|
}
|
|
1685
1776
|
case 'browser_page_click': {
|
|
1686
|
-
const { tabId, selector, text, point, role, exact,
|
|
1777
|
+
const { tabId, selector, text, point, role, exact, scope, clickMode, observe } = args;
|
|
1687
1778
|
result = await client.pageClick({
|
|
1688
1779
|
tabId,
|
|
1689
1780
|
selector,
|
|
@@ -1691,7 +1782,8 @@ async function main() {
|
|
|
1691
1782
|
point,
|
|
1692
1783
|
role,
|
|
1693
1784
|
exact,
|
|
1694
|
-
|
|
1785
|
+
scope,
|
|
1786
|
+
clickMode,
|
|
1695
1787
|
observe,
|
|
1696
1788
|
});
|
|
1697
1789
|
break;
|
|
@@ -1702,8 +1794,15 @@ async function main() {
|
|
|
1702
1794
|
break;
|
|
1703
1795
|
}
|
|
1704
1796
|
case 'browser_page_scroll': {
|
|
1705
|
-
const { tabId, direction, distance = 600, observe } = args;
|
|
1706
|
-
result = await client.pageScroll({
|
|
1797
|
+
const { tabId, direction, distance = 600, selector, containerPolicy, observe, } = args;
|
|
1798
|
+
result = await client.pageScroll({
|
|
1799
|
+
tabId,
|
|
1800
|
+
direction,
|
|
1801
|
+
distance,
|
|
1802
|
+
selector,
|
|
1803
|
+
containerPolicy,
|
|
1804
|
+
observe,
|
|
1805
|
+
});
|
|
1707
1806
|
break;
|
|
1708
1807
|
}
|
|
1709
1808
|
case 'browser_page_hover': {
|
|
@@ -1712,17 +1811,20 @@ async function main() {
|
|
|
1712
1811
|
break;
|
|
1713
1812
|
}
|
|
1714
1813
|
case 'browser_page_eval': {
|
|
1715
|
-
const { tabId, expression, frameId, awaitPromise = true } = args;
|
|
1716
|
-
result = await client.pageEval({ tabId, expression, frameId, awaitPromise });
|
|
1814
|
+
const { tabId, expression, frameId, awaitPromise = true, observe } = args;
|
|
1815
|
+
result = await client.pageEval({ tabId, expression, frameId, awaitPromise, observe });
|
|
1717
1816
|
break;
|
|
1718
1817
|
}
|
|
1719
1818
|
case 'browser_page_snapshot': {
|
|
1720
|
-
const { tabId, depth = 15, mode, rootSelector, maxNodes, maxChars } = args;
|
|
1819
|
+
const { tabId, depth = 15, mode, rootSelector, rootRef, ancestorDepth, query, maxNodes, maxChars, } = args;
|
|
1721
1820
|
result = await client.pageSnapshot({
|
|
1722
1821
|
tabId,
|
|
1723
1822
|
depth,
|
|
1724
1823
|
mode,
|
|
1725
1824
|
rootSelector,
|
|
1825
|
+
rootRef,
|
|
1826
|
+
ancestorDepth,
|
|
1827
|
+
query,
|
|
1726
1828
|
maxNodes,
|
|
1727
1829
|
maxChars,
|
|
1728
1830
|
});
|
|
@@ -1746,8 +1848,8 @@ async function main() {
|
|
|
1746
1848
|
break;
|
|
1747
1849
|
}
|
|
1748
1850
|
case 'browser_page_navigate': {
|
|
1749
|
-
const { tabId, url, refresh } = args;
|
|
1750
|
-
result = await client.pageNavigate({ tabId, url, refresh });
|
|
1851
|
+
const { tabId, url, refresh, history } = args;
|
|
1852
|
+
result = await client.pageNavigate({ tabId, url, refresh, history });
|
|
1751
1853
|
break;
|
|
1752
1854
|
}
|
|
1753
1855
|
case 'browser_page_upload': {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mearl/mcp-server",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "MCP Server for Mearl - enables AI to interact with Chrome browser requests and logs",
|
|
5
5
|
"mcpName": "io.github.F-loat/mearl",
|
|
6
6
|
"type": "module",
|
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@modelcontextprotocol/sdk": "^1.0.4",
|
|
32
32
|
"prompts": "^2.4.2",
|
|
33
|
-
"@mearl/client": "2.0
|
|
34
|
-
"@mearl/native-host": "2.0
|
|
33
|
+
"@mearl/client": "2.1.0",
|
|
34
|
+
"@mearl/native-host": "2.1.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@types/node": "^24.9.1",
|