webpilot-engine 2.0.0__py3-none-any.whl

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 (47) hide show
  1. adapters/__init__.py +14 -0
  2. adapters/base.py +34 -0
  3. adapters/browser_adapter.py +300 -0
  4. adapters/dom_scripts.py +518 -0
  5. adapters/envelope_builder.py +108 -0
  6. adapters/live_session_manager.py +326 -0
  7. adapters/upload_adapter.py +77 -0
  8. builder.py +83 -0
  9. cli.py +341 -0
  10. config/__init__.py +23 -0
  11. config/settings.py +339 -0
  12. constants/__init__.py +165 -0
  13. constants/contracts.py +95 -0
  14. constants/selectors.py +27 -0
  15. constants/timeouts.py +25 -0
  16. core/__init__.py +45 -0
  17. core/exceptions.py +33 -0
  18. core/models.py +273 -0
  19. engine.py +199 -0
  20. exceptions.py +23 -0
  21. mcp_server.py +347 -0
  22. models.py +29 -0
  23. orchestration/__init__.py +19 -0
  24. orchestration/context.py +71 -0
  25. orchestration/flow_orchestrator.py +358 -0
  26. orchestration/session_coordinator.py +88 -0
  27. services/__init__.py +17 -0
  28. services/auth_navigation_service.py +129 -0
  29. services/field_interaction_service.py +174 -0
  30. services/form_filler_service.py +106 -0
  31. services/inspection_formatter_service.py +68 -0
  32. services/inspection_service.py +40 -0
  33. services/reactive_interaction_service.py +174 -0
  34. supervisor/__init__.py +13 -0
  35. supervisor/client.py +171 -0
  36. supervisor/contracts.py +62 -0
  37. supervisor/master_daemon.py +303 -0
  38. supervisor/shell.py +194 -0
  39. supervisor/worker_process.py +400 -0
  40. validators/__init__.py +9 -0
  41. validators/form_validator.py +28 -0
  42. validators/payload_validator.py +25 -0
  43. webpilot_engine-2.0.0.dist-info/METADATA +299 -0
  44. webpilot_engine-2.0.0.dist-info/RECORD +47 -0
  45. webpilot_engine-2.0.0.dist-info/WHEEL +5 -0
  46. webpilot_engine-2.0.0.dist-info/entry_points.txt +6 -0
  47. webpilot_engine-2.0.0.dist-info/top_level.txt +14 -0
mcp_server.py ADDED
@@ -0,0 +1,347 @@
1
+ """WebPilot MCP Server (Model Context Protocol).
2
+
3
+ Exposes WebPilot's autonomous multi-tier browser engine as standard MCP tools
4
+ for AI Agents (Claude, Gemini, Antigravity, OpenCode, Cursor, etc.).
5
+
6
+ Features:
7
+ - Persistent browser state across tool calls (no restarting between actions)
8
+ - Zero administrative elevation required (Standard User Mode)
9
+ - Token-optimized DOM schemas returned directly to LLMs
10
+ - Reactive mutation tracking (redirects, new tabs, dynamic loaders)
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import json
16
+ import os
17
+ import sys
18
+ from typing import Any, Dict, List, Optional
19
+
20
+ PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__))
21
+ if PROJECT_ROOT not in sys.path:
22
+ sys.path.insert(0, PROJECT_ROOT)
23
+
24
+ from mcp.server.fastmcp import FastMCP
25
+ from supervisor.client import SupervisorClient
26
+ from supervisor.contracts import SupervisorActionRequest, SupervisorActionResponse
27
+
28
+ # Initialize FastMCP application
29
+ mcp = FastMCP(
30
+ "WebPilot",
31
+ instructions=(
32
+ "WebPilot is an autonomous multi-tier browser runtime for AI agents. "
33
+ "It provides persistent browser sessions across tool calls, universal form inspection, "
34
+ "reactive mutation tracking, and dynamic loading dissolution without requiring site-specific CSS selectors."
35
+ ),
36
+ )
37
+
38
+ client = SupervisorClient()
39
+
40
+
41
+ @mcp.tool()
42
+ def webpilot_inspect(
43
+ url: Optional[str] = None,
44
+ unpack_options: bool = False,
45
+ screenshot_path: Optional[str] = None,
46
+ tab_index: Optional[int] = None,
47
+ ) -> str:
48
+ """Inspects a web page and returns a token-optimized schema of all inputs, buttons, and dropdowns.
49
+
50
+ Args:
51
+ url: Target web page URL to inspect (optional if connecting to existing active page).
52
+ unpack_options: If True, probes dropdowns to sample options (burns more tokens).
53
+ screenshot_path: Optional file path to save a visual screenshot.
54
+ tab_index: Optional specific tab index to inspect (0-based).
55
+
56
+ Returns:
57
+ Structured JSON schema containing form fields, picklists, radio groups, and action buttons.
58
+ """
59
+ req = SupervisorActionRequest(
60
+ action="inspect",
61
+ url=url,
62
+ unpack_options=unpack_options,
63
+ screenshot_path=screenshot_path,
64
+ tab_index=tab_index,
65
+ )
66
+ resp = client.execute(req)
67
+ if not resp.success:
68
+ return f"Error inspecting page: {resp.message}"
69
+
70
+ output = {
71
+ "status": "success",
72
+ "active_url": resp.active_url,
73
+ "active_title": resp.active_title,
74
+ "active_tab": resp.active_tab_index,
75
+ "total_tabs": resp.total_tabs,
76
+ "schema": resp.schema,
77
+ "message": resp.message,
78
+ }
79
+ return json.dumps(output, indent=2, ensure_ascii=False)
80
+
81
+
82
+ @mcp.tool()
83
+ def webpilot_fill_form(
84
+ fields: Dict[str, str],
85
+ url: Optional[str] = None,
86
+ submit: bool = False,
87
+ cookies_path: Optional[str] = None,
88
+ screenshot_path: Optional[str] = None,
89
+ tab_index: Optional[int] = None,
90
+ ) -> str:
91
+ """Fills form fields on the active page by name or label, and optionally submits.
92
+
93
+ Args:
94
+ fields: Key-value dictionary of field names to values (e.g. {"username": "user@test.com", "password": "xyz"}).
95
+ url: Optional target URL if starting a fresh session.
96
+ submit: If True, automatically clicks the form submit button after filling fields.
97
+ cookies_path: Optional path to save updated session cookies.
98
+ screenshot_path: Optional path to save visual screenshot after interaction.
99
+ tab_index: Optional specific tab index to target (0-based).
100
+
101
+ Returns:
102
+ Confirmation of filled fields, any validation errors, and observed page state.
103
+ """
104
+ fill_args = [f"{k}={v}" for k, v in fields.items()]
105
+ req = SupervisorActionRequest(
106
+ action="apply",
107
+ url=url,
108
+ fill_arguments=fill_args,
109
+ submit=submit,
110
+ cookies_path=cookies_path,
111
+ screenshot_path=screenshot_path,
112
+ tab_index=tab_index,
113
+ )
114
+ resp = client.execute(req)
115
+
116
+ output = {
117
+ "success": resp.success,
118
+ "message": resp.message,
119
+ "active_url": resp.active_url,
120
+ "confirmed_fields": resp.confirmed_fields,
121
+ "failed_fields": resp.failed_fields,
122
+ "validation_errors": resp.validation_errors,
123
+ "logs": resp.output_lines,
124
+ }
125
+ return json.dumps(output, indent=2, ensure_ascii=False)
126
+
127
+
128
+ @mcp.tool()
129
+ def webpilot_click_button(
130
+ button_text: str,
131
+ screenshot_path: Optional[str] = None,
132
+ cookies_path: Optional[str] = None,
133
+ tab_index: Optional[int] = None,
134
+ ) -> str:
135
+ """Clicks a button, link, or tab with reactive mutation tracking (redirects, loaders, popups).
136
+
137
+ Args:
138
+ button_text: The visible text of the button or element to click (e.g. 'Sign In', 'Next', 'Submit').
139
+ screenshot_path: Optional file path to capture screenshot after clicking.
140
+ cookies_path: Optional file path to save updated session cookies.
141
+ tab_index: Optional tab index to target (0-based).
142
+
143
+ Returns:
144
+ Result of the click, detected redirects or new tabs, and updated page state.
145
+ """
146
+ req = SupervisorActionRequest(
147
+ action="apply",
148
+ press_buttons=[button_text],
149
+ screenshot_path=screenshot_path,
150
+ cookies_path=cookies_path,
151
+ tab_index=tab_index,
152
+ )
153
+ resp = client.execute(req)
154
+
155
+ output = {
156
+ "success": resp.success,
157
+ "message": resp.message,
158
+ "active_url": resp.active_url,
159
+ "active_title": resp.active_title,
160
+ "active_tab": resp.active_tab_index,
161
+ "total_tabs": resp.total_tabs,
162
+ "logs": resp.output_lines,
163
+ }
164
+ return json.dumps(output, indent=2, ensure_ascii=False)
165
+
166
+
167
+ @mcp.tool()
168
+ def webpilot_upload_file(
169
+ field_name: str,
170
+ file_path: str,
171
+ screenshot_path: Optional[str] = None,
172
+ ) -> str:
173
+ """Uploads a file or document to a file input or drag-and-drop zone.
174
+
175
+ Args:
176
+ field_name: Name or label of the file upload field (e.g. 'resume', 'cv', 'attachment').
177
+ file_path: Absolute or relative path to the local file to upload.
178
+ screenshot_path: Optional path to save visual screenshot after upload.
179
+
180
+ Returns:
181
+ Status of the upload action and confirmation.
182
+ """
183
+ upload_arg = f"{field_name}={file_path}"
184
+ req = SupervisorActionRequest(
185
+ action="apply",
186
+ upload_files=[upload_arg],
187
+ screenshot_path=screenshot_path,
188
+ )
189
+ resp = client.execute(req)
190
+ return json.dumps(
191
+ {
192
+ "success": resp.success,
193
+ "message": resp.message,
194
+ "logs": resp.output_lines,
195
+ },
196
+ indent=2,
197
+ ensure_ascii=False,
198
+ )
199
+
200
+
201
+ @mcp.tool()
202
+ def webpilot_screenshot(
203
+ output_path: str = "screenshot.png",
204
+ ) -> str:
205
+ """Captures a visual screenshot of the current active browser page.
206
+
207
+ Args:
208
+ output_path: File path where the screenshot PNG will be saved.
209
+
210
+ Returns:
211
+ Confirmation and file path of saved screenshot.
212
+ """
213
+ req = SupervisorActionRequest(action="apply", screenshot_path=output_path)
214
+ resp = client.execute(req)
215
+ if resp.success:
216
+ return f"Screenshot successfully captured and saved to '{output_path}'."
217
+ return f"Failed to capture screenshot: {resp.message}"
218
+
219
+
220
+ @mcp.tool()
221
+ def webpilot_list_tabs() -> str:
222
+ """Lists all open tabs and windows in the active persistent browser session.
223
+
224
+ Returns:
225
+ List of tabs with index, title, URL, and active flag.
226
+ """
227
+ req = SupervisorActionRequest(action="list_tabs")
228
+ resp = client.execute(req)
229
+ return json.dumps(
230
+ {
231
+ "success": resp.success,
232
+ "active_tab": resp.active_tab_index,
233
+ "total_tabs": resp.total_tabs,
234
+ "tabs": resp.tabs,
235
+ },
236
+ indent=2,
237
+ ensure_ascii=False,
238
+ )
239
+
240
+
241
+ @mcp.tool()
242
+ def webpilot_switch_tab(
243
+ tab_index: int,
244
+ ) -> str:
245
+ """Switches active focus to a specific tab by index.
246
+
247
+ Args:
248
+ tab_index: 0-based index of the target tab.
249
+
250
+ Returns:
251
+ Details of the newly active tab.
252
+ """
253
+ req = SupervisorActionRequest(action="switch_tab", tab_index=tab_index)
254
+ resp = client.execute(req)
255
+ return json.dumps(
256
+ {
257
+ "success": resp.success,
258
+ "active_tab_index": resp.active_tab_index,
259
+ "active_url": resp.active_url,
260
+ "active_title": resp.active_title,
261
+ },
262
+ indent=2,
263
+ ensure_ascii=False,
264
+ )
265
+
266
+
267
+ @mcp.tool()
268
+ def webpilot_save_cookies(
269
+ output_path: str = "session_cookies.json",
270
+ ) -> str:
271
+ """Exports current session cookies from the browser to a JSON file.
272
+
273
+ Args:
274
+ output_path: File path to save the cookies JSON.
275
+
276
+ Returns:
277
+ Confirmation message.
278
+ """
279
+ req = SupervisorActionRequest(action="apply", cookies_path=output_path)
280
+ resp = client.execute(req)
281
+ if resp.success:
282
+ return f"Cookies saved successfully to '{output_path}'."
283
+ return f"Failed to save cookies: {resp.message}"
284
+
285
+
286
+ @mcp.tool()
287
+ def webpilot_service_status() -> str:
288
+ """Returns the operational status and telemetry of all WebPilot tiers.
289
+
290
+ Returns:
291
+ Status of Layer 1 (Supervisor), Layer 2 (Worker PID), Layer 3 (Chromium PID), and tab count.
292
+ """
293
+ is_alive = client.is_running()
294
+ if not is_alive:
295
+ return json.dumps(
296
+ {
297
+ "status": "stopped",
298
+ "message": "Master Supervisor is not currently running. It will auto-spawn upon the next tool call.",
299
+ },
300
+ indent=2,
301
+ )
302
+
303
+ req = SupervisorActionRequest(action="list_tabs")
304
+ resp = client.execute(req)
305
+ return json.dumps(
306
+ {
307
+ "status": "active",
308
+ "layer1_supervisor_url": client.base_url,
309
+ "layer2_worker_pid": resp.worker_pid,
310
+ "layer3_chromium_pid": resp.browser_pid,
311
+ "active_tab_index": resp.active_tab_index,
312
+ "total_tabs": resp.total_tabs,
313
+ "active_url": resp.active_url,
314
+ "active_title": resp.active_title,
315
+ },
316
+ indent=2,
317
+ ensure_ascii=False,
318
+ )
319
+
320
+
321
+ @mcp.tool()
322
+ def webpilot_service_restart() -> str:
323
+ """Performs an instant cascading kill and restart of the browser session.
324
+
325
+ Cleans up any hanging processes and respawns a fresh worker tier in ~300ms.
326
+
327
+ Returns:
328
+ Restart confirmation.
329
+ """
330
+ resp = client.restart_worker()
331
+ return json.dumps(
332
+ {
333
+ "success": resp.success,
334
+ "message": resp.message,
335
+ "new_worker_pid": resp.worker_pid,
336
+ },
337
+ indent=2,
338
+ )
339
+
340
+
341
+ def main():
342
+ """Entry point for running the WebPilot MCP Server."""
343
+ mcp.run()
344
+
345
+
346
+ if __name__ == "__main__":
347
+ main()
models.py ADDED
@@ -0,0 +1,29 @@
1
+ """Backwards compatibility shim for models."""
2
+
3
+ from core.models import (
4
+ ProxyConfig,
5
+ DeviceProfile,
6
+ InputField,
7
+ DropdownField,
8
+ ChoiceGroup,
9
+ FileUploadField,
10
+ ActionButton,
11
+ InspectionResult,
12
+ FillSummary,
13
+ ReactiveClickOutcome,
14
+ ApplyExecutionResult,
15
+ )
16
+
17
+ __all__ = [
18
+ "ProxyConfig",
19
+ "DeviceProfile",
20
+ "InputField",
21
+ "DropdownField",
22
+ "ChoiceGroup",
23
+ "FileUploadField",
24
+ "ActionButton",
25
+ "InspectionResult",
26
+ "FillSummary",
27
+ "ReactiveClickOutcome",
28
+ "ApplyExecutionResult",
29
+ ]
@@ -0,0 +1,19 @@
1
+ """Orchestration package for universal web applier."""
2
+
3
+ from orchestration.context import (
4
+ SessionContext,
5
+ InspectionRequestContext,
6
+ ApplyRequestContext,
7
+ ApplyExecutionResult,
8
+ )
9
+ from orchestration.session_coordinator import SessionCoordinator
10
+ from orchestration.flow_orchestrator import FlowOrchestrator
11
+
12
+ __all__ = [
13
+ "SessionContext",
14
+ "InspectionRequestContext",
15
+ "ApplyRequestContext",
16
+ "ApplyExecutionResult",
17
+ "SessionCoordinator",
18
+ "FlowOrchestrator",
19
+ ]
@@ -0,0 +1,71 @@
1
+ """Context dataclasses for flow orchestration and lifecycle management."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from typing import Any, Dict, List, Optional, Tuple
7
+ from config.settings import Settings
8
+ from core.models import ApplyExecutionResult, DeviceProfile
9
+
10
+
11
+ @dataclass
12
+ class SessionContext:
13
+ """Encapsulates browser session configuration and runtime credentials."""
14
+ settings: Settings
15
+ device_profile: DeviceProfile
16
+ headless: bool = True
17
+ cookies_path: Optional[str] = None
18
+ keep_alive: bool = False
19
+ close_session: bool = False
20
+ timeout_minutes: int = 30
21
+ tab_index: Optional[int] = None
22
+
23
+
24
+ @dataclass
25
+ class InspectionRequestContext:
26
+ """Encapsulates all parameters for a form inspection flow."""
27
+ url: Optional[str] = None
28
+ output_path: Optional[str] = None
29
+ screenshot_path: Optional[str] = None
30
+ cookies_path: Optional[str] = None
31
+ unpack_options: bool = False
32
+ headed: bool = False
33
+ config_path: Optional[str] = None
34
+ device_profile_path: Optional[str] = None
35
+ env_file: Optional[str] = None
36
+ keep_alive: bool = False
37
+ close_session: bool = False
38
+ list_tabs: bool = False
39
+ tab_index: Optional[int] = None
40
+ timeout_minutes: Optional[int] = 30
41
+
42
+
43
+ @dataclass
44
+ class ApplyRequestContext:
45
+ """Encapsulates all parameters for an automated form filling and submission flow."""
46
+ url: Optional[str] = None
47
+ data_path: Optional[str] = None
48
+ fill_arguments: List[str] = field(default_factory=list)
49
+ press_buttons: List[str] = field(default_factory=list)
50
+ upload_files: List[str] = field(default_factory=list)
51
+ cookies_path: Optional[str] = None
52
+ submit: bool = False
53
+ screenshot_path: Optional[str] = None
54
+ headed: bool = False
55
+ config_path: Optional[str] = None
56
+ device_profile_path: Optional[str] = None
57
+ env_file: Optional[str] = None
58
+ keep_alive: bool = False
59
+ close_session: bool = False
60
+ list_tabs: bool = False
61
+ tab_index: Optional[int] = None
62
+ timeout_minutes: Optional[int] = 30
63
+ auto_inspect: bool = True
64
+
65
+
66
+ __all__ = [
67
+ "SessionContext",
68
+ "InspectionRequestContext",
69
+ "ApplyRequestContext",
70
+ "ApplyExecutionResult",
71
+ ]