aether-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.
@@ -0,0 +1,1236 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RegisterMcpTools = RegisterMcpTools;
4
+ const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
5
+ const zod_1 = require("zod");
6
+ const cdp_bridge_1 = require("./cdp-bridge");
7
+ const mcp_task_memory_1 = require("./mcp-task-memory");
8
+ const mcp_responses_1 = require("./mcp-responses");
9
+ const aether_memory_store_1 = require("./aether-memory-store");
10
+ const taskMemory = new mcp_task_memory_1.McpTaskMemory();
11
+ const aetherMemory = new aether_memory_store_1.AetherMemoryStore();
12
+ const Tools = [
13
+ {
14
+ name: "get_task_graph",
15
+ description: "Retrieve the hierarchical task graph for the current session (Aether v2 Task Orbit).",
16
+ inputSchema: { type: "object", properties: {} },
17
+ },
18
+ {
19
+ name: "configure_aether_memory",
20
+ description: "Initialize project-local Aether learning in <project>/.aether and add .aether/ to .gitignore. Use before storing or recalling repo-specific lessons and SKILL.md skills.",
21
+ inputSchema: {
22
+ type: "object",
23
+ properties: {
24
+ projectRoot: { type: "string", description: "Absolute path to the project/repo root. Defaults to AETHER_PROJECT_ROOT or the MCP process cwd." }
25
+ }
26
+ }
27
+ },
28
+ {
29
+ name: "remember_aether_lesson",
30
+ description: "Store a compact reusable lesson after a complex success, recovered error, user correction, or non-trivial workflow discovery. Stores only distilled issue/solution learning in project-local .aether.",
31
+ inputSchema: {
32
+ type: "object",
33
+ properties: {
34
+ projectRoot: { type: "string" },
35
+ title: { type: "string" },
36
+ trigger: { type: "string", description: "When this lesson should be considered again." },
37
+ problemPattern: { type: "string", description: "Reusable failure pattern or friction that appeared." },
38
+ symptoms: { type: "array", items: { type: "string" } },
39
+ failedApproach: { type: "string" },
40
+ betterApproach: { type: "string", description: "The reusable better way Aether learned." },
41
+ createdBecause: {
42
+ type: "string",
43
+ enum: [
44
+ "complex_task_succeeded",
45
+ "errors_overcome",
46
+ "user_corrected_approach_worked",
47
+ "non_trivial_workflow_discovered",
48
+ "user_asked_to_remember"
49
+ ]
50
+ },
51
+ evidence: { type: "string" },
52
+ tags: { type: "array", items: { type: "string" } },
53
+ confidence: { type: "number" }
54
+ },
55
+ required: ["title", "trigger", "problemPattern", "betterApproach", "createdBecause"]
56
+ }
57
+ },
58
+ {
59
+ name: "recall_aether_memory",
60
+ description: "Recall relevant project-local Aether lessons by intent, problem, or tags. Returns compact issue/solution records from .aether only.",
61
+ inputSchema: {
62
+ type: "object",
63
+ properties: {
64
+ projectRoot: { type: "string" },
65
+ intent: { type: "string" },
66
+ problem: { type: "string" },
67
+ tags: { type: "array", items: { type: "string" } },
68
+ limit: { type: "number" }
69
+ }
70
+ }
71
+ },
72
+ {
73
+ name: "record_aether_lesson_outcome",
74
+ description: "Update confidence for a stored Aether lesson after it succeeds or fails later.",
75
+ inputSchema: {
76
+ type: "object",
77
+ properties: {
78
+ projectRoot: { type: "string" },
79
+ id: { type: "string" },
80
+ success: { type: "boolean" },
81
+ evidence: { type: "string" }
82
+ },
83
+ required: ["id", "success"]
84
+ }
85
+ },
86
+ {
87
+ name: "create_aether_skill",
88
+ description: "Create or replace a project-local Claude-style skill at .aether/skills/<name>/SKILL.md for reusable procedures Aether should apply in this repo.",
89
+ inputSchema: {
90
+ type: "object",
91
+ properties: {
92
+ projectRoot: { type: "string" },
93
+ name: { type: "string", description: "Lowercase hyphenated skill name; normalized if needed." },
94
+ description: { type: "string", description: "Frontmatter description with what the skill does and when to use it." },
95
+ trigger: { type: "string", description: "Concise body text explaining when to apply the procedure." },
96
+ procedure: { type: "array", items: { type: "string" } },
97
+ examples: { type: "array", items: { type: "string" } },
98
+ edgeCases: { type: "array", items: { type: "string" } },
99
+ verification: { type: "array", items: { type: "string" } }
100
+ },
101
+ required: ["name", "description", "trigger", "procedure"]
102
+ }
103
+ },
104
+ {
105
+ name: "list_aether_skills",
106
+ description: "List project-local Aether SKILL.md procedures and their maintenance state.",
107
+ inputSchema: {
108
+ type: "object",
109
+ properties: {
110
+ projectRoot: { type: "string" }
111
+ }
112
+ }
113
+ },
114
+ {
115
+ name: "maintain_aether_skill",
116
+ description: "Maintain a project-local Aether skill: keep valuable skills, patch outdated instructions, consolidate near-duplicates, or prune stale skills.",
117
+ inputSchema: {
118
+ type: "object",
119
+ properties: {
120
+ projectRoot: { type: "string" },
121
+ name: { type: "string" },
122
+ action: { type: "string", enum: ["keep", "patch", "consolidate", "prune"] },
123
+ reason: { type: "string" },
124
+ patchBody: { type: "string", description: "Full replacement SKILL.md content when action=patch." },
125
+ consolidateInto: { type: "string", description: "Target skill name when action=consolidate." }
126
+ },
127
+ required: ["name", "action", "reason"]
128
+ }
129
+ },
130
+ {
131
+ name: "compact_aether_memory",
132
+ description: "Prune and compact project-local Aether memory according to caps, then refresh .aether/memory/learned.json.",
133
+ inputSchema: {
134
+ type: "object",
135
+ properties: {
136
+ projectRoot: { type: "string" }
137
+ }
138
+ }
139
+ },
140
+ {
141
+ name: "act",
142
+ description: "PRIMARY ACTION TOOL. Perform precise, high-speed actions in the browser. Uses native events which correctly trigger React/SPA state (unlike raw JS `value=` assignments). Supports navigation, clicking, typing, scrolling, and tab management with atomic verification.",
143
+ inputSchema: {
144
+ type: "object",
145
+ properties: {
146
+ action: {
147
+ type: "string",
148
+ enum: [
149
+ "navigate", "click", "type", "fill", "select", "check",
150
+ "hover", "scroll", "wait", "screenshot",
151
+ "new_tab", "switch_tab", "close_tab", "drag_and_drop", "upload_file",
152
+ "get_tree", "get_dom_tree", "configure", "print_pdf", "emulate_network",
153
+ "get_cookies", "set_cookie", "clear_cache", "set_geolocation", "set_timezone", "get_performance_metrics",
154
+ "start_screencast", "stop_screencast", "record_session", "sample_visual_frames",
155
+ "mock_network_request", "highlight_elements",
156
+ "assert", "start_tracing", "stop_tracing",
157
+ "screenshot_region", "verify_ui_state", "get_dom_snapshot", "get_event_listeners",
158
+ "get_computed_style", "get_network_traffic", "get_network_response",
159
+ "get_screencast_frames", "get_dom_storage", "get_logs", "press_key", "key_combo",
160
+ "click_text", "click_role", "fill_label", "element_at_point", "detect_captcha"
161
+ ],
162
+ description: "The action to perform."
163
+ },
164
+ selector: { type: "string", description: "CSS selector or text content to interact with." },
165
+ text: { type: "string", description: "Text to click, type, or match." },
166
+ key: { type: "string", description: "Keyboard key for press_key/key_combo." },
167
+ role: { type: "string", description: "ARIA/native role hint for semantic actions." },
168
+ name: { type: "string", description: "Accessible name for click_role." },
169
+ label: { type: "string", description: "Visible or accessible label for fill_label." },
170
+ elementId: { type: "string", description: "Element ID from `get_state` (e.g., '1' or '@1'). Preferred over selector. Both formats are accepted." },
171
+ value: { type: "string", description: "Value to type, option to select, or URL to navigate to." },
172
+ assertionType: { type: "string", description: "Assertion type for 'assert' action (e.g., 'element_exists', 'element_not_exists', 'element_contains_text', 'url_contains')." },
173
+ options: { type: "object", description: "Options for the action (e.g., {x, y, width, height} for screenshot_region)." },
174
+ domain: { type: "string", description: "CDP domain to enable (for enable_domain action)." },
175
+ coordinate: { type: "string", description: "X,Y coordinates (e.g., '100,200')." },
176
+ x: { type: "number", description: "X coordinate." },
177
+ y: { type: "number", description: "Y coordinate." },
178
+ visible: { type: "boolean", description: "Require visible selector when waiting. Default varies by action." },
179
+ stable: { type: "boolean", description: "Require stable element bounds when waiting. Default varies by action." },
180
+ parentId: { type: "string", description: "Parent task ID for hierarchical tracking (UFO3)." },
181
+ projectRoot: { type: "string", description: "Optional project root for recording distilled Aether lessons when recovery succeeds." },
182
+ tabId: { type: "number", description: "Tab ID for switching/closing." },
183
+ files: { type: "array", items: { type: "string" }, description: "Files for upload_file action" },
184
+ modifiers: { type: "array", items: { type: "string" }, description: "Key modifiers (Ctrl, Alt, etc.)" },
185
+ // Screencast / Record params
186
+ format: { type: "string", description: "Image format (jpeg/png). Default: jpeg" },
187
+ quality: { type: "number", description: "Compression quality (0-100). Default: 50" },
188
+ maxWidth: { type: "number", description: "Max width of the frame. Default: 1024" },
189
+ maxHeight: { type: "number", description: "Max height of the frame. Default: 768" },
190
+ everyNthFrame: { type: "number", description: "Frequency of captured frames. Default: 10" },
191
+ maxFrames: { type: "number", description: "Maximum number of frames to return. Default: all" },
192
+ duration: { type: "number", description: "Duration in ms to record (only for record_session). Default: 5000" },
193
+ // CDP specific params
194
+ cookieName: { type: "string", description: "Name of the cookie to set." },
195
+ cookieValue: { type: "string", description: "Value of the cookie to set." },
196
+ latitude: { type: "number", description: "Latitude for geolocation override." },
197
+ longitude: { type: "number", description: "Longitude for geolocation override." },
198
+ timezoneId: { type: "string", description: "Timezone ID (e.g., 'America/New_York')." },
199
+ // Mocking & Artifact params
200
+ urlPattern: { type: "string", description: "URL pattern to mock (e.g., '*api.example.com*')" },
201
+ mockResponse: { type: "string", description: "Stringified JSON to return as mocked response" },
202
+ markdownSummary: { type: "string", description: "Summary text for the artifact" },
203
+ requestId: { type: "string", description: "Request ID for get_network_response" },
204
+ // Network Emulation params
205
+ offline: { type: "boolean" },
206
+ latency: { type: "number" },
207
+ downloadThroughput: { type: "number" },
208
+ uploadThroughput: { type: "number" },
209
+ // PDF params (subset)
210
+ landscape: { type: "boolean" },
211
+ printBackground: { type: "boolean" },
212
+ // Configuration params
213
+ network: {
214
+ type: "object",
215
+ properties: { blockImages: { type: "boolean" }, blockAds: { type: "boolean" }, blockCSS: { type: "boolean" } }
216
+ },
217
+ emulation: {
218
+ type: "object",
219
+ properties: { width: { type: "number" }, height: { type: "number" }, mobile: { type: "boolean" }, userAgent: { type: "string" } }
220
+ },
221
+ script: {
222
+ type: "object",
223
+ properties: { onLoad: { type: "string" } }
224
+ }
225
+ },
226
+ required: ["action"],
227
+ },
228
+ },
229
+ {
230
+ name: "get_state",
231
+ description: "Capture the current browser state (v2). Lightweight by default; opt into screenshots, DOM snapshot, SoM overlay, or tabs when needed.",
232
+ inputSchema: {
233
+ type: "object",
234
+ properties: {
235
+ screenshot: { type: "boolean", description: "Include screenshot. Default false." },
236
+ domSnapshot: { type: "boolean", description: "Include full DOMSnapshot. Default false." },
237
+ elements: { type: "boolean", description: "Include interactive elements. Default true." },
238
+ som: { type: "boolean", description: "Inject Set-of-Marks overlay. Default false." },
239
+ tabs: { type: "boolean", description: "Include browser tabs. Default false." }
240
+ }
241
+ },
242
+ },
243
+ {
244
+ name: "browser_status",
245
+ description: "FAST IDE TOOL. Return compact browser connection and active target status without launching a browser.",
246
+ inputSchema: {
247
+ type: "object",
248
+ properties: {
249
+ includeTargets: { type: "boolean", description: "Include known CDP targets/tabs when already connected." }
250
+ }
251
+ }
252
+ },
253
+ {
254
+ name: "snapshot_compact",
255
+ description: "FAST IDE TOOL. Capture a small text-only page snapshot: title, URL, readyState, and a limited interactive element list. No screenshot or DOM snapshot by default.",
256
+ inputSchema: {
257
+ type: "object",
258
+ properties: {
259
+ maxElements: { type: "number", description: "Maximum interactive elements to return. Default 30." },
260
+ includeText: { type: "boolean", description: "Include short visible text for elements. Default true." }
261
+ }
262
+ }
263
+ },
264
+ {
265
+ name: "list_interactive_elements",
266
+ description: "FAST IDE TOOL. Return compact clickable/typable element references that can be passed to click_by_ref. Does not inject visual overlays unless requested.",
267
+ inputSchema: {
268
+ type: "object",
269
+ properties: {
270
+ maxElements: { type: "number", description: "Maximum elements to return. Default 50." },
271
+ withOverlay: { type: "boolean", description: "Inject Set-of-Marks overlay. Default false." }
272
+ }
273
+ }
274
+ },
275
+ {
276
+ name: "click_by_ref",
277
+ description: "FAST IDE TOOL. Click an element reference returned by snapshot_compact or list_interactive_elements.",
278
+ inputSchema: {
279
+ type: "object",
280
+ properties: {
281
+ ref: { type: "string", description: "Element reference, usually css:<selector>." }
282
+ },
283
+ required: ["ref"]
284
+ }
285
+ },
286
+ {
287
+ name: "click_by_selector",
288
+ description: "FAST IDE TOOL. Click a CSS selector with a compact response.",
289
+ inputSchema: {
290
+ type: "object",
291
+ properties: {
292
+ selector: { type: "string" },
293
+ timeout: { type: "number", description: "Timeout in ms. Default 5000." }
294
+ },
295
+ required: ["selector"]
296
+ }
297
+ },
298
+ {
299
+ name: "fill_by_selector",
300
+ description: "FAST IDE TOOL. Focus, clear, and type text into a CSS selector with a compact response.",
301
+ inputSchema: {
302
+ type: "object",
303
+ properties: {
304
+ selector: { type: "string" },
305
+ value: { type: "string" },
306
+ timeout: { type: "number", description: "Timeout in ms. Default 5000." }
307
+ },
308
+ required: ["selector", "value"]
309
+ }
310
+ },
311
+ {
312
+ name: "wait_for_selector",
313
+ description: "FAST IDE TOOL. Wait for a selector and return a compact boolean result.",
314
+ inputSchema: {
315
+ type: "object",
316
+ properties: {
317
+ selector: { type: "string" },
318
+ timeout: { type: "number", description: "Timeout in ms. Default 5000." }
319
+ },
320
+ required: ["selector"]
321
+ }
322
+ },
323
+ {
324
+ name: "wait_for_text",
325
+ description: "FAST IDE TOOL. Wait until visible page text contains the expected string.",
326
+ inputSchema: {
327
+ type: "object",
328
+ properties: {
329
+ text: { type: "string" },
330
+ timeout: { type: "number", description: "Timeout in ms. Default 5000." }
331
+ },
332
+ required: ["text"]
333
+ }
334
+ },
335
+ {
336
+ name: "get_network_errors",
337
+ description: "FAST IDE TOOL. Return recent failed network entries only, keeping debugging payloads small.",
338
+ inputSchema: {
339
+ type: "object",
340
+ properties: {
341
+ limit: { type: "number", description: "Maximum errors to return. Default 20." }
342
+ }
343
+ }
344
+ },
345
+ {
346
+ name: "browser_intent",
347
+ description: "NATURAL IDE TOOL. Perform a high-level browser intent such as click, fill, select, check, wait_for, inspect, or navigate. Resolves targets by selector, role, text, aria-label, label, placeholder, name, and visibility before acting.",
348
+ inputSchema: {
349
+ type: "object",
350
+ properties: {
351
+ intent: { type: "string", enum: ["click", "fill", "select", "check", "wait_for", "inspect", "navigate"] },
352
+ target: { type: "string", description: "Natural target such as 'login', 'Email', 'Submit', or a CSS selector." },
353
+ value: { type: "string", description: "Value for fill/select/navigate or expected text for wait_for." },
354
+ role: { type: "string", description: "Optional role hint such as button, link, textbox, checkbox, combobox." },
355
+ timeout: { type: "number", description: "Timeout in ms. Default 7000." },
356
+ verify: { type: "string", description: "Optional text to wait for after the action." },
357
+ includeCandidates: { type: "boolean", description: "Include resolver candidates for debugging. Default false." }
358
+ },
359
+ required: ["intent"]
360
+ }
361
+ },
362
+ {
363
+ name: "get_logs",
364
+ description: "FAST DEBUG TOOL. Return recent console logs, runtime exceptions, browser log entries, and JavaScript dialogs.",
365
+ inputSchema: {
366
+ type: "object",
367
+ properties: {
368
+ limit: { type: "number", description: "Maximum log entries to return. Default 50." }
369
+ }
370
+ }
371
+ },
372
+ {
373
+ name: "detect_captcha",
374
+ description: "SAFE GUARD TOOL. Detect common CAPTCHA/human-verification widgets and report manual-solve requirement without interacting with them.",
375
+ inputSchema: { type: "object", properties: {} }
376
+ },
377
+ {
378
+ name: "solve_captcha",
379
+ description: "AUTO-SOLVE TOOL. Solves the CAPTCHA on the current page by simulating human mouse movement and clicking the widget (no API key needed). Works for Cloudflare Turnstile, reCAPTCHA v2 checkbox, hCaptcha. Set useService=true to fall back to a third-party solving service for image challenges.",
380
+ inputSchema: {
381
+ type: "object",
382
+ properties: {
383
+ useService: { type: "boolean", description: "Use third-party service instead of human simulation. Default false." },
384
+ service: { type: "string", enum: ["2captcha", "capsolver"], description: "Service to use when useService=true. Default '2captcha'." },
385
+ apiKey: { type: "string", description: "API key for the service. Defaults to env CAPTCHA_API_KEY." },
386
+ pageUrl: { type: "string", description: "Page URL passed to the service. Defaults to current browser URL." },
387
+ waitAfterClick: { type: "number", description: "Ms to wait for captcha to clear after human click. Default 8000." },
388
+ timeout: { type: "number", description: "Max wait for service solution in ms. Default 120000." },
389
+ pollInterval: { type: "number", description: "Service poll interval in ms. Default 5000." }
390
+ }
391
+ }
392
+ },
393
+ {
394
+ name: "press_key",
395
+ description: "FAST IDE TOOL. Press a keyboard key or shortcut using native CDP key events.",
396
+ inputSchema: {
397
+ type: "object",
398
+ properties: {
399
+ key: { type: "string", description: "Key such as Enter, Tab, Escape, Backspace, ArrowDown, or a single letter." },
400
+ modifiers: { type: "array", items: { type: "string" }, description: "Optional modifiers such as Ctrl, Shift, Alt, Meta." }
401
+ },
402
+ required: ["key"]
403
+ }
404
+ },
405
+ {
406
+ name: "click_text",
407
+ description: "FAST IDE TOOL. Click a visible element by text with optional role hint and compact post-action facts.",
408
+ inputSchema: {
409
+ type: "object",
410
+ properties: {
411
+ text: { type: "string" },
412
+ role: { type: "string" },
413
+ timeout: { type: "number" }
414
+ },
415
+ required: ["text"]
416
+ }
417
+ },
418
+ {
419
+ name: "click_role",
420
+ description: "FAST IDE TOOL. Click a visible element by role and accessible name.",
421
+ inputSchema: {
422
+ type: "object",
423
+ properties: {
424
+ role: { type: "string" },
425
+ name: { type: "string" },
426
+ timeout: { type: "number" }
427
+ },
428
+ required: ["role"]
429
+ }
430
+ },
431
+ {
432
+ name: "fill_label",
433
+ description: "FAST IDE TOOL. Fill a textbox-like field by visible/accessibility label with compact post-action facts.",
434
+ inputSchema: {
435
+ type: "object",
436
+ properties: {
437
+ label: { type: "string" },
438
+ value: { type: "string" },
439
+ role: { type: "string" },
440
+ timeout: { type: "number" }
441
+ },
442
+ required: ["label", "value"]
443
+ }
444
+ },
445
+ {
446
+ name: "element_at_point",
447
+ description: "FAST DEBUG TOOL. Inspect the element that would receive a coordinate click.",
448
+ inputSchema: {
449
+ type: "object",
450
+ properties: {
451
+ x: { type: "number" },
452
+ y: { type: "number" },
453
+ coordinate: { type: "string", description: "Alternative X,Y coordinate string." }
454
+ }
455
+ }
456
+ },
457
+ {
458
+ name: "execute_script",
459
+ description: "Execute arbitrary JavaScript in the browser context. Tip: wrap your code in a block `{}` or IIFE to avoid `SyntaxError: Identifier has already been declared` when reusing variable names across multiple calls.",
460
+ inputSchema: {
461
+ type: "object",
462
+ properties: { script: { type: "string" } },
463
+ required: ["script"],
464
+ },
465
+ },
466
+ {
467
+ name: "cdp_command",
468
+ description: "Execute a raw Chrome DevTools Protocol (CDP) command on the active tab.",
469
+ inputSchema: {
470
+ type: "object",
471
+ properties: {
472
+ command: { type: "string", description: "The CDP command method (e.g., 'Network.getCookies')." },
473
+ args: { type: "object", description: "The JSON arguments required by the CDP command." }
474
+ },
475
+ required: ["command"],
476
+ }
477
+ },
478
+ {
479
+ name: "connect_browser",
480
+ description: "Connect to browser. Auto-detects and launches available browser if not connected.",
481
+ inputSchema: {
482
+ type: "object",
483
+ properties: {
484
+ mode: { type: "string", enum: ["connect", "launch", "auto", "ask"], description: "Connect to existing, launch new instance, or return selectable launch choices." },
485
+ port: { type: "number", description: "Browser debugging port (default: 9222)." },
486
+ headless: { type: "boolean", description: "Run in headless mode (only for launch mode)." },
487
+ browser: { type: "string", enum: ["chrome", "edge", "brave", "firefox"], description: "Browser to use (default: auto-detect, or brave when profile is set)." },
488
+ profile: { type: "string", description: "Named browser profile to launch, e.g. Personal or Work. Defaults browser to brave when set." },
489
+ profileDirectory: { type: "string", description: "Exact Chromium profile directory, e.g. Default or Profile 1." },
490
+ userDataDir: { type: "string", description: "Chromium user data root to use with profileDirectory." }
491
+ }
492
+ }
493
+ },
494
+ {
495
+ name: "launch_browser",
496
+ description: "Launch a browser (auto-detects available browsers if not specified).",
497
+ inputSchema: {
498
+ type: "object",
499
+ properties: {
500
+ browser: { type: "string", enum: ["chrome", "edge", "brave", "firefox"], description: "Browser to launch (default: auto-detect first available)." },
501
+ headless: { type: "boolean", description: "Run in headless mode." },
502
+ port: { type: "number", description: "Debugging port (default: 9222)." },
503
+ profile: { type: "string", description: "Named browser profile to launch, e.g. Personal or Work. Defaults browser to brave when set." },
504
+ profileDirectory: { type: "string", description: "Exact Chromium profile directory, e.g. Default or Profile 1." },
505
+ userDataDir: { type: "string", description: "Chromium user data root to use with profileDirectory." }
506
+ }
507
+ }
508
+ },
509
+ {
510
+ name: "kill_browser",
511
+ description: "Kill the launched browser process when done.",
512
+ inputSchema: { type: "object", properties: {} }
513
+ },
514
+ {
515
+ name: "list_browsers",
516
+ description: "List all available browsers on the system.",
517
+ inputSchema: { type: "object", properties: {} }
518
+ },
519
+ {
520
+ name: "list_browser_profiles",
521
+ description: "List Chromium profiles that can be launched with browser control. Defaults to Brave.",
522
+ inputSchema: {
523
+ type: "object",
524
+ properties: {
525
+ browser: { type: "string", enum: ["chrome", "edge", "brave"], description: "Browser profile store to inspect. Default: brave." }
526
+ }
527
+ }
528
+ },
529
+ {
530
+ name: "sample_visual_frames",
531
+ description: "VISION TOOL. Capture a few compressed screencast frames so the agent can inspect animation/video/dynamic UI without recording a large session.",
532
+ inputSchema: {
533
+ type: "object",
534
+ properties: {
535
+ duration: { type: "number", description: "Sampling duration in ms. Default 1500, max 10000." },
536
+ maxFrames: { type: "number", description: "Maximum frames to return. Default 4, max 12." },
537
+ quality: { type: "number", description: "JPEG quality. Default 45." },
538
+ maxWidth: { type: "number", description: "Max frame width. Default 800." },
539
+ maxHeight: { type: "number", description: "Max frame height. Default 600." },
540
+ everyNthFrame: { type: "number", description: "CDP frame sampling interval. Default 3." }
541
+ }
542
+ }
543
+ },
544
+ // ==================== AGENT-CENTRIC APIs ====================
545
+ {
546
+ name: "agent_action",
547
+ description: "WARNING: Can return massive payloads (>100KB) with full DOM state and logs. Prefer using `act` for routine operations. Execute an action and optionally verify UI state. Unified action API that combines action + wait + verify in one call. Returns screenshot after action.",
548
+ inputSchema: {
549
+ type: "object",
550
+ properties: {
551
+ action: {
552
+ type: "string",
553
+ enum: ["click", "type", "scroll", "hover", "drag", "key_press"],
554
+ description: "Action to perform."
555
+ },
556
+ target: {
557
+ type: "object",
558
+ properties: {
559
+ id: { type: "string", description: "Element ID from page_snapshot" },
560
+ selector: { type: "string", description: "CSS selector" },
561
+ text: { type: "string", description: "Text to match" },
562
+ x: { type: "number", description: "X coordinate" },
563
+ y: { type: "number", description: "Y coordinate" },
564
+ button: { type: "string", enum: ["left", "middle", "right"] },
565
+ clickCount: { type: "number" },
566
+ key: { type: "string", description: "Key to press" },
567
+ modifiers: { type: "array", items: { type: "string" } }
568
+ }
569
+ },
570
+ verify: {
571
+ type: "object",
572
+ properties: {
573
+ selector: { type: "string" },
574
+ expectedText: { type: "string" },
575
+ type: { type: "string", enum: ["element_exists", "element_contains_text", "text_match", "element_visible"] }
576
+ }
577
+ },
578
+ waitFor: {
579
+ type: "object",
580
+ properties: {
581
+ type: { type: "string", enum: ["network_idle", "element", "navigation"] },
582
+ selector: { type: "string" },
583
+ timeout: { type: "number" }
584
+ }
585
+ },
586
+ screenshot: { type: "boolean", description: "Return screenshot after action. Default false." },
587
+ timeout: { type: "number", description: "Timeout in ms (default: 10000)" }
588
+ },
589
+ required: ["action", "target"]
590
+ }
591
+ },
592
+ {
593
+ name: "smart_navigate",
594
+ description: "Navigate to URL with built-in waiting for page stability. Auto-dismisses popups. Returns screenshot of loaded page.",
595
+ inputSchema: {
596
+ type: "object",
597
+ properties: {
598
+ url: { type: "string", description: "URL to navigate to." },
599
+ waitFor: {
600
+ type: "object",
601
+ properties: {
602
+ type: { type: "string", enum: ["element", "network_idle"] },
603
+ selector: { type: "string" },
604
+ timeout: { type: "number" }
605
+ }
606
+ },
607
+ dismissPopups: { type: "boolean", description: "Auto-dismiss popups (default: true)" },
608
+ screenshot: { type: "boolean", description: "Return screenshot (default: true)" },
609
+ timeout: { type: "number", description: "Navigation timeout in ms (default: 30000)" }
610
+ },
611
+ required: ["url"]
612
+ }
613
+ },
614
+ {
615
+ name: "observe_and_act",
616
+ description: "Execute an action and observe page state changes. Returns before/after snapshots to detect what changed.",
617
+ inputSchema: {
618
+ type: "object",
619
+ properties: {
620
+ action: {
621
+ type: "object",
622
+ properties: {
623
+ type: { type: "string", enum: ["click", "type"] },
624
+ selector: { type: "string" },
625
+ text: { type: "string" }
626
+ }
627
+ },
628
+ observe: {
629
+ type: "object",
630
+ properties: {
631
+ type: { type: "string", enum: ["dom_change", "network_response"] }
632
+ }
633
+ },
634
+ returnScreenshot: { type: "boolean", description: "Return screenshots (default: true)" }
635
+ },
636
+ required: ["action"]
637
+ }
638
+ },
639
+ {
640
+ name: "agent_form_fill",
641
+ description: "Intelligently fill form fields. Auto-detects field types (text, select, checkbox, radio, file).",
642
+ inputSchema: {
643
+ type: "object",
644
+ properties: {
645
+ fields: {
646
+ type: "array",
647
+ items: {
648
+ type: "object",
649
+ properties: {
650
+ id: { type: "string" },
651
+ selector: { type: "string" },
652
+ type: { type: "string", enum: ["text", "email", "password", "select", "checkbox", "radio", "file", "textarea"] },
653
+ value: { type: "string" },
654
+ checked: { type: "boolean" },
655
+ files: { type: "array", items: { type: "string" } }
656
+ }
657
+ },
658
+ description: "Form fields to fill."
659
+ },
660
+ submitAfterFill: { type: "boolean", description: "Submit form after filling (default: false)" },
661
+ submitSelector: { type: "string", description: "Selector for submit button" }
662
+ },
663
+ required: ["fields"]
664
+ }
665
+ },
666
+ {
667
+ name: "page_snapshot",
668
+ description: "Capture page context optimized for LLM consumption. Lightweight by default; opt into screenshots, cookies, accessibility tree, or full DOM snapshot when needed.",
669
+ inputSchema: {
670
+ type: "object",
671
+ properties: {
672
+ fullPage: { type: "boolean", description: "Full page screenshot (default: false)" },
673
+ includeDOMSnapshot: { type: "boolean", description: "Include full DOM snapshot (default: false)" },
674
+ screenshot: { type: "boolean", description: "Include screenshot. Default false." },
675
+ cookies: { type: "boolean", description: "Include cookies. Default false." },
676
+ accessibilityTree: { type: "boolean", description: "Include simplified accessibility tree. Default false." }
677
+ }
678
+ }
679
+ }
680
+ ];
681
+ function RegisterMcpTools(server, wsServer) {
682
+ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({ tools: Tools }));
683
+ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
684
+ const { name, arguments: args } = request.params;
685
+ const a = args;
686
+ const bridge = (0, cdp_bridge_1.getCdpBridge)();
687
+ try {
688
+ if (name === "get_task_graph") {
689
+ return (0, mcp_responses_1.jsonContent)(taskMemory.graph(), true);
690
+ }
691
+ if (name === "configure_aether_memory") {
692
+ return (0, mcp_responses_1.jsonContent)(await aetherMemory.configure(a?.projectRoot), true);
693
+ }
694
+ if (name === "remember_aether_lesson") {
695
+ return (0, mcp_responses_1.jsonContent)(await aetherMemory.rememberLesson({
696
+ projectRoot: a?.projectRoot,
697
+ title: a.title,
698
+ trigger: a.trigger,
699
+ problemPattern: a.problemPattern,
700
+ symptoms: a?.symptoms,
701
+ failedApproach: a?.failedApproach,
702
+ betterApproach: a.betterApproach,
703
+ createdBecause: a.createdBecause,
704
+ evidence: a?.evidence,
705
+ tags: a?.tags,
706
+ confidence: a?.confidence,
707
+ }), true);
708
+ }
709
+ if (name === "recall_aether_memory") {
710
+ return (0, mcp_responses_1.jsonContent)(await aetherMemory.recallLessons({
711
+ projectRoot: a?.projectRoot,
712
+ intent: a?.intent,
713
+ problem: a?.problem,
714
+ tags: a?.tags,
715
+ limit: a?.limit,
716
+ }), true);
717
+ }
718
+ if (name === "record_aether_lesson_outcome") {
719
+ return (0, mcp_responses_1.jsonContent)(await aetherMemory.recordLessonOutcome({
720
+ projectRoot: a?.projectRoot,
721
+ id: a.id,
722
+ success: a.success,
723
+ evidence: a?.evidence,
724
+ }), true);
725
+ }
726
+ if (name === "create_aether_skill") {
727
+ return (0, mcp_responses_1.jsonContent)(await aetherMemory.createSkill({
728
+ projectRoot: a?.projectRoot,
729
+ name: a.name,
730
+ description: a.description,
731
+ trigger: a.trigger,
732
+ procedure: a.procedure,
733
+ examples: a?.examples,
734
+ edgeCases: a?.edgeCases,
735
+ verification: a?.verification,
736
+ }), true);
737
+ }
738
+ if (name === "list_aether_skills") {
739
+ return (0, mcp_responses_1.jsonContent)(await aetherMemory.listSkills(a?.projectRoot), true);
740
+ }
741
+ if (name === "maintain_aether_skill") {
742
+ return (0, mcp_responses_1.jsonContent)(await aetherMemory.maintainSkill({
743
+ projectRoot: a?.projectRoot,
744
+ name: a.name,
745
+ action: a.action,
746
+ reason: a.reason,
747
+ patchBody: a?.patchBody,
748
+ consolidateInto: a?.consolidateInto,
749
+ }), true);
750
+ }
751
+ if (name === "compact_aether_memory") {
752
+ return (0, mcp_responses_1.jsonContent)(await aetherMemory.compact(a?.projectRoot), true);
753
+ }
754
+ if (name === "connect_browser") {
755
+ const mode = a?.mode || "auto";
756
+ const port = a?.port || 9222;
757
+ const browser = a?.browser || ((a?.profile || a?.profileDirectory) ? "brave" : undefined);
758
+ if (mode === "ask") {
759
+ const profiles = await bridge.listBrowserProfiles("brave");
760
+ const choices = [
761
+ { id: "clean", label: "Aether clean browser" },
762
+ ...profiles.map((p) => ({ id: `${p.browser}:${p.directory}`, label: `${p.name} (${p.browser})` }))
763
+ ];
764
+ const clientCapabilities = server.getClientCapabilities?.();
765
+ if (clientCapabilities?.elicitation) {
766
+ try {
767
+ const response = await server.request({
768
+ method: "elicitation/create",
769
+ params: {
770
+ message: "Which browser should Aether control?",
771
+ requestedSchema: {
772
+ type: "object",
773
+ properties: {
774
+ choice: {
775
+ type: "string",
776
+ enum: choices.map((choice) => choice.id),
777
+ description: choices.map((choice) => `${choice.id} = ${choice.label}`).join("; ")
778
+ }
779
+ },
780
+ required: ["choice"]
781
+ }
782
+ }
783
+ }, zod_1.z.object({
784
+ action: zod_1.z.string(),
785
+ content: zod_1.z.any().optional()
786
+ }).passthrough());
787
+ if (response.action !== "accept") {
788
+ return { content: [{ type: "text", text: "Browser launch cancelled." }] };
789
+ }
790
+ const choice = response.content?.choice;
791
+ if (choice === "clean") {
792
+ const result = await bridge.launchBrowser({ port, headless: a?.headless });
793
+ return { content: [{ type: "text", text: result }] };
794
+ }
795
+ const selected = profiles.find((p) => `${p.browser}:${p.directory}` === choice);
796
+ if (!selected) {
797
+ throw new Error(`Unknown browser choice: ${choice}`);
798
+ }
799
+ const result = await bridge.launchBrowser({
800
+ browser: selected.browser,
801
+ profileDirectory: selected.directory,
802
+ userDataDir: selected.userDataDir,
803
+ port,
804
+ headless: a?.headless,
805
+ });
806
+ return { content: [{ type: "text", text: result }] };
807
+ }
808
+ catch (error) {
809
+ console.error("[MCP] Elicitation failed; falling back to text choices:", error);
810
+ }
811
+ }
812
+ const fallbackChoices = [
813
+ `Aether clean browser: call launch_browser({ "port": ${port} })`,
814
+ ...profiles.map((p) => `${p.name}: call launch_browser({ "browser": "${p.browser}", "profile": "${p.name}", "port": ${port} })`)
815
+ ].join("\n");
816
+ return { content: [{ type: "text", text: `Available controlled browser choices:\n${fallbackChoices}` }] };
817
+ }
818
+ if (mode === "connect") {
819
+ await bridge.sendCommand("connect", { port });
820
+ return { content: [{ type: "text", text: "Connected to browser successfully" }] };
821
+ }
822
+ else if (mode === "launch") {
823
+ const result = await bridge.launchBrowser({
824
+ browser,
825
+ headless: a?.headless,
826
+ port,
827
+ profile: a?.profile,
828
+ profileDirectory: a?.profileDirectory,
829
+ userDataDir: a?.userDataDir,
830
+ });
831
+ return { content: [{ type: "text", text: result }] };
832
+ }
833
+ else {
834
+ // auto mode - detect and launch
835
+ const result = await bridge.launchBrowser({
836
+ browser,
837
+ headless: a?.headless,
838
+ port,
839
+ profile: a?.profile,
840
+ profileDirectory: a?.profileDirectory,
841
+ userDataDir: a?.userDataDir,
842
+ });
843
+ return { content: [{ type: "text", text: result }] };
844
+ }
845
+ }
846
+ if (name === "launch_browser") {
847
+ const browser = a?.browser || ((a?.profile || a?.profileDirectory) ? "brave" : undefined);
848
+ const result = await bridge.launchBrowser({
849
+ browser,
850
+ headless: a?.headless,
851
+ port: a?.port,
852
+ profile: a?.profile,
853
+ profileDirectory: a?.profileDirectory,
854
+ userDataDir: a?.userDataDir,
855
+ });
856
+ return { content: [{ type: "text", text: result }] };
857
+ }
858
+ if (name === "kill_browser") {
859
+ const result = await bridge.killBrowser();
860
+ return { content: [{ type: "text", text: result }] };
861
+ }
862
+ if (name === "list_browsers") {
863
+ const browsers = await bridge.listBrowsers();
864
+ if (browsers.length === 0) {
865
+ return { content: [{ type: "text", text: "No supported browsers found. Please install Chrome, Edge, Brave, or Firefox." }] };
866
+ }
867
+ const list = browsers.map((b) => `${b.name}: ${b.path}`).join("\n");
868
+ return { content: [{ type: "text", text: `Available browsers:\n${list}` }] };
869
+ }
870
+ if (name === "list_browser_profiles") {
871
+ const profiles = await bridge.listBrowserProfiles(a?.browser || "brave");
872
+ if (profiles.length === 0) {
873
+ return { content: [{ type: "text", text: `No profiles found for ${a?.browser || "brave"}.` }] };
874
+ }
875
+ const list = profiles
876
+ .map((p) => `${p.name}: ${p.browser}, directory=${p.directory}, userDataDir=${p.userDataDir}`)
877
+ .join("\n");
878
+ return { content: [{ type: "text", text: `Available browser profiles:\n${list}` }] };
879
+ }
880
+ if (name === "sample_visual_frames") {
881
+ const result = await bridge.sendCommand("sample_visual_frames", {
882
+ duration: a?.duration,
883
+ maxFrames: a?.maxFrames,
884
+ quality: a?.quality,
885
+ maxWidth: a?.maxWidth,
886
+ maxHeight: a?.maxHeight,
887
+ everyNthFrame: a?.everyNthFrame
888
+ });
889
+ const content = [{
890
+ type: "text",
891
+ text: JSON.stringify({
892
+ success: result.success,
893
+ frameCount: result.frameCount,
894
+ duration: result.duration,
895
+ timestamps: result.timestamps
896
+ })
897
+ }];
898
+ for (const frame of result.frames || []) {
899
+ content.push({ type: "image", data: frame, mimeType: "image/jpeg" });
900
+ }
901
+ return { content };
902
+ }
903
+ if (name === "get_state") {
904
+ const result = await bridge.sendCommand("get_state", {
905
+ screenshot: a?.screenshot === true,
906
+ domSnapshot: a?.domSnapshot === true || a?.includeDOMSnapshot === true,
907
+ som: a?.som === true || a?.withOverlay === true,
908
+ tabs: a?.tabs === true,
909
+ elements: a?.elements !== false,
910
+ });
911
+ if (!result)
912
+ throw new Error("Received empty state");
913
+ taskMemory.recordSession({ title: result.title, url: result.url });
914
+ const content = [
915
+ { type: "text", text: `Title: ${result.title}\nURL: ${result.url}` },
916
+ ];
917
+ if (result.screenshot) {
918
+ content.push({ type: "image", data: result.screenshot, mimeType: "image/jpeg" });
919
+ }
920
+ return { content };
921
+ }
922
+ if (name === "browser_status") {
923
+ const result = await bridge.sendCommand("browser_status", { includeTargets: a?.includeTargets });
924
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
925
+ }
926
+ if (name === "snapshot_compact") {
927
+ const result = await bridge.sendCommand("snapshot_compact", {
928
+ maxElements: a?.maxElements,
929
+ includeText: a?.includeText
930
+ });
931
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
932
+ }
933
+ if (name === "list_interactive_elements") {
934
+ const result = await bridge.sendCommand("list_interactive_elements", {
935
+ maxElements: a?.maxElements,
936
+ withOverlay: a?.withOverlay
937
+ });
938
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
939
+ }
940
+ if (name === "click_by_ref") {
941
+ const result = await bridge.sendCommand("click_by_ref", { ref: a.ref });
942
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
943
+ }
944
+ if (name === "click_by_selector") {
945
+ const result = await bridge.sendCommand("click_by_selector", {
946
+ selector: a.selector,
947
+ timeout: a?.timeout
948
+ });
949
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
950
+ }
951
+ if (name === "fill_by_selector") {
952
+ const result = await bridge.sendCommand("fill_by_selector", {
953
+ selector: a.selector,
954
+ value: a.value,
955
+ timeout: a?.timeout
956
+ });
957
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
958
+ }
959
+ if (name === "wait_for_selector") {
960
+ const result = await bridge.sendCommand("wait_for_selector", {
961
+ selector: a.selector,
962
+ timeout: a?.timeout
963
+ });
964
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
965
+ }
966
+ if (name === "wait_for_text") {
967
+ const result = await bridge.sendCommand("wait_for_text", {
968
+ text: a.text,
969
+ timeout: a?.timeout
970
+ });
971
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
972
+ }
973
+ if (name === "get_network_errors") {
974
+ const result = await bridge.sendCommand("get_network_errors", { limit: a?.limit });
975
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
976
+ }
977
+ if (name === "browser_intent") {
978
+ const result = await bridge.sendCommand("browser_intent", {
979
+ intent: a.intent,
980
+ target: a?.target,
981
+ value: a?.value,
982
+ role: a?.role,
983
+ timeout: a?.timeout,
984
+ verify: a?.verify,
985
+ includeCandidates: a?.includeCandidates
986
+ });
987
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
988
+ }
989
+ if (name === "get_logs") {
990
+ const result = await bridge.sendCommand("get_logs", { limit: a?.limit });
991
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
992
+ }
993
+ if (name === "detect_captcha") {
994
+ const result = await bridge.sendCommand("detect_captcha", {});
995
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
996
+ }
997
+ if (name === "solve_captcha") {
998
+ const result = await bridge.sendCommand("solve_captcha", {
999
+ useService: a?.useService,
1000
+ service: a?.service,
1001
+ apiKey: a?.apiKey,
1002
+ pageUrl: a?.pageUrl,
1003
+ waitAfterClick: a?.waitAfterClick,
1004
+ timeout: a?.timeout,
1005
+ pollInterval: a?.pollInterval,
1006
+ });
1007
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
1008
+ }
1009
+ if (name === "press_key") {
1010
+ const result = await bridge.sendCommand("press_key", { key: a.key, modifiers: a?.modifiers });
1011
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
1012
+ }
1013
+ if (name === "click_text") {
1014
+ const result = await bridge.sendCommand("click_text", {
1015
+ text: a.text,
1016
+ role: a?.role,
1017
+ timeout: a?.timeout
1018
+ });
1019
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
1020
+ }
1021
+ if (name === "click_role") {
1022
+ const result = await bridge.sendCommand("click_role", {
1023
+ role: a.role,
1024
+ name: a?.name,
1025
+ timeout: a?.timeout
1026
+ });
1027
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
1028
+ }
1029
+ if (name === "fill_label") {
1030
+ const result = await bridge.sendCommand("fill_label", {
1031
+ label: a.label,
1032
+ value: a.value,
1033
+ role: a?.role,
1034
+ timeout: a?.timeout
1035
+ });
1036
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
1037
+ }
1038
+ if (name === "element_at_point") {
1039
+ const result = await bridge.sendCommand("element_at_point", {
1040
+ x: a?.x,
1041
+ y: a?.y,
1042
+ coordinate: a?.coordinate
1043
+ });
1044
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
1045
+ }
1046
+ if (name === "execute_script") {
1047
+ const result = await bridge.sendCommand("evaluate", { script: String(a?.script) });
1048
+ return { content: [{ type: "text", text: `Result: ${JSON.stringify(result)}` }] };
1049
+ }
1050
+ if (name === "cdp_command") {
1051
+ const result = await bridge.sendCommand("cdp_command", { command: a.command, args: a.args || {} });
1052
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1053
+ }
1054
+ if (name === "act") {
1055
+ const action = a.action;
1056
+ let currentState = { url: "unknown" };
1057
+ try {
1058
+ currentState = await bridge.sendCommand("get_state", {
1059
+ screenshot: false,
1060
+ domSnapshot: false,
1061
+ elements: false,
1062
+ som: false,
1063
+ tabs: false,
1064
+ });
1065
+ }
1066
+ catch { }
1067
+ const node = taskMemory.create(action, currentState.url, a.parentId);
1068
+ let resultMsg = "";
1069
+ const eid = a.elementId ? String(a.elementId).replace(/@/g, '') : undefined;
1070
+ try {
1071
+ if (action === "click") {
1072
+ try {
1073
+ if (eid)
1074
+ resultMsg = await bridge.sendCommand("click_element", { id: eid, text: a.value });
1075
+ else if (a.selector)
1076
+ resultMsg = await bridge.sendCommand("click_element_by_selector", { selector: a.selector });
1077
+ else if (a.coordinate) {
1078
+ const [x, y] = String(a.coordinate).split(',').map(Number);
1079
+ resultMsg = await bridge.sendCommand("click", { x, y });
1080
+ }
1081
+ else {
1082
+ resultMsg = await bridge.sendCommand(action, a);
1083
+ }
1084
+ }
1085
+ catch (err) {
1086
+ // SELF-HEALING: Try fuzzy match if exact failed
1087
+ console.error(`[Aether] Action failed, attempting self-healing...`);
1088
+ const resolved = await bridge.resolveSelector({
1089
+ originalSelector: a.selector,
1090
+ text: a.value || a.text
1091
+ }).catch(() => null);
1092
+ if (resolved) {
1093
+ console.error(`[Aether] Self-healing resolved to: ${resolved.selector} (${resolved.method})`);
1094
+ resultMsg = await bridge.sendCommand("click_element_by_selector", { selector: resolved.selector });
1095
+ if (aetherMemory.canWrite(a?.projectRoot)) {
1096
+ await aetherMemory.rememberLesson({
1097
+ projectRoot: a?.projectRoot,
1098
+ title: "Recover from brittle click selector",
1099
+ trigger: "When a click fails because the exact selector or text no longer matches, but a nearby semantic element can be resolved.",
1100
+ problemPattern: "A brittle click selector failed during browser automation.",
1101
+ symptoms: ["click_element_by_selector threw", "selector self-healing found a replacement"],
1102
+ failedApproach: a.selector || a.value || a.text || "original click target",
1103
+ betterApproach: `Use resolved selector "${resolved.selector}" from ${resolved.method} after checking visible interactive candidates.`,
1104
+ createdBecause: "errors_overcome",
1105
+ evidence: `Recovered ${action} on ${currentState.url}. Confidence: ${resolved.confidence}.`,
1106
+ tags: ["selector", "click", "self-healing"],
1107
+ confidence: resolved.confidence || 0.72,
1108
+ }).catch((memoryError) => console.error("[Aether] Failed to remember click recovery:", memoryError));
1109
+ }
1110
+ }
1111
+ else {
1112
+ throw err;
1113
+ }
1114
+ }
1115
+ }
1116
+ else if (action === "type") {
1117
+ try {
1118
+ if (eid || a.selector)
1119
+ await bridge.sendCommand(eid ? "click_element" : "click_element_by_selector", { id: eid, selector: a.selector });
1120
+ resultMsg = await bridge.sendCommand("type", { text: a.value || a.text });
1121
+ }
1122
+ catch (err) {
1123
+ // SELF-HEALING
1124
+ const resolved = await bridge.resolveSelector({
1125
+ originalSelector: a.selector,
1126
+ text: a.value || a.text
1127
+ }).catch(() => null);
1128
+ if (resolved) {
1129
+ await bridge.sendCommand("click_element_by_selector", { selector: resolved.selector });
1130
+ resultMsg = await bridge.sendCommand("type", { text: a.value || a.text });
1131
+ if (aetherMemory.canWrite(a?.projectRoot)) {
1132
+ await aetherMemory.rememberLesson({
1133
+ projectRoot: a?.projectRoot,
1134
+ title: "Recover from brittle typing target",
1135
+ trigger: "When typing fails because the original input selector no longer resolves, but a semantic replacement can be found.",
1136
+ problemPattern: "A brittle typing selector failed during browser automation.",
1137
+ symptoms: ["typing target could not be focused", "selector self-healing found a replacement"],
1138
+ failedApproach: a.selector || "original typing target",
1139
+ betterApproach: `Focus resolved selector "${resolved.selector}" from ${resolved.method}, then type with native input events.`,
1140
+ createdBecause: "errors_overcome",
1141
+ evidence: `Recovered ${action} on ${currentState.url}. Confidence: ${resolved.confidence}.`,
1142
+ tags: ["selector", "typing", "self-healing"],
1143
+ confidence: resolved.confidence || 0.72,
1144
+ }).catch((memoryError) => console.error("[Aether] Failed to remember typing recovery:", memoryError));
1145
+ }
1146
+ }
1147
+ else {
1148
+ throw err;
1149
+ }
1150
+ }
1151
+ }
1152
+ else if (action === "navigate") {
1153
+ resultMsg = await bridge.sendCommand("navigate", { url: a.value });
1154
+ }
1155
+ else {
1156
+ resultMsg = await bridge.sendCommand(action, a);
1157
+ }
1158
+ node.status = 'success';
1159
+ }
1160
+ catch (err) {
1161
+ node.status = 'failure';
1162
+ node.error = err.message;
1163
+ throw err;
1164
+ }
1165
+ return { content: [{ type: "text", text: typeof resultMsg === 'string' ? resultMsg : JSON.stringify(resultMsg) }] };
1166
+ }
1167
+ // ==================== AGENT-CENTRIC APIs ====================
1168
+ if (name === "agent_action") {
1169
+ const result = await bridge.sendCommand("agent_action", {
1170
+ action: a.action,
1171
+ target: a.target,
1172
+ verify: a.verify,
1173
+ waitFor: a.waitFor,
1174
+ timeout: a.timeout,
1175
+ screenshot: a.screenshot === true
1176
+ });
1177
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1178
+ }
1179
+ if (name === "smart_navigate") {
1180
+ const result = await bridge.sendCommand("smart_navigate", {
1181
+ url: a.url,
1182
+ waitFor: a.waitFor,
1183
+ dismissPopups: a.dismissPopups,
1184
+ screenshot: a.screenshot,
1185
+ timeout: a.timeout
1186
+ });
1187
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1188
+ }
1189
+ if (name === "observe_and_act") {
1190
+ const result = await bridge.sendCommand("observe_and_act", {
1191
+ action: a.action,
1192
+ observe: a.observe,
1193
+ returnScreenshot: a.returnScreenshot
1194
+ });
1195
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1196
+ }
1197
+ if (name === "agent_form_fill") {
1198
+ const result = await bridge.sendCommand("agent_form_fill", {
1199
+ fields: a.fields,
1200
+ submitAfterFill: a.submitAfterFill,
1201
+ submitSelector: a.submitSelector
1202
+ });
1203
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1204
+ }
1205
+ if (name === "page_snapshot") {
1206
+ const result = await bridge.sendCommand("page_snapshot", {
1207
+ fullPage: a.fullPage,
1208
+ includeDOMSnapshot: a.includeDOMSnapshot,
1209
+ screenshot: a.screenshot === true,
1210
+ cookies: a.cookies === true,
1211
+ accessibilityTree: a.accessibilityTree === true
1212
+ });
1213
+ const content = [
1214
+ { type: "text", text: `Title: ${result.title}\nURL: ${result.url}` }
1215
+ ];
1216
+ if (result.screenshot) {
1217
+ content.push({ type: "image", data: result.screenshot, mimeType: "image/jpeg" });
1218
+ }
1219
+ if (result.elements) {
1220
+ content.push({ type: "text", text: `\nInteractive Elements: ${JSON.stringify(result.elements, null, 2)}` });
1221
+ }
1222
+ return { content };
1223
+ }
1224
+ throw new Error(`Unknown tool: ${name}`);
1225
+ }
1226
+ catch (error) {
1227
+ if (error.captcha) {
1228
+ return (0, mcp_responses_1.toolError)(error);
1229
+ }
1230
+ if (error.message?.includes("not connected") || error.message?.includes("No active extension")) {
1231
+ return (0, mcp_responses_1.toolError)(error);
1232
+ }
1233
+ return (0, mcp_responses_1.toolError)(error);
1234
+ }
1235
+ });
1236
+ }