genat-mcp 1.2.2 → 1.2.4
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/README.md +2 -2
- package/detect-login.js +78 -0
- package/index.js +13 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -32,7 +32,7 @@ npm install /path/to/n8n_playwright_tests/mcp-genat
|
|
|
32
32
|
|
|
33
33
|
| Variable | Required | Default | Description |
|
|
34
34
|
|--------------------|----------|---------|-------------|
|
|
35
|
-
| **N8N_WEBHOOK_URL** | No | `http://localhost:5678/webhook-test/webhook-genat` | n8n GenAT webhook URL. Set this if your n8n instance is elsewhere (e.g. `https://your-n8n-host/webhook-test/webhook-genat`). |
|
|
35
|
+
| **N8N_WEBHOOK_URL** | No | `http://localhost:5678/webhook-test/webhook-genat-login` | n8n GenAT webhook URL. Set this if your n8n instance is elsewhere (e.g. `https://your-n8n-host/webhook-test/webhook-genat-login`). |
|
|
36
36
|
| **N8N_INSECURE_TLS** | No | (off) | Set to `1`, `true`, or `yes` to skip TLS certificate verification for HTTPS webhooks. Use only for dev/internal n8n with self-signed or internal CA certs (e.g. K8s). Not recommended for production. |
|
|
37
37
|
| **SERVICE_BASE_URL** | No | (none) | Base URL for DOM Analyzer and Accessibility Analyzer (e.g. `http://host.docker.internal` or `http://your-tunnel.ngrok.io`). Required when n8n runs on a remote host (K8s) so the workflow can reach the services. The workflow appends `:3456/analyze-dom` and `:3458/analyze-accessibility`. |
|
|
38
38
|
|
|
@@ -68,7 +68,7 @@ If the global binary is not on PATH, use the full path, e.g. `node $(npm root -g
|
|
|
68
68
|
## Requirements
|
|
69
69
|
|
|
70
70
|
- **Node.js** 20+
|
|
71
|
-
- **n8n** with the [GenAT workflow](../workflows/genat-accessibility-tests.json) imported and activated (webhook path `webhook-test/webhook-genat`)
|
|
71
|
+
- **n8n** with the [GenAT workflow](../workflows/genat-accessibility-tests-with-login.json) imported and activated (webhook path `webhook-test/webhook-genat-login`)
|
|
72
72
|
- **DOM Analyzer** and **Accessibility Analyzer** services running (see main README: `npm run services` from repo root)
|
|
73
73
|
|
|
74
74
|
## Tool: GenAT
|
package/detect-login.js
ADDED
|
@@ -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,9 +9,10 @@ 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
|
-
const N8N_WEBHOOK_URL = process.env.N8N_WEBHOOK_URL || 'http://localhost:5678/webhook-test/webhook-genat';
|
|
15
|
+
const N8N_WEBHOOK_URL = process.env.N8N_WEBHOOK_URL || 'http://localhost:5678/webhook-test/webhook-genat-login';
|
|
15
16
|
const SERVICE_BASE_URL = process.env.SERVICE_BASE_URL || null;
|
|
16
17
|
const N8N_INSECURE_TLS = /^1|true|yes$/i.test(process.env.N8N_INSECURE_TLS || '');
|
|
17
18
|
|
|
@@ -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.
|
|
54
|
+
version: '1.2.4',
|
|
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.
|
|
3
|
+
"version": "1.2.4",
|
|
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",
|