@kaitranntt/ccs 7.74.0-dev.1 → 7.74.0-dev.10

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 (94) hide show
  1. package/dist/api/services/profile-writer.d.ts +1 -1
  2. package/dist/api/services/profile-writer.d.ts.map +1 -1
  3. package/dist/api/services/profile-writer.js +11 -5
  4. package/dist/api/services/profile-writer.js.map +1 -1
  5. package/dist/bin/ccsxp-runtime.d.ts +8 -0
  6. package/dist/bin/ccsxp-runtime.d.ts.map +1 -1
  7. package/dist/bin/ccsxp-runtime.js +60 -3
  8. package/dist/bin/ccsxp-runtime.js.map +1 -1
  9. package/dist/cliproxy/binary/lifecycle.d.ts.map +1 -1
  10. package/dist/cliproxy/binary/lifecycle.js +2 -1
  11. package/dist/cliproxy/binary/lifecycle.js.map +1 -1
  12. package/dist/cliproxy/codex-plan-compatibility.d.ts.map +1 -1
  13. package/dist/cliproxy/codex-plan-compatibility.js +1 -0
  14. package/dist/cliproxy/codex-plan-compatibility.js.map +1 -1
  15. package/dist/cliproxy/model-catalog.d.ts.map +1 -1
  16. package/dist/cliproxy/model-catalog.js +12 -0
  17. package/dist/cliproxy/model-catalog.js.map +1 -1
  18. package/dist/cliproxy/service-manager.d.ts +17 -1
  19. package/dist/cliproxy/service-manager.d.ts.map +1 -1
  20. package/dist/cliproxy/service-manager.js +10 -5
  21. package/dist/cliproxy/service-manager.js.map +1 -1
  22. package/dist/cliproxy/sync/profile-mapper.d.ts.map +1 -1
  23. package/dist/cliproxy/sync/profile-mapper.js +21 -0
  24. package/dist/cliproxy/sync/profile-mapper.js.map +1 -1
  25. package/dist/cliproxy/types.d.ts +8 -0
  26. package/dist/cliproxy/types.d.ts.map +1 -1
  27. package/dist/cliproxy/types.js.map +1 -1
  28. package/dist/commands/api-command/create-command.d.ts.map +1 -1
  29. package/dist/commands/api-command/create-command.js +4 -1
  30. package/dist/commands/api-command/create-command.js.map +1 -1
  31. package/dist/commands/api-command/help.d.ts.map +1 -1
  32. package/dist/commands/api-command/help.js +4 -0
  33. package/dist/commands/api-command/help.js.map +1 -1
  34. package/dist/commands/api-command/shared.d.ts +2 -1
  35. package/dist/commands/api-command/shared.d.ts.map +1 -1
  36. package/dist/commands/api-command/shared.js +9 -0
  37. package/dist/commands/api-command/shared.js.map +1 -1
  38. package/dist/commands/command-catalog.d.ts.map +1 -1
  39. package/dist/commands/command-catalog.js +4 -1
  40. package/dist/commands/command-catalog.js.map +1 -1
  41. package/dist/targets/target-metadata.js +1 -1
  42. package/dist/targets/target-metadata.js.map +1 -1
  43. package/dist/ui/assets/{accounts-D0Z5AVKW.js → accounts-DCJVsC1R.js} +1 -1
  44. package/dist/ui/assets/{alert-dialog-CzJucE0e.js → alert-dialog-BZtIoyBc.js} +1 -1
  45. package/dist/ui/assets/{api-DXAf4i-v.js → api-DNXD8EA9.js} +1 -1
  46. package/dist/ui/assets/{auth-section-BrnnHBi-.js → auth-section-2-a8KRNY.js} +1 -1
  47. package/dist/ui/assets/{backups-section-DCBcfZ-C.js → backups-section-CezuWNFi.js} +1 -1
  48. package/dist/ui/assets/{channels-BSapONLx.js → channels-DaijLXYu.js} +1 -1
  49. package/dist/ui/assets/{checkbox-Bd7X5Xud.js → checkbox-D6sWns2x.js} +1 -1
  50. package/dist/ui/assets/{claude-extension-BYJxm4B5.js → claude-extension-BFbvT7N_.js} +1 -1
  51. package/dist/ui/assets/{cliproxy-G5JuFm2j.js → cliproxy-4Z6gEF3R.js} +1 -1
  52. package/dist/ui/assets/{cliproxy-ai-providers-D7r2Us_Y.js → cliproxy-ai-providers-BZ0mTP-l.js} +1 -1
  53. package/dist/ui/assets/{cliproxy-control-panel-Cmo3ArVL.js → cliproxy-control-panel-CFFjKRRQ.js} +1 -1
  54. package/dist/ui/assets/codex-D9ivkuYH.js +27 -0
  55. package/dist/ui/assets/{confirm-dialog--zJgz4y0.js → confirm-dialog-CpZGygf5.js} +1 -1
  56. package/dist/ui/assets/{copilot-2ARJ9YVb.js → copilot-DMmn_rxB.js} +1 -1
  57. package/dist/ui/assets/{cursor-CQNCrVt0.js → cursor-D_Q86_Ei.js} +1 -1
  58. package/dist/ui/assets/{droid-Dl5wRjDv.js → droid-BAf_vL2l.js} +1 -1
  59. package/dist/ui/assets/{globalenv-section-T1imQsSA.js → globalenv-section-B8jXcXvb.js} +1 -1
  60. package/dist/ui/assets/health-BO8TUGV-.js +1 -0
  61. package/dist/ui/assets/index-8bXW-BpK.js +72 -0
  62. package/dist/ui/assets/index-BXEJ1fT2.css +1 -0
  63. package/dist/ui/assets/{index-DH916Tyi.js → index-BsAeVEGk.js} +1 -1
  64. package/dist/ui/assets/{index-CqJFugM4.js → index-CC4Wj5Im.js} +1 -1
  65. package/dist/ui/assets/{index-BcLnqcxD.js → index-DcX4r1mv.js} +1 -1
  66. package/dist/ui/assets/index-DwhLYuc2.js +1 -0
  67. package/dist/ui/assets/{index-DFgCoUVv.js → index-mHMlp8Vb.js} +1 -1
  68. package/dist/ui/assets/{index-fV5BBuAO.js → index-vlkxIfEe.js} +1 -1
  69. package/dist/ui/assets/{logs-CZsWbq7X.js → logs-Be6Ybx9a.js} +1 -1
  70. package/dist/ui/assets/{masked-input-_EkW1Pzo.js → masked-input-DXnXVYlE.js} +1 -1
  71. package/dist/ui/assets/proxy-g4_N-fbB.js +9 -0
  72. package/dist/ui/assets/{proxy-status-widget-D82T2X4F.js → proxy-status-widget-BnvVPoy1.js} +1 -1
  73. package/dist/ui/assets/{raw-json-settings-editor-panel-DNmb_vR-.js → raw-json-settings-editor-panel-Bzz3efQU.js} +1 -1
  74. package/dist/ui/assets/{searchable-select--iFe00dY.js → searchable-select-DZale_hJ.js} +1 -1
  75. package/dist/ui/assets/{separator-CaGbNw8E.js → separator-BkmmPPJc.js} +1 -1
  76. package/dist/ui/assets/{shared-Cgk48Wu3.js → shared-ruYPWqHA.js} +1 -1
  77. package/dist/ui/assets/{table-CKuoPAz4.js → table-DdulTUSB.js} +1 -1
  78. package/dist/ui/assets/updates-COyrJbQd.js +1 -0
  79. package/dist/ui/index.html +2 -2
  80. package/dist/web-server/routes/profile-routes.d.ts.map +1 -1
  81. package/dist/web-server/routes/profile-routes.js +13 -3
  82. package/dist/web-server/routes/profile-routes.js.map +1 -1
  83. package/dist/web-server/routes/route-helpers.d.ts +1 -0
  84. package/dist/web-server/routes/route-helpers.d.ts.map +1 -1
  85. package/dist/web-server/routes/route-helpers.js +11 -0
  86. package/dist/web-server/routes/route-helpers.js.map +1 -1
  87. package/lib/mcp/ccs-browser-server.cjs +309 -121
  88. package/package.json +1 -1
  89. package/dist/ui/assets/codex-4VNBJ4Nb.js +0 -27
  90. package/dist/ui/assets/health-CNous3WN.js +0 -1
  91. package/dist/ui/assets/index-2X8-14xr.js +0 -72
  92. package/dist/ui/assets/index-Be3dhMW3.js +0 -9
  93. package/dist/ui/assets/index-DuTB1_9r.css +0 -1
  94. package/dist/ui/assets/updates-Dr8Yq-Pm.js +0 -1
@@ -298,7 +298,8 @@ function getTools() {
298
298
  pageIndex: {
299
299
  type: 'integer',
300
300
  minimum: 0,
301
- description: 'Optional zero-based page index from browser_get_session_info. Defaults to the first page.',
301
+ description:
302
+ 'Optional zero-based page index from browser_get_session_info. Defaults to the first page.',
302
303
  },
303
304
  },
304
305
  additionalProperties: false,
@@ -314,7 +315,8 @@ function getTools() {
314
315
  pageIndex: {
315
316
  type: 'integer',
316
317
  minimum: 0,
317
- description: 'Optional zero-based page index from browser_get_session_info. Defaults to the first page.',
318
+ description:
319
+ 'Optional zero-based page index from browser_get_session_info. Defaults to the first page.',
318
320
  },
319
321
  },
320
322
  additionalProperties: false,
@@ -330,7 +332,8 @@ function getTools() {
330
332
  pageIndex: {
331
333
  type: 'integer',
332
334
  minimum: 0,
333
- description: 'Optional zero-based page index from browser_get_session_info. Defaults to the first page.',
335
+ description:
336
+ 'Optional zero-based page index from browser_get_session_info. Defaults to the first page.',
334
337
  },
335
338
  },
336
339
  additionalProperties: false,
@@ -346,7 +349,8 @@ function getTools() {
346
349
  pageIndex: {
347
350
  type: 'integer',
348
351
  minimum: 0,
349
- description: 'Optional zero-based page index from browser_get_session_info. Defaults to the first page.',
352
+ description:
353
+ 'Optional zero-based page index from browser_get_session_info. Defaults to the first page.',
350
354
  },
351
355
  url: {
352
356
  type: 'string',
@@ -367,7 +371,8 @@ function getTools() {
367
371
  pageIndex: {
368
372
  type: 'integer',
369
373
  minimum: 0,
370
- description: 'Optional zero-based page index from browser_get_session_info. Defaults to the first page.',
374
+ description:
375
+ 'Optional zero-based page index from browser_get_session_info. Defaults to the first page.',
371
376
  },
372
377
  selector: {
373
378
  type: 'string',
@@ -376,11 +381,13 @@ function getTools() {
376
381
  nth: {
377
382
  type: 'integer',
378
383
  minimum: 0,
379
- description: 'Optional zero-based match index for selectors returning multiple elements.',
384
+ description:
385
+ 'Optional zero-based match index for selectors returning multiple elements.',
380
386
  },
381
387
  frameSelector: {
382
388
  type: 'string',
383
- description: 'Optional CSS selector for an iframe whose document should be used as the query root.',
389
+ description:
390
+ 'Optional CSS selector for an iframe whose document should be used as the query root.',
384
391
  },
385
392
  pierceShadow: {
386
393
  type: 'boolean',
@@ -388,11 +395,13 @@ function getTools() {
388
395
  },
389
396
  offsetX: {
390
397
  type: 'number',
391
- description: "Optional horizontal offset in CSS pixels from the target element's left edge.",
398
+ description:
399
+ "Optional horizontal offset in CSS pixels from the target element's left edge.",
392
400
  },
393
401
  offsetY: {
394
402
  type: 'number',
395
- description: "Optional vertical offset in CSS pixels from the target element's top edge.",
403
+ description:
404
+ "Optional vertical offset in CSS pixels from the target element's top edge.",
396
405
  },
397
406
  button: {
398
407
  type: 'string',
@@ -419,7 +428,8 @@ function getTools() {
419
428
  pageIndex: {
420
429
  type: 'integer',
421
430
  minimum: 0,
422
- description: 'Optional zero-based page index from browser_get_session_info. Defaults to the first page.',
431
+ description:
432
+ 'Optional zero-based page index from browser_get_session_info. Defaults to the first page.',
423
433
  },
424
434
  selector: {
425
435
  type: 'string',
@@ -448,7 +458,8 @@ function getTools() {
448
458
  pageIndex: {
449
459
  type: 'integer',
450
460
  minimum: 0,
451
- description: 'Optional zero-based page index from browser_get_session_info. Defaults to the first page.',
461
+ description:
462
+ 'Optional zero-based page index from browser_get_session_info. Defaults to the first page.',
452
463
  },
453
464
  key: {
454
465
  type: 'string',
@@ -482,7 +493,8 @@ function getTools() {
482
493
  pageIndex: {
483
494
  type: 'integer',
484
495
  minimum: 0,
485
- description: 'Optional zero-based page index from browser_get_session_info. Defaults to the first page.',
496
+ description:
497
+ 'Optional zero-based page index from browser_get_session_info. Defaults to the first page.',
486
498
  },
487
499
  selector: {
488
500
  type: 'string',
@@ -490,7 +502,8 @@ function getTools() {
490
502
  },
491
503
  frameSelector: {
492
504
  type: 'string',
493
- description: 'Optional CSS selector for an iframe whose document should be used as the query root.',
505
+ description:
506
+ 'Optional CSS selector for an iframe whose document should be used as the query root.',
494
507
  },
495
508
  pierceShadow: {
496
509
  type: 'boolean',
@@ -516,7 +529,8 @@ function getTools() {
516
529
  },
517
530
  {
518
531
  name: TOOL_SELECT_PAGE,
519
- description: 'Select the current page target by page index or page id for subsequent browser tool calls.',
532
+ description:
533
+ 'Select the current page target by page index or page id for subsequent browser tool calls.',
520
534
  inputSchema: {
521
535
  type: 'object',
522
536
  properties: {
@@ -539,7 +553,8 @@ function getTools() {
539
553
  },
540
554
  {
541
555
  name: TOOL_CLOSE_PAGE,
542
- description: 'Close a browser page target by page index or page id. Defaults to the selected page.',
556
+ description:
557
+ 'Close a browser page target by page index or page id. Defaults to the selected page.',
543
558
  inputSchema: {
544
559
  type: 'object',
545
560
  properties: {
@@ -769,7 +784,8 @@ function getTools() {
769
784
  },
770
785
  {
771
786
  name: TOOL_START_RECORDING,
772
- description: 'Start recording real browser interactions on the selected page and store them as structured steps.',
787
+ description:
788
+ 'Start recording real browser interactions on the selected page and store them as structured steps.',
773
789
  inputSchema: {
774
790
  type: 'object',
775
791
  properties: {
@@ -781,7 +797,8 @@ function getTools() {
781
797
  },
782
798
  {
783
799
  name: TOOL_STOP_RECORDING,
784
- description: 'Stop the active browser recording session and keep the recorded result in session-local state.',
800
+ description:
801
+ 'Stop the active browser recording session and keep the recorded result in session-local state.',
785
802
  inputSchema: {
786
803
  type: 'object',
787
804
  properties: {},
@@ -808,7 +825,8 @@ function getTools() {
808
825
  },
809
826
  {
810
827
  name: TOOL_START_REPLAY,
811
- description: 'Start replaying a sequence of structured Browser MCP steps on the selected page.',
828
+ description:
829
+ 'Start replaying a sequence of structured Browser MCP steps on the selected page.',
812
830
  inputSchema: {
813
831
  type: 'object',
814
832
  properties: {
@@ -840,7 +858,8 @@ function getTools() {
840
858
  },
841
859
  {
842
860
  name: TOOL_START_ORCHESTRATION,
843
- description: 'Start an orchestration session that runs fixed browser workflow blocks on the selected page.',
861
+ description:
862
+ 'Start an orchestration session that runs fixed browser workflow blocks on the selected page.',
844
863
  inputSchema: {
845
864
  type: 'object',
846
865
  properties: {
@@ -854,7 +873,8 @@ function getTools() {
854
873
  },
855
874
  {
856
875
  name: TOOL_GET_ORCHESTRATION,
857
- description: 'Read the current orchestration status and result summary from session-local state.',
876
+ description:
877
+ 'Read the current orchestration status and result summary from session-local state.',
858
878
  inputSchema: {
859
879
  type: 'object',
860
880
  properties: {},
@@ -863,7 +883,8 @@ function getTools() {
863
883
  },
864
884
  {
865
885
  name: TOOL_CANCEL_ORCHESTRATION,
866
- description: 'Cancel the active orchestration session and keep the summary in session-local state.',
886
+ description:
887
+ 'Cancel the active orchestration session and keep the summary in session-local state.',
867
888
  inputSchema: {
868
889
  type: 'object',
869
890
  properties: {},
@@ -872,7 +893,8 @@ function getTools() {
872
893
  },
873
894
  {
874
895
  name: TOOL_EXPORT_ARTIFACT,
875
- description: 'Export the current recording, replay, or orchestration artifact to a local JSON file.',
896
+ description:
897
+ 'Export the current recording, replay, or orchestration artifact to a local JSON file.',
876
898
  inputSchema: {
877
899
  type: 'object',
878
900
  properties: {
@@ -885,7 +907,8 @@ function getTools() {
885
907
  },
886
908
  {
887
909
  name: TOOL_IMPORT_ARTIFACT,
888
- description: 'Import a recording, replay, or orchestration artifact from a local JSON file into session-local state.',
910
+ description:
911
+ 'Import a recording, replay, or orchestration artifact from a local JSON file into session-local state.',
889
912
  inputSchema: {
890
913
  type: 'object',
891
914
  properties: {
@@ -926,7 +949,8 @@ function getTools() {
926
949
  pageIndex: {
927
950
  type: 'integer',
928
951
  minimum: 0,
929
- description: 'Optional zero-based page index from browser_get_session_info. Defaults to the first page.',
952
+ description:
953
+ 'Optional zero-based page index from browser_get_session_info. Defaults to the first page.',
930
954
  },
931
955
  fullPage: {
932
956
  type: 'boolean',
@@ -946,7 +970,8 @@ function getTools() {
946
970
  pageIndex: {
947
971
  type: 'integer',
948
972
  minimum: 0,
949
- description: 'Optional zero-based page index from browser_get_session_info. Defaults to the first page.',
973
+ description:
974
+ 'Optional zero-based page index from browser_get_session_info. Defaults to the first page.',
950
975
  },
951
976
  selector: {
952
977
  type: 'string',
@@ -955,11 +980,13 @@ function getTools() {
955
980
  nth: {
956
981
  type: 'integer',
957
982
  minimum: 0,
958
- description: 'Optional zero-based match index for selectors returning multiple elements.',
983
+ description:
984
+ 'Optional zero-based match index for selectors returning multiple elements.',
959
985
  },
960
986
  frameSelector: {
961
987
  type: 'string',
962
- description: 'Optional CSS selector for an iframe whose document should be used as the query root.',
988
+ description:
989
+ 'Optional CSS selector for an iframe whose document should be used as the query root.',
963
990
  },
964
991
  pierceShadow: {
965
992
  type: 'boolean',
@@ -994,7 +1021,8 @@ function getTools() {
994
1021
  pageIndex: {
995
1022
  type: 'integer',
996
1023
  minimum: 0,
997
- description: 'Optional zero-based page index from browser_get_session_info. Defaults to the first page.',
1024
+ description:
1025
+ 'Optional zero-based page index from browser_get_session_info. Defaults to the first page.',
998
1026
  },
999
1027
  expression: {
1000
1028
  type: 'string',
@@ -1020,7 +1048,8 @@ function getTools() {
1020
1048
  pageIndex: {
1021
1049
  type: 'integer',
1022
1050
  minimum: 0,
1023
- description: 'Optional zero-based page index from browser_get_session_info. Defaults to the first page.',
1051
+ description:
1052
+ 'Optional zero-based page index from browser_get_session_info. Defaults to the first page.',
1024
1053
  },
1025
1054
  selector: {
1026
1055
  type: 'string',
@@ -1028,7 +1057,8 @@ function getTools() {
1028
1057
  },
1029
1058
  frameSelector: {
1030
1059
  type: 'string',
1031
- description: 'Optional CSS selector for an iframe whose document should be used as the query root.',
1060
+ description:
1061
+ 'Optional CSS selector for an iframe whose document should be used as the query root.',
1032
1062
  },
1033
1063
  pierceShadow: {
1034
1064
  type: 'boolean',
@@ -1049,7 +1079,8 @@ function getTools() {
1049
1079
  pageIndex: {
1050
1080
  type: 'integer',
1051
1081
  minimum: 0,
1052
- description: 'Optional zero-based page index from browser_get_session_info. Defaults to the first page.',
1082
+ description:
1083
+ 'Optional zero-based page index from browser_get_session_info. Defaults to the first page.',
1053
1084
  },
1054
1085
  selector: {
1055
1086
  type: 'string',
@@ -1058,11 +1089,13 @@ function getTools() {
1058
1089
  nth: {
1059
1090
  type: 'integer',
1060
1091
  minimum: 0,
1061
- description: 'Optional zero-based match index for selectors returning multiple elements.',
1092
+ description:
1093
+ 'Optional zero-based match index for selectors returning multiple elements.',
1062
1094
  },
1063
1095
  frameSelector: {
1064
1096
  type: 'string',
1065
- description: 'Optional CSS selector for an iframe whose document should be used as the query root.',
1097
+ description:
1098
+ 'Optional CSS selector for an iframe whose document should be used as the query root.',
1066
1099
  },
1067
1100
  pierceShadow: {
1068
1101
  type: 'boolean',
@@ -1088,7 +1121,8 @@ function getTools() {
1088
1121
  pageIndex: {
1089
1122
  type: 'integer',
1090
1123
  minimum: 0,
1091
- description: 'Optional zero-based page index from browser_get_session_info. Defaults to the first page.',
1124
+ description:
1125
+ 'Optional zero-based page index from browser_get_session_info. Defaults to the first page.',
1092
1126
  },
1093
1127
  selector: {
1094
1128
  type: 'string',
@@ -1096,7 +1130,8 @@ function getTools() {
1096
1130
  },
1097
1131
  frameSelector: {
1098
1132
  type: 'string',
1099
- description: 'Optional CSS selector for an iframe whose document should be used as the query root.',
1133
+ description:
1134
+ 'Optional CSS selector for an iframe whose document should be used as the query root.',
1100
1135
  },
1101
1136
  pierceShadow: {
1102
1137
  type: 'boolean',
@@ -1116,7 +1151,8 @@ function getTools() {
1116
1151
  pageIndex: {
1117
1152
  type: 'integer',
1118
1153
  minimum: 0,
1119
- description: 'Optional zero-based page index from browser_get_session_info. Defaults to the first page.',
1154
+ description:
1155
+ 'Optional zero-based page index from browser_get_session_info. Defaults to the first page.',
1120
1156
  },
1121
1157
  timeoutMs: {
1122
1158
  type: 'integer',
@@ -1135,14 +1171,25 @@ function getTools() {
1135
1171
  ];
1136
1172
  }
1137
1173
 
1138
- async function fetchJson(url) {
1139
- const response = await fetch(url);
1174
+ async function fetchJson(url, options = undefined) {
1175
+ const response = await fetch(url, options);
1140
1176
  if (!response.ok) {
1141
1177
  throw new Error(`HTTP ${response.status} for ${url}`);
1142
1178
  }
1143
1179
  return await response.json();
1144
1180
  }
1145
1181
 
1182
+ function isUsablePageTarget(target) {
1183
+ if (!target || typeof target !== 'object' || target.type !== 'page') {
1184
+ return false;
1185
+ }
1186
+ const url = typeof target.url === 'string' ? target.url : '';
1187
+ if (url.startsWith('chrome://omnibox-popup') || url.startsWith('chrome://top-chrome')) {
1188
+ return false;
1189
+ }
1190
+ return true;
1191
+ }
1192
+
1146
1193
  function getHttpUrl() {
1147
1194
  const value = process.env.CCS_BROWSER_DEVTOOLS_HTTP_URL;
1148
1195
  if (!value) {
@@ -1157,16 +1204,14 @@ async function listPageTargets() {
1157
1204
  throw new Error('Browser MCP received an invalid /json/list response.');
1158
1205
  }
1159
1206
 
1160
- return targets
1161
- .filter((target) => target && typeof target === 'object' && target.type === 'page')
1162
- .map((target) => ({
1163
- id: typeof target.id === 'string' ? target.id : '',
1164
- title: typeof target.title === 'string' ? target.title : '',
1165
- url: typeof target.url === 'string' ? target.url : '',
1166
- type: typeof target.type === 'string' ? target.type : 'page',
1167
- webSocketDebuggerUrl:
1168
- typeof target.webSocketDebuggerUrl === 'string' ? target.webSocketDebuggerUrl : '',
1169
- }));
1207
+ return targets.filter(isUsablePageTarget).map((target) => ({
1208
+ id: typeof target.id === 'string' ? target.id : '',
1209
+ title: typeof target.title === 'string' ? target.title : '',
1210
+ url: typeof target.url === 'string' ? target.url : '',
1211
+ type: typeof target.type === 'string' ? target.type : 'page',
1212
+ webSocketDebuggerUrl:
1213
+ typeof target.webSocketDebuggerUrl === 'string' ? target.webSocketDebuggerUrl : '',
1214
+ }));
1170
1215
  }
1171
1216
 
1172
1217
  function parsePageIndex(toolArgs) {
@@ -1314,9 +1359,13 @@ function parseOptionalHeaderMatchers(value) {
1314
1359
  }
1315
1360
  const name = requireNonEmptyString(entry.name, 'headerMatchers.name');
1316
1361
  const valueIncludes =
1317
- entry.valueIncludes === undefined ? '' : requireNonEmptyString(entry.valueIncludes, 'headerMatchers.valueIncludes');
1362
+ entry.valueIncludes === undefined
1363
+ ? ''
1364
+ : requireNonEmptyString(entry.valueIncludes, 'headerMatchers.valueIncludes');
1318
1365
  const valueRegex =
1319
- entry.valueRegex === undefined ? '' : requireNonEmptyString(entry.valueRegex, 'headerMatchers.valueRegex');
1366
+ entry.valueRegex === undefined
1367
+ ? ''
1368
+ : requireNonEmptyString(entry.valueRegex, 'headerMatchers.valueRegex');
1320
1369
  if (!valueIncludes && !valueRegex) {
1321
1370
  throw new Error('headerMatchers entry must include valueIncludes or valueRegex');
1322
1371
  }
@@ -1342,7 +1391,14 @@ function validateInterceptMatcherSet({
1342
1391
  if (urlPattern && urlRegex) {
1343
1392
  throw new Error('urlPattern and urlRegex cannot be used together');
1344
1393
  }
1345
- if (!urlIncludes && !method && !resourceType && !urlPattern && !urlRegex && headerMatchers.length === 0) {
1394
+ if (
1395
+ !urlIncludes &&
1396
+ !method &&
1397
+ !resourceType &&
1398
+ !urlPattern &&
1399
+ !urlRegex &&
1400
+ headerMatchers.length === 0
1401
+ ) {
1346
1402
  throw new Error('at least one matching condition is required');
1347
1403
  }
1348
1404
  }
@@ -1505,7 +1561,9 @@ function resolveTargetPage(pages, toolArgs, defaultSelectedId = selectedPageId,
1505
1561
  const selectedIndex = findPageIndexById(pages, defaultSelectedId);
1506
1562
  if (selectedIndex === -1) {
1507
1563
  if (!allowImplicitFallback && defaultSelectedId) {
1508
- throw new Error('Selected page is no longer available; specify pageIndex or pageId explicitly.');
1564
+ throw new Error(
1565
+ 'Selected page is no longer available; specify pageIndex or pageId explicitly.'
1566
+ );
1509
1567
  }
1510
1568
  const fallbackPage = pages[0];
1511
1569
  if (!fallbackPage) {
@@ -1526,7 +1584,9 @@ async function getSelectedPage(toolArgs) {
1526
1584
  const pageIndex = parsePageIndex(toolArgs);
1527
1585
  const page = pages[pageIndex];
1528
1586
  if (!page) {
1529
- throw new Error(`Browser MCP page index ${pageIndex} is out of range (found ${pages.length} pages).`);
1587
+ throw new Error(
1588
+ `Browser MCP page index ${pageIndex} is out of range (found ${pages.length} pages).`
1589
+ );
1530
1590
  }
1531
1591
  if (!page.webSocketDebuggerUrl) {
1532
1592
  throw new Error(`Browser MCP page ${pageIndex} does not expose a websocket debugger URL.`);
@@ -1655,7 +1715,11 @@ async function getBrowserTarget() {
1655
1715
  const browserTarget = Array.isArray(targets)
1656
1716
  ? targets.find((target) => target && typeof target === 'object' && target.type === 'browser')
1657
1717
  : null;
1658
- if (!browserTarget || typeof browserTarget.webSocketDebuggerUrl !== 'string' || !browserTarget.webSocketDebuggerUrl) {
1718
+ if (
1719
+ !browserTarget ||
1720
+ typeof browserTarget.webSocketDebuggerUrl !== 'string' ||
1721
+ !browserTarget.webSocketDebuggerUrl
1722
+ ) {
1659
1723
  throw new Error('browser-level download events are unavailable');
1660
1724
  }
1661
1725
  return browserTarget;
@@ -1795,11 +1859,15 @@ async function ensureBrowserDownloadSession() {
1795
1859
  receivedBytes: Number(message.params?.receivedBytes || 0),
1796
1860
  totalBytes: Number(message.params?.totalBytes || 0),
1797
1861
  savedPath: typeof message.params?.filePath === 'string' ? message.params.filePath : '',
1798
- finishedAt: status === 'completed' || status === 'canceled' ? new Date().toISOString() : '',
1862
+ finishedAt:
1863
+ status === 'completed' || status === 'canceled' ? new Date().toISOString() : '',
1799
1864
  });
1800
1865
  }
1801
1866
  })();
1802
- activityChain = activityChain.catch(() => {}).then(() => activity).catch(() => {});
1867
+ activityChain = activityChain
1868
+ .catch(() => {})
1869
+ .then(() => activity)
1870
+ .catch(() => {});
1803
1871
  void activity.catch(() => {});
1804
1872
  });
1805
1873
 
@@ -1836,7 +1904,7 @@ async function evaluateInPage(page, kind) {
1836
1904
  throw new Error(result.description || 'DevTools evaluation returned an error.');
1837
1905
  }
1838
1906
 
1839
- return typeof result.value === 'string' ? result.value : result.value ?? '';
1907
+ return typeof result.value === 'string' ? result.value : (result.value ?? '');
1840
1908
  }
1841
1909
 
1842
1910
  async function evaluateExpression(page, expression) {
@@ -1854,7 +1922,7 @@ async function evaluateExpression(page, expression) {
1854
1922
  throw new Error(result.description || 'DevTools evaluation returned an error.');
1855
1923
  }
1856
1924
 
1857
- return typeof result.value === 'string' ? result.value : result.value ?? '';
1925
+ return typeof result.value === 'string' ? result.value : (result.value ?? '');
1858
1926
  }
1859
1927
 
1860
1928
  async function withPageCommandSession(page, callback) {
@@ -2084,7 +2152,10 @@ function requireOptionalStringArray(value, label, allowedValues) {
2084
2152
  if (value === undefined) {
2085
2153
  return [];
2086
2154
  }
2087
- if (!Array.isArray(value) || value.some((item) => typeof item !== 'string' || item.trim() === '')) {
2155
+ if (
2156
+ !Array.isArray(value) ||
2157
+ value.some((item) => typeof item !== 'string' || item.trim() === '')
2158
+ ) {
2088
2159
  throw new Error(`${label} must be an array of non-empty strings`);
2089
2160
  }
2090
2161
  const normalized = value.map((item) => item.trim());
@@ -2282,7 +2353,10 @@ function buildScopedMatchesExpression(selector, nth, frameSelector, pierceShadow
2282
2353
  }
2283
2354
 
2284
2355
  async function getScopedDiagnostics(page, selector, nth, frameSelector, pierceShadow) {
2285
- const raw = await evaluateExpression(page, buildScopedMatchesExpression(selector, nth, frameSelector, pierceShadow));
2356
+ const raw = await evaluateExpression(
2357
+ page,
2358
+ buildScopedMatchesExpression(selector, nth, frameSelector, pierceShadow)
2359
+ );
2286
2360
  return JSON.parse(raw);
2287
2361
  }
2288
2362
 
@@ -2573,9 +2647,7 @@ function readArtifactFile(filePath) {
2573
2647
  throw new Error('artifact file is not a regular file');
2574
2648
  }
2575
2649
  if (stat.size > MAX_ARTIFACT_FILE_BYTES) {
2576
- throw new Error(
2577
- `artifact file exceeds maximum size of ${MAX_ARTIFACT_FILE_BYTES} bytes`
2578
- );
2650
+ throw new Error(`artifact file exceeds maximum size of ${MAX_ARTIFACT_FILE_BYTES} bytes`);
2579
2651
  }
2580
2652
  const raw = fs.readFileSync(filePath, 'utf8');
2581
2653
  let parsed;
@@ -2590,7 +2662,11 @@ function readArtifactFile(filePath) {
2590
2662
  if (parsed.version !== 1) {
2591
2663
  throw new Error('unsupported artifact version');
2592
2664
  }
2593
- if (typeof parsed.kind !== 'string' || typeof parsed.name !== 'string' || !('payload' in parsed)) {
2665
+ if (
2666
+ typeof parsed.kind !== 'string' ||
2667
+ typeof parsed.name !== 'string' ||
2668
+ !('payload' in parsed)
2669
+ ) {
2594
2670
  throw new Error('invalid artifact payload');
2595
2671
  }
2596
2672
  return parsed;
@@ -2667,10 +2743,7 @@ function formatOrchestrationSummary(session) {
2667
2743
  }
2668
2744
 
2669
2745
  async function waitForSessionToSettle(task) {
2670
- await Promise.race([
2671
- task.then(() => undefined),
2672
- sleep(SESSION_START_SETTLE_WINDOW_MS),
2673
- ]);
2746
+ await Promise.race([task.then(() => undefined), sleep(SESSION_START_SETTLE_WINDOW_MS)]);
2674
2747
  }
2675
2748
 
2676
2749
  function createReplaySession(page, pageIndex, steps) {
@@ -2961,7 +3034,12 @@ function validateSequenceStep(step) {
2961
3034
  }
2962
3035
 
2963
3036
  function requireCrossPageRunBlock(block) {
2964
- if (!block.args || typeof block.args !== 'object' || !block.args.run || typeof block.args.run !== 'object') {
3037
+ if (
3038
+ !block.args ||
3039
+ typeof block.args !== 'object' ||
3040
+ !block.args.run ||
3041
+ typeof block.args.run !== 'object'
3042
+ ) {
2965
3043
  throw new Error('cross-page run block is required');
2966
3044
  }
2967
3045
  if (CROSS_PAGE_BLOCK_TYPES.has(block.args.run.type)) {
@@ -3117,7 +3195,8 @@ function matchesObservedEvent(event, observed) {
3117
3195
  if (event.kind === 'download') {
3118
3196
  return (
3119
3197
  (!event.urlIncludes || String(observed.url || '').includes(event.urlIncludes)) &&
3120
- (!event.suggestedFilenameIncludes || String(observed.suggestedFilename || '').includes(event.suggestedFilenameIncludes))
3198
+ (!event.suggestedFilenameIncludes ||
3199
+ String(observed.suggestedFilename || '').includes(event.suggestedFilenameIncludes))
3121
3200
  );
3122
3201
  }
3123
3202
  return false;
@@ -3128,7 +3207,10 @@ function sleep(ms) {
3128
3207
  }
3129
3208
 
3130
3209
  async function getNavigationState(page) {
3131
- const raw = await evaluateExpression(page, `JSON.stringify({ href: location.href, readyState: document.readyState })`);
3210
+ const raw = await evaluateExpression(
3211
+ page,
3212
+ `JSON.stringify({ href: location.href, readyState: document.readyState })`
3213
+ );
3132
3214
  const parsed = JSON.parse(raw);
3133
3215
  return {
3134
3216
  href: typeof parsed.href === 'string' ? parsed.href : '',
@@ -3207,10 +3289,16 @@ async function handleClick(toolArgs) {
3207
3289
  const targetIndex = nth ?? 0;
3208
3290
  const frameSelector = parseOptionalNonEmptyString(toolArgs.frameSelector);
3209
3291
  const pierceShadow = toolArgs.pierceShadow === true;
3210
- const offsetX = toolArgs.offsetX === undefined ? undefined : requireFiniteNumber(toolArgs.offsetX, 'offsetX');
3211
- const offsetY = toolArgs.offsetY === undefined ? undefined : requireFiniteNumber(toolArgs.offsetY, 'offsetY');
3212
- const button = requireEnumString(toolArgs.button, 'button', ['left', 'middle', 'right']) || 'left';
3213
- const clickCount = toolArgs.clickCount === undefined ? 1 : requirePositiveInteger(toolArgs.clickCount, 'clickCount');
3292
+ const offsetX =
3293
+ toolArgs.offsetX === undefined ? undefined : requireFiniteNumber(toolArgs.offsetX, 'offsetX');
3294
+ const offsetY =
3295
+ toolArgs.offsetY === undefined ? undefined : requireFiniteNumber(toolArgs.offsetY, 'offsetY');
3296
+ const button =
3297
+ requireEnumString(toolArgs.button, 'button', ['left', 'middle', 'right']) || 'left';
3298
+ const clickCount =
3299
+ toolArgs.clickCount === undefined
3300
+ ? 1
3301
+ : requirePositiveInteger(toolArgs.clickCount, 'clickCount');
3214
3302
 
3215
3303
  const expression = `(() => {
3216
3304
  const selector = JSON.parse(${JSON.stringify(JSON.stringify(selector))});
@@ -3445,7 +3533,8 @@ async function handleType(toolArgs) {
3445
3533
 
3446
3534
  const raw = await evaluateExpression(page, expression);
3447
3535
  const parsed = JSON.parse(raw);
3448
- const typedLength = typeof parsed.typedLength === 'number' ? parsed.typedLength : String(parsed.value || '').length;
3536
+ const typedLength =
3537
+ typeof parsed.typedLength === 'number' ? parsed.typedLength : String(parsed.value || '').length;
3449
3538
  return `pageIndex: ${pageIndex}\nselector: ${selector}\ntypedLength: ${typedLength}\nstatus: typed`;
3450
3539
  }
3451
3540
 
@@ -3458,7 +3547,8 @@ async function handlePressKey(toolArgs) {
3458
3547
  'Meta',
3459
3548
  'Shift',
3460
3549
  ]);
3461
- const repeat = toolArgs.repeat === undefined ? 1 : requirePositiveInteger(toolArgs.repeat, 'repeat');
3550
+ const repeat =
3551
+ toolArgs.repeat === undefined ? 1 : requirePositiveInteger(toolArgs.repeat, 'repeat');
3462
3552
  const modifierMask =
3463
3553
  (modifiers.includes('Alt') ? 1 : 0) |
3464
3554
  (modifiers.includes('Control') ? 2 : 0) |
@@ -3626,8 +3716,19 @@ async function handleScroll(toolArgs) {
3626
3716
  return lines.join('\n');
3627
3717
  }
3628
3718
 
3719
+ async function ensureDrawableViewport(page) {
3720
+ const metrics = await sendCdpCommand(page, 'Page.getLayoutMetrics');
3721
+ const viewport = metrics.cssVisualViewport || metrics.visualViewport || {};
3722
+ const width = Number(viewport.clientWidth || 0);
3723
+ const height = Number(viewport.clientHeight || 0);
3724
+ if (width <= 0 || height <= 0) {
3725
+ throw new Error('page has no drawable viewport for screenshot');
3726
+ }
3727
+ }
3728
+
3629
3729
  async function handleScreenshot(toolArgs) {
3630
3730
  const { page, pageIndex } = await getSelectedPage(toolArgs);
3731
+ await ensureDrawableViewport(page);
3631
3732
  const fullPage = toolArgs.fullPage === true;
3632
3733
  const response = await sendCdpCommand(page, 'Page.captureScreenshot', {
3633
3734
  format: 'png',
@@ -3642,11 +3743,23 @@ async function handleScreenshot(toolArgs) {
3642
3743
  return `pageIndex: ${pageIndex}\nformat: png\nfullPage: ${fullPage ? 'true' : 'false'}\ndata: ${data}`;
3643
3744
  }
3644
3745
 
3645
- async function getElementDiagnostics(page, selector, nth, frameSelector = '', pierceShadow = false) {
3746
+ async function getElementDiagnostics(
3747
+ page,
3748
+ selector,
3749
+ nth,
3750
+ frameSelector = '',
3751
+ pierceShadow = false
3752
+ ) {
3646
3753
  return await getScopedDiagnostics(page, selector, nth, frameSelector, pierceShadow);
3647
3754
  }
3648
3755
 
3649
- async function getScrolledElementStateAt(page, selector, nth, frameSelector = '', pierceShadow = false) {
3756
+ async function getScrolledElementStateAt(
3757
+ page,
3758
+ selector,
3759
+ nth,
3760
+ frameSelector = '',
3761
+ pierceShadow = false
3762
+ ) {
3650
3763
  const expression = `(() => {
3651
3764
  const selector = JSON.parse(${JSON.stringify(JSON.stringify(selector))});
3652
3765
  const nth = ${nth === undefined ? 'undefined' : String(nth)};
@@ -3860,7 +3973,9 @@ function formatQueryResponse(pageIndex, selector, nth, diagnostics, fields) {
3860
3973
  }
3861
3974
  if (diagnostics.targetMissing) {
3862
3975
  if (hasTargetSpecificQueryField(fields)) {
3863
- throw new Error(`element index ${diagnostics.targetIndex} is out of range for selector: ${selector}`);
3976
+ throw new Error(
3977
+ `element index ${diagnostics.targetIndex} is out of range for selector: ${selector}`
3978
+ );
3864
3979
  }
3865
3980
  for (const field of fields) {
3866
3981
  if (field === 'exists') {
@@ -3897,7 +4012,11 @@ async function handleWaitFor(toolArgs) {
3897
4012
  const nth = requireOptionalNonNegativeInteger(toolArgs.nth, 'nth');
3898
4013
  const frameSelector = parseOptionalNonEmptyString(toolArgs.frameSelector);
3899
4014
  const pierceShadow = toolArgs.pierceShadow === true;
3900
- const timeoutMs = requirePositiveIntegerOrDefault(toolArgs.timeoutMs, 'timeoutMs', DEFAULT_WAIT_TIMEOUT_MS);
4015
+ const timeoutMs = requirePositiveIntegerOrDefault(
4016
+ toolArgs.timeoutMs,
4017
+ 'timeoutMs',
4018
+ DEFAULT_WAIT_TIMEOUT_MS
4019
+ );
3901
4020
  const pollIntervalMs = requirePositiveIntegerOrDefault(
3902
4021
  toolArgs.pollIntervalMs,
3903
4022
  'pollIntervalMs',
@@ -3993,7 +4112,14 @@ function interpolatePoints(sourcePoint, targetPoint, steps) {
3993
4112
  return points;
3994
4113
  }
3995
4114
 
3996
- async function resolveElementCenterPoint(page, selector, nth, frameSelector, pierceShadow, missingLabel) {
4115
+ async function resolveElementCenterPoint(
4116
+ page,
4117
+ selector,
4118
+ nth,
4119
+ frameSelector,
4120
+ pierceShadow,
4121
+ missingLabel
4122
+ ) {
3997
4123
  const state = await getScrolledElementStateAt(page, selector, nth, frameSelector, pierceShadow);
3998
4124
  if (!state.exists) {
3999
4125
  throw new Error(missingLabel);
@@ -4041,7 +4167,10 @@ async function handlePointerAction(toolArgs) {
4041
4167
  }
4042
4168
 
4043
4169
  if (action.type === 'pause') {
4044
- const durationMs = action.durationMs === undefined ? 0 : requireNonNegativeInteger(action.durationMs, 'durationMs');
4170
+ const durationMs =
4171
+ action.durationMs === undefined
4172
+ ? 0
4173
+ : requireNonNegativeInteger(action.durationMs, 'durationMs');
4045
4174
  if (durationMs > 0) {
4046
4175
  await sleep(durationMs);
4047
4176
  }
@@ -4070,7 +4199,14 @@ async function handlePointerAction(toolArgs) {
4070
4199
 
4071
4200
  pointerX = point.x;
4072
4201
  pointerY = point.y;
4073
- await dispatchMousePointerEvent(page, 'mouseMoved', pointerX, pointerY, pressedButton || 'left', Boolean(pressedButton));
4202
+ await dispatchMousePointerEvent(
4203
+ page,
4204
+ 'mouseMoved',
4205
+ pointerX,
4206
+ pointerY,
4207
+ pressedButton || 'left',
4208
+ Boolean(pressedButton)
4209
+ );
4074
4210
  continue;
4075
4211
  }
4076
4212
 
@@ -4082,21 +4218,36 @@ async function handlePointerAction(toolArgs) {
4082
4218
  if (pressedButton) {
4083
4219
  throw new Error('pointer state error');
4084
4220
  }
4085
- pressedButton = requireEnumString(action.button, 'button', ['left', 'middle', 'right']) || 'left';
4086
- await dispatchMousePointerEvent(page, 'mousePressed', pointerX, pointerY, pressedButton, true);
4221
+ pressedButton =
4222
+ requireEnumString(action.button, 'button', ['left', 'middle', 'right']) || 'left';
4223
+ await dispatchMousePointerEvent(
4224
+ page,
4225
+ 'mousePressed',
4226
+ pointerX,
4227
+ pointerY,
4228
+ pressedButton,
4229
+ true
4230
+ );
4087
4231
  continue;
4088
4232
  }
4089
4233
 
4090
4234
  if (!pressedButton) {
4091
4235
  throw new Error('pointer state error');
4092
4236
  }
4093
- const releaseButton =
4094
- requireEnumString(action.button, 'button', ['left', 'middle', 'right']) || pressedButton;
4095
- if (releaseButton !== pressedButton) {
4096
- throw new Error('pointer state error');
4097
- }
4098
- await dispatchMousePointerEvent(page, 'mouseReleased', pointerX, pointerY, releaseButton, false);
4099
- pressedButton = null;
4237
+ const releaseButton =
4238
+ requireEnumString(action.button, 'button', ['left', 'middle', 'right']) || pressedButton;
4239
+ if (releaseButton !== pressedButton) {
4240
+ throw new Error('pointer state error');
4241
+ }
4242
+ await dispatchMousePointerEvent(
4243
+ page,
4244
+ 'mouseReleased',
4245
+ pointerX,
4246
+ pointerY,
4247
+ releaseButton,
4248
+ false
4249
+ );
4250
+ pressedButton = null;
4100
4251
  }
4101
4252
 
4102
4253
  if (pressedButton) {
@@ -4161,14 +4312,24 @@ async function handleDragElement(toolArgs) {
4161
4312
  y: requireFiniteNumber(toolArgs.targetY, 'targetY'),
4162
4313
  };
4163
4314
 
4164
- const steps = toolArgs.steps === undefined ? DEFAULT_DRAG_STEPS : requirePositiveInteger(toolArgs.steps, 'steps');
4315
+ const steps =
4316
+ toolArgs.steps === undefined
4317
+ ? DEFAULT_DRAG_STEPS
4318
+ : requirePositiveInteger(toolArgs.steps, 'steps');
4165
4319
 
4166
4320
  await dispatchMousePointerEvent(page, 'mouseMoved', sourcePoint.x, sourcePoint.y, 'left', false);
4167
4321
  await dispatchMousePointerEvent(page, 'mousePressed', sourcePoint.x, sourcePoint.y, 'left', true);
4168
4322
  for (const point of interpolatePoints(sourcePoint, targetPoint, steps)) {
4169
4323
  await dispatchMousePointerEvent(page, 'mouseMoved', point.x, point.y, 'left', true);
4170
4324
  }
4171
- await dispatchMousePointerEvent(page, 'mouseReleased', targetPoint.x, targetPoint.y, 'left', false);
4325
+ await dispatchMousePointerEvent(
4326
+ page,
4327
+ 'mouseReleased',
4328
+ targetPoint.x,
4329
+ targetPoint.y,
4330
+ 'left',
4331
+ false
4332
+ );
4172
4333
 
4173
4334
  return [
4174
4335
  `pageIndex: ${pageIndex}`,
@@ -4197,7 +4358,14 @@ async function handleHover(toolArgs) {
4197
4358
  throw new Error(`element is hidden or not interactable for selector: ${selector}`);
4198
4359
  }
4199
4360
 
4200
- await dispatchMousePointerEvent(page, 'mouseMoved', state.centerPoint.x, state.centerPoint.y, 'left', false);
4361
+ await dispatchMousePointerEvent(
4362
+ page,
4363
+ 'mouseMoved',
4364
+ state.centerPoint.x,
4365
+ state.centerPoint.y,
4366
+ 'left',
4367
+ false
4368
+ );
4201
4369
 
4202
4370
  return `pageIndex: ${pageIndex}\nselector: ${selector}${formatScopedSelectorSuffix(frameSelector, pierceShadow)}\nstatus: hovered`;
4203
4371
  }
@@ -4231,6 +4399,7 @@ async function handleElementScreenshot(toolArgs) {
4231
4399
  if (state.visibleClip.width <= 0 || state.visibleClip.height <= 0) {
4232
4400
  throw new Error(`element has empty bounds for selector: ${selector}`);
4233
4401
  }
4402
+ await ensureDrawableViewport(page);
4234
4403
 
4235
4404
  const response = await sendCdpCommand(page, 'Page.captureScreenshot', {
4236
4405
  format: 'png',
@@ -4363,7 +4532,11 @@ async function waitForBrowserDownloadEvent(page, timeoutMs, event) {
4363
4532
  const browserTarget = Array.isArray(targets)
4364
4533
  ? targets.find((target) => target && typeof target === 'object' && target.type === 'browser')
4365
4534
  : null;
4366
- if (!browserTarget || typeof browserTarget.webSocketDebuggerUrl !== 'string' || !browserTarget.webSocketDebuggerUrl) {
4535
+ if (
4536
+ !browserTarget ||
4537
+ typeof browserTarget.webSocketDebuggerUrl !== 'string' ||
4538
+ !browserTarget.webSocketDebuggerUrl
4539
+ ) {
4367
4540
  throw new Error('browser-level download events are unavailable');
4368
4541
  }
4369
4542
 
@@ -4449,7 +4622,11 @@ async function waitForMatchingEvent({ page, timeoutMs, event }) {
4449
4622
 
4450
4623
  async function handleWaitForEvent(toolArgs) {
4451
4624
  const { page, pageIndex } = await getSelectedPage(toolArgs);
4452
- const timeoutMs = requirePositiveIntegerOrDefault(toolArgs.timeoutMs, 'timeoutMs', DEFAULT_WAIT_TIMEOUT_MS);
4625
+ const timeoutMs = requirePositiveIntegerOrDefault(
4626
+ toolArgs.timeoutMs,
4627
+ 'timeoutMs',
4628
+ DEFAULT_WAIT_TIMEOUT_MS
4629
+ );
4453
4630
  const event = parseEventCondition(toolArgs.event);
4454
4631
  const observed = await waitForMatchingEvent({ page, pageIndex, timeoutMs, event });
4455
4632
  return `pageIndex: ${pageIndex}\nevent: ${event.kind}\nstatus: observed\ndetail: ${JSON.stringify(observed)}`;
@@ -4628,7 +4805,9 @@ async function ensureInterceptSession(page) {
4628
4805
  return;
4629
4806
  }
4630
4807
  const paused = message.params || {};
4631
- const matchedRule = getRulesForMatching(page.id).find((rule) => matchesInterceptRule(rule, paused));
4808
+ const matchedRule = getRulesForMatching(page.id).find((rule) =>
4809
+ matchesInterceptRule(rule, paused)
4810
+ );
4632
4811
  const action = matchedRule ? matchedRule.action : 'continue';
4633
4812
  if (action === 'fail') {
4634
4813
  ws.send(
@@ -4671,7 +4850,10 @@ async function ensureInterceptSession(page) {
4671
4850
  statusCode: action === 'fulfill' ? matchedRule.statusCode : 0,
4672
4851
  });
4673
4852
  })();
4674
- activityChain = activityChain.catch(() => {}).then(() => activity).catch(() => {});
4853
+ activityChain = activityChain
4854
+ .catch(() => {})
4855
+ .then(() => activity)
4856
+ .catch(() => {});
4675
4857
  void activity.catch(() => {});
4676
4858
  });
4677
4859
 
@@ -4716,7 +4898,7 @@ async function handleOpenPage(toolArgs) {
4716
4898
  const query = toolArgs?.url
4717
4899
  ? `?${new URLSearchParams({ url: requireValidHttpUrl(toolArgs.url) }).toString()}`
4718
4900
  : '';
4719
- const createdTarget = await fetchJson(`${getHttpUrl()}/json/new${query}`);
4901
+ const createdTarget = await fetchJson(`${getHttpUrl()}/json/new${query}`, { method: 'PUT' });
4720
4902
  const pageId = typeof createdTarget?.id === 'string' ? createdTarget.id : '';
4721
4903
  if (!pageId) {
4722
4904
  throw new Error('Browser MCP failed to create a new page target.');
@@ -4763,7 +4945,10 @@ async function handleClosePage(toolArgs) {
4763
4945
  if (findPageIndexById(remainingPages, previousSelectedPageId) !== -1) {
4764
4946
  selectedPageId = previousSelectedPageId;
4765
4947
  } else {
4766
- selectedPageId = resolveFallbackSelectedPageId(remainingPages, pageIndex > 0 ? pageIndex - 1 : 0);
4948
+ selectedPageId = resolveFallbackSelectedPageId(
4949
+ remainingPages,
4950
+ pageIndex > 0 ? pageIndex - 1 : 0
4951
+ );
4767
4952
  }
4768
4953
  const selectedIndex = findPageIndexById(remainingPages, selectedPageId);
4769
4954
  return [
@@ -4805,7 +4990,9 @@ async function handleAddInterceptRule(toolArgs) {
4805
4990
  });
4806
4991
  const statusCode = parseOptionalStatusCode(toolArgs.statusCode);
4807
4992
  const contentType =
4808
- toolArgs.contentType === undefined ? '' : requireNonEmptyString(toolArgs.contentType, 'contentType');
4993
+ toolArgs.contentType === undefined
4994
+ ? ''
4995
+ : requireNonEmptyString(toolArgs.contentType, 'contentType');
4809
4996
  const body = parseOptionalBody(toolArgs.body);
4810
4997
  const responseHeaders = parseOptionalResponseHeaders(toolArgs.responseHeaders, contentType);
4811
4998
  const rule = {
@@ -4900,7 +5087,8 @@ async function handleListRequests(toolArgs) {
4900
5087
  async function handleSetDownloadBehavior(toolArgs) {
4901
5088
  const behavior = requireEnumString(toolArgs.behavior, 'behavior', ['accept', 'deny']);
4902
5089
  const downloadPath = parseOptionalNonEmptyString(toolArgs.downloadPath);
4903
- const eventsEnabled = toolArgs.eventsEnabled === undefined ? true : toolArgs.eventsEnabled === true;
5090
+ const eventsEnabled =
5091
+ toolArgs.eventsEnabled === undefined ? true : toolArgs.eventsEnabled === true;
4904
5092
 
4905
5093
  if (behavior === 'deny' && downloadPath) {
4906
5094
  throw new Error('downloadPath is only allowed when behavior=accept');
@@ -5172,7 +5360,9 @@ async function finalizeRecordingCapture(session) {
5172
5360
  const warnings = Array.isArray(value.warnings) ? value.warnings : [];
5173
5361
 
5174
5362
  session.rawEvents = rawEvents;
5175
- session.steps = rawEvents.map((event) => normalizeRecordedEvent(session.pageId, event)).filter(Boolean);
5363
+ session.steps = rawEvents
5364
+ .map((event) => normalizeRecordedEvent(session.pageId, event))
5365
+ .filter(Boolean);
5176
5366
  session.warnings = warnings.map((warning) => String(warning.message || warning));
5177
5367
  }
5178
5368
 
@@ -5225,9 +5415,7 @@ async function handleStopRecording() {
5225
5415
  await finalizeRecordingCapture(session);
5226
5416
  } catch (error) {
5227
5417
  finalizeError = error instanceof Error ? error : new Error(String(error));
5228
- session.warnings.push(
5229
- `recording capture finalization failed: ${finalizeError.message}`
5230
- );
5418
+ session.warnings.push(`recording capture finalization failed: ${finalizeError.message}`);
5231
5419
  }
5232
5420
  session.status = 'stopped';
5233
5421
  session.stoppedAt = new Date().toISOString();
@@ -5325,7 +5513,9 @@ function buildReplayToolArgs(step, replayPage) {
5325
5513
  nth: step.nth,
5326
5514
  frameSelector: step.frameSelector,
5327
5515
  pierceShadow: step.pierceShadow,
5328
- ...(step.args?.targetSelector !== undefined ? { targetSelector: step.args.targetSelector } : {}),
5516
+ ...(step.args?.targetSelector !== undefined
5517
+ ? { targetSelector: step.args.targetSelector }
5518
+ : {}),
5329
5519
  ...(step.args?.targetNth !== undefined ? { targetNth: step.args.targetNth } : {}),
5330
5520
  ...(step.args?.targetX !== undefined ? { targetX: step.args.targetX } : {}),
5331
5521
  ...(step.args?.targetY !== undefined ? { targetY: step.args.targetY } : {}),
@@ -5430,9 +5620,7 @@ async function handleStartReplay(toolArgs) {
5430
5620
 
5431
5621
  latestReplaySession = session;
5432
5622
  activeReplaySession = session;
5433
- const task = Promise.resolve().then(() =>
5434
- runReplaySession(session, { manageActiveState: true })
5435
- );
5623
+ const task = Promise.resolve().then(() => runReplaySession(session, { manageActiveState: true }));
5436
5624
  activeReplayTask = task;
5437
5625
  task.catch(() => {
5438
5626
  // Session state is recorded in runReplaySession.
@@ -5718,9 +5906,7 @@ async function handleExportArtifact(toolArgs) {
5718
5906
  const artifact = buildArtifact(kind, name, payload);
5719
5907
  const serialized = JSON.stringify(artifact, null, 2);
5720
5908
  if (Buffer.byteLength(serialized, 'utf8') > MAX_ARTIFACT_FILE_BYTES) {
5721
- throw new Error(
5722
- `artifact file exceeds maximum size of ${MAX_ARTIFACT_FILE_BYTES} bytes`
5723
- );
5909
+ throw new Error(`artifact file exceeds maximum size of ${MAX_ARTIFACT_FILE_BYTES} bytes`);
5724
5910
  }
5725
5911
  fs.writeFileSync(filePath, serialized, { encoding: 'utf8', mode: 0o600 });
5726
5912
  return `name: ${name}\nkind: ${kind}\npath: ${filePath}\nstatus: exported`;
@@ -5736,12 +5922,14 @@ async function handleListArtifacts() {
5736
5922
  return 'artifacts: []';
5737
5923
  }
5738
5924
  return entries
5739
- .map((entry) => [
5740
- `name: ${entry.name}`,
5741
- `kind: ${entry.kind}`,
5742
- `createdAt: ${entry.createdAt}`,
5743
- `path: ${getArtifactPath(entry.name)}`,
5744
- ].join('\n'))
5925
+ .map((entry) =>
5926
+ [
5927
+ `name: ${entry.name}`,
5928
+ `kind: ${entry.kind}`,
5929
+ `createdAt: ${entry.createdAt}`,
5930
+ `path: ${getArtifactPath(entry.name)}`,
5931
+ ].join('\n')
5932
+ )
5745
5933
  .join('\n---\n');
5746
5934
  }
5747
5935