genat-mcp 1.2.2 → 1.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Detect login page object and credentials from project folder.
3
+ * Used by GenAT MCP before calling n8n (for login-enabled workflow).
4
+ */
5
+ import { readFileSync, readdirSync, existsSync } from 'fs';
6
+ import { join, resolve, relative } from 'path';
7
+
8
+ /**
9
+ * @param {string} parentProjectFolder - absolute or relative path to project root
10
+ * @returns {{ loginUrl?: string, usernameSelector?: string, passwordSelector?: string, submitSelector?: string, username?: string, password?: string }}
11
+ */
12
+ export function detectLogin(parentProjectFolder) {
13
+ const root = resolve(parentProjectFolder);
14
+ const result = {};
15
+
16
+ if (!existsSync(root)) return result;
17
+
18
+ // 1. Read credentials from .env, .env.test, .env.local
19
+ for (const envFile of ['.env', '.env.test', '.env.local']) {
20
+ const path = join(root, envFile);
21
+ if (existsSync(path)) {
22
+ try {
23
+ const content = readFileSync(path, 'utf8');
24
+ for (const line of content.split('\n')) {
25
+ const m = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.+?)\s*$/);
26
+ if (m) {
27
+ const key = m[1];
28
+ const val = m[2].replace(/^["']|["']$/g, '').trim();
29
+ if (key === 'TEST_USER' || key === 'LOGIN_USER' || key === 'LOGIN_USERNAME') result.username = val;
30
+ if (key === 'TEST_PASSWORD' || key === 'LOGIN_PASSWORD' || key === 'LOGIN_PASS') result.password = val;
31
+ }
32
+ }
33
+ if (result.username && result.password) break;
34
+ } catch (_) {}
35
+ }
36
+ }
37
+
38
+ // 2. Scan for LoginPage and parse selectors (optional overrides)
39
+ const loginFiles = findLoginFiles(root);
40
+ for (const file of loginFiles) {
41
+ try {
42
+ const content = readFileSync(join(root, file), 'utf8');
43
+ if (!result.usernameSelector) {
44
+ const m = content.match(/locator\s*\(\s*['"`](input\[[^'"`]+\])['"`]\)|getByPlaceholder\s*\(\s*['"`][^'"`]+['"`]\)/i);
45
+ if (m && m[1]) result.usernameSelector = m[1];
46
+ }
47
+ if (!result.passwordSelector && /input\[type\s*=\s*["']password["']\]/i.test(content)) {
48
+ result.passwordSelector = 'input[type="password"]';
49
+ }
50
+ if (!result.submitSelector && /submit|button\[type/i.test(content)) {
51
+ result.submitSelector = 'button[type="submit"], input[type="submit"]';
52
+ }
53
+ } catch (_) {}
54
+ }
55
+
56
+ return result;
57
+ }
58
+
59
+ function findLoginFiles(root, acc = []) {
60
+ try {
61
+ const entries = readdirSync(root, { withFileTypes: true });
62
+ for (const e of entries) {
63
+ const full = join(root, e.name);
64
+ if (e.isDirectory()) {
65
+ if (!['node_modules', '.git', '__pycache__', '.venv', 'venv'].includes(e.name)) {
66
+ findLoginFiles(full, acc);
67
+ }
68
+ } else if (
69
+ /LoginPage\.(ts|tsx|js|jsx)$/i.test(e.name) ||
70
+ /login_page\.py$/i.test(e.name) ||
71
+ /LoginPage\.py$/i.test(e.name)
72
+ ) {
73
+ acc.push(relative(root, full));
74
+ }
75
+ }
76
+ } catch (_) {}
77
+ return acc;
78
+ }
package/index.js CHANGED
@@ -9,6 +9,7 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
9
9
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
10
10
  import { z } from 'zod';
11
11
  import { detectFramework } from './detect-framework.js';
12
+ import { detectLogin } from './detect-login.js';
12
13
  import { writeGeneratedFiles } from './write-files.js';
13
14
 
14
15
  const N8N_WEBHOOK_URL = process.env.N8N_WEBHOOK_URL || 'http://localhost:5678/webhook-test/webhook-genat';
@@ -50,7 +51,7 @@ function insecureHttpsFetch(url, { method = 'GET', headers = {}, body }) {
50
51
  const server = new McpServer(
51
52
  {
52
53
  name: 'GenAT',
53
- version: '1.2.2',
54
+ version: '1.2.3',
54
55
  },
55
56
  {
56
57
  capabilities: {
@@ -102,6 +103,10 @@ server.registerTool(
102
103
  };
103
104
  }
104
105
 
106
+ let login = {};
107
+ try {
108
+ login = detectLogin(parentProjectFolder || '.') || {};
109
+ } catch (_) {}
105
110
  const body = {
106
111
  url,
107
112
  scriptType: framework.scriptType,
@@ -109,6 +114,12 @@ server.registerTool(
109
114
  pageObject: framework.pageObject,
110
115
  projectSummary: framework.projectSummary,
111
116
  ...(SERVICE_BASE_URL && { serviceBaseUrl: SERVICE_BASE_URL }),
117
+ ...(login.username && { loginUsername: login.username }),
118
+ ...(login.password && { loginPassword: login.password }),
119
+ ...(login.loginUrl && { loginUrl: login.loginUrl }),
120
+ ...(login.usernameSelector && { loginUsernameSelector: login.usernameSelector }),
121
+ ...(login.passwordSelector && { loginPasswordSelector: login.passwordSelector }),
122
+ ...(login.submitSelector && { loginSubmitSelector: login.submitSelector }),
112
123
  };
113
124
 
114
125
  const useInsecureTls = N8N_INSECURE_TLS && N8N_WEBHOOK_URL.startsWith('https://');
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "genat-mcp",
3
- "version": "1.2.2",
3
+ "version": "1.2.3",
4
4
  "mcpName": "io.github.asokans@oclc.org/genat",
5
5
  "description": "MCP server GenAT: generate accessibility tests via n8n workflow (url + project folder)",
6
6
  "type": "module",
7
7
  "main": "index.js",
8
8
  "bin": { "genat-mcp": "index.js" },
9
9
  "engines": { "node": ">=20" },
10
- "files": ["index.js", "detect-framework.js", "write-files.js"],
10
+ "files": ["index.js", "detect-framework.js", "detect-login.js", "write-files.js"],
11
11
  "keywords": ["mcp", "accessibility", "playwright", "n8n", "model-context-protocol", "a11y", "testing"],
12
12
  "repository": {
13
13
  "type": "git",