botbrowser-mcp 0.1.6 → 0.1.8

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
@@ -1,16 +1,18 @@
1
- # botbrowser-mcp
1
+ # BotBrowser MCP
2
2
 
3
- A wrapper around [@playwright/mcp](https://www.npmjs.com/package/@playwright/mcp).
3
+ 多实例浏览器自动化 MCP Server,集成 [@playwright/mcp](https://www.npmjs.com/package/@playwright/mcp) 的 22 个浏览器工具,支持配置管理、账号管理和多实例切换。
4
4
 
5
- ## Installation
5
+ **版本:** v0.1.6
6
+
7
+ ## 安装
6
8
 
7
9
  ```bash
8
10
  npm install -g botbrowser-mcp
9
11
  ```
10
12
 
11
- ## Usage
13
+ ## 配置
12
14
 
13
- Add to your MCP client configuration (e.g., Claude Desktop config):
15
+ MCP 客户端(如 Claude Desktop)的配置文件中添加:
14
16
 
15
17
  ```json
16
18
  {
@@ -22,7 +24,7 @@ Add to your MCP client configuration (e.g., Claude Desktop config):
22
24
  }
23
25
  ```
24
26
 
25
- Or use with npx (no installation needed):
27
+ 或使用 npx(无需安装):
26
28
 
27
29
  ```json
28
30
  {
@@ -35,14 +37,226 @@ Or use with npx (no installation needed):
35
37
  }
36
38
  ```
37
39
 
38
- ## Troubleshooting
40
+ ## 核心概念
41
+
42
+ ### 三层数据模型
43
+
44
+ 1. **浏览器配置 (Profile)** - 定义浏览器启动参数
45
+ - Chrome 可执行文件路径
46
+ - Cookie/LocalStorage 存储路径
47
+ - 代理设置
48
+
49
+ 2. **账号 (Account)** - 绑定到配置的用户账号
50
+ - 平台标识(twitter, github 等)
51
+ - 用户名和元数据(密码、2FA 等)
52
+
53
+ 3. **浏览器实例 (Instance)** - 运行中的浏览器
54
+ - 基于某个配置启动
55
+ - 可选关联某个账号
56
+ - 同时只有一个实例为活跃状态
57
+
58
+ ## 快速开始
59
+
60
+ ### 示例:管理多个 Twitter 账号
61
+
62
+ ```
63
+ # 1. 创建浏览器配置
64
+ create_browser_profile(
65
+ alias: "twitter-bot",
66
+ executable_path: "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
67
+ storage_state_path: "/Users/me/.botbrowser/twitter_cookies.json"
68
+ )
69
+
70
+ # 2. 添加账号
71
+ add_account(
72
+ profile_alias: "twitter-bot",
73
+ platform: "twitter",
74
+ identifier: "account1",
75
+ username: "account1@twitter.com",
76
+ metadata: "password: SecurePass123"
77
+ )
78
+
79
+ # 3. 启动浏览器(自动成为活跃实例)
80
+ launch_browser(profile_alias: "twitter-bot", account_id: 1)
81
+
82
+ # 4. 使用浏览器工具(自动作用于活跃实例)
83
+ browser_navigate(url: "https://twitter.com")
84
+ browser_click(element: "登录按钮")
85
+
86
+ # 5. 启动第二个实例
87
+ launch_browser(profile_alias: "twitter-bot", account_id: 2)
88
+
89
+ # 6. 切换回第一个实例
90
+ switch_browser_instance(instance_id: 1)
91
+ ```
92
+
93
+ ## 可用工具 (36个)
94
+
95
+ ### 配置管理 (4个)
96
+ - `list_browser_profiles` - 列出所有配置
97
+ - `create_browser_profile` - 创建新配置
98
+ - `update_browser_profile` - 更新配置
99
+ - `delete_browser_profile` - 删除配置
100
+
101
+ ### 账号管理 (5个)
102
+ - `add_account` - 添加账号
103
+ - `list_accounts` - 列出账号
104
+ - `find_account` - 查找账号
105
+ - `update_account` - 更新账号
106
+ - `delete_account` - 删除账号
107
+
108
+ ### 实例管理 (5个)
109
+ - `launch_browser` - 启动浏览器
110
+ - `list_browser_instances` - 列出实例
111
+ - `switch_browser_instance` - 切换活跃实例
112
+ - `stop_browser_instance` - 停止实例
113
+ - `cleanup_orphaned_instances` - 清理孤立记录
114
+
115
+ ### 浏览器操作 (22个 - 来自 @playwright/mcp)
116
+ - `browser_navigate` - 导航到 URL
117
+ - `browser_click` - 点击元素
118
+ - `browser_type` - 输入文本
119
+ - `browser_screenshot` - 截图
120
+ - `browser_evaluate` - 执行 JavaScript
121
+ - `browser_fill_form` - 批量填充表单
122
+ - `browser_select_option` - 选择下拉选项
123
+ - `browser_hover` - 鼠标悬停
124
+ - `browser_drag` - 拖拽操作
125
+ - `browser_handle_dialog` - 处理对话框
126
+ - `browser_file_upload` - 文件上传
127
+ - `browser_navigate_back` - 后退
128
+ - `browser_tabs` - 标签页管理
129
+ - `browser_wait_for` - 等待元素/文本/时间
130
+ - `browser_console_messages` - 获取控制台消息
131
+ - `browser_network_requests` - 获取网络请求
132
+ - `browser_run_code` - 执行 Playwright 代码片段
133
+ - `browser_close` - 关闭页面
134
+ - 其他高级操作工具...
135
+
136
+ ## 架构
137
+
138
+ ```
139
+ ┌─────────────────────────────────────────┐
140
+ │ LLM Agent (Claude/Cursor) │
141
+ │ "启动 twitter-bot 的 account1" │
142
+ └────────────────┬────────────────────────┘
143
+ │ stdio (MCP Protocol)
144
+
145
+ ┌─────────────────────────────────────────┐
146
+ │ BotBrowser-MCP Server │
147
+ │ │
148
+ │ ┌─────────────────────────────────┐ │
149
+ │ │ 自定义工具 (14个) │ │
150
+ │ │ - 配置管理 (4) │ │
151
+ │ │ - 账号管理 (5) │ │
152
+ │ │ - 实例管理 (5) │ │
153
+ │ └─────────────────────────────────┘ │
154
+ │ │
155
+ │ ┌─────────────────────────────────┐ │
156
+ │ │ PlaywrightMCPProxy │ │
157
+ │ │ - 包装 @playwright/mcp │ │
158
+ │ │ - 动态上下文切换 │ │
159
+ │ │ - 浏览器工具 (22个) │ │
160
+ │ └─────────────────────────────────┘ │
161
+ │ │
162
+ │ ┌─────────────────────────────────┐ │
163
+ │ │ PlaywrightManager │ │
164
+ │ │ instances: Map { │ │
165
+ │ │ 1: { context, browser } │ │
166
+ │ │ 2: { context, browser } │ │
167
+ │ │ } │ │
168
+ │ │ activeInstanceId: 1 │ │
169
+ │ └─────────────────────────────────┘ │
170
+ │ │
171
+ │ ┌─────────────────────────────────┐ │
172
+ │ │ SQLite Database │ │
173
+ │ │ ~/.botbrowser-mcp/ │ │
174
+ │ │ - browser_profiles │ │
175
+ │ │ - accounts │ │
176
+ │ │ - browser_instances │ │
177
+ │ └─────────────────────────────────┘ │
178
+ └─────────────────────────────────────────┘
179
+
180
+
181
+ ┌────────────────────────────┐
182
+ │ 实际运行的浏览器 │
183
+ │ Instance 1: Chrome │
184
+ │ Instance 2: Chrome │
185
+ └────────────────────────────┘
186
+ ```
187
+
188
+ ## 技术特性
189
+
190
+ ### 多实例浏览器切换
191
+
192
+ - **动态上下文切换**: 使用 `PlaywrightMCPProxy` 包装 @playwright/mcp,通过 `resetContext()` 清除缓存
193
+ - **独立浏览器状态**: 每个实例维护独立的页面、会话和导航历史
194
+ - **完整配置支持**: 提供包含超时设置的完整 BrowserServerBackend 配置
195
+
196
+ **工作原理:**
197
+ 1. @playwright/mcp 的 Context 类会缓存 BrowserContext
198
+ 2. 调用 `switch_browser_instance` 后,清除 `_browserContextPromise` 缓存
199
+ 3. 下次工具调用时,自动使用当前活跃实例的上下文
200
+
201
+ ### 元数据字段
202
+
203
+ 账号的 `metadata` 字段支持任意格式,LLM 可自由存取:
204
+
205
+ ```
206
+ # 键值对格式
207
+ metadata: "password: abc123, 2fa: JBSWY3DP, email: backup@gmail.com"
208
+
209
+ # JSON 格式
210
+ metadata: '{"password":"abc123","2fa":"JBSWY3DP","recovery":["c1","c2"]}'
211
+
212
+ # 自然语言
213
+ metadata: "密码是 abc123,双因素认证是 JBSWY3DP,备用邮箱 backup@gmail.com"
214
+ ```
215
+
216
+ ## 数据存储
217
+
218
+ 所有数据存储在: `~/.botbrowser-mcp/botbrowser.db` (SQLite)
219
+
220
+ **数据库表:**
221
+ - `browser_profiles` - 浏览器配置
222
+ - `accounts` - 账号信息
223
+ - `browser_instances` - 运行中的实例
224
+
225
+ 可使用 SQLite 客户端查看:
226
+ ```bash
227
+ sqlite3 ~/.botbrowser-mcp/botbrowser.db
228
+ SELECT * FROM browser_profiles;
229
+ ```
230
+
231
+ ## 常见问题
232
+
233
+ **Q: 如何知道哪个实例是活跃的?**
234
+ A: 使用 `list_browser_instances` 查看,`is_active: 1` 的实例就是当前活跃实例。
235
+
236
+ **Q: 可以同时操作多个实例吗?**
237
+ A: 不可以。浏览器操作工具只作用于活跃实例,需要先 `switch_browser_instance` 切换。
238
+
239
+ **Q: Cookie 什么时候保存?**
240
+ A: 停止实例时(`stop_browser_instance`)会自动保存到 `storage_state_path`。
241
+
242
+ **Q: 孤立实例记录是什么?**
243
+ A: 如果程序异常退出,浏览器已关闭但数据库记录还在,使用 `cleanup_orphaned_instances` 清理。
244
+
245
+ ## 故障排查
246
+
247
+ 如果遇到 "Connection closed" 错误:
248
+ 1. 确保使用最新版本: `npm install -g botbrowser-mcp@latest`
249
+ 2. 尝试完整路径: `"command": "/usr/local/bin/botbrowser-mcp"`
250
+ 3. 检查 Node.js 已安装: `node --version`
251
+
252
+ ## 依赖
39
253
 
40
- If you get "Connection closed" error:
41
- 1. Make sure you're using the latest version: `npm install -g botbrowser-mcp@latest`
42
- 2. Try with full path: `"command": "/usr/local/bin/botbrowser-mcp"`
43
- 3. Check Node.js is installed: `node --version`
254
+ - Node.js 18+
255
+ - @playwright/mcp - 浏览器自动化工具
256
+ - playwright - 浏览器驱动
257
+ - better-sqlite3 - SQLite 数据库
44
258
 
45
- ## License
259
+ ## 许可证
46
260
 
47
261
  Apache-2.0
48
262
 
package/dist/cli.d.ts DELETED
@@ -1,5 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * botbrowser-mcp CLI
4
- */
5
- export {};
package/dist/cli.js DELETED
@@ -1,13 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * botbrowser-mcp CLI
4
- */
5
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
6
- import { createConnection } from './index';
7
- (async () => {
8
- // 直接使用 @playwright/mcp 的 createConnection
9
- const server = await createConnection();
10
- const transport = new StdioServerTransport();
11
- await server.connect(transport);
12
- })();
13
- //# sourceMappingURL=cli.js.map
package/dist/cli.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;GAEG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAE3C,CAAC,KAAK,IAAI,EAAE;IACV,0CAA0C;IAC1C,MAAM,MAAM,GAAG,MAAM,gBAAgB,EAAE,CAAC;IACxC,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC,CAAC,EAAE,CAAC"}
@@ -1,178 +0,0 @@
1
- /**
2
- * 浏览器操作工具
3
- * 基于 Playwright API 实现常用浏览器操作
4
- */
5
- import { PlaywrightManager } from '../playwright/manager.js';
6
- export declare function setManager(m: PlaywrightManager): void;
7
- export declare const browserTools: {
8
- browser_navigate: {
9
- description: string;
10
- inputSchema: {
11
- type: string;
12
- properties: {
13
- url: {
14
- type: string;
15
- description: string;
16
- };
17
- };
18
- required: string[];
19
- };
20
- handler: (args: any) => Promise<{
21
- content: {
22
- type: string;
23
- text: string;
24
- }[];
25
- }>;
26
- };
27
- browser_click: {
28
- description: string;
29
- inputSchema: {
30
- type: string;
31
- properties: {
32
- selector: {
33
- type: string;
34
- description: string;
35
- };
36
- };
37
- required: string[];
38
- };
39
- handler: (args: any) => Promise<{
40
- content: {
41
- type: string;
42
- text: string;
43
- }[];
44
- }>;
45
- };
46
- browser_fill: {
47
- description: string;
48
- inputSchema: {
49
- type: string;
50
- properties: {
51
- selector: {
52
- type: string;
53
- description: string;
54
- };
55
- value: {
56
- type: string;
57
- description: string;
58
- };
59
- };
60
- required: string[];
61
- };
62
- handler: (args: any) => Promise<{
63
- content: {
64
- type: string;
65
- text: string;
66
- }[];
67
- }>;
68
- };
69
- browser_screenshot: {
70
- description: string;
71
- inputSchema: {
72
- type: string;
73
- properties: {
74
- path: {
75
- type: string;
76
- description: string;
77
- };
78
- fullPage: {
79
- type: string;
80
- description: string;
81
- };
82
- };
83
- required: string[];
84
- };
85
- handler: (args: any) => Promise<{
86
- content: {
87
- type: string;
88
- text: string;
89
- }[];
90
- }>;
91
- };
92
- browser_get_text: {
93
- description: string;
94
- inputSchema: {
95
- type: string;
96
- properties: {
97
- selector: {
98
- type: string;
99
- description: string;
100
- };
101
- };
102
- required: string[];
103
- };
104
- handler: (args: any) => Promise<{
105
- content: {
106
- type: string;
107
- text: string;
108
- }[];
109
- }>;
110
- };
111
- browser_wait: {
112
- description: string;
113
- inputSchema: {
114
- type: string;
115
- properties: {
116
- selector: {
117
- type: string;
118
- description: string;
119
- };
120
- timeout: {
121
- type: string;
122
- description: string;
123
- };
124
- };
125
- };
126
- handler: (args: any) => Promise<{
127
- content: {
128
- type: string;
129
- text: string;
130
- }[];
131
- }>;
132
- };
133
- browser_evaluate: {
134
- description: string;
135
- inputSchema: {
136
- type: string;
137
- properties: {
138
- script: {
139
- type: string;
140
- description: string;
141
- };
142
- };
143
- required: string[];
144
- };
145
- handler: (args: any) => Promise<{
146
- content: {
147
- type: string;
148
- text: string;
149
- }[];
150
- }>;
151
- };
152
- browser_new_page: {
153
- description: string;
154
- inputSchema: {
155
- type: string;
156
- properties: {};
157
- };
158
- handler: () => Promise<{
159
- content: {
160
- type: string;
161
- text: string;
162
- }[];
163
- }>;
164
- };
165
- browser_get_url: {
166
- description: string;
167
- inputSchema: {
168
- type: string;
169
- properties: {};
170
- };
171
- handler: () => Promise<{
172
- content: {
173
- type: string;
174
- text: string;
175
- }[];
176
- }>;
177
- };
178
- };
@@ -1,211 +0,0 @@
1
- let manager;
2
- export function setManager(m) {
3
- manager = m;
4
- }
5
- async function getActivePage() {
6
- const context = manager.getActiveContext();
7
- if (!context) {
8
- throw new Error('没有活跃的浏览器实例,请先使用 launch_browser 启动');
9
- }
10
- const pages = context.pages();
11
- if (pages.length === 0) {
12
- return await context.newPage();
13
- }
14
- return pages[pages.length - 1];
15
- }
16
- export const browserTools = {
17
- browser_navigate: {
18
- description: '导航到指定URL',
19
- inputSchema: {
20
- type: 'object',
21
- properties: {
22
- url: { type: 'string', description: 'URL地址' },
23
- },
24
- required: ['url'],
25
- },
26
- handler: async (args) => {
27
- const page = await getActivePage();
28
- await page.goto(args.url);
29
- return {
30
- content: [{
31
- type: 'text',
32
- text: `已导航到 ${args.url}`,
33
- }],
34
- };
35
- },
36
- },
37
- browser_click: {
38
- description: '点击页面元素',
39
- inputSchema: {
40
- type: 'object',
41
- properties: {
42
- selector: { type: 'string', description: 'CSS选择器或文本选择器' },
43
- },
44
- required: ['selector'],
45
- },
46
- handler: async (args) => {
47
- const page = await getActivePage();
48
- await page.click(args.selector);
49
- return {
50
- content: [{
51
- type: 'text',
52
- text: `已点击元素: ${args.selector}`,
53
- }],
54
- };
55
- },
56
- },
57
- browser_fill: {
58
- description: '填充输入框',
59
- inputSchema: {
60
- type: 'object',
61
- properties: {
62
- selector: { type: 'string', description: 'CSS选择器' },
63
- value: { type: 'string', description: '填充的值' },
64
- },
65
- required: ['selector', 'value'],
66
- },
67
- handler: async (args) => {
68
- const page = await getActivePage();
69
- await page.fill(args.selector, args.value);
70
- return {
71
- content: [{
72
- type: 'text',
73
- text: `已填充 ${args.selector}`,
74
- }],
75
- };
76
- },
77
- },
78
- browser_screenshot: {
79
- description: '截取页面截图',
80
- inputSchema: {
81
- type: 'object',
82
- properties: {
83
- path: { type: 'string', description: '保存路径' },
84
- fullPage: { type: 'boolean', description: '是否截取整个页面' },
85
- },
86
- required: ['path'],
87
- },
88
- handler: async (args) => {
89
- const page = await getActivePage();
90
- await page.screenshot({
91
- path: args.path,
92
- fullPage: args.fullPage || false
93
- });
94
- return {
95
- content: [{
96
- type: 'text',
97
- text: `截图已保存到 ${args.path}`,
98
- }],
99
- };
100
- },
101
- },
102
- browser_get_text: {
103
- description: '获取元素文本内容',
104
- inputSchema: {
105
- type: 'object',
106
- properties: {
107
- selector: { type: 'string', description: 'CSS选择器' },
108
- },
109
- required: ['selector'],
110
- },
111
- handler: async (args) => {
112
- const page = await getActivePage();
113
- const text = await page.textContent(args.selector);
114
- return {
115
- content: [{
116
- type: 'text',
117
- text: text || '',
118
- }],
119
- };
120
- },
121
- },
122
- browser_wait: {
123
- description: '等待指定时间或元素出现',
124
- inputSchema: {
125
- type: 'object',
126
- properties: {
127
- selector: { type: 'string', description: 'CSS选择器(等待元素出现)' },
128
- timeout: { type: 'number', description: '超时时间(毫秒)' },
129
- },
130
- },
131
- handler: async (args) => {
132
- const page = await getActivePage();
133
- if (args.selector) {
134
- await page.waitForSelector(args.selector, { timeout: args.timeout || 30000 });
135
- return {
136
- content: [{
137
- type: 'text',
138
- text: `元素 ${args.selector} 已出现`,
139
- }],
140
- };
141
- }
142
- else if (args.timeout) {
143
- await page.waitForTimeout(args.timeout);
144
- return {
145
- content: [{
146
- type: 'text',
147
- text: `已等待 ${args.timeout}ms`,
148
- }],
149
- };
150
- }
151
- throw new Error('必须提供 selector 或 timeout');
152
- },
153
- },
154
- browser_evaluate: {
155
- description: '在页面中执行JavaScript代码',
156
- inputSchema: {
157
- type: 'object',
158
- properties: {
159
- script: { type: 'string', description: 'JavaScript代码' },
160
- },
161
- required: ['script'],
162
- },
163
- handler: async (args) => {
164
- const page = await getActivePage();
165
- const result = await page.evaluate(args.script);
166
- return {
167
- content: [{
168
- type: 'text',
169
- text: JSON.stringify(result, null, 2),
170
- }],
171
- };
172
- },
173
- },
174
- browser_new_page: {
175
- description: '打开新标签页',
176
- inputSchema: {
177
- type: 'object',
178
- properties: {},
179
- },
180
- handler: async () => {
181
- const context = manager.getActiveContext();
182
- if (!context) {
183
- throw new Error('没有活跃的浏览器实例');
184
- }
185
- await context.newPage();
186
- return {
187
- content: [{
188
- type: 'text',
189
- text: '已打开新标签页',
190
- }],
191
- };
192
- },
193
- },
194
- browser_get_url: {
195
- description: '获取当前页面URL',
196
- inputSchema: {
197
- type: 'object',
198
- properties: {},
199
- },
200
- handler: async () => {
201
- const page = await getActivePage();
202
- return {
203
- content: [{
204
- type: 'text',
205
- text: page.url(),
206
- }],
207
- };
208
- },
209
- },
210
- };
211
- //# sourceMappingURL=browser.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"browser.js","sourceRoot":"","sources":["../../src/tools/browser.ts"],"names":[],"mappings":"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"}