@mearl/mcp-server 2.0.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 ADDED
@@ -0,0 +1,116 @@
1
+ # Mearl MCP Server
2
+
3
+ 让 AI 能够与浏览器实时交互的 MCP 服务器。
4
+
5
+ ## 功能
6
+
7
+ - 📊 **browser_get_requests**: 获取历史请求记录
8
+ - 📊 **browser_get_events**: 获取 RUM/aplus 埋点事件
9
+ - 📝 **browser_get_logs**: 获取控制台日志
10
+ - 🎭 **browser_set_mock**: 动态设置 Mock 数据
11
+ - 🌐 **browser_list**: 统一列出普通浏览器和托管浏览器,返回默认目标、类型及连接状态
12
+ - 🚀 **browser_launch / browser_close**: 启动或关闭独立 Profile 的托管 Chrome
13
+ - 🌐 **browser_page_snapshot**: 首次获取页面结构和可交互元素
14
+ - ⚡ **browser_page_click/type/hover/scroll/press/upload**: 执行页面动作,默认内置观察,返回通知、可交互控件和焦点等高置信度信号
15
+
16
+ ## 浏览器交互工作流
17
+
18
+ 首次进入页面时调用 `browser_page_snapshot` 建立结构基线。后续点击、输入、悬停、滚动、按键和上传直接调用 `browser_page_click/type/hover/scroll/press/upload`,它们默认内置观察,动作与异步同步在一次调用内完成;同页结果读取 `effects.notifications`、`effects.interactives` 和 `effects.focus`,导航结果读取新 URL 和就绪状态。仅在导航后、`fullSnapshotRecommended: true` 或需要视觉判断时重新 snapshot 或截图。
19
+
20
+ 长列表重新感知时优先使用 `browser_page_snapshot(mode: "viewport")`;已知弹层或区域时使用 `rootSelector`;仅需要控件时使用 `mode: "interactive"`,避免整页 AX Tree 占用上下文。页面缺少 AX 控件语义时,`interactive` 会自动回退到 viewport,并返回 `fallbackMode: "viewport"`。
21
+
22
+ 页面动作传 `observe: false` 可关闭内置观察仅执行裸动作;不需要在每次动作后固定追加 wait、snapshot 或 screenshot。
23
+
24
+ ## 安装
25
+
26
+ **方式一:使用 npx(推荐,保持最新)**
27
+
28
+ ```bash
29
+ # 直接运行配置向导,会自动生成 npx 配置
30
+ npx @mearl/mcp-server --configure
31
+ ```
32
+
33
+ **方式二:全局安装(启动更快)**
34
+
35
+ ```bash
36
+ # 全局安装
37
+ npm install -g @mearl/mcp-server
38
+
39
+ # 运行配置向导
40
+ mearl-mcp-configure
41
+ ```
42
+
43
+ 配置向导会自动完成所有设置,支持 **VSCode Copilot**、**Claude Code**、**Cursor**。完成后重启 MCP 客户端即可使用。
44
+
45
+ ## 使用
46
+
47
+ 打开任意淘宝/飞猪页面,在 AI 客户端中发送:
48
+
49
+ ```
50
+ 获取最近 5 个请求
51
+ 帮我分析购物车相关的接口
52
+ 把购物车总价改成 150 元测试一下
53
+ ```
54
+
55
+ ## 本地开发
56
+
57
+ ```bash
58
+ cd mcp-server
59
+ npm install
60
+ npm run build
61
+ npm link
62
+ ```
63
+
64
+ 运行配置向导,选择使用自定义 Extension ID:
65
+
66
+ ```bash
67
+ mearl-mcp-configure
68
+ # 选择 "使用自定义 Chrome 插件 ID?" -> Yes
69
+ # 输入你的开发版 Extension ID
70
+ ```
71
+
72
+ > 💡 在 `chrome://extensions/` 查看你的 Extension ID
73
+
74
+ 发布新版本:
75
+
76
+ ```bash
77
+ npm version patch
78
+ npm run build
79
+ npm publish --access public
80
+ ```
81
+
82
+ ## 卸载
83
+
84
+ ```bash
85
+ # 全局安装的版本
86
+ mearl-mcp-uninstall
87
+
88
+ # 或使用 npx
89
+ npx @mearl/mcp-server --uninstall
90
+ ```
91
+
92
+ ## 故障排查
93
+
94
+ **配置失败**:检查目录权限和路径是否存在
95
+
96
+ ```bash
97
+ # macOS
98
+ mkdir -p ~/Library/Application\ Support/Google/Chrome/NativeMessagingHosts
99
+ ```
100
+
101
+ **"Native host has exited"**:删除配置后重新运行
102
+
103
+ ```bash
104
+ rm ~/Library/Application\ Support/Google/Chrome/NativeMessagingHosts/com.alibaba.mearl.mcp.*
105
+ ```
106
+
107
+ **Extension ID 不匹配**:使用 `mearl-mcp-configure` 重新配置
108
+
109
+ **查看日志**:
110
+
111
+ - MCP Server: AI 客户端的输出面板
112
+ - Chrome Extension: `chrome://extensions/` → Mearl → background page
113
+
114
+ ## License
115
+
116
+ ISC
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * mearl-mcp-configure - 配置 Native Messaging Host 和 MCP 客户端
4
+ *
5
+ * 使用方式:
6
+ * mearl-mcp-configure # 交互式配置(会询问是否使用自定义 Extension ID)
7
+ * mearl-mcp-configure --extension-id=YOUR_ID # 直接指定自定义 Extension ID
8
+ * mearl-mcp-configure --skip-mcp # 跳过 MCP 配置
9
+ * mearl-mcp-configure --get # 查看当前配置
10
+ */
11
+ export {};
@@ -0,0 +1,289 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * mearl-mcp-configure - 配置 Native Messaging Host 和 MCP 客户端
4
+ *
5
+ * 使用方式:
6
+ * mearl-mcp-configure # 交互式配置(会询问是否使用自定义 Extension ID)
7
+ * mearl-mcp-configure --extension-id=YOUR_ID # 直接指定自定义 Extension ID
8
+ * mearl-mcp-configure --skip-mcp # 跳过 MCP 配置
9
+ * mearl-mcp-configure --get # 查看当前配置
10
+ */
11
+ import { installNativeHost, resolveHostDir } from '@mearl/native-host';
12
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
13
+ import { join, dirname } from 'path';
14
+ import { HOST_NAME, DEFAULT_EXTENSION_ID } from './constants.js';
15
+ import prompts from 'prompts';
16
+ import { MCP_CLIENTS, getMCPServerConfig, isNpxMode, isClientInstalled, } from './mcp-clients.js';
17
+ // 日志输出
18
+ const log = (...args) => {
19
+ console.log('[Configure]', ...args);
20
+ };
21
+ // 解析命令行参数
22
+ function parseArgs() {
23
+ const args = process.argv.slice(2);
24
+ let extensionId;
25
+ let skipMcp = false;
26
+ let get = false;
27
+ for (const arg of args) {
28
+ if (arg.startsWith('--extension-id=')) {
29
+ extensionId = arg.split('=')[1];
30
+ }
31
+ else if (arg.startsWith('--id=')) {
32
+ extensionId = arg.split('=')[1];
33
+ }
34
+ else if (arg === '--skip-mcp') {
35
+ skipMcp = true;
36
+ }
37
+ else if (arg === '--get') {
38
+ get = true;
39
+ }
40
+ }
41
+ return { extensionId, skipMcp, get };
42
+ }
43
+ // 替换或追加 TOML 中的 mcp_servers.xxx 节
44
+ function replaceTOMLSection(content, serverName, newSectionLines) {
45
+ const header = `[mcp_servers.${serverName}]`;
46
+ const lines = content.split('\n');
47
+ let sectionStart = -1;
48
+ let sectionEnd = lines.length;
49
+ for (let i = 0; i < lines.length; i++) {
50
+ const trimmed = lines[i].trim();
51
+ if (trimmed === header) {
52
+ sectionStart = i;
53
+ }
54
+ else if (sectionStart !== -1 && i > sectionStart && trimmed.startsWith('[')) {
55
+ sectionEnd = i;
56
+ break;
57
+ }
58
+ }
59
+ if (sectionStart === -1) {
60
+ const separator = content.length === 0 || content.endsWith('\n') ? '\n' : '\n\n';
61
+ return content + separator + newSectionLines.join('\n') + '\n';
62
+ }
63
+ const before = lines.slice(0, sectionStart);
64
+ const after = lines.slice(sectionEnd);
65
+ return [...before, ...newSectionLines, '', ...after].join('\n');
66
+ }
67
+ // 添加或更新 Codex TOML 配置
68
+ function updateTomlMCPConfig(configPath, useNpx) {
69
+ try {
70
+ const dir = dirname(configPath);
71
+ if (!existsSync(dir)) {
72
+ mkdirSync(dir, { recursive: true });
73
+ }
74
+ let content = '';
75
+ if (existsSync(configPath)) {
76
+ content = readFileSync(configPath, 'utf-8');
77
+ }
78
+ const sectionLines = ['[mcp_servers.mearl]', 'type = "stdio"'];
79
+ if (useNpx) {
80
+ sectionLines.push('command = "npx"');
81
+ sectionLines.push('args = ["-y", "@mearl/mcp-server"]');
82
+ }
83
+ else {
84
+ sectionLines.push('command = "mearl-mcp-server"');
85
+ }
86
+ content = replaceTOMLSection(content, 'mearl', sectionLines);
87
+ writeFileSync(configPath, content);
88
+ return true;
89
+ }
90
+ catch (error) {
91
+ console.error(` ❌ 配置失败: ${error instanceof Error ? error.message : String(error)}`);
92
+ return false;
93
+ }
94
+ }
95
+ // 添加或更新 MCP 配置
96
+ function updateMCPConfig(configPath, clientConfig, useNpx) {
97
+ if (clientConfig.useTomlFormat) {
98
+ return updateTomlMCPConfig(configPath, useNpx);
99
+ }
100
+ try {
101
+ // 确保目录存在
102
+ const dir = dirname(configPath);
103
+ if (!existsSync(dir)) {
104
+ mkdirSync(dir, { recursive: true });
105
+ }
106
+ const useServersFormat = clientConfig.useServersFormat;
107
+ // 读取或创建配置文件
108
+ let config;
109
+ if (useServersFormat) {
110
+ // VS Code 格式
111
+ config = { inputs: [], servers: {} };
112
+ if (existsSync(configPath)) {
113
+ try {
114
+ const content = readFileSync(configPath, 'utf-8');
115
+ config = JSON.parse(content);
116
+ if (!config.servers) {
117
+ config.servers = {};
118
+ }
119
+ if (!config.inputs) {
120
+ config.inputs = [];
121
+ }
122
+ }
123
+ catch (error) {
124
+ console.error(` ⚠️ 配置文件格式错误,将创建新配置: ${error instanceof Error ? error.message : String(error)}`);
125
+ config = { inputs: [], servers: {} };
126
+ }
127
+ }
128
+ // 添加或更新 mearl 配置
129
+ config.servers['mearl'] = getMCPServerConfig(true, useNpx);
130
+ }
131
+ else {
132
+ // Claude Code/Cursor 格式
133
+ config = { mcpServers: {} };
134
+ if (existsSync(configPath)) {
135
+ try {
136
+ const content = readFileSync(configPath, 'utf-8');
137
+ config = JSON.parse(content);
138
+ if (!config.mcpServers) {
139
+ config.mcpServers = {};
140
+ }
141
+ }
142
+ catch (error) {
143
+ console.error(` ⚠️ 配置文件格式错误,将创建新配置: ${error instanceof Error ? error.message : String(error)}`);
144
+ config = { mcpServers: {} };
145
+ }
146
+ }
147
+ // 添加或更新 mearl 配置
148
+ config.mcpServers['mearl'] = getMCPServerConfig(false, useNpx);
149
+ }
150
+ // 写回文件
151
+ writeFileSync(configPath, JSON.stringify(config, null, 2));
152
+ return true;
153
+ }
154
+ catch (error) {
155
+ console.error(` ❌ 配置失败: ${error instanceof Error ? error.message : String(error)}`);
156
+ return false;
157
+ }
158
+ }
159
+ // 查看当前配置
160
+ function showCurrentConfig() {
161
+ console.log('📋 当前 Native Messaging 配置\n');
162
+ try {
163
+ const manifestPath = join(resolveHostDir(), `${HOST_NAME}.json`);
164
+ if (!existsSync(manifestPath)) {
165
+ console.log('❌ 未找到配置文件');
166
+ console.log(` 路径: ${manifestPath}`);
167
+ console.log('\n💡 运行 "mearl-mcp-configure" 进行配置\n');
168
+ return;
169
+ }
170
+ const content = readFileSync(manifestPath, 'utf-8');
171
+ const manifest = JSON.parse(content);
172
+ console.log('✅ 配置文件已存在');
173
+ console.log(`📍 路径: ${manifestPath}\n`);
174
+ console.log('📝 配置内容:');
175
+ console.log(` Name: ${manifest.name}`);
176
+ console.log(` Path: ${manifest.path}`);
177
+ console.log(` Type: ${manifest.type}`);
178
+ if (manifest.allowed_origins && manifest.allowed_origins.length > 0) {
179
+ console.log('\n🔑 允许的扩展 ID:');
180
+ manifest.allowed_origins.forEach((origin) => {
181
+ const match = origin.match(/chrome-extension:\/\/([^/]+)\//);
182
+ if (match) {
183
+ console.log(` ${match[1]}`);
184
+ }
185
+ });
186
+ }
187
+ // 检查 wrapper 脚本是否存在
188
+ if (existsSync(manifest.path)) {
189
+ console.log('\n✅ Wrapper 脚本存在');
190
+ }
191
+ else {
192
+ console.log('\n⚠️ Wrapper 脚本不存在,需要重新配置');
193
+ }
194
+ console.log('');
195
+ }
196
+ catch (error) {
197
+ console.error('❌ 读取配置失败:', error instanceof Error ? error.message : String(error));
198
+ console.log('\n💡 运行 "mearl-mcp-configure" 重新配置\n');
199
+ }
200
+ }
201
+ async function main() {
202
+ const parseResult = parseArgs();
203
+ const { skipMcp, get } = parseResult;
204
+ const extensionId = parseResult.extensionId;
205
+ // 如果是查看配置,显示后退出
206
+ if (get) {
207
+ showCurrentConfig();
208
+ return;
209
+ }
210
+ console.log('🔧 Mearl MCP 配置\n');
211
+ // 检测 npx 模式
212
+ const useNpx = isNpxMode();
213
+ if (useNpx) {
214
+ log('检测到 npx 模式,将生成 npx 配置');
215
+ }
216
+ // 配置 Native Messaging Host
217
+ console.log('\n📦 配置 Chrome Native Messaging Host...');
218
+ // Install both host manifests so MCP and Skills integrations can be used at
219
+ // the same time (they share the same Unix socket and handlers).
220
+ await installNativeHost({
221
+ mode: 'both',
222
+ extensionId: extensionId || process.env.MEARL_EXTENSION_ID || DEFAULT_EXTENSION_ID,
223
+ silent: true,
224
+ });
225
+ console.log('✅ 配置完成\n');
226
+ // 配置 MCP 客户端
227
+ if (skipMcp) {
228
+ console.log('跳过 MCP 客户端配置\n');
229
+ }
230
+ else {
231
+ const response = await prompts({
232
+ type: 'confirm',
233
+ name: 'setupMcp',
234
+ message: '配置 MCP 客户端?',
235
+ initial: true,
236
+ });
237
+ if (response.setupMcp) {
238
+ // 列出全部支持的客户端,已检测到安装的默认预选
239
+ const allClients = Object.entries(MCP_CLIENTS).map(([key, client]) => ({
240
+ title: isClientInstalled(key) ? `${client.name} ✓` : client.name,
241
+ value: key,
242
+ selected: isClientInstalled(key),
243
+ }));
244
+ const detectedNames = allClients.filter(c => c.selected).map(c => c.title);
245
+ if (detectedNames.length > 0) {
246
+ console.log(`\n已检测到: ${detectedNames.join(', ')}\n`);
247
+ }
248
+ else {
249
+ console.log('\n未检测到已安装的客户端,可手动选择\n');
250
+ }
251
+ const clientResponse = await prompts({
252
+ type: 'multiselect',
253
+ name: 'clients',
254
+ message: '选择要配置的客户端:',
255
+ choices: allClients,
256
+ instructions: false,
257
+ hint: '空格选择,回车确认',
258
+ });
259
+ if (clientResponse.clients && clientResponse.clients.length > 0) {
260
+ console.log('');
261
+ for (const clientKey of clientResponse.clients) {
262
+ const client = MCP_CLIENTS[clientKey];
263
+ if (updateMCPConfig(client.configPath, client, useNpx)) {
264
+ console.log(`✅ ${client.name}`);
265
+ }
266
+ else {
267
+ console.log(`❌ ${client.name} 配置失败`);
268
+ }
269
+ }
270
+ console.log('');
271
+ }
272
+ else {
273
+ console.log('已取消\n');
274
+ }
275
+ }
276
+ else {
277
+ console.log('跳过 MCP 客户端配置\n');
278
+ }
279
+ }
280
+ console.log('\n✅ 全部配置完成!');
281
+ console.log('\n💡 后续步骤:');
282
+ console.log(' 1. 重启 MCP 客户端');
283
+ console.log(' 2. 打开淘宝/飞猪页面');
284
+ console.log(' 3. 在 AI 中测试: "获取最近 5 个请求"\n');
285
+ }
286
+ main().catch(error => {
287
+ console.error('Configuration failed:', error);
288
+ process.exit(1);
289
+ });
@@ -0,0 +1,10 @@
1
+ /**
2
+ * MCP Server 共享常量
3
+ */
4
+ export declare const DEFAULT_MAX_BODY_SIZE = 50000;
5
+ export declare const RESPONSE_SIZE_SMALL: number;
6
+ export declare const RESPONSE_SIZE_MEDIUM: number;
7
+ export declare const RESPONSE_SIZE_LARGE: number;
8
+ export declare const RESPONSE_SIZE_MAX: number;
9
+ export declare const HOST_NAME = "com.alibaba.mearl.mcp";
10
+ export declare const DEFAULT_EXTENSION_ID = "aoehhjnofngknnjefamjbplchbolghkm";
@@ -0,0 +1,13 @@
1
+ /**
2
+ * MCP Server 共享常量
3
+ */
4
+ // 响应体大小限制
5
+ export const DEFAULT_MAX_BODY_SIZE = 50000; // 默认最大 50KB,避免传输过大数据
6
+ // MCP 响应大小阈值
7
+ export const RESPONSE_SIZE_SMALL = 50 * 1024; // 50KB - 小响应,格式化输出
8
+ export const RESPONSE_SIZE_MEDIUM = 200 * 1024; // 200KB - 中等响应,格式化+提示
9
+ export const RESPONSE_SIZE_LARGE = 500 * 1024; // 500KB - 大响应,压缩+提示
10
+ export const RESPONSE_SIZE_MAX = 1024 * 1024; // 1MB - 超大响应,截断+建议
11
+ // Native Messaging Host 配置
12
+ export const HOST_NAME = 'com.alibaba.mearl.mcp';
13
+ export const DEFAULT_EXTENSION_ID = 'aoehhjnofngknnjefamjbplchbolghkm';
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Mearl MCP Server
4
+ *
5
+ * 提供 MCP 工具让 AI 能够与浏览器交互:
6
+ * - browser_get_requests: 获取历史请求
7
+ * - browser_get_events: 获取 RUM/aplus 埋点事件
8
+ * - browser_get_logs: 获取控制台日志
9
+ * - browser_set_mock: 设置 Mock 数据
10
+ *
11
+ * 命令行参数:
12
+ * - configure / --configure: 运行配置向导
13
+ * - uninstall / --uninstall: 卸载并清理配置
14
+ * - --extension-id=ID: 指定 Chrome Extension ID
15
+ */
16
+ export {};