@mearl/mcp-server 2.0.2 → 2.2.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
@@ -9,6 +9,7 @@
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**: 执行页面动作,默认内置观察,返回通知、可交互控件和焦点等高置信度信号
package/dist/index.js CHANGED
@@ -22,7 +22,51 @@ 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;
29
+ const MCP_TOOL_ACTIONS = {
30
+ browser_get_requests: 'get_requests',
31
+ browser_get_events: 'get_events',
32
+ browser_get_logs: 'get_logs',
33
+ browser_set_mock: 'set_mock',
34
+ browser_get_mocks: 'get_mocks',
35
+ browser_get_api_schema: 'get_api_schema',
36
+ browser_send_request: 'send_request',
37
+ browser_send_mtop_request: 'send_mtop_request',
38
+ browser_set_rule: 'set_rule',
39
+ browser_get_rules: 'get_rules',
40
+ browser_page_screenshot: 'page_screenshot',
41
+ browser_page_selected_element: 'page_selected_element',
42
+ get_user_info: 'get_user_info',
43
+ tdbank_account: 'tdbank_account',
44
+ browser_launch: 'browser_launch',
45
+ browser_close: 'browser_close',
46
+ browser_release: 'browser_release',
47
+ browser_tab_open: 'tab_open',
48
+ browser_tab_close: 'tab_close',
49
+ browser_tab_list: 'tab_list',
50
+ browser_page_click: 'page_click',
51
+ browser_page_type: 'page_type',
52
+ browser_page_scroll: 'page_scroll',
53
+ browser_page_hover: 'page_hover',
54
+ browser_page_eval: 'page_eval',
55
+ browser_page_snapshot: 'page_snapshot',
56
+ browser_page_press: 'page_press',
57
+ browser_page_wait: 'page_wait',
58
+ browser_page_navigate: 'page_navigate',
59
+ browser_page_upload: 'page_upload',
60
+ browser_page_frames: 'page_frames',
61
+ browser_set_device_emulation: 'set_device_emulation',
62
+ browser_set_timezone: 'set_timezone',
63
+ browser_run_actions: 'run_actions',
64
+ browser_list: 'browser_list',
65
+ browser_select_browser: 'browser_select_browser',
66
+ };
67
+ function defineMcpTools(tools) {
68
+ return tools;
69
+ }
26
70
  // 日志输出到 stderr(不影响 stdio 通信)
27
71
  const LOG_FILE = join(tmpdir(), 'mearl-mcp-server.log');
28
72
  const log = (...args) => {
@@ -245,35 +289,32 @@ function formatResponseWithSizeCheck(result, toolName, args) {
245
289
  },
246
290
  ];
247
291
  }
248
- // 策略 5: 超超大响应(>1MB 压缩)- 截断 + 错误级别建议
249
- const truncateSize = RESPONSE_SIZE_MAX;
250
- const truncatedStr = compactStr.substring(0, truncateSize);
292
+ // 策略 5: 超超大响应(>1MB 压缩)- 返回可解析的诊断信息
251
293
  const suggestions = getOptimizationSuggestions(toolName, args, result, stats);
252
- let header = `🛑 Response too large: ${sizeKB}KB (TRUNCATED to ${Math.round(truncateSize / 1024)}KB)\n`;
253
- header += `Original size: ${sizeKB}KB, showing only ${Math.round(truncateSize / 1024)}KB\n`;
254
- header += `\n⚠️ This response is unusable at this size. You MUST optimize the request.\n`;
255
- if (stats.meta) {
256
- header += `\n📊 Data: ${stats.meta.count} items (${stats.meta.totalMatched} matched, ${stats.meta.totalInBuffer} in buffer)`;
257
- }
258
- if (stats.largestFields && stats.largestFields.length > 0) {
259
- header += `\nLargest fields per item:`;
260
- stats.largestFields.forEach(f => {
261
- header += `\n • ${f.path}: ~${Math.round(f.size / 1024)}KB (${Math.round((f.size / compactSize) * 100)}% of total)`;
262
- });
263
- }
264
- header += `\n\n🎯 REQUIRED actions:`;
265
- if (suggestions.length > 0) {
266
- header += suggestions.map((s, i) => `\n ${i + 1}. ${s}`).join('');
267
- }
268
- else {
269
- header += `\n 1. Significantly reduce the data requested`;
270
- header += `\n 2. Use "fields" to extract only essential data`;
271
- header += `\n 3. Add filters to narrow down results`;
272
- }
294
+ const diagnostic = {
295
+ error: {
296
+ code: 'RESPONSE_TOO_LARGE',
297
+ message: 'The result exceeded the MCP response limit. Narrow the request and retry.',
298
+ originalSizeBytes: compactSize,
299
+ limitBytes: RESPONSE_SIZE_MAX,
300
+ },
301
+ meta: stats.meta,
302
+ largestFields: stats.largestFields?.map(field => ({
303
+ ...field,
304
+ percentageOfTotal: Math.round((field.size / compactSize) * 100),
305
+ })),
306
+ suggestions: suggestions.length > 0
307
+ ? suggestions
308
+ : [
309
+ 'Significantly reduce the data requested',
310
+ 'Use "fields" to extract only essential data',
311
+ 'Add filters to narrow down results',
312
+ ],
313
+ };
273
314
  return [
274
315
  {
275
316
  type: 'text',
276
- text: `${header}\n\n${'─'.repeat(60)}\n\n${truncatedStr}\n\n... <TRUNCATED ${sizeKB - Math.round(truncateSize / 1024)}KB>`,
317
+ text: JSON.stringify(diagnostic, null, 2),
277
318
  },
278
319
  ];
279
320
  }
@@ -317,7 +358,7 @@ async function main() {
317
358
  // 创建 MCP Server
318
359
  const server = new Server({
319
360
  name: 'mearl',
320
- version: '1.0.0',
361
+ version: MCP_SERVER_VERSION,
321
362
  }, {
322
363
  capabilities: {
323
364
  tools: {},
@@ -360,7 +401,7 @@ async function main() {
360
401
  },
361
402
  };
362
403
  // 定义工具列表
363
- const tools = [
404
+ const tools = defineMcpTools([
364
405
  {
365
406
  name: 'browser_get_requests',
366
407
  description: '获取浏览器中最近的 API 请求历史记录,包含完整的请求和响应信息。适用于分析性能、排查问题、查看数据流等场景。无需等待用户操作,立即返回已有数据。',
@@ -466,6 +507,10 @@ async function main() {
466
507
  inputSchema: {
467
508
  type: 'object',
468
509
  properties: {
510
+ tabId: {
511
+ type: 'number',
512
+ description: '目标标签页 ID,从 browser_tab_list 获取;不传时使用当前活动标签页',
513
+ },
469
514
  level: {
470
515
  type: 'string',
471
516
  enum: ['log', 'warn', 'error', 'info', 'all'],
@@ -494,6 +539,10 @@ async function main() {
494
539
  inputSchema: {
495
540
  type: 'object',
496
541
  properties: {
542
+ tabId: {
543
+ type: 'number',
544
+ description: '目标标签页 ID,从 browser_tab_list 获取;不传时使用当前活动标签页',
545
+ },
497
546
  apiName: {
498
547
  type: 'string',
499
548
  description: 'API 名称或正则表达式模式。例如: "mtop.cart.query" 精确匹配, ".*\\.cart\\..*" 匹配所有购物车接口',
@@ -547,6 +596,10 @@ async function main() {
547
596
  inputSchema: {
548
597
  type: 'object',
549
598
  properties: {
599
+ tabId: {
600
+ type: 'number',
601
+ description: '目标标签页 ID,从 browser_tab_list 获取;不传时使用当前活动标签页',
602
+ },
550
603
  apiName: {
551
604
  type: 'string',
552
605
  description: '过滤指定接口,支持正则表达式。例如: "mtop.cart.query" 或 ".*\\.cart\\..*"。不传则返回所有 Mock',
@@ -964,6 +1017,14 @@ async function main() {
964
1017
  required: ['browser'],
965
1018
  },
966
1019
  },
1020
+ {
1021
+ name: 'browser_release',
1022
+ description: '仅当用户明确要求结束调试、退出接管或清理会话资源时,释放当前目标浏览器的 debugger/CDP 控制;不要作为普通任务收尾。它会停止持续采集,并立即清除设备/时区模拟、会话级 Mock 和录制,但保留浏览器与全部标签页。后续浏览器操作仍可自动重新建立控制。',
1023
+ inputSchema: {
1024
+ type: 'object',
1025
+ properties: {},
1026
+ },
1027
+ },
967
1028
  {
968
1029
  name: 'browser_tab_open',
969
1030
  description: '打开或按 URL 规则复用标签页。默认新建并等待加载完成;reuse=prefer 可优先复用,reuse=require 可限制为只复用。响应的 opened/reused 可用于判断流程结束时是否应关闭该 Tab。',
@@ -1435,7 +1496,7 @@ async function main() {
1435
1496
  },
1436
1497
  {
1437
1498
  name: 'browser_set_device_emulation',
1438
- description: '把目标标签页切换为移动端模拟态:视口尺寸 / DPR / UA / 触摸事件全部按指定设备生效,等价于 Chrome DevTools 设备工具栏,作用域仅限该 tab,会话结束自动还原。适用于:调试 H5 移动页面、让 page_click 在 swiper/fastclick 等只听 touchstart 的页面生效、让服务端按移动 UA 返回数据。启用后默认 reload 一次让页面以移动模式重新初始化。enabled=false 清除模拟。',
1499
+ description: '把目标标签页切换为移动端模拟态:视口尺寸 / DPR / UA / 触摸事件全部按指定设备生效,等价于 Chrome DevTools 设备工具栏,作用域仅限该 tab,释放 debugger/CDP 控制会自动还原。适用于:调试 H5 移动页面、让 page_click 在 swiper/fastclick 等只听 touchstart 的页面生效、让服务端按移动 UA 返回数据。启用后默认 reload 一次让页面以移动模式重新初始化;成功响应中的实际配置可直接验证设置结果,无需固定追加快照。enabled=false 清除模拟。',
1439
1500
  inputSchema: {
1440
1501
  type: 'object',
1441
1502
  properties: {
@@ -1549,7 +1610,7 @@ async function main() {
1549
1610
  required: ['browser'],
1550
1611
  },
1551
1612
  },
1552
- ];
1613
+ ]);
1553
1614
  // 处理工具列表请求
1554
1615
  server.setRequestHandler(ListToolsRequestSchema, async () => {
1555
1616
  return { tools };
@@ -1558,8 +1619,11 @@ async function main() {
1558
1619
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
1559
1620
  const { name, arguments: args } = request.params;
1560
1621
  try {
1622
+ if (!(name in MCP_TOOL_ACTIONS))
1623
+ throw new Error(`Unknown tool: ${name}`);
1624
+ const toolName = name;
1561
1625
  let result;
1562
- switch (name) {
1626
+ switch (toolName) {
1563
1627
  case 'browser_get_requests': {
1564
1628
  const { tabId, count = 10, filter, since, includeBody = true, includeHeaders = false, maxBodySize = 50000, fields, keysOnly = false, source, } = args;
1565
1629
  result = await client.getRequests({
@@ -1590,13 +1654,13 @@ async function main() {
1590
1654
  break;
1591
1655
  }
1592
1656
  case 'browser_get_logs': {
1593
- const { level = 'all', since, limit = 100, filter } = args;
1594
- result = await client.getLogs({ level, since, limit, filter });
1657
+ const { tabId, level = 'all', since, limit = 100, filter } = args;
1658
+ result = await client.getLogs({ tabId, level, since, limit, filter });
1595
1659
  break;
1596
1660
  }
1597
1661
  case 'browser_set_mock': {
1598
1662
  const { payloadFile } = args;
1599
- let { apiName, mockData, fields, enabled, duration, conditions } = args;
1663
+ let { tabId, apiName, mockData, fields, enabled, duration, conditions } = args;
1600
1664
  enabled = enabled ?? true;
1601
1665
  // 如果提供了 payloadFile,从文件读取 mock 数据
1602
1666
  if (payloadFile) {
@@ -1605,7 +1669,9 @@ async function main() {
1605
1669
  }
1606
1670
  const fileContent = readFileSync(payloadFile, 'utf-8');
1607
1671
  const fileData = JSON.parse(fileContent);
1608
- // 文件内容可以包含 apiName、mockData、fields、enabled、duration、conditions 等参数
1672
+ // 文件内容可以包含 tabId、apiName、mockData、fields、enabled、duration、conditions 等参数
1673
+ if (fileData.tabId !== undefined)
1674
+ tabId = fileData.tabId;
1609
1675
  if (fileData.apiName)
1610
1676
  apiName = fileData.apiName;
1611
1677
  if (fileData.mockData !== undefined)
@@ -1620,6 +1686,7 @@ async function main() {
1620
1686
  conditions = fileData.conditions;
1621
1687
  }
1622
1688
  result = await client.setMock({
1689
+ tabId,
1623
1690
  apiName,
1624
1691
  mockData,
1625
1692
  fields,
@@ -1630,8 +1697,8 @@ async function main() {
1630
1697
  break;
1631
1698
  }
1632
1699
  case 'browser_get_mocks': {
1633
- const { apiName } = args;
1634
- result = await client.getMocks({ apiName });
1700
+ const { tabId, apiName } = args;
1701
+ result = await client.getMocks({ tabId, apiName });
1635
1702
  break;
1636
1703
  }
1637
1704
  case 'browser_get_api_schema': {
@@ -1723,6 +1790,10 @@ async function main() {
1723
1790
  result = await client.browserClose(args);
1724
1791
  break;
1725
1792
  }
1793
+ case 'browser_release': {
1794
+ result = await client.browserRelease();
1795
+ break;
1796
+ }
1726
1797
  case 'browser_tab_open': {
1727
1798
  const { url, active = false, closeExisting = false, reuse = 'never', match, restoreFocusOnClose = false, emulation, group, } = args;
1728
1799
  result = await client.tabOpen({
@@ -1879,8 +1950,10 @@ async function main() {
1879
1950
  result = await client.selectBrowser(args);
1880
1951
  break;
1881
1952
  }
1882
- default:
1883
- throw new Error(`Unknown tool: ${name}`);
1953
+ default: {
1954
+ const unhandledTool = toolName;
1955
+ throw new Error(`Unknown tool: ${unhandledTool}`);
1956
+ }
1884
1957
  }
1885
1958
  // 使用智能格式化返回结果
1886
1959
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mearl/mcp-server",
3
- "version": "2.0.2",
3
+ "version": "2.2.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/native-host": "2.0.2",
34
- "@mearl/client": "2.0.2"
33
+ "@mearl/client": "2.2.0",
34
+ "@mearl/native-host": "2.2.0"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@types/node": "^24.9.1",
package/dist/types.d.ts DELETED
@@ -1,120 +0,0 @@
1
- export interface MtopRequest {
2
- id: number;
3
- timestamp: string;
4
- timestampMs: number;
5
- method: string;
6
- url: string;
7
- apiName: string;
8
- status: number;
9
- statusText: string;
10
- headers: Array<{
11
- name: string;
12
- value: string;
13
- }>;
14
- responseHeaders: Array<{
15
- name: string;
16
- value: string;
17
- }>;
18
- queryString: Record<string, string>;
19
- postData: Record<string, any> | null;
20
- responseBody: any;
21
- duration?: number;
22
- proxyData?: any;
23
- }
24
- export interface ConsoleLog {
25
- level: 'log' | 'warn' | 'error' | 'info';
26
- message: string;
27
- timestamp: number;
28
- /** 来源文件和行号,格式 "filename.js:42",仅在可获取时存在 */
29
- source?: string;
30
- args: any[];
31
- }
32
- export interface WaitForRequestParams {
33
- pattern: string;
34
- timeout?: number;
35
- includeExisting?: boolean;
36
- }
37
- export interface GetRequestsParams {
38
- count?: number;
39
- filter?: string;
40
- since?: number;
41
- }
42
- export interface GetLogsParams {
43
- level?: 'log' | 'warn' | 'error' | 'info' | 'all';
44
- since?: number;
45
- limit?: number;
46
- filter?: string;
47
- }
48
- export interface SetMockParams {
49
- apiName: string;
50
- mockData: any;
51
- enabled?: boolean;
52
- duration?: number;
53
- conditions?: Record<string, any>;
54
- }
55
- export interface GetApiSchemaParams {
56
- api: string;
57
- version?: string;
58
- }
59
- export interface GetSelectedElementParams {
60
- includeOuterHTML?: boolean;
61
- includeScreenshot?: boolean;
62
- }
63
- export interface RequestsResponse {
64
- meta: {
65
- /** 实际返回的请求数量 */
66
- count: number;
67
- /** 缓冲区中符合过滤条件的总请求数 */
68
- totalMatched: number;
69
- /** 缓冲区中的总请求数(未过滤) */
70
- totalInBuffer: number;
71
- /** 应用的过滤器(如果有) */
72
- filter?: string;
73
- /** 时间过滤器(秒) */
74
- since?: number;
75
- /** 是否包含请求/响应体 */
76
- includeBody: boolean;
77
- /** 是否包含请求/响应头 */
78
- includeHeaders: boolean;
79
- /** 提取的字段(如果有) */
80
- fields?: string[];
81
- /** 是否只返回键结构 */
82
- keysOnly: boolean;
83
- };
84
- /** 请求数据列表 */
85
- requests: MtopRequest[];
86
- }
87
- export interface LogsResponse {
88
- meta: {
89
- /** 实际返回的日志数量 */
90
- count: number;
91
- /** 符合过滤条件的总日志数 */
92
- totalMatched: number;
93
- /** 应用的日志级别过滤 */
94
- level: string;
95
- /** 时间过滤器(秒) */
96
- since?: number;
97
- /** 应用的过滤模式 */
98
- filter?: string;
99
- };
100
- /** 日志数据列表 */
101
- logs: ConsoleLog[];
102
- }
103
- export interface ScreenshotResponse {
104
- meta: {
105
- /** 截图格式 */
106
- format: 'png' | 'jpeg';
107
- /** 截图宽度 */
108
- width: number;
109
- /** 截图高度 */
110
- height: number;
111
- /** 截图数据大小(字节) */
112
- size: number;
113
- /** 截图的页面 URL */
114
- url: string;
115
- /** 截图的页面标题 */
116
- title: string;
117
- };
118
- /** Base64 编码的截图数据 */
119
- data: string;
120
- }
package/dist/types.js DELETED
@@ -1,2 +0,0 @@
1
- // 消息类型定义
2
- export {};