browser-use 0.0.1 → 0.0.2

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 (200) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +761 -0
  3. package/dist/agent/cloud-events.d.ts +264 -0
  4. package/dist/agent/cloud-events.js +318 -0
  5. package/dist/agent/gif.d.ts +15 -0
  6. package/dist/agent/gif.js +215 -0
  7. package/dist/agent/index.d.ts +8 -0
  8. package/dist/agent/index.js +8 -0
  9. package/dist/agent/message-manager/service.d.ts +30 -0
  10. package/dist/agent/message-manager/service.js +208 -0
  11. package/dist/agent/message-manager/utils.d.ts +2 -0
  12. package/dist/agent/message-manager/utils.js +41 -0
  13. package/dist/agent/message-manager/views.d.ts +26 -0
  14. package/dist/agent/message-manager/views.js +73 -0
  15. package/dist/agent/prompts.d.ts +52 -0
  16. package/dist/agent/prompts.js +259 -0
  17. package/dist/agent/service.d.ts +290 -0
  18. package/dist/agent/service.js +2200 -0
  19. package/dist/agent/views.d.ts +741 -0
  20. package/dist/agent/views.js +537 -0
  21. package/dist/browser/browser.d.ts +7 -0
  22. package/dist/browser/browser.js +5 -0
  23. package/dist/browser/context.d.ts +8 -0
  24. package/dist/browser/context.js +4 -0
  25. package/dist/browser/dvd-screensaver.d.ts +101 -0
  26. package/dist/browser/dvd-screensaver.js +270 -0
  27. package/dist/browser/extensions.d.ts +63 -0
  28. package/dist/browser/extensions.js +359 -0
  29. package/dist/browser/index.d.ts +10 -0
  30. package/dist/browser/index.js +9 -0
  31. package/dist/browser/playwright-manager.d.ts +47 -0
  32. package/dist/browser/playwright-manager.js +146 -0
  33. package/dist/browser/profile.d.ts +196 -0
  34. package/dist/browser/profile.js +815 -0
  35. package/dist/browser/session.d.ts +505 -0
  36. package/dist/browser/session.js +3409 -0
  37. package/dist/browser/types.d.ts +1184 -0
  38. package/dist/browser/types.js +1 -0
  39. package/dist/browser/utils.d.ts +1 -0
  40. package/dist/browser/utils.js +19 -0
  41. package/dist/browser/views.d.ts +78 -0
  42. package/dist/browser/views.js +72 -0
  43. package/dist/cli.d.ts +2 -0
  44. package/dist/cli.js +44 -0
  45. package/dist/config.d.ts +108 -0
  46. package/dist/config.js +430 -0
  47. package/dist/controller/index.d.ts +3 -0
  48. package/dist/controller/index.js +3 -0
  49. package/dist/controller/registry/index.d.ts +2 -0
  50. package/dist/controller/registry/index.js +2 -0
  51. package/dist/controller/registry/service.d.ts +45 -0
  52. package/dist/controller/registry/service.js +184 -0
  53. package/dist/controller/registry/views.d.ts +55 -0
  54. package/dist/controller/registry/views.js +174 -0
  55. package/dist/controller/service.d.ts +49 -0
  56. package/dist/controller/service.js +1176 -0
  57. package/dist/controller/views.d.ts +241 -0
  58. package/dist/controller/views.js +88 -0
  59. package/dist/dom/clickable-element-processor/service.d.ts +11 -0
  60. package/dist/dom/clickable-element-processor/service.js +60 -0
  61. package/dist/dom/dom_tree/index.js +1400 -0
  62. package/dist/dom/history-tree-processor/service.d.ts +14 -0
  63. package/dist/dom/history-tree-processor/service.js +75 -0
  64. package/dist/dom/history-tree-processor/view.d.ts +54 -0
  65. package/dist/dom/history-tree-processor/view.js +56 -0
  66. package/dist/dom/playground/extraction.d.ts +19 -0
  67. package/dist/dom/playground/extraction.js +187 -0
  68. package/dist/dom/playground/process-dom.d.ts +1 -0
  69. package/dist/dom/playground/process-dom.js +5 -0
  70. package/dist/dom/playground/test-accessibility.d.ts +44 -0
  71. package/dist/dom/playground/test-accessibility.js +111 -0
  72. package/dist/dom/service.d.ts +19 -0
  73. package/dist/dom/service.js +227 -0
  74. package/dist/dom/utils.d.ts +1 -0
  75. package/dist/dom/utils.js +6 -0
  76. package/dist/dom/views.d.ts +61 -0
  77. package/dist/dom/views.js +247 -0
  78. package/dist/event-bus.d.ts +11 -0
  79. package/dist/event-bus.js +19 -0
  80. package/dist/exceptions.d.ts +10 -0
  81. package/dist/exceptions.js +22 -0
  82. package/dist/filesystem/file-system.d.ts +68 -0
  83. package/dist/filesystem/file-system.js +412 -0
  84. package/dist/filesystem/index.d.ts +1 -0
  85. package/dist/filesystem/index.js +1 -0
  86. package/dist/index.d.ts +31 -0
  87. package/dist/index.js +33 -0
  88. package/dist/integrations/gmail/actions.d.ts +12 -0
  89. package/dist/integrations/gmail/actions.js +113 -0
  90. package/dist/integrations/gmail/index.d.ts +2 -0
  91. package/dist/integrations/gmail/index.js +2 -0
  92. package/dist/integrations/gmail/service.d.ts +61 -0
  93. package/dist/integrations/gmail/service.js +260 -0
  94. package/dist/llm/anthropic/chat.d.ts +28 -0
  95. package/dist/llm/anthropic/chat.js +126 -0
  96. package/dist/llm/anthropic/index.d.ts +2 -0
  97. package/dist/llm/anthropic/index.js +2 -0
  98. package/dist/llm/anthropic/serializer.d.ts +68 -0
  99. package/dist/llm/anthropic/serializer.js +285 -0
  100. package/dist/llm/aws/chat-anthropic.d.ts +61 -0
  101. package/dist/llm/aws/chat-anthropic.js +176 -0
  102. package/dist/llm/aws/chat-bedrock.d.ts +15 -0
  103. package/dist/llm/aws/chat-bedrock.js +80 -0
  104. package/dist/llm/aws/index.d.ts +3 -0
  105. package/dist/llm/aws/index.js +3 -0
  106. package/dist/llm/aws/serializer.d.ts +5 -0
  107. package/dist/llm/aws/serializer.js +68 -0
  108. package/dist/llm/azure/chat.d.ts +15 -0
  109. package/dist/llm/azure/chat.js +83 -0
  110. package/dist/llm/azure/index.d.ts +1 -0
  111. package/dist/llm/azure/index.js +1 -0
  112. package/dist/llm/base.d.ts +16 -0
  113. package/dist/llm/base.js +1 -0
  114. package/dist/llm/deepseek/chat.d.ts +15 -0
  115. package/dist/llm/deepseek/chat.js +51 -0
  116. package/dist/llm/deepseek/index.d.ts +2 -0
  117. package/dist/llm/deepseek/index.js +2 -0
  118. package/dist/llm/deepseek/serializer.d.ts +6 -0
  119. package/dist/llm/deepseek/serializer.js +57 -0
  120. package/dist/llm/exceptions.d.ts +10 -0
  121. package/dist/llm/exceptions.js +18 -0
  122. package/dist/llm/google/chat.d.ts +20 -0
  123. package/dist/llm/google/chat.js +144 -0
  124. package/dist/llm/google/index.d.ts +2 -0
  125. package/dist/llm/google/index.js +2 -0
  126. package/dist/llm/google/serializer.d.ts +6 -0
  127. package/dist/llm/google/serializer.js +64 -0
  128. package/dist/llm/groq/chat.d.ts +15 -0
  129. package/dist/llm/groq/chat.js +52 -0
  130. package/dist/llm/groq/index.d.ts +3 -0
  131. package/dist/llm/groq/index.js +3 -0
  132. package/dist/llm/groq/parser.d.ts +32 -0
  133. package/dist/llm/groq/parser.js +189 -0
  134. package/dist/llm/groq/serializer.d.ts +6 -0
  135. package/dist/llm/groq/serializer.js +56 -0
  136. package/dist/llm/messages.d.ts +77 -0
  137. package/dist/llm/messages.js +157 -0
  138. package/dist/llm/ollama/chat.d.ts +15 -0
  139. package/dist/llm/ollama/chat.js +77 -0
  140. package/dist/llm/ollama/index.d.ts +2 -0
  141. package/dist/llm/ollama/index.js +2 -0
  142. package/dist/llm/ollama/serializer.d.ts +6 -0
  143. package/dist/llm/ollama/serializer.js +53 -0
  144. package/dist/llm/openai/chat.d.ts +38 -0
  145. package/dist/llm/openai/chat.js +174 -0
  146. package/dist/llm/openai/index.d.ts +3 -0
  147. package/dist/llm/openai/index.js +3 -0
  148. package/dist/llm/openai/like.d.ts +17 -0
  149. package/dist/llm/openai/like.js +19 -0
  150. package/dist/llm/openai/serializer.d.ts +6 -0
  151. package/dist/llm/openai/serializer.js +57 -0
  152. package/dist/llm/openrouter/chat.d.ts +15 -0
  153. package/dist/llm/openrouter/chat.js +74 -0
  154. package/dist/llm/openrouter/index.d.ts +2 -0
  155. package/dist/llm/openrouter/index.js +2 -0
  156. package/dist/llm/openrouter/serializer.d.ts +3 -0
  157. package/dist/llm/openrouter/serializer.js +3 -0
  158. package/dist/llm/schema.d.ts +6 -0
  159. package/dist/llm/schema.js +77 -0
  160. package/dist/llm/views.d.ts +15 -0
  161. package/dist/llm/views.js +12 -0
  162. package/dist/logging-config.d.ts +25 -0
  163. package/dist/logging-config.js +89 -0
  164. package/dist/mcp/client.d.ts +142 -0
  165. package/dist/mcp/client.js +638 -0
  166. package/dist/mcp/controller.d.ts +6 -0
  167. package/dist/mcp/controller.js +38 -0
  168. package/dist/mcp/index.d.ts +3 -0
  169. package/dist/mcp/index.js +3 -0
  170. package/dist/mcp/server.d.ts +134 -0
  171. package/dist/mcp/server.js +759 -0
  172. package/dist/observability-decorators.d.ts +158 -0
  173. package/dist/observability-decorators.js +286 -0
  174. package/dist/observability.d.ts +23 -0
  175. package/dist/observability.js +58 -0
  176. package/dist/screenshots/index.d.ts +1 -0
  177. package/dist/screenshots/index.js +1 -0
  178. package/dist/screenshots/service.d.ts +6 -0
  179. package/dist/screenshots/service.js +28 -0
  180. package/dist/sync/auth.d.ts +27 -0
  181. package/dist/sync/auth.js +205 -0
  182. package/dist/sync/index.d.ts +2 -0
  183. package/dist/sync/index.js +2 -0
  184. package/dist/sync/service.d.ts +21 -0
  185. package/dist/sync/service.js +146 -0
  186. package/dist/telemetry/index.d.ts +2 -0
  187. package/dist/telemetry/index.js +2 -0
  188. package/dist/telemetry/service.d.ts +12 -0
  189. package/dist/telemetry/service.js +85 -0
  190. package/dist/telemetry/views.d.ts +112 -0
  191. package/dist/telemetry/views.js +112 -0
  192. package/dist/tokens/index.d.ts +2 -0
  193. package/dist/tokens/index.js +2 -0
  194. package/dist/tokens/service.d.ts +35 -0
  195. package/dist/tokens/service.js +423 -0
  196. package/dist/tokens/views.d.ts +58 -0
  197. package/dist/tokens/views.js +1 -0
  198. package/dist/utils.d.ts +128 -0
  199. package/dist/utils.js +529 -0
  200. package/package.json +94 -5
@@ -0,0 +1,638 @@
1
+ /**
2
+ * MCP (Model Context Protocol) client integration for browser-use.
3
+ *
4
+ * This module provides integration between external MCP servers and browser-use's action registry.
5
+ * MCP tools are dynamically discovered and registered as browser-use actions.
6
+ *
7
+ * Example usage:
8
+ * import { Controller } from './controller/service.js';
9
+ * import { MCPClient } from './mcp/client.js';
10
+ *
11
+ * const controller = new Controller();
12
+ *
13
+ * // Connect to an MCP server
14
+ * const mcpClient = new MCPClient(
15
+ * 'my-server',
16
+ * 'npx',
17
+ * ['@mycompany/mcp-server@latest']
18
+ * );
19
+ *
20
+ * // Register all MCP tools as browser-use actions
21
+ * await mcpClient.registerToController(controller);
22
+ *
23
+ * // Now use with Agent as normal - MCP tools are available as actions
24
+ */
25
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
26
+ import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
27
+ import { CallToolRequestSchema, ListToolsRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
28
+ import { z } from 'zod';
29
+ import { createLogger } from '../logging-config.js';
30
+ import { ActionResult } from '../agent/views.js';
31
+ import { productTelemetry } from '../telemetry/service.js';
32
+ import { MCPClientTelemetryEvent } from '../telemetry/views.js';
33
+ import { get_browser_use_version, retryAsync } from '../utils.js';
34
+ const logger = createLogger('browser_use.mcp.client');
35
+ export class MCPClient {
36
+ client;
37
+ command;
38
+ args;
39
+ env;
40
+ serverName;
41
+ _tools = new Map();
42
+ _prompts = new Map();
43
+ _registeredActions = new Set();
44
+ _connected = false;
45
+ _connecting = false;
46
+ _toolCallCount = 0;
47
+ _errorCount = 0;
48
+ _lastConnectTime;
49
+ _lastHealthCheck;
50
+ _healthCheckInterval;
51
+ // Options
52
+ maxRetries;
53
+ connectionTimeout;
54
+ toolCallTimeout;
55
+ autoReconnect;
56
+ healthCheckIntervalSeconds;
57
+ constructor(serverName, command, args = [], env, options = {}) {
58
+ this.serverName = serverName;
59
+ this.command = command;
60
+ this.args = args;
61
+ this.env = env;
62
+ // Set options with defaults
63
+ this.maxRetries = options.maxRetries ?? 3;
64
+ this.connectionTimeout = options.connectionTimeout ?? 30;
65
+ this.toolCallTimeout = options.toolCallTimeout ?? 60;
66
+ this.autoReconnect = options.autoReconnect ?? true;
67
+ this.healthCheckIntervalSeconds = options.healthCheckInterval ?? 30;
68
+ this.client = new Client({
69
+ name: 'browser-use',
70
+ version: get_browser_use_version(),
71
+ }, {
72
+ capabilities: {},
73
+ });
74
+ }
75
+ /**
76
+ * Connect to the MCP server and discover available tools
77
+ */
78
+ async connect(timeout) {
79
+ if (this._connected) {
80
+ logger.debug(`Already connected to ${this.serverName}`);
81
+ return;
82
+ }
83
+ if (this._connecting) {
84
+ logger.debug(`Connection already in progress for ${this.serverName}`);
85
+ return;
86
+ }
87
+ this._connecting = true;
88
+ const actualTimeout = timeout ?? this.connectionTimeout;
89
+ const startTime = Date.now() / 1000;
90
+ let errorMsg = null;
91
+ try {
92
+ logger.info(`🔌 Connecting to MCP server '${this.serverName}': ${this.command} ${this.args.join(' ')}`);
93
+ // Use retry logic for connection
94
+ await retryAsync(async () => {
95
+ // Create transport with environment variables
96
+ const transport = new StdioClientTransport({
97
+ command: this.command,
98
+ args: this.args,
99
+ env: this.env,
100
+ });
101
+ // Connect with timeout
102
+ await this._connectWithTimeout(transport, actualTimeout);
103
+ }, {
104
+ maxAttempts: this.maxRetries,
105
+ delayMs: 1000,
106
+ backoffMultiplier: 2,
107
+ onRetry: (error, attempt, delay) => {
108
+ logger.warning(`Connection attempt ${attempt} failed for '${this.serverName}': ${error.message}. Retrying in ${delay}ms...`);
109
+ },
110
+ });
111
+ this._connected = true;
112
+ this._lastConnectTime = Date.now() / 1000;
113
+ // Discover available tools
114
+ const result = await this.client.request(ListToolsRequestSchema, {});
115
+ this._tools = new Map(result.tools.map((tool) => [tool.name, tool]));
116
+ // Try to discover prompts (optional)
117
+ try {
118
+ await this.listPrompts();
119
+ }
120
+ catch {
121
+ // Prompts are optional, ignore failures
122
+ }
123
+ logger.info(`📦 Discovered ${this._tools.size} tools${this._prompts.size > 0 ? ` and ${this._prompts.size} prompts` : ''} from '${this.serverName}'`);
124
+ // Start health checks
125
+ this._startHealthCheck();
126
+ }
127
+ catch (error) {
128
+ errorMsg = error instanceof Error ? error.message : String(error);
129
+ this._connected = false;
130
+ throw error;
131
+ }
132
+ finally {
133
+ this._connecting = false;
134
+ // Capture telemetry for connect action
135
+ const duration = Date.now() / 1000 - startTime;
136
+ productTelemetry.capture(new MCPClientTelemetryEvent({
137
+ server_name: this.serverName,
138
+ command: this.command,
139
+ tools_discovered: this._tools.size,
140
+ version: get_browser_use_version(),
141
+ action: 'connect',
142
+ duration_seconds: duration,
143
+ error_message: errorMsg,
144
+ }));
145
+ }
146
+ }
147
+ async _connectWithTimeout(transport, timeoutSeconds) {
148
+ return new Promise(async (resolve, reject) => {
149
+ const timeoutHandle = setTimeout(() => {
150
+ reject(new Error(`Failed to connect to MCP server '${this.serverName}' after ${timeoutSeconds} seconds`));
151
+ }, timeoutSeconds * 1000);
152
+ try {
153
+ await this.client.connect(transport);
154
+ clearTimeout(timeoutHandle);
155
+ resolve();
156
+ }
157
+ catch (error) {
158
+ clearTimeout(timeoutHandle);
159
+ reject(error);
160
+ }
161
+ });
162
+ }
163
+ /**
164
+ * Disconnect from the MCP server
165
+ */
166
+ async disconnect() {
167
+ if (!this._connected) {
168
+ return;
169
+ }
170
+ const startTime = Date.now() / 1000;
171
+ let errorMsg = null;
172
+ try {
173
+ logger.info(`🔌 Disconnecting from MCP server '${this.serverName}'`);
174
+ // Stop health checks
175
+ this._stopHealthCheck();
176
+ await this.client.close();
177
+ this._connected = false;
178
+ this._tools.clear();
179
+ this._prompts.clear();
180
+ this._registeredActions.clear();
181
+ const stats = this.getStats();
182
+ logger.info(`Disconnected from '${this.serverName}' (${stats.toolCallCount} tool calls, ${(stats.successRate * 100).toFixed(1)}% success rate)`);
183
+ }
184
+ catch (error) {
185
+ errorMsg = error instanceof Error ? error.message : String(error);
186
+ logger.error(`Error disconnecting from MCP server: ${errorMsg}`);
187
+ }
188
+ finally {
189
+ // Capture telemetry for disconnect action
190
+ const duration = Date.now() / 1000 - startTime;
191
+ productTelemetry.capture(new MCPClientTelemetryEvent({
192
+ server_name: this.serverName,
193
+ command: this.command,
194
+ tools_discovered: 0, // Tools cleared on disconnect
195
+ version: get_browser_use_version(),
196
+ action: 'disconnect',
197
+ duration_seconds: duration,
198
+ error_message: errorMsg,
199
+ }));
200
+ productTelemetry.flush();
201
+ }
202
+ }
203
+ /**
204
+ * List all available tools from the MCP server
205
+ */
206
+ async listTools() {
207
+ if (!this._connected) {
208
+ await this.connect();
209
+ }
210
+ return Array.from(this._tools.values());
211
+ }
212
+ /**
213
+ * Call a tool on the MCP server
214
+ */
215
+ async callTool(name, args) {
216
+ if (!this._connected) {
217
+ throw new Error(`MCP server '${this.serverName}' not connected`);
218
+ }
219
+ const startTime = Date.now() / 1000;
220
+ let errorMsg = null;
221
+ try {
222
+ logger.debug(`🔧 Calling MCP tool '${name}' with params: ${JSON.stringify(args)}`);
223
+ this._toolCallCount++;
224
+ const result = await this.client.request(CallToolRequestSchema, {
225
+ name,
226
+ arguments: args,
227
+ });
228
+ return result.content;
229
+ }
230
+ catch (error) {
231
+ this._errorCount++;
232
+ errorMsg = error instanceof Error ? error.message : String(error);
233
+ logger.error(`MCP tool '${name}' failed: ${errorMsg}`);
234
+ throw error;
235
+ }
236
+ finally {
237
+ // Capture telemetry for tool call
238
+ const duration = Date.now() / 1000 - startTime;
239
+ productTelemetry.capture(new MCPClientTelemetryEvent({
240
+ server_name: this.serverName,
241
+ command: this.command,
242
+ tools_discovered: this._tools.size,
243
+ version: get_browser_use_version(),
244
+ action: 'tool_call',
245
+ tool_name: name,
246
+ duration_seconds: duration,
247
+ error_message: errorMsg,
248
+ }));
249
+ }
250
+ }
251
+ /**
252
+ * Register MCP tools as actions in the browser-use controller
253
+ *
254
+ * @param controller - Browser-use controller to register actions to
255
+ * @param toolFilter - Optional list of tool names to register (undefined = all tools)
256
+ * @param prefix - Optional prefix to add to action names (e.g., "playwright_")
257
+ */
258
+ async registerToController(controller, toolFilter, prefix) {
259
+ if (!this._connected) {
260
+ await this.connect();
261
+ }
262
+ const registry = controller.registry;
263
+ for (const [toolName, tool] of this._tools.entries()) {
264
+ // Skip if not in filter
265
+ if (toolFilter && !toolFilter.includes(toolName)) {
266
+ continue;
267
+ }
268
+ // Apply prefix if specified
269
+ const actionName = prefix ? `${prefix}${toolName}` : toolName;
270
+ // Skip if already registered
271
+ if (this._registeredActions.has(actionName)) {
272
+ continue;
273
+ }
274
+ // Register the tool as an action
275
+ this._registerToolAsAction(registry, actionName, tool);
276
+ this._registeredActions.add(actionName);
277
+ }
278
+ logger.info(`✅ Registered ${this._registeredActions.size} MCP tools from '${this.serverName}' as browser-use actions`);
279
+ }
280
+ _registerToolAsAction(registry, actionName, tool) {
281
+ /**
282
+ * Register a single MCP tool as a browser-use action
283
+ */
284
+ // Create async wrapper function for the MCP tool
285
+ const mcpActionWrapper = async (params) => {
286
+ if (!this._connected) {
287
+ return new ActionResult({
288
+ error: `MCP server '${this.serverName}' not connected`,
289
+ success: false,
290
+ });
291
+ }
292
+ const startTime = Date.now() / 1000;
293
+ let errorMsg = null;
294
+ try {
295
+ // Call the MCP tool
296
+ const result = await this.callTool(tool.name, params || {});
297
+ // Convert MCP result to ActionResult
298
+ const extractedContent = this._formatMcpResult(result);
299
+ return new ActionResult({
300
+ extracted_content: extractedContent,
301
+ long_term_memory: `Used MCP tool '${tool.name}' from ${this.serverName}`,
302
+ });
303
+ }
304
+ catch (error) {
305
+ errorMsg = error instanceof Error ? error.message : String(error);
306
+ logger.error(`MCP tool '${tool.name}' failed: ${errorMsg}`);
307
+ return new ActionResult({
308
+ error: `MCP tool '${tool.name}' failed: ${errorMsg}`,
309
+ success: false,
310
+ });
311
+ }
312
+ };
313
+ // Set function metadata for better debugging
314
+ Object.defineProperty(mcpActionWrapper, 'name', { value: actionName });
315
+ // Register the action with browser-use
316
+ const description = tool.description || `MCP tool from ${this.serverName}: ${tool.name}`;
317
+ const paramModel = this._convertToolSchemaToParamModel(tool.inputSchema);
318
+ // Use the registry's action decorator
319
+ registry.action(description, {
320
+ param_model: paramModel,
321
+ })(mcpActionWrapper);
322
+ logger.debug(`✅ Registered MCP tool '${tool.name}' as action '${actionName}'`);
323
+ }
324
+ _convertToolSchemaToParamModel(inputSchema) {
325
+ if (!inputSchema || typeof inputSchema !== 'object') {
326
+ return z.object({}).strict();
327
+ }
328
+ const schemaObject = inputSchema;
329
+ if (Object.keys(schemaObject).length === 0) {
330
+ return z.object({}).strict();
331
+ }
332
+ const converted = this._jsonSchemaToZod(schemaObject, 'input');
333
+ if (converted instanceof z.ZodObject) {
334
+ return converted.strict();
335
+ }
336
+ return z.any();
337
+ }
338
+ _jsonSchemaToZod(schema, path) {
339
+ const typeValue = schema.type;
340
+ const enumValues = Array.isArray(schema.enum) ? schema.enum : null;
341
+ if ('const' in schema) {
342
+ if (this._isLiteralPrimitive(schema.const)) {
343
+ return this._applySchemaMetadata(z.literal(schema.const), schema);
344
+ }
345
+ return this._applySchemaMetadata(z.any(), schema);
346
+ }
347
+ if (enumValues && enumValues.length > 0) {
348
+ const enumSchema = this._toLiteralUnion(enumValues);
349
+ return this._applySchemaMetadata(enumSchema, schema);
350
+ }
351
+ if (Array.isArray(schema.oneOf) && schema.oneOf.length > 0) {
352
+ const options = schema.oneOf.map((option, index) => this._jsonSchemaToZod(this._toJsonSchemaObject(option), `${path}.oneOf[${index}]`));
353
+ const union = options.length === 1 ? options[0] : z.union(options);
354
+ return this._applySchemaMetadata(union, schema);
355
+ }
356
+ if (Array.isArray(schema.anyOf) && schema.anyOf.length > 0) {
357
+ const options = schema.anyOf.map((option, index) => this._jsonSchemaToZod(this._toJsonSchemaObject(option), `${path}.anyOf[${index}]`));
358
+ const union = options.length === 1 ? options[0] : z.union(options);
359
+ return this._applySchemaMetadata(union, schema);
360
+ }
361
+ if (Array.isArray(typeValue)) {
362
+ const hasNull = typeValue.includes('null');
363
+ const nonNullTypes = typeValue.filter((entry) => entry !== 'null');
364
+ if (nonNullTypes.length === 1) {
365
+ const base = this._jsonSchemaToZod({ ...schema, type: nonNullTypes[0] }, path);
366
+ return hasNull ? base.nullable() : base;
367
+ }
368
+ if (nonNullTypes.length > 1) {
369
+ const options = nonNullTypes.map((entry) => this._jsonSchemaToZod({ ...schema, type: entry }, path));
370
+ const union = options.length === 1 ? options[0] : z.union(options);
371
+ return hasNull ? union.nullable() : union;
372
+ }
373
+ }
374
+ let zodSchema;
375
+ if (typeValue === 'object' || schema.properties) {
376
+ const properties = schema.properties && typeof schema.properties === 'object'
377
+ ? schema.properties
378
+ : {};
379
+ const required = new Set(Array.isArray(schema.required)
380
+ ? schema.required.filter((entry) => typeof entry === 'string')
381
+ : []);
382
+ const shape = {};
383
+ for (const [key, value] of Object.entries(properties)) {
384
+ const propertySchema = this._toJsonSchemaObject(value);
385
+ const hasDefault = Object.prototype.hasOwnProperty.call(propertySchema, 'default');
386
+ let field = this._jsonSchemaToZod(propertySchema, `${path}.${key}`);
387
+ if (hasDefault) {
388
+ field = this._applyDefault(field, propertySchema.default);
389
+ }
390
+ else if (!required.has(key)) {
391
+ field = field.optional();
392
+ }
393
+ shape[key] = field;
394
+ }
395
+ zodSchema = z.object(shape);
396
+ }
397
+ else if (typeValue === 'array') {
398
+ const itemsSchema = this._toJsonSchemaObject(schema.items);
399
+ zodSchema = z.array(this._jsonSchemaToZod(itemsSchema, `${path}[]`));
400
+ }
401
+ else if (typeValue === 'string') {
402
+ zodSchema = z.string();
403
+ }
404
+ else if (typeValue === 'integer') {
405
+ zodSchema = z.number().int();
406
+ }
407
+ else if (typeValue === 'number') {
408
+ zodSchema = z.number();
409
+ }
410
+ else if (typeValue === 'boolean') {
411
+ zodSchema = z.boolean();
412
+ }
413
+ else if (typeValue === 'null') {
414
+ zodSchema = z.null();
415
+ }
416
+ else {
417
+ zodSchema = z.any();
418
+ }
419
+ return this._applySchemaMetadata(zodSchema, schema);
420
+ }
421
+ _toJsonSchemaObject(input) {
422
+ if (!input || typeof input !== 'object') {
423
+ return {};
424
+ }
425
+ return input;
426
+ }
427
+ _toLiteralUnion(values) {
428
+ const literalValues = values.filter((value) => this._isLiteralPrimitive(value));
429
+ if (!literalValues.length) {
430
+ return z.any();
431
+ }
432
+ if (literalValues.length === 1) {
433
+ return z.literal(literalValues[0]);
434
+ }
435
+ const literals = literalValues.map((value) => z.literal(value));
436
+ return z.union(literals);
437
+ }
438
+ _isLiteralPrimitive(value) {
439
+ return (typeof value === 'string' ||
440
+ typeof value === 'number' ||
441
+ typeof value === 'boolean' ||
442
+ value === null);
443
+ }
444
+ _applyDefault(schema, value) {
445
+ try {
446
+ return schema.default(value);
447
+ }
448
+ catch {
449
+ return schema;
450
+ }
451
+ }
452
+ _applySchemaMetadata(schema, sourceSchema) {
453
+ let result = schema;
454
+ if (sourceSchema.nullable === true) {
455
+ result = result.nullable();
456
+ }
457
+ if (typeof sourceSchema.description === 'string') {
458
+ result = result.describe(sourceSchema.description);
459
+ }
460
+ return result;
461
+ }
462
+ _formatMcpResult(result) {
463
+ /**
464
+ * Format MCP tool result into a string for ActionResult
465
+ */
466
+ // Handle different MCP result formats
467
+ if (result && typeof result === 'object') {
468
+ if (Array.isArray(result)) {
469
+ // List of content items
470
+ const parts = [];
471
+ for (const item of result) {
472
+ if (item && typeof item === 'object' && 'text' in item) {
473
+ parts.push(String(item.text));
474
+ }
475
+ else {
476
+ parts.push(String(item));
477
+ }
478
+ }
479
+ return parts.join('\n');
480
+ }
481
+ else if ('text' in result) {
482
+ return String(result.text);
483
+ }
484
+ else if ('content' in result) {
485
+ // Structured content response
486
+ if (Array.isArray(result.content)) {
487
+ const parts = [];
488
+ for (const item of result.content) {
489
+ if (item && typeof item === 'object' && 'text' in item) {
490
+ parts.push(String(item.text));
491
+ }
492
+ else {
493
+ parts.push(String(item));
494
+ }
495
+ }
496
+ return parts.join('\n');
497
+ }
498
+ else {
499
+ return String(result.content);
500
+ }
501
+ }
502
+ }
503
+ // Direct result or unknown format
504
+ return String(result);
505
+ }
506
+ /**
507
+ * List available prompts from the MCP server
508
+ */
509
+ async listPrompts() {
510
+ if (!this._connected) {
511
+ await this.connect();
512
+ }
513
+ try {
514
+ const result = await this.client.request(ListPromptsRequestSchema, {});
515
+ this._prompts = new Map(result.prompts.map((prompt) => [prompt.name, prompt]));
516
+ return Array.from(this._prompts.values());
517
+ }
518
+ catch (error) {
519
+ logger.debug(`Server '${this.serverName}' does not support prompts`);
520
+ return [];
521
+ }
522
+ }
523
+ /**
524
+ * Get a prompt with arguments
525
+ */
526
+ async getPrompt(name, args) {
527
+ if (!this._connected) {
528
+ await this.connect();
529
+ }
530
+ try {
531
+ const result = await this.client.request(GetPromptRequestSchema, {
532
+ name,
533
+ arguments: args,
534
+ });
535
+ return result;
536
+ }
537
+ catch (error) {
538
+ logger.error(`Failed to get prompt '${name}': ${error}`);
539
+ throw error;
540
+ }
541
+ }
542
+ /**
543
+ * Start health check monitoring
544
+ */
545
+ _startHealthCheck() {
546
+ if (this.healthCheckIntervalSeconds <= 0 || this._healthCheckInterval) {
547
+ return;
548
+ }
549
+ this._healthCheckInterval = setInterval(async () => {
550
+ try {
551
+ await this._performHealthCheck();
552
+ }
553
+ catch (error) {
554
+ logger.warning(`Health check failed for '${this.serverName}': ${error}`);
555
+ if (this.autoReconnect) {
556
+ await this._attemptReconnect();
557
+ }
558
+ }
559
+ }, this.healthCheckIntervalSeconds * 1000);
560
+ }
561
+ /**
562
+ * Stop health check monitoring
563
+ */
564
+ _stopHealthCheck() {
565
+ if (this._healthCheckInterval) {
566
+ clearInterval(this._healthCheckInterval);
567
+ this._healthCheckInterval = undefined;
568
+ }
569
+ }
570
+ /**
571
+ * Perform health check by listing tools
572
+ */
573
+ async _performHealthCheck() {
574
+ if (!this._connected) {
575
+ return;
576
+ }
577
+ try {
578
+ await this.client.request(ListToolsRequestSchema, {});
579
+ this._lastHealthCheck = Date.now() / 1000;
580
+ }
581
+ catch (error) {
582
+ this._connected = false;
583
+ throw error;
584
+ }
585
+ }
586
+ /**
587
+ * Attempt to reconnect to the server
588
+ */
589
+ async _attemptReconnect() {
590
+ logger.info(`Attempting to reconnect to '${this.serverName}'...`);
591
+ this._connected = false;
592
+ try {
593
+ await this.connect(this.connectionTimeout);
594
+ logger.info(`✅ Reconnected to '${this.serverName}'`);
595
+ }
596
+ catch (error) {
597
+ logger.error(`Failed to reconnect to '${this.serverName}': ${error}`);
598
+ }
599
+ }
600
+ /**
601
+ * Get client statistics
602
+ */
603
+ getStats() {
604
+ const uptime = this._lastConnectTime
605
+ ? Date.now() / 1000 - this._lastConnectTime
606
+ : undefined;
607
+ const successRate = this._toolCallCount > 0 ? 1 - this._errorCount / this._toolCallCount : 1;
608
+ return {
609
+ serverName: this.serverName,
610
+ connected: this._connected,
611
+ toolsDiscovered: this._tools.size,
612
+ promptsDiscovered: this._prompts.size,
613
+ toolCallCount: this._toolCallCount,
614
+ errorCount: this._errorCount,
615
+ successRate,
616
+ uptime,
617
+ lastHealthCheck: this._lastHealthCheck,
618
+ };
619
+ }
620
+ /**
621
+ * Check if client is connected
622
+ */
623
+ isConnected() {
624
+ return this._connected;
625
+ }
626
+ /**
627
+ * Reset statistics
628
+ */
629
+ resetStats() {
630
+ this._toolCallCount = 0;
631
+ this._errorCount = 0;
632
+ logger.debug(`Reset statistics for '${this.serverName}'`);
633
+ }
634
+ // Async context manager support
635
+ async [Symbol.asyncDispose]() {
636
+ await this.disconnect();
637
+ }
638
+ }
@@ -0,0 +1,6 @@
1
+ export declare class MCPController {
2
+ private clients;
3
+ constructor();
4
+ addServer(command: string, args: string[]): Promise<void>;
5
+ private registerTools;
6
+ }
@@ -0,0 +1,38 @@
1
+ import { MCPClient } from './client.js';
2
+ import { z } from 'zod';
3
+ // This controller integrates MCP tools into the browser-use registry
4
+ export class MCPController {
5
+ clients = [];
6
+ constructor() { }
7
+ async addServer(command, args) {
8
+ const client = new MCPClient('browser-use-client', command, args);
9
+ await client.connect();
10
+ this.clients.push(client);
11
+ await this.registerTools(client);
12
+ }
13
+ async registerTools(client) {
14
+ const tools = await client.listTools();
15
+ for (const tool of tools) {
16
+ // We need to convert JSON schema to Zod schema dynamically if we want to validate
17
+ // For now, we might just use a generic schema or skip validation at the registry level
18
+ // and let the MCP server validate.
19
+ // However, the Registry expects a Zod schema.
20
+ // We can create a dynamic Zod schema that accepts anything if we can't easily convert.
21
+ // Or we can try to convert using json-schema-to-zod (if we had it) or just use z.any()
22
+ const paramModel = z.any().describe(tool.description || '');
23
+ // Register the action
24
+ // Note: We need to access the singleton registry or pass it in.
25
+ // Assuming we can use the decorator or manual registration.
26
+ // Since we are registering dynamically, we might need a method on Registry to register actions at runtime.
27
+ // Let's assume Registry has a static method or we can access the instance.
28
+ // In the migration plan, Registry is a class.
29
+ // We might need to modify Registry to support runtime registration if it doesn't already.
30
+ // But wait, the Registry in `src/controller/registry/service.ts` has an `action` method which is a decorator.
31
+ // It also has a `registry` property which holds the actions.
32
+ // We'll assume we can access the global registry or pass it to this controller.
33
+ // For now, I'll just log it as a placeholder for actual registration logic which might require
34
+ // more complex integration with the Controller class.
35
+ console.log(`Discovered MCP tool: ${tool.name}`);
36
+ }
37
+ }
38
+ }
@@ -0,0 +1,3 @@
1
+ export * from './server.js';
2
+ export * from './client.js';
3
+ export * from './controller.js';
@@ -0,0 +1,3 @@
1
+ export * from './server.js';
2
+ export * from './client.js';
3
+ export * from './controller.js';