@everydaydevopsio/pilot 0.5.2 → 0.6.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.
Files changed (280) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/CLAUDE.md +7 -7
  3. package/README.md +108 -53
  4. package/dist/browser/browser-manager.d.ts +60 -0
  5. package/dist/browser/browser-manager.d.ts.map +1 -0
  6. package/dist/browser/browser-manager.js +348 -0
  7. package/dist/browser/browser-manager.js.map +1 -0
  8. package/dist/browser/chrome-launcher.d.ts +30 -0
  9. package/dist/browser/chrome-launcher.d.ts.map +1 -0
  10. package/dist/browser/chrome-launcher.js +217 -0
  11. package/dist/browser/chrome-launcher.js.map +1 -0
  12. package/dist/browser/connection.d.ts +23 -0
  13. package/dist/browser/connection.d.ts.map +1 -0
  14. package/dist/browser/connection.js +53 -0
  15. package/dist/browser/connection.js.map +1 -0
  16. package/dist/browser/events.d.ts +9 -0
  17. package/dist/browser/events.d.ts.map +1 -0
  18. package/dist/browser/events.js +99 -0
  19. package/dist/browser/events.js.map +1 -0
  20. package/dist/browser/index.d.ts +4 -0
  21. package/dist/browser/index.d.ts.map +1 -0
  22. package/dist/browser/index.js +4 -0
  23. package/dist/browser/index.js.map +1 -0
  24. package/dist/browser/inspect/element-ref.d.ts +23 -0
  25. package/dist/browser/inspect/element-ref.d.ts.map +1 -0
  26. package/dist/browser/inspect/element-ref.js +52 -0
  27. package/dist/browser/inspect/element-ref.js.map +1 -0
  28. package/dist/browser/inspect/snapshot.d.ts +11 -0
  29. package/dist/browser/inspect/snapshot.d.ts.map +1 -0
  30. package/dist/browser/inspect/snapshot.js +282 -0
  31. package/dist/browser/inspect/snapshot.js.map +1 -0
  32. package/dist/browser/inspect/styles.d.ts +45 -0
  33. package/dist/browser/inspect/styles.d.ts.map +1 -0
  34. package/dist/browser/inspect/styles.js +190 -0
  35. package/dist/browser/inspect/styles.js.map +1 -0
  36. package/dist/browser/inspect/types.d.ts +35 -0
  37. package/dist/browser/inspect/types.d.ts.map +1 -0
  38. package/dist/browser/inspect/types.js +2 -0
  39. package/dist/browser/inspect/types.js.map +1 -0
  40. package/dist/browser/interaction/dialogs.d.ts +18 -0
  41. package/dist/browser/interaction/dialogs.d.ts.map +1 -0
  42. package/dist/browser/interaction/dialogs.js +35 -0
  43. package/dist/browser/interaction/dialogs.js.map +1 -0
  44. package/dist/browser/interaction/downloads.d.ts +23 -0
  45. package/dist/browser/interaction/downloads.d.ts.map +1 -0
  46. package/dist/browser/interaction/downloads.js +87 -0
  47. package/dist/browser/interaction/downloads.js.map +1 -0
  48. package/dist/browser/interaction/events.d.ts +17 -0
  49. package/dist/browser/interaction/events.d.ts.map +1 -0
  50. package/dist/browser/interaction/events.js +66 -0
  51. package/dist/browser/interaction/events.js.map +1 -0
  52. package/dist/browser/interaction/ref-resolver.d.ts +18 -0
  53. package/dist/browser/interaction/ref-resolver.d.ts.map +1 -0
  54. package/dist/browser/interaction/ref-resolver.js +32 -0
  55. package/dist/browser/interaction/ref-resolver.js.map +1 -0
  56. package/dist/browser/interaction/uploads.d.ts +4 -0
  57. package/dist/browser/interaction/uploads.d.ts.map +1 -0
  58. package/dist/browser/interaction/uploads.js +43 -0
  59. package/dist/browser/interaction/uploads.js.map +1 -0
  60. package/dist/browser/network/network-buffer.d.ts +19 -0
  61. package/dist/browser/network/network-buffer.d.ts.map +1 -0
  62. package/dist/browser/network/network-buffer.js +118 -0
  63. package/dist/browser/network/network-buffer.js.map +1 -0
  64. package/dist/browser/network/network-monitor.d.ts +8 -0
  65. package/dist/browser/network/network-monitor.d.ts.map +1 -0
  66. package/dist/browser/network/network-monitor.js +85 -0
  67. package/dist/browser/network/network-monitor.js.map +1 -0
  68. package/dist/browser/network/redaction.d.ts +3 -0
  69. package/dist/browser/network/redaction.d.ts.map +1 -0
  70. package/dist/browser/network/redaction.js +36 -0
  71. package/dist/browser/network/redaction.js.map +1 -0
  72. package/dist/browser/network/types.d.ts +45 -0
  73. package/dist/browser/network/types.d.ts.map +1 -0
  74. package/dist/browser/network/types.js +2 -0
  75. package/dist/browser/network/types.js.map +1 -0
  76. package/dist/browser/performance/analyze.d.ts +5 -0
  77. package/dist/browser/performance/analyze.d.ts.map +1 -0
  78. package/dist/browser/performance/analyze.js +230 -0
  79. package/dist/browser/performance/analyze.js.map +1 -0
  80. package/dist/browser/performance/trace.d.ts +10 -0
  81. package/dist/browser/performance/trace.d.ts.map +1 -0
  82. package/dist/browser/performance/trace.js +38 -0
  83. package/dist/browser/performance/trace.js.map +1 -0
  84. package/dist/browser/performance/types.d.ts +31 -0
  85. package/dist/browser/performance/types.d.ts.map +1 -0
  86. package/dist/browser/performance/types.js +2 -0
  87. package/dist/browser/performance/types.js.map +1 -0
  88. package/dist/browser/security/origins.d.ts +18 -0
  89. package/dist/browser/security/origins.d.ts.map +1 -0
  90. package/dist/browser/security/origins.js +84 -0
  91. package/dist/browser/security/origins.js.map +1 -0
  92. package/dist/browser/tabs.d.ts +15 -0
  93. package/dist/browser/tabs.d.ts.map +1 -0
  94. package/dist/browser/tabs.js +71 -0
  95. package/dist/browser/tabs.js.map +1 -0
  96. package/dist/browser/types.d.ts +31 -0
  97. package/dist/browser/types.d.ts.map +1 -0
  98. package/dist/browser/types.js +2 -0
  99. package/dist/browser/types.js.map +1 -0
  100. package/dist/browser.d.ts +2 -99
  101. package/dist/browser.d.ts.map +1 -1
  102. package/dist/browser.js +3 -653
  103. package/dist/browser.js.map +1 -1
  104. package/dist/commands/check.d.ts +17 -0
  105. package/dist/commands/check.d.ts.map +1 -0
  106. package/dist/commands/check.js +38 -0
  107. package/dist/commands/check.js.map +1 -0
  108. package/dist/commands/click.d.ts +3 -1
  109. package/dist/commands/click.d.ts.map +1 -1
  110. package/dist/commands/click.js +16 -4
  111. package/dist/commands/click.js.map +1 -1
  112. package/dist/commands/connect.d.ts +14 -0
  113. package/dist/commands/connect.d.ts.map +1 -0
  114. package/dist/commands/connect.js +19 -0
  115. package/dist/commands/connect.js.map +1 -0
  116. package/dist/commands/dialog.d.ts +19 -0
  117. package/dist/commands/dialog.d.ts.map +1 -0
  118. package/dist/commands/dialog.js +51 -0
  119. package/dist/commands/dialog.js.map +1 -0
  120. package/dist/commands/downloads.d.ts +30 -0
  121. package/dist/commands/downloads.d.ts.map +1 -0
  122. package/dist/commands/downloads.js +66 -0
  123. package/dist/commands/downloads.js.map +1 -0
  124. package/dist/commands/fill.d.ts +16 -0
  125. package/dist/commands/fill.d.ts.map +1 -0
  126. package/dist/commands/fill.js +15 -0
  127. package/dist/commands/fill.js.map +1 -0
  128. package/dist/commands/find.d.ts +14 -0
  129. package/dist/commands/find.d.ts.map +1 -0
  130. package/dist/commands/find.js +26 -0
  131. package/dist/commands/find.js.map +1 -0
  132. package/dist/commands/hover.d.ts +13 -0
  133. package/dist/commands/hover.d.ts.map +1 -0
  134. package/dist/commands/hover.js +24 -0
  135. package/dist/commands/hover.js.map +1 -0
  136. package/dist/commands/navigate.d.ts.map +1 -1
  137. package/dist/commands/navigate.js +13 -0
  138. package/dist/commands/navigate.js.map +1 -1
  139. package/dist/commands/network.d.ts +33 -0
  140. package/dist/commands/network.d.ts.map +1 -0
  141. package/dist/commands/network.js +84 -0
  142. package/dist/commands/network.js.map +1 -0
  143. package/dist/commands/performance.d.ts +12 -0
  144. package/dist/commands/performance.d.ts.map +1 -0
  145. package/dist/commands/performance.js +26 -0
  146. package/dist/commands/performance.js.map +1 -0
  147. package/dist/commands/press_key.d.ts +10 -0
  148. package/dist/commands/press_key.d.ts.map +1 -0
  149. package/dist/commands/press_key.js +103 -0
  150. package/dist/commands/press_key.js.map +1 -0
  151. package/dist/commands/scroll.d.ts +21 -0
  152. package/dist/commands/scroll.d.ts.map +1 -0
  153. package/dist/commands/scroll.js +54 -0
  154. package/dist/commands/scroll.js.map +1 -0
  155. package/dist/commands/select.d.ts +18 -0
  156. package/dist/commands/select.d.ts.map +1 -0
  157. package/dist/commands/select.js +53 -0
  158. package/dist/commands/select.js.map +1 -0
  159. package/dist/commands/snapshot.d.ts +9 -0
  160. package/dist/commands/snapshot.d.ts.map +1 -0
  161. package/dist/commands/snapshot.js +7 -0
  162. package/dist/commands/snapshot.js.map +1 -0
  163. package/dist/commands/styles.d.ts +13 -0
  164. package/dist/commands/styles.d.ts.map +1 -0
  165. package/dist/commands/styles.js +7 -0
  166. package/dist/commands/styles.js.map +1 -0
  167. package/dist/commands/type.d.ts +3 -1
  168. package/dist/commands/type.d.ts.map +1 -1
  169. package/dist/commands/type.js +11 -2
  170. package/dist/commands/type.js.map +1 -1
  171. package/dist/commands/upload.d.ts +12 -0
  172. package/dist/commands/upload.d.ts.map +1 -0
  173. package/dist/commands/upload.js +13 -0
  174. package/dist/commands/upload.js.map +1 -0
  175. package/dist/mcp/console-buffer.d.ts +9 -0
  176. package/dist/mcp/console-buffer.d.ts.map +1 -1
  177. package/dist/mcp/console-buffer.js.map +1 -1
  178. package/dist/mcp/server.d.ts +10 -0
  179. package/dist/mcp/server.d.ts.map +1 -1
  180. package/dist/mcp/server.js +63 -2
  181. package/dist/mcp/server.js.map +1 -1
  182. package/dist/mcp/tools/browser.d.ts.map +1 -1
  183. package/dist/mcp/tools/browser.js +99 -8
  184. package/dist/mcp/tools/browser.js.map +1 -1
  185. package/dist/mcp/tools/connect.d.ts +5 -0
  186. package/dist/mcp/tools/connect.d.ts.map +1 -0
  187. package/dist/mcp/tools/connect.js +48 -0
  188. package/dist/mcp/tools/connect.js.map +1 -0
  189. package/dist/mcp/tools/files.d.ts +4 -0
  190. package/dist/mcp/tools/files.d.ts.map +1 -0
  191. package/dist/mcp/tools/files.js +85 -0
  192. package/dist/mcp/tools/files.js.map +1 -0
  193. package/dist/mcp/tools/interaction.d.ts +4 -0
  194. package/dist/mcp/tools/interaction.d.ts.map +1 -0
  195. package/dist/mcp/tools/interaction.js +176 -0
  196. package/dist/mcp/tools/interaction.js.map +1 -0
  197. package/dist/mcp/tools/names.d.ts +1 -1
  198. package/dist/mcp/tools/names.d.ts.map +1 -1
  199. package/dist/mcp/tools/names.js +19 -1
  200. package/dist/mcp/tools/names.js.map +1 -1
  201. package/dist/mcp/tools/network.d.ts +4 -0
  202. package/dist/mcp/tools/network.d.ts.map +1 -0
  203. package/dist/mcp/tools/network.js +68 -0
  204. package/dist/mcp/tools/network.js.map +1 -0
  205. package/dist/mcp/tools/performance.d.ts +4 -0
  206. package/dist/mcp/tools/performance.d.ts.map +1 -0
  207. package/dist/mcp/tools/performance.js +26 -0
  208. package/dist/mcp/tools/performance.js.map +1 -0
  209. package/dist/mcp/tools/runtime.d.ts +4 -0
  210. package/dist/mcp/tools/runtime.d.ts.map +1 -0
  211. package/dist/mcp/tools/runtime.js +128 -0
  212. package/dist/mcp/tools/runtime.js.map +1 -0
  213. package/dist/mcp/tools/snapshot.d.ts +4 -0
  214. package/dist/mcp/tools/snapshot.d.ts.map +1 -0
  215. package/dist/mcp/tools/snapshot.js +42 -0
  216. package/dist/mcp/tools/snapshot.js.map +1 -0
  217. package/dist/mcp/tools/styles.d.ts +4 -0
  218. package/dist/mcp/tools/styles.d.ts.map +1 -0
  219. package/dist/mcp/tools/styles.js +27 -0
  220. package/dist/mcp/tools/styles.js.map +1 -0
  221. package/dist/util/config.d.ts.map +1 -1
  222. package/dist/util/config.js +3 -2
  223. package/dist/util/config.js.map +1 -1
  224. package/package.json +3 -3
  225. package/src/browser/browser-manager.ts +457 -0
  226. package/src/browser/chrome-launcher.ts +271 -0
  227. package/src/browser/connection.ts +84 -0
  228. package/src/browser/events.ts +121 -0
  229. package/src/browser/index.ts +20 -0
  230. package/src/browser/inspect/element-ref.ts +68 -0
  231. package/src/browser/inspect/snapshot.ts +348 -0
  232. package/src/browser/inspect/styles.ts +258 -0
  233. package/src/browser/inspect/types.ts +37 -0
  234. package/src/browser/interaction/dialogs.ts +54 -0
  235. package/src/browser/interaction/downloads.ts +116 -0
  236. package/src/browser/interaction/events.ts +79 -0
  237. package/src/browser/interaction/ref-resolver.ts +53 -0
  238. package/src/browser/interaction/uploads.ts +60 -0
  239. package/src/browser/network/network-buffer.ts +133 -0
  240. package/src/browser/network/network-monitor.ts +107 -0
  241. package/src/browser/network/redaction.ts +42 -0
  242. package/src/browser/network/types.ts +47 -0
  243. package/src/browser/performance/analyze.ts +298 -0
  244. package/src/browser/performance/trace.ts +60 -0
  245. package/src/browser/performance/types.ts +34 -0
  246. package/src/browser/security/origins.ts +102 -0
  247. package/src/browser/tabs.ts +110 -0
  248. package/src/browser/types.ts +31 -0
  249. package/src/browser.ts +22 -798
  250. package/src/commands/check.ts +59 -0
  251. package/src/commands/click.ts +18 -4
  252. package/src/commands/connect.ts +40 -0
  253. package/src/commands/dialog.ts +79 -0
  254. package/src/commands/downloads.ts +111 -0
  255. package/src/commands/fill.ts +34 -0
  256. package/src/commands/find.ts +52 -0
  257. package/src/commands/hover.ts +42 -0
  258. package/src/commands/navigate.ts +20 -0
  259. package/src/commands/network.ts +141 -0
  260. package/src/commands/performance.ts +51 -0
  261. package/src/commands/press_key.ts +134 -0
  262. package/src/commands/scroll.ts +79 -0
  263. package/src/commands/select.ts +78 -0
  264. package/src/commands/snapshot.ts +18 -0
  265. package/src/commands/styles.ts +24 -0
  266. package/src/commands/type.ts +12 -2
  267. package/src/commands/upload.ts +36 -0
  268. package/src/mcp/console-buffer.ts +10 -0
  269. package/src/mcp/server.ts +74 -2
  270. package/src/mcp/tools/browser.ts +113 -7
  271. package/src/mcp/tools/connect.ts +62 -0
  272. package/src/mcp/tools/files.ts +120 -0
  273. package/src/mcp/tools/interaction.ts +232 -0
  274. package/src/mcp/tools/names.ts +19 -1
  275. package/src/mcp/tools/network.ts +86 -0
  276. package/src/mcp/tools/performance.ts +40 -0
  277. package/src/mcp/tools/runtime.ts +164 -0
  278. package/src/mcp/tools/snapshot.ts +64 -0
  279. package/src/mcp/tools/styles.ts +41 -0
  280. package/src/util/config.ts +3 -2
@@ -57,6 +57,10 @@ const navigateShape = {
57
57
  };
58
58
 
59
59
  const clickShape = {
60
+ ref: z
61
+ .string()
62
+ .optional()
63
+ .describe('Element ref from browser_snapshot (e.g. "e3")'),
60
64
  selector: z.string().optional().describe('CSS selector to click'),
61
65
  x: z.number().optional().describe('Viewport X coordinate'),
62
66
  y: z.number().optional().describe('Viewport Y coordinate'),
@@ -80,6 +84,10 @@ const clickShape = {
80
84
 
81
85
  const typeShape = {
82
86
  text: z.string().describe('Text to type'),
87
+ ref: z
88
+ .string()
89
+ .optional()
90
+ .describe('Element ref from browser_snapshot (e.g. "e3")'),
83
91
  selector: z.string().optional().describe('CSS selector to focus first'),
84
92
  clearFirst: z
85
93
  .boolean()
@@ -128,7 +136,7 @@ const startShape = {
128
136
  .boolean()
129
137
  .optional()
130
138
  .describe(
131
- 'Run Chrome headless. When omitted, falls back to PILOT_HEADLESS (default: true).'
139
+ 'Run Chrome headless. When omitted, falls back to PILOT_HEADLESS (accepts true/1 or false/0; default: false).'
132
140
  ),
133
141
  chromePath: z
134
142
  .string()
@@ -190,6 +198,24 @@ const switchTabShape = {
190
198
  targetId: z.string().describe('Target ID of the tab to switch to')
191
199
  };
192
200
 
201
+ const tabsShape = {
202
+ action: z
203
+ .enum(['list', 'new', 'select', 'close'])
204
+ .describe(
205
+ 'Action: list tabs, open new tab, select/switch tab, or close tab'
206
+ ),
207
+ targetId: z
208
+ .string()
209
+ .optional()
210
+ .describe('Target ID (required for select and close actions)'),
211
+ url: z
212
+ .string()
213
+ .optional()
214
+ .describe(
215
+ 'URL to open in the new tab (new action only, opens blank tab if omitted)'
216
+ )
217
+ };
218
+
193
219
  const viewportResizeShape = {
194
220
  width: z.number().int().positive().describe('New viewport width in pixels'),
195
221
  height: z.number().int().positive().describe('New viewport height in pixels'),
@@ -361,10 +387,11 @@ export function registerBrowserTools(
361
387
 
362
388
  server.tool(
363
389
  'browser_click',
364
- 'Click an element by CSS selector or coordinates. Either selector or x/y coordinates required.',
390
+ 'Click an element by ref (from browser_snapshot), CSS selector, or coordinates.',
365
391
  clickShape,
366
392
  async (params) => {
367
393
  if (
394
+ params.ref === undefined &&
368
395
  params.selector === undefined &&
369
396
  (params.x === undefined || params.y === undefined)
370
397
  ) {
@@ -372,7 +399,7 @@ export function registerBrowserTools(
372
399
  content: [
373
400
  {
374
401
  type: 'text' as const,
375
- text: 'Error: Either selector or x/y coordinates are required'
402
+ text: 'Error: Either ref, selector, or x/y coordinates are required'
376
403
  }
377
404
  ],
378
405
  isError: true
@@ -380,7 +407,7 @@ export function registerBrowserTools(
380
407
  }
381
408
 
382
409
  const { client } = requireClient(context);
383
- const result = await executeClick(client, params);
410
+ const result = await executeClick(client, params, context.elementRefMap);
384
411
 
385
412
  return {
386
413
  content: [
@@ -395,17 +422,18 @@ export function registerBrowserTools(
395
422
 
396
423
  server.tool(
397
424
  'browser_type',
398
- 'Type text into the focused element or a specified selector',
425
+ 'Type text into the focused element, a ref (from browser_snapshot), or a CSS selector. Generates keystroke events (use browser_fill to replace a field value entirely).',
399
426
  typeShape,
400
427
  async (params) => {
401
428
  const { client } = requireClient(context);
402
- await executeType(client, params);
429
+ await executeType(client, params, context.elementRefMap);
403
430
 
431
+ const target = params.ref ?? params.selector;
404
432
  return {
405
433
  content: [
406
434
  {
407
435
  type: 'text' as const,
408
- text: `Typed "${params.text}"${params.selector ? ` into ${params.selector}` : ''}`
436
+ text: `Typed "${params.text}"${target ? ` into ${target}` : ''}`
409
437
  }
410
438
  ]
411
439
  };
@@ -602,4 +630,82 @@ export function registerBrowserTools(
602
630
  };
603
631
  }
604
632
  );
633
+
634
+ server.tool(
635
+ 'browser_tabs',
636
+ 'Manage browser tabs. Actions: "list" shows all tabs, "new" opens a tab (optional url), "select" switches to a tab (requires targetId), "close" closes a tab (requires targetId).',
637
+ tabsShape,
638
+ async ({ action, targetId, url }) => {
639
+ const { manager } = requireClient(context);
640
+
641
+ switch (action) {
642
+ case 'list': {
643
+ const tabs = await executeListTabs(manager);
644
+ return {
645
+ content: [
646
+ { type: 'text' as const, text: JSON.stringify(tabs, null, 2) }
647
+ ]
648
+ };
649
+ }
650
+
651
+ case 'new': {
652
+ const result = await executeNewTab(manager, url);
653
+ return {
654
+ content: [
655
+ {
656
+ type: 'text' as const,
657
+ text: `New tab opened (targetId: ${result.targetId}, url: ${result.url})`
658
+ }
659
+ ]
660
+ };
661
+ }
662
+
663
+ case 'select': {
664
+ if (!targetId) {
665
+ return {
666
+ content: [
667
+ {
668
+ type: 'text' as const,
669
+ text: 'Error: targetId is required for select action'
670
+ }
671
+ ],
672
+ isError: true
673
+ };
674
+ }
675
+ await executeSwitchTab(manager, targetId);
676
+ return {
677
+ content: [
678
+ {
679
+ type: 'text' as const,
680
+ text: `Switched to tab ${targetId}.`
681
+ }
682
+ ]
683
+ };
684
+ }
685
+
686
+ case 'close': {
687
+ if (!targetId) {
688
+ return {
689
+ content: [
690
+ {
691
+ type: 'text' as const,
692
+ text: 'Error: targetId is required for close action'
693
+ }
694
+ ],
695
+ isError: true
696
+ };
697
+ }
698
+ await executeCloseTab(manager, targetId);
699
+ return {
700
+ content: [
701
+ {
702
+ type: 'text' as const,
703
+ text: `Tab ${targetId} closed.`
704
+ }
705
+ ]
706
+ };
707
+ }
708
+ }
709
+ }
710
+ );
605
711
  }
@@ -0,0 +1,62 @@
1
+ import { z } from 'zod';
2
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ import type { BrowserContext } from '../server.js';
4
+ import type { BrowserManager } from '../../browser/browser-manager.js';
5
+ import { executeConnect, formatConnectResult } from '../../commands/connect.js';
6
+
7
+ const connectShape = {
8
+ host: z
9
+ .string()
10
+ .default('127.0.0.1')
11
+ .describe('Chrome debugging host (default: 127.0.0.1)'),
12
+ port: z
13
+ .number()
14
+ .int()
15
+ .min(1)
16
+ .max(65535)
17
+ .default(9222)
18
+ .describe('Chrome debugging port (default: 9222)')
19
+ };
20
+
21
+ export function registerConnectTools(
22
+ server: McpServer,
23
+ context: BrowserContext,
24
+ makeBrowserManager: () => BrowserManager
25
+ ): void {
26
+ server.tool(
27
+ 'browser_connect',
28
+ 'Connect to an existing Chrome instance with remote debugging enabled. Start Chrome with --remote-debugging-port=PORT, then use this tool to connect. Lists available tabs after connecting. Use browser_switch_tab to select a specific tab.',
29
+ connectShape,
30
+ async ({ host, port }) => {
31
+ if (context.manager?.isConnected()) {
32
+ return {
33
+ content: [
34
+ {
35
+ type: 'text' as const,
36
+ text: 'Already connected to a browser. Call browser_stop to disconnect first.'
37
+ }
38
+ ]
39
+ };
40
+ }
41
+
42
+ if (context.manager) {
43
+ await context.manager.destroy();
44
+ context.manager = null;
45
+ }
46
+
47
+ const manager = makeBrowserManager();
48
+ try {
49
+ const result = await executeConnect(manager, { host, port });
50
+ context.manager = manager;
51
+ return {
52
+ content: [
53
+ { type: 'text' as const, text: formatConnectResult(result) }
54
+ ]
55
+ };
56
+ } catch (err) {
57
+ await manager.destroy();
58
+ throw err;
59
+ }
60
+ }
61
+ );
62
+ }
@@ -0,0 +1,120 @@
1
+ import { z } from 'zod';
2
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ import type { BrowserContext } from '../server.js';
4
+ import { executeDialog, formatDialogResult } from '../../commands/dialog.js';
5
+ import { executeUpload } from '../../commands/upload.js';
6
+ import {
7
+ executeDownloads,
8
+ formatDownloadsResult
9
+ } from '../../commands/downloads.js';
10
+
11
+ const dialogShape = {
12
+ action: z
13
+ .enum(['list', 'accept', 'dismiss'])
14
+ .describe(
15
+ 'Action: list pending dialogs, accept (OK/Yes), or dismiss (Cancel/No)'
16
+ ),
17
+ promptText: z
18
+ .string()
19
+ .optional()
20
+ .describe('Text to enter for prompt dialogs (accept action only)')
21
+ };
22
+
23
+ const uploadShape = {
24
+ ref: z
25
+ .string()
26
+ .describe('Element ref of a file input (from browser_snapshot)'),
27
+ files: z
28
+ .array(z.string())
29
+ .describe(
30
+ 'File paths to upload. Must exist, be regular files, and be under PILOT_UPLOAD_ROOTS (default: cwd).'
31
+ )
32
+ };
33
+
34
+ const downloadsShape = {
35
+ action: z
36
+ .enum(['list', 'wait', 'clear'])
37
+ .describe(
38
+ 'Action: list tracked downloads, wait for one to complete, or clear'
39
+ ),
40
+ guid: z
41
+ .string()
42
+ .optional()
43
+ .describe('Download GUID (required for wait action)'),
44
+ timeoutMs: z
45
+ .number()
46
+ .int()
47
+ .positive()
48
+ .optional()
49
+ .describe('Timeout for wait action in milliseconds (default: 30000)')
50
+ };
51
+
52
+ function requireContext(context: BrowserContext) {
53
+ const manager = context.manager;
54
+ if (!manager || !manager.isConnected()) {
55
+ throw new Error('Browser not started. Call browser_start first.');
56
+ }
57
+ const client = manager.getClient()!;
58
+ return { client };
59
+ }
60
+
61
+ export function registerFilesTools(
62
+ server: McpServer,
63
+ context: BrowserContext
64
+ ): void {
65
+ server.tool(
66
+ 'browser_dialog',
67
+ 'Handle browser dialogs (alert, confirm, prompt). Use "list" to see pending dialogs, "accept" to click OK/Yes, or "dismiss" to click Cancel/No. For prompt dialogs, provide promptText with accept.',
68
+ dialogShape,
69
+ async ({ action, promptText }) => {
70
+ const { client } = requireContext(context);
71
+ const result = await executeDialog(client, context.dialogQueue, {
72
+ action,
73
+ promptText
74
+ });
75
+ return {
76
+ content: [{ type: 'text' as const, text: formatDialogResult(result) }]
77
+ };
78
+ }
79
+ );
80
+
81
+ server.tool(
82
+ 'browser_upload',
83
+ 'Upload files to a file input element by ref. Files must exist, be regular files, and be under allowed roots (PILOT_UPLOAD_ROOTS env, default: cwd). Use browser_snapshot to find file input refs.',
84
+ uploadShape,
85
+ async ({ ref, files }) => {
86
+ const { client } = requireContext(context);
87
+ const result = await executeUpload(client, context.elementRefMap, {
88
+ ref,
89
+ files
90
+ });
91
+ return {
92
+ content: [
93
+ {
94
+ type: 'text' as const,
95
+ text: `Uploaded ${result.files.length} file(s) to ${result.ref}`
96
+ }
97
+ ]
98
+ };
99
+ }
100
+ );
101
+
102
+ server.tool(
103
+ 'browser_downloads',
104
+ 'Track file downloads. Actions: "list" shows tracked downloads with status/progress; "wait" blocks until a download (by GUID) completes; "clear" resets the tracker.',
105
+ downloadsShape,
106
+ async ({ action, guid, timeoutMs }) => {
107
+ requireContext(context);
108
+ const result = await executeDownloads(context.downloadTracker, {
109
+ action,
110
+ guid,
111
+ timeoutMs
112
+ });
113
+ return {
114
+ content: [
115
+ { type: 'text' as const, text: formatDownloadsResult(result) }
116
+ ]
117
+ };
118
+ }
119
+ );
120
+ }
@@ -0,0 +1,232 @@
1
+ import { z } from 'zod';
2
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ import type { BrowserContext } from '../server.js';
4
+ import { executeFill } from '../../commands/fill.js';
5
+ import { executeHover } from '../../commands/hover.js';
6
+ import { executePressKey } from '../../commands/press_key.js';
7
+ import { executeSelect } from '../../commands/select.js';
8
+ import { executeCheck } from '../../commands/check.js';
9
+ import { executeScroll } from '../../commands/scroll.js';
10
+
11
+ function requireContext(context: BrowserContext) {
12
+ const manager = context.manager;
13
+ if (!manager || !manager.isConnected()) {
14
+ throw new Error('Browser not started. Call browser_start first.');
15
+ }
16
+ const client = manager.getClient()!;
17
+ return { client, refMap: context.elementRefMap };
18
+ }
19
+
20
+ const fillShape = {
21
+ ref: z.string().describe('Element ref from browser_snapshot (e.g. "e3")'),
22
+ value: z.string().describe('Value to set in the field')
23
+ };
24
+
25
+ const hoverShape = {
26
+ ref: z
27
+ .string()
28
+ .optional()
29
+ .describe('Element ref from browser_snapshot (e.g. "e3")'),
30
+ x: z.number().optional().describe('Viewport X coordinate'),
31
+ y: z.number().optional().describe('Viewport Y coordinate')
32
+ };
33
+
34
+ const pressKeyShape = {
35
+ key: z
36
+ .string()
37
+ .describe(
38
+ 'Key to press. Named keys: Enter, Tab, Escape, Backspace, Delete, Space, ArrowUp/Down/Left/Right, Home, End, PageUp/Down, F1-F12. Modifier combos: Control+a, Meta+c, Shift+Enter.'
39
+ )
40
+ };
41
+
42
+ const selectShape = {
43
+ ref: z.string().describe('Element ref of a <select> element'),
44
+ value: z.string().optional().describe('Option value attribute to select'),
45
+ label: z.string().optional().describe('Option visible text to select'),
46
+ index: z
47
+ .number()
48
+ .int()
49
+ .min(0)
50
+ .optional()
51
+ .describe('Option index to select (0-based)')
52
+ };
53
+
54
+ const checkShape = {
55
+ ref: z.string().describe('Element ref of a checkbox or radio button'),
56
+ checked: z.boolean().optional().describe('Desired state. Omit to toggle.')
57
+ };
58
+
59
+ const scrollShape = {
60
+ ref: z.string().optional().describe('Element ref to scroll into view'),
61
+ direction: z
62
+ .enum(['up', 'down', 'left', 'right'])
63
+ .optional()
64
+ .describe('Scroll direction'),
65
+ amount: z
66
+ .number()
67
+ .int()
68
+ .positive()
69
+ .optional()
70
+ .describe('Scroll amount in pixels (default: 300)'),
71
+ x: z.number().optional().describe('Absolute scroll X position'),
72
+ y: z.number().optional().describe('Absolute scroll Y position')
73
+ };
74
+
75
+ export function registerInteractionTools(
76
+ server: McpServer,
77
+ context: BrowserContext
78
+ ): void {
79
+ server.tool(
80
+ 'browser_fill',
81
+ 'Fill a form field by element ref. Replaces the entire field value and fires input/change events compatible with React, Vue, and Svelte. Use browser_snapshot first to get refs.',
82
+ fillShape,
83
+ async ({ ref, value }) => {
84
+ const { client, refMap } = requireContext(context);
85
+ const result = await executeFill(client, refMap, { ref, value });
86
+ return {
87
+ content: [
88
+ {
89
+ type: 'text' as const,
90
+ text: `Filled ${result.ref} with "${result.value}"`
91
+ }
92
+ ]
93
+ };
94
+ }
95
+ );
96
+
97
+ server.tool(
98
+ 'browser_hover',
99
+ 'Hover over an element by ref or coordinates. Dispatches a mouseMoved event.',
100
+ hoverShape,
101
+ async (params) => {
102
+ if (
103
+ params.ref === undefined &&
104
+ (params.x === undefined || params.y === undefined)
105
+ ) {
106
+ return {
107
+ content: [
108
+ {
109
+ type: 'text' as const,
110
+ text: 'Error: Either ref or x/y coordinates are required'
111
+ }
112
+ ],
113
+ isError: true
114
+ };
115
+ }
116
+ const { client, refMap } = requireContext(context);
117
+ const result = await executeHover(client, refMap, params);
118
+ return {
119
+ content: [
120
+ {
121
+ type: 'text' as const,
122
+ text: `Hovered at (${result.x}, ${result.y})`
123
+ }
124
+ ]
125
+ };
126
+ }
127
+ );
128
+
129
+ server.tool(
130
+ 'browser_press_key',
131
+ 'Press a key or key combination. Supports named keys (Enter, Tab, Escape, etc.) and modifier combos (Control+a, Meta+c, Shift+Enter).',
132
+ pressKeyShape,
133
+ async ({ key }) => {
134
+ const { client } = requireContext(context);
135
+ const result = await executePressKey(client, { key });
136
+ const modStr =
137
+ result.modifiers.length > 0
138
+ ? ` with modifiers: ${result.modifiers.join('+')}`
139
+ : '';
140
+ return {
141
+ content: [
142
+ { type: 'text' as const, text: `Pressed ${result.key}${modStr}` }
143
+ ]
144
+ };
145
+ }
146
+ );
147
+
148
+ server.tool(
149
+ 'browser_select',
150
+ 'Select an option in a <select> element by value, label, or index. Use browser_snapshot to get the ref.',
151
+ selectShape,
152
+ async (params) => {
153
+ if (
154
+ params.value === undefined &&
155
+ params.label === undefined &&
156
+ params.index === undefined
157
+ ) {
158
+ return {
159
+ content: [
160
+ {
161
+ type: 'text' as const,
162
+ text: 'Error: One of value, label, or index is required'
163
+ }
164
+ ],
165
+ isError: true
166
+ };
167
+ }
168
+ const { client, refMap } = requireContext(context);
169
+ const result = await executeSelect(client, refMap, params);
170
+ return {
171
+ content: [
172
+ {
173
+ type: 'text' as const,
174
+ text: `Selected "${result.selectedLabel}" (value="${result.selectedValue}") in ${result.ref}`
175
+ }
176
+ ]
177
+ };
178
+ }
179
+ );
180
+
181
+ server.tool(
182
+ 'browser_check',
183
+ 'Toggle a checkbox or radio button by ref. Optionally set to a specific checked state.',
184
+ checkShape,
185
+ async ({ ref, checked }) => {
186
+ const { client, refMap } = requireContext(context);
187
+ const result = await executeCheck(client, refMap, { ref, checked });
188
+ return {
189
+ content: [
190
+ {
191
+ type: 'text' as const,
192
+ text: `${result.ref} is now ${result.checked ? 'checked' : 'unchecked'}`
193
+ }
194
+ ]
195
+ };
196
+ }
197
+ );
198
+
199
+ server.tool(
200
+ 'browser_scroll',
201
+ 'Scroll the page or an element. Use ref to scroll an element into view, direction/amount for relative scrolling, or x/y for absolute scroll position.',
202
+ scrollShape,
203
+ async (params) => {
204
+ if (
205
+ params.ref === undefined &&
206
+ params.direction === undefined &&
207
+ params.x === undefined &&
208
+ params.y === undefined
209
+ ) {
210
+ return {
211
+ content: [
212
+ {
213
+ type: 'text' as const,
214
+ text: 'Error: At least one of ref, direction, or x/y is required'
215
+ }
216
+ ],
217
+ isError: true
218
+ };
219
+ }
220
+ const { client, refMap } = requireContext(context);
221
+ const result = await executeScroll(client, refMap, params);
222
+ return {
223
+ content: [
224
+ {
225
+ type: 'text' as const,
226
+ text: `Scrolled to (${result.scrollX}, ${result.scrollY})`
227
+ }
228
+ ]
229
+ };
230
+ }
231
+ );
232
+ }
@@ -19,7 +19,25 @@ export const EXPECTED_TOOLS = [
19
19
  'browser_viewport_resize',
20
20
  'browser_get_console_logs',
21
21
  'browser_get_errors',
22
- 'browser_clear_errors'
22
+ 'browser_clear_errors',
23
+ 'browser_snapshot',
24
+ 'browser_find',
25
+ 'browser_fill',
26
+ 'browser_hover',
27
+ 'browser_press_key',
28
+ 'browser_select',
29
+ 'browser_check',
30
+ 'browser_scroll',
31
+ 'browser_network',
32
+ 'browser_console',
33
+ 'browser_errors',
34
+ 'browser_styles',
35
+ 'browser_dialog',
36
+ 'browser_upload',
37
+ 'browser_downloads',
38
+ 'browser_performance',
39
+ 'browser_connect',
40
+ 'browser_tabs'
23
41
  ] as const;
24
42
 
25
43
  export type ToolName = (typeof EXPECTED_TOOLS)[number];