@extension.dev/mcp 4.2.2 → 4.4.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.
package/dist/module.js CHANGED
@@ -12,7 +12,7 @@ import { filterKeysForThisBrowser } from "browser-extension-manifest-fields";
12
12
  import ws from "ws";
13
13
  import node_http from "node:http";
14
14
  import { clearCredentials, exchangeAndPersist, fetchLoginConfig, pollForToken, publish, readCredentials, resolveApiBase, resolveToken, safeApiBase, startDeviceCode } from "@extension.dev/core";
15
- import { extensionInstall, getManagedBrowsersCacheRoot } from "extension-install";
15
+ import { extensionInstall, extensionUninstall, getManagedBrowsersCacheRoot } from "extension-install";
16
16
  import { execFile } from "node:child_process";
17
17
  import { promisify } from "node:util";
18
18
  var add_feature_namespaceObject = {};
@@ -45,6 +45,12 @@ __webpack_require__.d(dev_namespaceObject, {
45
45
  handler: ()=>dev_handler,
46
46
  schema: ()=>dev_schema
47
47
  });
48
+ var doctor_namespaceObject = {};
49
+ __webpack_require__.r(doctor_namespaceObject);
50
+ __webpack_require__.d(doctor_namespaceObject, {
51
+ handler: ()=>doctor_handler,
52
+ schema: ()=>doctor_schema
53
+ });
48
54
  var dom_inspect_namespaceObject = {};
49
55
  __webpack_require__.r(dom_inspect_namespaceObject);
50
56
  __webpack_require__.d(dom_inspect_namespaceObject, {
@@ -171,6 +177,12 @@ __webpack_require__.d(storage_namespaceObject, {
171
177
  handler: ()=>storage_handler,
172
178
  schema: ()=>storage_schema
173
179
  });
180
+ var uninstall_browser_namespaceObject = {};
181
+ __webpack_require__.r(uninstall_browser_namespaceObject);
182
+ __webpack_require__.d(uninstall_browser_namespaceObject, {
183
+ handler: ()=>uninstall_browser_handler,
184
+ schema: ()=>uninstall_browser_schema
185
+ });
174
186
  var wait_namespaceObject = {};
175
187
  __webpack_require__.r(wait_namespaceObject);
176
188
  __webpack_require__.d(wait_namespaceObject, {
@@ -183,7 +195,7 @@ __webpack_require__.d(whoami_namespaceObject, {
183
195
  handler: ()=>whoami_handler,
184
196
  schema: ()=>whoami_schema
185
197
  });
186
- var package_namespaceObject = JSON.parse('{"rE":"4.2.2","El":{"OP":"^4.0.8"}}');
198
+ var package_namespaceObject = JSON.parse('{"rE":"4.4.0","El":{"OP":"^4.0.11"}}');
187
199
  const create_schema = {
188
200
  name: "extension_create",
189
201
  description: "Create a new browser extension project from a template in the extension.dev template catalog. Use extension_list_templates to see available options.",
@@ -371,10 +383,20 @@ const build_schema = {
371
383
  type: "string",
372
384
  enum: [
373
385
  "chrome",
386
+ "chromium",
374
387
  "edge",
388
+ "brave",
389
+ "opera",
390
+ "vivaldi",
391
+ "yandex",
375
392
  "firefox",
393
+ "waterfox",
394
+ "librewolf",
395
+ "safari",
376
396
  "chromium-based",
377
- "gecko-based"
397
+ "gecko-based",
398
+ "firefox-based",
399
+ "webkit-based"
378
400
  ],
379
401
  default: "chrome",
380
402
  description: "Target browser"
@@ -388,6 +410,30 @@ const build_schema = {
388
410
  type: "boolean",
389
411
  default: false,
390
412
  description: "Include source code zip (required by some stores)"
413
+ },
414
+ zipFilename: {
415
+ type: "string",
416
+ description: "Custom .zip file name (defaults to name and version)"
417
+ },
418
+ polyfill: {
419
+ type: "boolean",
420
+ default: false,
421
+ description: "Apply cross-browser polyfill"
422
+ },
423
+ silent: {
424
+ type: "boolean",
425
+ default: false,
426
+ description: "Suppress build output"
427
+ },
428
+ mode: {
429
+ type: "string",
430
+ enum: [
431
+ "development",
432
+ "production",
433
+ "none"
434
+ ],
435
+ default: "production",
436
+ description: "Bundler mode override (also sets NODE_ENV)"
391
437
  }
392
438
  },
393
439
  required: [
@@ -402,6 +448,18 @@ async function build_handler(args) {
402
448
  browser: args.browser ?? "chrome",
403
449
  zip: args.zip ?? false,
404
450
  zipSource: args.zipSource ?? false,
451
+ ...args.zipFilename ? {
452
+ zipFilename: args.zipFilename
453
+ } : {},
454
+ ...void 0 !== args.polyfill ? {
455
+ polyfill: args.polyfill
456
+ } : {},
457
+ ...void 0 !== args.silent ? {
458
+ silent: args.silent
459
+ } : {},
460
+ ...args.mode ? {
461
+ mode: args.mode
462
+ } : {},
405
463
  exitOnError: false
406
464
  });
407
465
  return JSON.stringify({
@@ -514,6 +572,50 @@ function removeSession(projectPath, browser) {
514
572
  function listSessions() {
515
573
  return Array.from(process_manager_sessions.values());
516
574
  }
575
+ const LAUNCH_FLAG_SCHEMA = {
576
+ profile: {
577
+ type: "string",
578
+ description: 'Browser profile path, or "false" to reuse the default user profile. Omit for a fresh throwaway profile.'
579
+ },
580
+ startingUrl: {
581
+ type: "string",
582
+ description: "URL the browser opens on launch"
583
+ },
584
+ chromiumBinary: {
585
+ type: "string",
586
+ description: "Path to a custom Chromium-based binary (overrides browser)"
587
+ },
588
+ geckoBinary: {
589
+ type: "string",
590
+ description: "Path to a custom Gecko/Firefox binary (overrides browser)"
591
+ },
592
+ host: {
593
+ type: "string",
594
+ description: "Dev server bind host. Use 0.0.0.0 for Docker or devcontainers. Defaults to 127.0.0.1"
595
+ },
596
+ publicHost: {
597
+ type: "string",
598
+ description: "Connectable host the browser dials for HMR and reload when it differs from the bind host"
599
+ },
600
+ extensions: {
601
+ type: "array",
602
+ items: {
603
+ type: "string"
604
+ },
605
+ description: "Companion extension paths or store URLs to load alongside the project"
606
+ }
607
+ };
608
+ function launchFlagArgs(args) {
609
+ const cli = [];
610
+ if (void 0 !== args.profile) cli.push("--profile", args.profile);
611
+ if (args.startingUrl) cli.push("--starting-url", args.startingUrl);
612
+ if (args.chromiumBinary) cli.push("--chromium-binary", args.chromiumBinary);
613
+ if (args.geckoBinary) cli.push("--gecko-binary", args.geckoBinary);
614
+ if (args.host) cli.push("--host", args.host);
615
+ if (args.publicHost) cli.push("--public-host", args.publicHost);
616
+ if (args.extensions?.length) cli.push("--extensions", args.extensions.join(","));
617
+ return cli;
618
+ }
517
619
  const dev_schema = {
518
620
  name: "extension_dev",
519
621
  description: "Start the extension development server with hot module replacement. Launches a browser with the extension loaded. Returns process info for use with extension_wait and extension_source_inspect.",
@@ -528,10 +630,20 @@ const dev_schema = {
528
630
  type: "string",
529
631
  enum: [
530
632
  "chrome",
633
+ "chromium",
531
634
  "edge",
635
+ "brave",
636
+ "opera",
637
+ "vivaldi",
638
+ "yandex",
532
639
  "firefox",
640
+ "waterfox",
641
+ "librewolf",
642
+ "safari",
533
643
  "chromium-based",
534
- "gecko-based"
644
+ "gecko-based",
645
+ "firefox-based",
646
+ "webkit-based"
535
647
  ],
536
648
  default: "chrome"
537
649
  },
@@ -544,6 +656,12 @@ const dev_schema = {
544
656
  default: false,
545
657
  description: "Start dev server without launching browser"
546
658
  },
659
+ polyfill: {
660
+ type: "boolean",
661
+ default: true,
662
+ description: "Apply cross-browser polyfill"
663
+ },
664
+ ...LAUNCH_FLAG_SCHEMA,
547
665
  allowControl: {
548
666
  type: "boolean",
549
667
  default: false,
@@ -570,6 +688,8 @@ async function dev_handler(args) {
570
688
  ];
571
689
  if (void 0 !== args.port) cliArgs.push("--port", String(args.port));
572
690
  if (args.noBrowser) cliArgs.push("--no-browser");
691
+ if (false === args.polyfill) cliArgs.push("--polyfill", "false");
692
+ cliArgs.push(...launchFlagArgs(args));
573
693
  if (args.allowControl) cliArgs.push("--allow-control");
574
694
  if (args.allowEval) cliArgs.push("--allow-eval");
575
695
  const child = spawnExtensionCli(cliArgs, {
@@ -617,10 +737,20 @@ const start_schema = {
617
737
  type: "string",
618
738
  enum: [
619
739
  "chrome",
740
+ "chromium",
620
741
  "edge",
742
+ "brave",
743
+ "opera",
744
+ "vivaldi",
745
+ "yandex",
621
746
  "firefox",
747
+ "waterfox",
748
+ "librewolf",
749
+ "safari",
622
750
  "chromium-based",
623
- "gecko-based"
751
+ "gecko-based",
752
+ "firefox-based",
753
+ "webkit-based"
624
754
  ],
625
755
  default: "chrome"
626
756
  },
@@ -628,7 +758,17 @@ const start_schema = {
628
758
  type: "boolean",
629
759
  default: true,
630
760
  description: "Apply cross-browser polyfill"
631
- }
761
+ },
762
+ port: {
763
+ type: "number",
764
+ description: "Server port (0 for auto-assign)"
765
+ },
766
+ noBrowser: {
767
+ type: "boolean",
768
+ default: false,
769
+ description: "Build and serve without launching a browser"
770
+ },
771
+ ...LAUNCH_FLAG_SCHEMA
632
772
  },
633
773
  required: [
634
774
  "projectPath"
@@ -643,7 +783,10 @@ async function start_handler(args) {
643
783
  "--browser",
644
784
  browser
645
785
  ];
646
- if (false === args.polyfill) cliArgs.push("--no-polyfill");
786
+ if (false === args.polyfill) cliArgs.push("--polyfill", "false");
787
+ if (void 0 !== args.port) cliArgs.push("--port", String(args.port));
788
+ if (args.noBrowser) cliArgs.push("--no-browser");
789
+ cliArgs.push(...launchFlagArgs(args));
647
790
  const child = spawnExtensionCli(cliArgs, {
648
791
  projectDir: args.projectPath
649
792
  });
@@ -687,13 +830,33 @@ const preview_schema = {
687
830
  type: "string",
688
831
  enum: [
689
832
  "chrome",
833
+ "chromium",
690
834
  "edge",
835
+ "brave",
836
+ "opera",
837
+ "vivaldi",
838
+ "yandex",
691
839
  "firefox",
840
+ "waterfox",
841
+ "librewolf",
842
+ "safari",
692
843
  "chromium-based",
693
- "gecko-based"
844
+ "gecko-based",
845
+ "firefox-based",
846
+ "webkit-based"
694
847
  ],
695
848
  default: "chrome"
696
- }
849
+ },
850
+ port: {
851
+ type: "number",
852
+ description: "Server port (0 for auto-assign)"
853
+ },
854
+ noBrowser: {
855
+ type: "boolean",
856
+ default: false,
857
+ description: "Serve the preview without launching a browser"
858
+ },
859
+ ...LAUNCH_FLAG_SCHEMA
697
860
  },
698
861
  required: [
699
862
  "projectPath"
@@ -708,6 +871,9 @@ async function preview_handler(args) {
708
871
  "--browser",
709
872
  browser
710
873
  ];
874
+ if (void 0 !== args.port) cliArgs.push("--port", String(args.port));
875
+ if (args.noBrowser) cliArgs.push("--no-browser");
876
+ cliArgs.push(...launchFlagArgs(args));
711
877
  const child = spawnExtensionCli(cliArgs, {
712
878
  projectDir: args.projectPath
713
879
  });
@@ -902,6 +1068,33 @@ async function get_template_source_handler(args) {
902
1068
  } : {}
903
1069
  });
904
1070
  }
1071
+ const CHROMIUM_FAMILY = new Set([
1072
+ "chrome",
1073
+ "chromium",
1074
+ "edge",
1075
+ "brave",
1076
+ "opera",
1077
+ "vivaldi",
1078
+ "yandex",
1079
+ "chromium-based"
1080
+ ]);
1081
+ const GECKO_FAMILY = new Set([
1082
+ "firefox",
1083
+ "waterfox",
1084
+ "librewolf",
1085
+ "gecko-based",
1086
+ "firefox-based"
1087
+ ]);
1088
+ const WEBKIT_FAMILY = new Set([
1089
+ "safari",
1090
+ "webkit-based"
1091
+ ]);
1092
+ function isChromiumFamily(browser) {
1093
+ return CHROMIUM_FAMILY.has(browser);
1094
+ }
1095
+ function isGeckoFamily(browser) {
1096
+ return GECKO_FAMILY.has(browser);
1097
+ }
905
1098
  const manifest_validate_schema = {
906
1099
  name: "extension_manifest_validate",
907
1100
  description: "Validate a manifest.json file for correctness across browsers. Reports missing fields, invalid permissions, and cross-browser compatibility issues.",
@@ -961,15 +1154,8 @@ async function manifest_validate_handler(args) {
961
1154
  const chromiumManifest = filterKeysForThisBrowser(manifest, "chrome");
962
1155
  if (!chromiumManifest.manifest_version) result.errors.push('Missing manifest_version. Use "chromium:manifest_version": 3 and "firefox:manifest_version": 2 for cross-browser support.');
963
1156
  for (const browser of browsers){
964
- const isChromium = [
965
- "chrome",
966
- "edge",
967
- "chromium-based"
968
- ].includes(browser);
969
- const isFirefox = [
970
- "firefox",
971
- "gecko-based"
972
- ].includes(browser);
1157
+ const isChromium = isChromiumFamily(browser);
1158
+ const isFirefox = isGeckoFamily(browser);
973
1159
  const effective = filterKeysForThisBrowser(manifest, browser);
974
1160
  const issues = [];
975
1161
  if (isChromium) {
@@ -1749,13 +1935,7 @@ async function source_inspect_handler(args) {
1749
1935
  "console"
1750
1936
  ]);
1751
1937
  const maxBytes = args.maxBytes ?? 262144;
1752
- const isChromium = [
1753
- "chrome",
1754
- "chromium",
1755
- "edge",
1756
- "chromium-based"
1757
- ].includes(browser);
1758
- if (!isChromium) return JSON.stringify({
1938
+ if (!isChromiumFamily(browser)) return JSON.stringify({
1759
1939
  error: `Source inspection for ${browser} uses RDP (Remote Debug Protocol). Currently only Chromium CDP is supported.`,
1760
1940
  hint: 'Pass browser: "chrome" (against a Chromium-family dev session).'
1761
1941
  });
@@ -1868,13 +2048,7 @@ const list_extensions_schema = {
1868
2048
  };
1869
2049
  async function list_extensions_handler(args) {
1870
2050
  const { browser } = resolveSessionBrowser(args.projectPath, args.browser, "chrome");
1871
- const isChromium = [
1872
- "chrome",
1873
- "chromium",
1874
- "edge",
1875
- "chromium-based"
1876
- ].includes(browser);
1877
- if (!isChromium) return JSON.stringify({
2051
+ if (!isChromiumFamily(browser)) return JSON.stringify({
1878
2052
  error: `Listing extensions for ${browser} uses RDP (Remote Debug Protocol). Currently only Chromium CDP is supported.`,
1879
2053
  hint: 'Pass browser: "chrome" (against a Chromium-family dev session).'
1880
2054
  });
@@ -2824,7 +2998,7 @@ async function wait_handler(args) {
2824
2998
  }
2825
2999
  const add_feature_schema = {
2826
3000
  name: "extension_add_feature",
2827
- description: "Add a new feature surface to an existing extension. Generates the required files and updates manifest.json. Sources patterns from the extension.dev template catalog.",
3001
+ description: "Plan a new feature surface for an existing extension. Returns step-by-step instructions, the manifest additions to make, and reference templates from the extension.dev catalog. Does not modify files; apply the returned plan yourself.",
2828
3002
  inputSchema: {
2829
3003
  type: "object",
2830
3004
  properties: {
@@ -3260,6 +3434,57 @@ async function install_browser_handler(args) {
3260
3434
  });
3261
3435
  }
3262
3436
  }
3437
+ const uninstall_browser_schema = {
3438
+ name: "extension_uninstall_browser",
3439
+ description: "Remove a managed browser binary from the Extension.js cache. Only touches the managed cache, never system-installed browsers.",
3440
+ inputSchema: {
3441
+ type: "object",
3442
+ properties: {
3443
+ browser: {
3444
+ type: "string",
3445
+ enum: [
3446
+ "chrome",
3447
+ "chromium",
3448
+ "edge",
3449
+ "firefox"
3450
+ ],
3451
+ description: "Managed browser to remove"
3452
+ },
3453
+ all: {
3454
+ type: "boolean",
3455
+ default: false,
3456
+ description: "Remove every managed browser binary"
3457
+ }
3458
+ },
3459
+ required: []
3460
+ }
3461
+ };
3462
+ async function uninstall_browser_handler(args) {
3463
+ const start = Date.now();
3464
+ if (!args.browser && !args.all) return JSON.stringify({
3465
+ status: "error",
3466
+ message: "Provide a browser to remove, or set all: true."
3467
+ });
3468
+ try {
3469
+ await extensionUninstall({
3470
+ browser: args.browser,
3471
+ all: args.all
3472
+ });
3473
+ return JSON.stringify({
3474
+ status: "uninstalled",
3475
+ target: args.all ? "all" : args.browser,
3476
+ duration: Date.now() - start,
3477
+ hint: "Use extension_list_browsers to confirm what remains in the managed cache."
3478
+ });
3479
+ } catch (err) {
3480
+ return JSON.stringify({
3481
+ status: "error",
3482
+ target: args.all ? "all" : args.browser,
3483
+ message: err instanceof Error ? err.message : String(err),
3484
+ duration: Date.now() - start
3485
+ });
3486
+ }
3487
+ }
3263
3488
  const list_browsers_schema = {
3264
3489
  name: "extension_list_browsers",
3265
3490
  description: "List managed browser binaries installed by the extension.dev platform. Shows what browsers are available in the managed cache without checking system-installed browsers.",
@@ -3333,7 +3558,14 @@ const detect_browsers_schema = {
3333
3558
  "chrome",
3334
3559
  "chromium",
3335
3560
  "edge",
3336
- "firefox"
3561
+ "brave",
3562
+ "opera",
3563
+ "vivaldi",
3564
+ "yandex",
3565
+ "firefox",
3566
+ "waterfox",
3567
+ "librewolf",
3568
+ "safari"
3337
3569
  ]
3338
3570
  },
3339
3571
  description: "Browsers to check. If omitted, checks all."
@@ -3345,8 +3577,21 @@ const ALL_BROWSERS = [
3345
3577
  "chrome",
3346
3578
  "chromium",
3347
3579
  "edge",
3348
- "firefox"
3580
+ "brave",
3581
+ "opera",
3582
+ "vivaldi",
3583
+ "yandex",
3584
+ "firefox",
3585
+ "waterfox",
3586
+ "librewolf",
3587
+ "safari"
3349
3588
  ];
3589
+ const MANAGED_INSTALLABLE = new Set([
3590
+ "chrome",
3591
+ "chromium",
3592
+ "edge",
3593
+ "firefox"
3594
+ ]);
3350
3595
  const SYSTEM_PATHS = {
3351
3596
  darwin: {
3352
3597
  chrome: [
@@ -3364,6 +3609,33 @@ const SYSTEM_PATHS = {
3364
3609
  firefox: [
3365
3610
  "/Applications/Firefox.app/Contents/MacOS/firefox",
3366
3611
  `${process.env.HOME}/Applications/Firefox.app/Contents/MacOS/firefox`
3612
+ ],
3613
+ brave: [
3614
+ "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",
3615
+ `${process.env.HOME}/Applications/Brave Browser.app/Contents/MacOS/Brave Browser`
3616
+ ],
3617
+ opera: [
3618
+ "/Applications/Opera.app/Contents/MacOS/Opera",
3619
+ `${process.env.HOME}/Applications/Opera.app/Contents/MacOS/Opera`
3620
+ ],
3621
+ vivaldi: [
3622
+ "/Applications/Vivaldi.app/Contents/MacOS/Vivaldi",
3623
+ `${process.env.HOME}/Applications/Vivaldi.app/Contents/MacOS/Vivaldi`
3624
+ ],
3625
+ yandex: [
3626
+ "/Applications/Yandex.app/Contents/MacOS/Yandex",
3627
+ `${process.env.HOME}/Applications/Yandex.app/Contents/MacOS/Yandex`
3628
+ ],
3629
+ waterfox: [
3630
+ "/Applications/Waterfox.app/Contents/MacOS/waterfox",
3631
+ `${process.env.HOME}/Applications/Waterfox.app/Contents/MacOS/waterfox`
3632
+ ],
3633
+ librewolf: [
3634
+ "/Applications/LibreWolf.app/Contents/MacOS/librewolf",
3635
+ `${process.env.HOME}/Applications/LibreWolf.app/Contents/MacOS/librewolf`
3636
+ ],
3637
+ safari: [
3638
+ "/Applications/Safari.app/Contents/MacOS/Safari"
3367
3639
  ]
3368
3640
  },
3369
3641
  linux: {
@@ -3386,6 +3658,31 @@ const SYSTEM_PATHS = {
3386
3658
  "/usr/bin/firefox",
3387
3659
  "/snap/bin/firefox",
3388
3660
  "/usr/lib/firefox/firefox"
3661
+ ],
3662
+ brave: [
3663
+ "/usr/bin/brave-browser",
3664
+ "/usr/bin/brave",
3665
+ "/snap/bin/brave"
3666
+ ],
3667
+ opera: [
3668
+ "/usr/bin/opera",
3669
+ "/snap/bin/opera"
3670
+ ],
3671
+ vivaldi: [
3672
+ "/usr/bin/vivaldi",
3673
+ "/usr/bin/vivaldi-stable"
3674
+ ],
3675
+ yandex: [
3676
+ "/usr/bin/yandex-browser",
3677
+ "/usr/bin/yandex-browser-stable"
3678
+ ],
3679
+ waterfox: [
3680
+ "/usr/bin/waterfox",
3681
+ "/opt/waterfox/waterfox"
3682
+ ],
3683
+ librewolf: [
3684
+ "/usr/bin/librewolf",
3685
+ "/opt/librewolf/librewolf"
3389
3686
  ]
3390
3687
  },
3391
3688
  win32: {
@@ -3403,6 +3700,30 @@ const SYSTEM_PATHS = {
3403
3700
  firefox: [
3404
3701
  "C:\\Program Files\\Mozilla Firefox\\firefox.exe",
3405
3702
  "C:\\Program Files (x86)\\Mozilla Firefox\\firefox.exe"
3703
+ ],
3704
+ brave: [
3705
+ "C:\\Program Files\\BraveSoftware\\Brave-Browser\\Application\\brave.exe",
3706
+ "C:\\Program Files (x86)\\BraveSoftware\\Brave-Browser\\Application\\brave.exe"
3707
+ ],
3708
+ opera: [
3709
+ `${process.env.LOCALAPPDATA || ""}\\Programs\\Opera\\opera.exe`,
3710
+ "C:\\Program Files\\Opera\\opera.exe"
3711
+ ],
3712
+ vivaldi: [
3713
+ `${process.env.LOCALAPPDATA || ""}\\Vivaldi\\Application\\vivaldi.exe`,
3714
+ "C:\\Program Files\\Vivaldi\\Application\\vivaldi.exe"
3715
+ ],
3716
+ yandex: [
3717
+ `${process.env.LOCALAPPDATA || ""}\\Yandex\\YandexBrowser\\Application\\browser.exe`,
3718
+ "C:\\Program Files (x86)\\Yandex\\YandexBrowser\\Application\\browser.exe"
3719
+ ],
3720
+ waterfox: [
3721
+ "C:\\Program Files\\Waterfox\\waterfox.exe",
3722
+ "C:\\Program Files (x86)\\Waterfox\\waterfox.exe"
3723
+ ],
3724
+ librewolf: [
3725
+ "C:\\Program Files\\LibreWolf\\librewolf.exe",
3726
+ "C:\\Program Files (x86)\\LibreWolf\\librewolf.exe"
3406
3727
  ]
3407
3728
  }
3408
3729
  };
@@ -3513,7 +3834,8 @@ async function detect_browsers_handler(args) {
3513
3834
  if (node_fs.existsSync(browserDir)) managed.installed.push(browser);
3514
3835
  }
3515
3836
  for (const browser of browsersToCheck){
3516
- const isGecko = "firefox" === browser;
3837
+ const isGecko = isGeckoFamily(browser);
3838
+ const isWebkit = WEBKIT_FAMILY.has(browser);
3517
3839
  let binaryPath = findManagedBinary(browser);
3518
3840
  let source = "managed";
3519
3841
  if (!binaryPath) {
@@ -3521,14 +3843,14 @@ async function detect_browsers_handler(args) {
3521
3843
  source = binaryPath ? "system" : "not_found";
3522
3844
  }
3523
3845
  let version = null;
3524
- if (binaryPath) version = await getVersion(binaryPath, browser);
3846
+ if (binaryPath && !isWebkit) version = await getVersion(binaryPath, browser);
3525
3847
  detected.push({
3526
3848
  browser,
3527
3849
  binaryPath,
3528
3850
  source,
3529
- engine: isGecko ? "gecko" : "chromium",
3851
+ engine: isWebkit ? "webkit" : isGecko ? "gecko" : "chromium",
3530
3852
  version,
3531
- cdpSupport: !isGecko,
3853
+ cdpSupport: !isGecko && !isWebkit,
3532
3854
  rdpSupport: isGecko
3533
3855
  });
3534
3856
  }
@@ -3541,9 +3863,66 @@ async function detect_browsers_handler(args) {
3541
3863
  available: available.map((d)=>d.browser),
3542
3864
  missing: missing.map((d)=>d.browser)
3543
3865
  },
3544
- hint: missing.length ? `Missing browser(s): ${missing.map((d)=>d.browser).join(", ")}. Use extension_install_browser to install them.` : "All requested browsers are available."
3866
+ hint: missing.length ? `Missing browser(s): ${missing.map((d)=>d.browser).join(", ")}.${missing.some((d)=>MANAGED_INSTALLABLE.has(d.browser)) ? ` Use extension_install_browser to install ${missing.filter((d)=>MANAGED_INSTALLABLE.has(d.browser)).map((d)=>d.browser).join(", ")}.` : ""}` : "All requested browsers are available."
3545
3867
  });
3546
3868
  }
3869
+ const doctor_schema = {
3870
+ name: "extension_doctor",
3871
+ description: "Diagnose a dev session end-to-end: ready contract, dev-server process, control-port agreement, control channel, eval token, executor, and browser liveness. Returns one {check, status, detail, remediation?} entry per leg in dependency order — a 'skip' names the check that blocked it and is NOT a pass. Run this first when any act tool (storage/reload/eval/open) errors unexpectedly. Wraps `extension doctor`.",
3872
+ inputSchema: {
3873
+ type: "object",
3874
+ properties: {
3875
+ projectPath: {
3876
+ type: "string",
3877
+ description: "Path to the extension project root"
3878
+ },
3879
+ browser: {
3880
+ type: "string",
3881
+ description: "Browser session to diagnose. Defaults to the active dev session's browser for this project."
3882
+ }
3883
+ },
3884
+ required: [
3885
+ "projectPath"
3886
+ ]
3887
+ }
3888
+ };
3889
+ async function doctor_handler(args) {
3890
+ const { browser } = resolveSessionBrowser(args.projectPath, args.browser);
3891
+ const { code, stdout, stderr } = await runExtensionCli([
3892
+ "doctor",
3893
+ args.projectPath,
3894
+ "--browser",
3895
+ browser,
3896
+ "--output",
3897
+ "json"
3898
+ ], {
3899
+ cwd: args.projectPath
3900
+ });
3901
+ const out = stdout.trim();
3902
+ try {
3903
+ const checks = JSON.parse(out);
3904
+ if (!Array.isArray(checks)) throw new Error("not a check array");
3905
+ for (const check of checks){
3906
+ if ("string" == typeof check.detail) check.detail = toMcpSpeak(check.detail);
3907
+ if ("string" == typeof check.remediation) check.remediation = toMcpSpeak(check.remediation);
3908
+ }
3909
+ return JSON.stringify({
3910
+ browser,
3911
+ healthy: 0 === code,
3912
+ checks
3913
+ });
3914
+ } catch {
3915
+ const message = stderr.trim() || `extension exited with code ${code}`;
3916
+ return JSON.stringify({
3917
+ ok: false,
3918
+ error: {
3919
+ name: "CliError",
3920
+ message: toMcpSpeak(message),
3921
+ hint: "extension doctor requires a recent extension CLI — the project's local install may predate it."
3922
+ }
3923
+ });
3924
+ }
3925
+ }
3547
3926
  function typeOf(value) {
3548
3927
  if (Array.isArray(value)) return "array";
3549
3928
  if (null === value) return "null";
@@ -3635,8 +4014,10 @@ const tools = [
3635
4014
  whoami_namespaceObject,
3636
4015
  logout_namespaceObject,
3637
4016
  install_browser_namespaceObject,
4017
+ uninstall_browser_namespaceObject,
3638
4018
  list_browsers_namespaceObject,
3639
- detect_browsers_namespaceObject
4019
+ detect_browsers_namespaceObject,
4020
+ doctor_namespaceObject
3640
4021
  ];
3641
4022
  const toolMap = new Map();
3642
4023
  for (const tool of tools)toolMap.set(tool.schema.name, tool);