@deriv-com/fe-mcp-servers 0.0.13 → 0.0.15

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.
@@ -273,7 +273,6 @@ Parses and validates acceptance criteria from a ClickUp task for testability.
273
273
  ⚠️ **HARD VALIDATION** - Maps acceptance criteria items to UI elements and **FAILS on mismatches**.
274
274
 
275
275
  This tool performs strict validation of AC against actual UI implementation. If the AC specifies one value but the UI has a different value, this tool will:
276
-
277
276
  1. **FAIL** (`success: false`)
278
277
  2. **Report** the exact mismatch
279
278
  3. **Block** test generation until resolved
@@ -19449,12 +19449,224 @@ ${analysis.shouldCreateTest ? `1. Review the changed UI files above
19449
19449
  }
19450
19450
  };
19451
19451
  }
19452
+ function detectProjectBaseUrl(projectPath = process.cwd(), explicitUrl = null) {
19453
+ if (explicitUrl) {
19454
+ const portMatch = explicitUrl.match(/:(\d+)/);
19455
+ return {
19456
+ url: explicitUrl,
19457
+ source: "explicit",
19458
+ port: portMatch ? parseInt(portMatch[1], 10) : 443,
19459
+ https: explicitUrl.startsWith("https")
19460
+ };
19461
+ }
19462
+ let detectedPort = null;
19463
+ let detectedHttps = false;
19464
+ let source = "fallback";
19465
+ const configChecks = [
19466
+ // Vite config
19467
+ {
19468
+ files: ["vite.config.js", "vite.config.ts", "vite.config.mjs"],
19469
+ patterns: [
19470
+ /server\s*:\s*\{[^}]*port\s*:\s*(\d+)/s,
19471
+ /port\s*:\s*(\d+)/,
19472
+ /preview\s*:\s*\{[^}]*port\s*:\s*(\d+)/s
19473
+ ],
19474
+ httpsPatterns: [/https\s*:\s*true/, /server\s*:\s*\{[^}]*https\s*:/s],
19475
+ name: "vite.config"
19476
+ },
19477
+ // Next.js config
19478
+ {
19479
+ files: ["next.config.js", "next.config.mjs", "next.config.ts"],
19480
+ patterns: [/port\s*:\s*(\d+)/, /PORT\s*[=:]\s*(\d+)/],
19481
+ httpsPatterns: [],
19482
+ name: "next.config"
19483
+ },
19484
+ // Webpack config
19485
+ {
19486
+ files: ["webpack.config.js", "webpack.config.ts"],
19487
+ patterns: [
19488
+ /devServer\s*:\s*\{[^}]*port\s*:\s*(\d+)/s,
19489
+ /port\s*:\s*(\d+)/
19490
+ ],
19491
+ httpsPatterns: [/https\s*:\s*true/],
19492
+ name: "webpack.config"
19493
+ },
19494
+ // Angular config
19495
+ {
19496
+ files: ["angular.json"],
19497
+ patterns: [/"port"\s*:\s*(\d+)/, /"ssl"\s*:\s*true/],
19498
+ httpsPatterns: [/"ssl"\s*:\s*true/],
19499
+ name: "angular.json"
19500
+ }
19501
+ ];
19502
+ for (const config2 of configChecks) {
19503
+ for (const fileName of config2.files) {
19504
+ try {
19505
+ const configPath = join(projectPath, fileName);
19506
+ if (existsSync(configPath)) {
19507
+ const content = readFileSync(configPath, "utf-8");
19508
+ for (const pattern of config2.patterns) {
19509
+ const match = content.match(pattern);
19510
+ if (match && match[1]) {
19511
+ detectedPort = parseInt(match[1], 10);
19512
+ source = config2.name;
19513
+ break;
19514
+ }
19515
+ }
19516
+ for (const httpsPattern of config2.httpsPatterns) {
19517
+ if (httpsPattern.test(content)) {
19518
+ detectedHttps = true;
19519
+ break;
19520
+ }
19521
+ }
19522
+ if (detectedPort) break;
19523
+ }
19524
+ } catch {
19525
+ }
19526
+ }
19527
+ if (detectedPort) break;
19528
+ }
19529
+ if (!detectedPort) {
19530
+ try {
19531
+ const packageJsonPath = join(projectPath, "package.json");
19532
+ if (existsSync(packageJsonPath)) {
19533
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
19534
+ const scripts = packageJson.scripts || {};
19535
+ const scriptKeys = ["dev", "start", "serve", "develop"];
19536
+ for (const key of scriptKeys) {
19537
+ const script = scripts[key];
19538
+ if (script) {
19539
+ const portPatterns = [
19540
+ /--port[=\s]+(\d+)/,
19541
+ /-p[=\s]+(\d+)/,
19542
+ /PORT[=:]\s*(\d+)/,
19543
+ /localhost:(\d+)/,
19544
+ /127\.0\.0\.1:(\d+)/,
19545
+ /:(\d{4,5})\b/
19546
+ // Generic 4-5 digit port
19547
+ ];
19548
+ for (const pattern of portPatterns) {
19549
+ const match = script.match(pattern);
19550
+ if (match && match[1]) {
19551
+ detectedPort = parseInt(match[1], 10);
19552
+ source = `package.json scripts.${key}`;
19553
+ break;
19554
+ }
19555
+ }
19556
+ if (/--https|HTTPS=true|ssl/.test(script)) {
19557
+ detectedHttps = true;
19558
+ }
19559
+ if (detectedPort) break;
19560
+ }
19561
+ }
19562
+ }
19563
+ } catch {
19564
+ }
19565
+ }
19566
+ if (!detectedPort) {
19567
+ const envFiles = [".env", ".env.local", ".env.development", ".env.dev"];
19568
+ for (const envFile of envFiles) {
19569
+ try {
19570
+ const envPath = join(projectPath, envFile);
19571
+ if (existsSync(envPath)) {
19572
+ const content = readFileSync(envPath, "utf-8");
19573
+ const portPatterns = [
19574
+ /^PORT\s*=\s*(\d+)/m,
19575
+ /^VITE_PORT\s*=\s*(\d+)/m,
19576
+ /^DEV_PORT\s*=\s*(\d+)/m,
19577
+ /^SERVER_PORT\s*=\s*(\d+)/m
19578
+ ];
19579
+ for (const pattern of portPatterns) {
19580
+ const match = content.match(pattern);
19581
+ if (match && match[1]) {
19582
+ detectedPort = parseInt(match[1], 10);
19583
+ source = envFile;
19584
+ break;
19585
+ }
19586
+ }
19587
+ if (detectedPort) break;
19588
+ }
19589
+ } catch {
19590
+ }
19591
+ }
19592
+ }
19593
+ if (!detectedPort && process.env.MAESTRO_BASE_URL) {
19594
+ const envUrl = process.env.MAESTRO_BASE_URL;
19595
+ const portMatch = envUrl.match(/:(\d+)/);
19596
+ return {
19597
+ url: envUrl,
19598
+ source: "MAESTRO_BASE_URL env",
19599
+ port: portMatch ? parseInt(portMatch[1], 10) : 443,
19600
+ https: envUrl.startsWith("https")
19601
+ };
19602
+ }
19603
+ if (!detectedPort) {
19604
+ detectedPort = 3e3;
19605
+ source = "fallback (default)";
19606
+ }
19607
+ const protocol = detectedHttps ? "https" : "http";
19608
+ const url2 = `${protocol}://localhost:${detectedPort}`;
19609
+ return {
19610
+ url: url2,
19611
+ source,
19612
+ port: detectedPort,
19613
+ https: detectedHttps
19614
+ };
19615
+ }
19616
+ function enforceMandatoryTemplate(yaml, name = "", projectPath = process.cwd(), explicitBaseUrl = null) {
19617
+ if (!yaml || typeof yaml !== "string") {
19618
+ yaml = "";
19619
+ }
19620
+ const detected = detectProjectBaseUrl(projectPath, explicitBaseUrl);
19621
+ const detectedUrl = detected.url;
19622
+ const mandatoryHeader = `appId: web
19623
+ name: '${name}'
19624
+ url: \${BASE_URL}
19625
+
19626
+ env:
19627
+ BASE_URL: \${MAESTRO_BASE_URL || "${detectedUrl}"}
19628
+ `;
19629
+ const hasAppIdWeb = /^appId:\s*web/m.test(yaml);
19630
+ const hasName = /^name:\s*['"]/m.test(yaml);
19631
+ const hasUrl = /^url:\s*\$\{BASE_URL\}/m.test(yaml);
19632
+ const hasEnv = /^env:\s*$/m.test(yaml);
19633
+ const hasBaseUrl = /BASE_URL:\s*\$\{MAESTRO_BASE_URL/m.test(yaml);
19634
+ if (hasAppIdWeb && hasName && hasUrl && hasEnv && hasBaseUrl) {
19635
+ if (name && yaml.match(/^name:\s*['"]\s*['"]/m)) {
19636
+ yaml = yaml.replace(/^name:\s*['"]\s*['"]/m, `name: '${name}'`);
19637
+ }
19638
+ return yaml;
19639
+ }
19640
+ let testSteps = "";
19641
+ const stepsMatch = yaml.match(/^---\s*\n([\s\S]*)$/m);
19642
+ if (stepsMatch) {
19643
+ testSteps = stepsMatch[1];
19644
+ } else {
19645
+ const stepsStart = yaml.search(
19646
+ /\n(- launchApp|- tapOn|- assertVisible|- inputText|- extendedWaitUntil)/
19647
+ );
19648
+ if (stepsStart > 0) {
19649
+ testSteps = yaml.substring(stepsStart + 1);
19650
+ } else {
19651
+ if (yaml.trim().startsWith("-")) {
19652
+ testSteps = yaml.trim();
19653
+ } else {
19654
+ testSteps = yaml.trim();
19655
+ }
19656
+ }
19657
+ }
19658
+ const enforcedYaml = `${mandatoryHeader}---
19659
+ ${testSteps.trim()}
19660
+ `;
19661
+ return enforcedYaml;
19662
+ }
19452
19663
  function writeTestFile(options = {}) {
19453
19664
  const {
19454
19665
  yaml,
19455
19666
  fileName,
19456
19667
  directory = "maestro",
19457
19668
  basePath = process.cwd(),
19669
+ baseUrl = null,
19458
19670
  execute = true,
19459
19671
  deviceId = null,
19460
19672
  env = {},
@@ -19478,6 +19690,14 @@ function writeTestFile(options = {}) {
19478
19690
  message: "\u274C Missing required parameters: yaml and fileName are required"
19479
19691
  };
19480
19692
  }
19693
+ const testName = feature && action ? `${feature} - ${action}` : feature || action || "";
19694
+ const enforcedYaml = enforceMandatoryTemplate(
19695
+ yaml,
19696
+ testName,
19697
+ basePath,
19698
+ baseUrl
19699
+ );
19700
+ const detectedUrl = detectProjectBaseUrl(basePath, baseUrl);
19481
19701
  const normalizedFileName = fileName.endsWith(".yaml") ? fileName : `${fileName}.yaml`;
19482
19702
  const targetDir = join(basePath, directory);
19483
19703
  const filePath = join(targetDir, normalizedFileName);
@@ -19485,16 +19705,19 @@ function writeTestFile(options = {}) {
19485
19705
  if (!existsSync(targetDir)) {
19486
19706
  mkdirSync(targetDir, { recursive: true });
19487
19707
  }
19488
- writeFileSync(filePath, yaml, "utf-8");
19708
+ writeFileSync(filePath, enforcedYaml, "utf-8");
19489
19709
  const fd = openSync(filePath, "r");
19490
19710
  fsyncSync(fd);
19491
19711
  closeSync(fd);
19492
19712
  const result = {
19493
19713
  success: true,
19494
19714
  filePath,
19495
- message: `\u2705 Test file written successfully to: ${filePath}`,
19496
- generation
19715
+ message: `\u2705 Test file written successfully to: ${filePath}
19716
+ \u{1F4CD} Base URL: ${detectedUrl.url} (detected from: ${detectedUrl.source})`,
19717
+ generation,
19497
19718
  // Include test generation guidelines and instructions
19719
+ detectedUrl
19720
+ // Include URL detection info
19498
19721
  };
19499
19722
  if (execute) {
19500
19723
  const sleep = (ms) => {
@@ -20978,11 +21201,21 @@ Saves generated test YAML to a file and AUTOMATICALLY RUNS IT. This is the prima
20978
21201
  - The execution result is included in the response
20979
21202
  - This tool internally calls maestro_generate_test to provide guidelines and instructions
20980
21203
 
21204
+ ## URL Auto-Detection
21205
+ Base URL is automatically detected from project config files:
21206
+ 1. **Explicit baseUrl parameter** (highest priority)
21207
+ 2. **Config files**: vite.config.js, next.config.js, webpack.config.js, angular.json
21208
+ 3. **package.json scripts**: --port flags in dev/start scripts
21209
+ 4. **.env files**: PORT, VITE_PORT, DEV_PORT variables
21210
+ 5. **MAESTRO_BASE_URL env var**
21211
+ 6. **Fallback**: http://localhost:3000
21212
+
20981
21213
  ## Parameters
20982
21214
  - yaml: The YAML content to write (required)
20983
21215
  - fileName: Name of the file, e.g., "login_test.yaml" (required)
20984
21216
  - directory: Target directory (default: "maestro")
20985
21217
  - basePath: Project base path (default: current directory)
21218
+ - baseUrl: Explicit base URL override (optional, auto-detected if not provided)
20986
21219
  - execute: Whether to auto-execute after writing (default: true)
20987
21220
  - deviceId: Device ID for execution (optional)
20988
21221
  - env: Environment variables for execution (optional)
@@ -20995,8 +21228,9 @@ Saves generated test YAML to a file and AUTOMATICALLY RUNS IT. This is the prima
20995
21228
  ## Output
20996
21229
  - success: boolean
20997
21230
  - filePath: Path where file was written
20998
- - message: Status message
21231
+ - message: Status message with detected URL info
20999
21232
  - generation: Test generation guidelines and instructions (from maestro_generate_test)
21233
+ - detectedUrl: URL detection info (url, source, port, https)
21000
21234
  - execution: Test execution results (if execute=true)`,
21001
21235
  inputSchema: {
21002
21236
  type: "object",
@@ -21017,6 +21251,10 @@ Saves generated test YAML to a file and AUTOMATICALLY RUNS IT. This is the prima
21017
21251
  type: "string",
21018
21252
  description: "Project base path (default: current directory)"
21019
21253
  },
21254
+ baseUrl: {
21255
+ type: "string",
21256
+ description: "Explicit base URL override (optional). If not provided, auto-detected from project config (vite.config, next.config, package.json scripts, .env files)"
21257
+ },
21020
21258
  execute: {
21021
21259
  type: "boolean",
21022
21260
  description: "Whether to auto-execute the test after writing (default: true)"
@@ -21386,6 +21624,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
21386
21624
  fileName,
21387
21625
  directory,
21388
21626
  basePath,
21627
+ baseUrl,
21389
21628
  execute,
21390
21629
  deviceId,
21391
21630
  env,
@@ -21400,6 +21639,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
21400
21639
  fileName,
21401
21640
  directory: directory || "maestro",
21402
21641
  basePath: basePath || process.cwd(),
21642
+ baseUrl: baseUrl || null,
21643
+ // Auto-detected from project config if not provided
21403
21644
  execute: execute !== false,
21404
21645
  // defaults to true
21405
21646
  deviceId: deviceId || null,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deriv-com/fe-mcp-servers",
3
- "version": "0.0.13",
3
+ "version": "0.0.15",
4
4
  "description": "Collection of Front-End Model Context Protocol (MCP) servers for reusability and standardization",
5
5
  "type": "module",
6
6
  "bin": {