@mearl/mcp-server 2.0.2 → 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.
Files changed (3) hide show
  1. package/README.md +1 -0
  2. package/dist/index.js +59 -32
  3. package/package.json +3 -3
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,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
- 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
- }
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: `${header}\n\n${'─'.repeat(60)}\n\n${truncatedStr}\n\n... <TRUNCATED ${sizeKB - Math.round(truncateSize / 1024)}KB>`,
276
+ text: JSON.stringify(diagnostic, null, 2),
277
277
  },
278
278
  ];
279
279
  }
@@ -317,7 +317,7 @@ async function main() {
317
317
  // 创建 MCP Server
318
318
  const server = new Server({
319
319
  name: 'mearl',
320
- version: '1.0.0',
320
+ version: MCP_SERVER_VERSION,
321
321
  }, {
322
322
  capabilities: {
323
323
  tools: {},
@@ -466,6 +466,10 @@ async function main() {
466
466
  inputSchema: {
467
467
  type: 'object',
468
468
  properties: {
469
+ tabId: {
470
+ type: 'number',
471
+ description: '目标标签页 ID,从 browser_tab_list 获取;不传时使用当前活动标签页',
472
+ },
469
473
  level: {
470
474
  type: 'string',
471
475
  enum: ['log', 'warn', 'error', 'info', 'all'],
@@ -494,6 +498,10 @@ async function main() {
494
498
  inputSchema: {
495
499
  type: 'object',
496
500
  properties: {
501
+ tabId: {
502
+ type: 'number',
503
+ description: '目标标签页 ID,从 browser_tab_list 获取;不传时使用当前活动标签页',
504
+ },
497
505
  apiName: {
498
506
  type: 'string',
499
507
  description: 'API 名称或正则表达式模式。例如: "mtop.cart.query" 精确匹配, ".*\\.cart\\..*" 匹配所有购物车接口',
@@ -547,6 +555,10 @@ async function main() {
547
555
  inputSchema: {
548
556
  type: 'object',
549
557
  properties: {
558
+ tabId: {
559
+ type: 'number',
560
+ description: '目标标签页 ID,从 browser_tab_list 获取;不传时使用当前活动标签页',
561
+ },
550
562
  apiName: {
551
563
  type: 'string',
552
564
  description: '过滤指定接口,支持正则表达式。例如: "mtop.cart.query" 或 ".*\\.cart\\..*"。不传则返回所有 Mock',
@@ -964,6 +976,14 @@ async function main() {
964
976
  required: ['browser'],
965
977
  },
966
978
  },
979
+ {
980
+ name: 'browser_release',
981
+ description: '释放 Agent 对当前目标浏览器的 debugger/CDP 控制,停止持续采集和会话级临时效果,但保留浏览器与全部标签页。后续浏览器操作仍可自动重新建立控制。',
982
+ inputSchema: {
983
+ type: 'object',
984
+ properties: {},
985
+ },
986
+ },
967
987
  {
968
988
  name: 'browser_tab_open',
969
989
  description: '打开或按 URL 规则复用标签页。默认新建并等待加载完成;reuse=prefer 可优先复用,reuse=require 可限制为只复用。响应的 opened/reused 可用于判断流程结束时是否应关闭该 Tab。',
@@ -1590,13 +1610,13 @@ async function main() {
1590
1610
  break;
1591
1611
  }
1592
1612
  case 'browser_get_logs': {
1593
- const { level = 'all', since, limit = 100, filter } = args;
1594
- 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 });
1595
1615
  break;
1596
1616
  }
1597
1617
  case 'browser_set_mock': {
1598
1618
  const { payloadFile } = args;
1599
- let { apiName, mockData, fields, enabled, duration, conditions } = args;
1619
+ let { tabId, apiName, mockData, fields, enabled, duration, conditions } = args;
1600
1620
  enabled = enabled ?? true;
1601
1621
  // 如果提供了 payloadFile,从文件读取 mock 数据
1602
1622
  if (payloadFile) {
@@ -1605,7 +1625,9 @@ async function main() {
1605
1625
  }
1606
1626
  const fileContent = readFileSync(payloadFile, 'utf-8');
1607
1627
  const fileData = JSON.parse(fileContent);
1608
- // 文件内容可以包含 apiName、mockData、fields、enabled、duration、conditions 等参数
1628
+ // 文件内容可以包含 tabId、apiName、mockData、fields、enabled、duration、conditions 等参数
1629
+ if (fileData.tabId !== undefined)
1630
+ tabId = fileData.tabId;
1609
1631
  if (fileData.apiName)
1610
1632
  apiName = fileData.apiName;
1611
1633
  if (fileData.mockData !== undefined)
@@ -1620,6 +1642,7 @@ async function main() {
1620
1642
  conditions = fileData.conditions;
1621
1643
  }
1622
1644
  result = await client.setMock({
1645
+ tabId,
1623
1646
  apiName,
1624
1647
  mockData,
1625
1648
  fields,
@@ -1630,8 +1653,8 @@ async function main() {
1630
1653
  break;
1631
1654
  }
1632
1655
  case 'browser_get_mocks': {
1633
- const { apiName } = args;
1634
- result = await client.getMocks({ apiName });
1656
+ const { tabId, apiName } = args;
1657
+ result = await client.getMocks({ tabId, apiName });
1635
1658
  break;
1636
1659
  }
1637
1660
  case 'browser_get_api_schema': {
@@ -1723,6 +1746,10 @@ async function main() {
1723
1746
  result = await client.browserClose(args);
1724
1747
  break;
1725
1748
  }
1749
+ case 'browser_release': {
1750
+ result = await client.browserRelease();
1751
+ break;
1752
+ }
1726
1753
  case 'browser_tab_open': {
1727
1754
  const { url, active = false, closeExisting = false, reuse = 'never', match, restoreFocusOnClose = false, emulation, group, } = args;
1728
1755
  result = await client.tabOpen({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mearl/mcp-server",
3
- "version": "2.0.2",
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/native-host": "2.0.2",
34
- "@mearl/client": "2.0.2"
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",