genat-mcp 2.1.2 → 2.2.2
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 +98 -2
- package/index.js +57 -11
- package/package.json +1 -1
- package/run-genat.mjs +12 -0
package/README.md
CHANGED
|
@@ -35,6 +35,9 @@ npm install /path/to/n8n_playwright_tests/mcp-genat
|
|
|
35
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
|
+
| **N8N_MAX_ASSERTIONS** | No | (none) | Override assertion count for complex pages (e.g. `25`). Used as default when the tool is invoked without `maxAssertions`; also used by run-genat.mjs. |
|
|
39
|
+
| **N8N_SCOPE_TO_REGIONS** | No | (none) | Comma-separated regions to focus tests on (e.g. `header,main,table`). Used as default when the tool is invoked without `scopeToRegions`; also used by run-genat.mjs. |
|
|
40
|
+
| **N8N_ANALYZE_STATES** | No | (off) | Set to `1`, `true`, or `yes` to analyze dropdown/combobox expanded states and include state-specific violations in generated tests. Used as default when the tool is invoked without `analyzeStates`; also used by run-genat.mjs. Increases analysis time. |
|
|
38
41
|
|
|
39
42
|
## Cursor MCP config
|
|
40
43
|
|
|
@@ -63,12 +66,51 @@ npm install /path/to/n8n_playwright_tests/mcp-genat
|
|
|
63
66
|
}
|
|
64
67
|
```
|
|
65
68
|
|
|
69
|
+
**With environment variables** (e.g. remote n8n, self-signed certs):
|
|
70
|
+
|
|
71
|
+
```json
|
|
72
|
+
{
|
|
73
|
+
"mcpServers": {
|
|
74
|
+
"GenAT": {
|
|
75
|
+
"command": "npx",
|
|
76
|
+
"args": ["genat-mcp"],
|
|
77
|
+
"env": {
|
|
78
|
+
"N8N_WEBHOOK_URL": "https://n8n.example.com/webhook-test/webhook-genat-login",
|
|
79
|
+
"N8N_INSECURE_TLS": "1",
|
|
80
|
+
"SERVICE_BASE_URL": "http://your-tunnel.ngrok.io"
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**With optional params for complex pages** (`N8N_MAX_ASSERTIONS`, `N8N_SCOPE_TO_REGIONS`, `N8N_ANALYZE_STATES`):
|
|
88
|
+
|
|
89
|
+
```json
|
|
90
|
+
{
|
|
91
|
+
"mcpServers": {
|
|
92
|
+
"GenAT": {
|
|
93
|
+
"command": "npx",
|
|
94
|
+
"args": ["genat-mcp"],
|
|
95
|
+
"env": {
|
|
96
|
+
"N8N_WEBHOOK_URL": "https://n8n.example.com/webhook-test/webhook-genat-login",
|
|
97
|
+
"N8N_MAX_ASSERTIONS": "30",
|
|
98
|
+
"N8N_SCOPE_TO_REGIONS": "header,main,table",
|
|
99
|
+
"N8N_ANALYZE_STATES": "1"
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
These env vars are passed to the webhook: `maxAssertions` (number), `scopeToRegions` (array), `analyzeStates` (boolean). Use `N8N_MAX_ASSERTIONS` for complex pages that need more assertions; `N8N_SCOPE_TO_REGIONS` to focus tests on specific regions; `N8N_ANALYZE_STATES` to run axe in expanded dropdown/combobox states (increases analysis time).
|
|
107
|
+
|
|
66
108
|
If the global binary is not on PATH, use the full path, e.g. `node $(npm root -g)/genat-mcp/index.js`.
|
|
67
109
|
|
|
68
110
|
## Requirements
|
|
69
111
|
|
|
70
112
|
- **Node.js** 20+
|
|
71
|
-
- **n8n** with
|
|
113
|
+
- **n8n** with a GenAT workflow imported and activated. Use [genat-accessibility-tests-with-login-jira.json](../workflows/genat-accessibility-tests-with-login-jira.json) (path `webhook-test/webhook-genat-login-jira`) for JIRA support, or [genat-accessibility-tests-with-login.json](../workflows/genat-accessibility-tests-with-login.json) (path `webhook-test/webhook-genat-login`). Set **N8N_WEBHOOK_URL** to the workflow's webhook URL.
|
|
72
114
|
- **DOM Analyzer** and **Accessibility Analyzer** services running (see main README: `npm run services` from repo root)
|
|
73
115
|
|
|
74
116
|
## Standalone script: run-genat.mjs
|
|
@@ -83,7 +125,16 @@ N8N_WEBHOOK_URL='https://your-n8n/webhook-test/webhook-genat-login' N8N_INSECURE
|
|
|
83
125
|
N8N_WEBHOOK_URL='https://your-n8n/webhook-test/webhook-genat-login' N8N_INSECURE_TLS=1 node run-genat.mjs https://example.com /path/to/project
|
|
84
126
|
```
|
|
85
127
|
|
|
86
|
-
**
|
|
128
|
+
**Optional env vars** (same as MCP tool parameters):
|
|
129
|
+
- `N8N_MAX_ASSERTIONS` – Override assertion count (e.g. `25`)
|
|
130
|
+
- `N8N_SCOPE_TO_REGIONS` – Comma-separated regions (e.g. `header,main,table`)
|
|
131
|
+
- `N8N_ANALYZE_STATES` – Set to `1` to analyze expanded dropdown/combobox states
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
N8N_MAX_ASSERTIONS=30 N8N_SCOPE_TO_REGIONS=header,main N8N_ANALYZE_STATES=1 node node_modules/genat-mcp/run-genat.mjs https://example.com .
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
**Important:** Use `webhook-genat-login` or `webhook-genat-login-jira` (not `webhook-genat`). For JIRA support, import and activate [genat-accessibility-tests-with-login-jira.json](../workflows/genat-accessibility-tests-with-login-jira.json) and set N8N_WEBHOOK_URL to its webhook URL.
|
|
87
138
|
|
|
88
139
|
**Optional – Cursor rule:** Copy the rule so the AI uses the correct webhook when creating workarounds (the MCP error responses also include this hint):
|
|
89
140
|
```bash
|
|
@@ -111,9 +162,54 @@ When creating run-genat.mjs: Use webhook-genat-login, NOT webhook-genat. Prefer
|
|
|
111
162
|
- **url** (string): Page URL to analyze for accessibility.
|
|
112
163
|
- **parentProjectFolder** (string): Path to the project root (TypeScript, JavaScript, or Python Playwright).
|
|
113
164
|
- **writeToProject** (boolean, optional): If `true`, write generated files under `tests/accessibility/` in the project folder.
|
|
165
|
+
- **maxAssertions** (number, optional): Override default assertion count for complex pages (e.g. `25`).
|
|
166
|
+
- **scopeToRegions** (string[], optional): Limit tests to specific regions (e.g. `["header", "main", "table"]`).
|
|
167
|
+
- **analyzeStates** (boolean, optional): If `true`, run axe in expanded dropdown/combobox states and include state-specific violations. Increases analysis time.
|
|
168
|
+
- **loginUsername** (string, optional): Login username. Overrides .env when provided. Requires loginPassword.
|
|
169
|
+
- **loginPassword** (string, optional): Login password. Overrides .env when provided. Requires loginUsername.
|
|
170
|
+
- **loginUrl** (string, optional): Login page URL if different from target URL.
|
|
171
|
+
- **jiraNumber** (string, optional): JIRA issue key (e.g. PROJ-123). When provided, the workflow fetches the issue and injects acceptance criteria into the AI prompt.
|
|
172
|
+
|
|
173
|
+
Login credentials: provide in the prompt (loginUsername, loginPassword) or in project `.env` (TEST_USER, TEST_PASSWORD, LOGIN_USER, LOGIN_PASSWORD). Tool params override .env. For sensitive environments, prefer .env (gitignored) since credentials in the prompt are visible in chat history.
|
|
114
174
|
|
|
115
175
|
Framework detection: language (TypeScript vs JavaScript vs Python), BDD (Cucumber, pytest-bdd, Behave), and Page Object pattern (e.g. `pages/` directory or `*Page.ts`, `*Page.js`, `*_page.py` files). Returns JSON with `testCode`, `featureFile`, `stepDefCode`, `pageObjectCode` (and `_written` paths when writing).
|
|
116
176
|
|
|
177
|
+
Generated tests include keyboard accessibility checks (Tab order, focus) by default. For complex pages, the workflow uses page complexity (regions, tables, widgets) to scale assertion count.
|
|
178
|
+
|
|
179
|
+
## Sample prompts
|
|
180
|
+
|
|
181
|
+
See [prompts.md](prompts.md) for a full list of prompts including specific parent folders, login, JIRA, and combined options.
|
|
182
|
+
|
|
183
|
+
**With login credentials in the prompt:**
|
|
184
|
+
|
|
185
|
+
```
|
|
186
|
+
Use GenAT to generate accessibility tests for https://myapp.example.com/dashboard. Use parentProjectFolder . and writeToProject true. Login with username myuser and password mypass.
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
```
|
|
190
|
+
Use GenAT with url https://myapp.example.com, loginUsername admin, loginPassword secret123, parentProjectFolder . Write the files to the project.
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
**With credentials in .env:**
|
|
194
|
+
|
|
195
|
+
```
|
|
196
|
+
Use GenAT for https://myapp.example.com/dashboard. Credentials are in .env.
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
```
|
|
200
|
+
Use GenAT to generate accessibility tests for https://myapp.example.com/dashboard using this project folder: . Write the files to the project.
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
**With JIRA acceptance criteria:**
|
|
204
|
+
|
|
205
|
+
```
|
|
206
|
+
Use GenAT for https://myapp.example.com/dashboard with jiraNumber PROJ-123. Write the files to the project.
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
```
|
|
210
|
+
Generate accessibility tests for https://example.com using JIRA ticket A11Y-456. Use parentProjectFolder . and writeToProject true.
|
|
211
|
+
```
|
|
212
|
+
|
|
117
213
|
## Publishing to npm
|
|
118
214
|
|
|
119
215
|
Until the package is published, `npm install genat-mcp` returns 404. To publish:
|
package/index.js
CHANGED
|
@@ -51,7 +51,7 @@ function insecureHttpsFetch(url, { method = 'GET', headers = {}, body }) {
|
|
|
51
51
|
const server = new McpServer(
|
|
52
52
|
{
|
|
53
53
|
name: 'GenAT',
|
|
54
|
-
version: '2.
|
|
54
|
+
version: '2.2.2',
|
|
55
55
|
},
|
|
56
56
|
{
|
|
57
57
|
capabilities: {
|
|
@@ -64,7 +64,7 @@ server.registerTool(
|
|
|
64
64
|
'GenAT',
|
|
65
65
|
{
|
|
66
66
|
description:
|
|
67
|
-
'Generate accessibility tests for a URL based on the project folder. Analyzes the page (DOM + a11y), detects project framework (TypeScript, JavaScript, or Python, BDD, Page Object), and returns generated test files (feature, spec/step defs, page objects). Optionally write files to the project folder. On failure:
|
|
67
|
+
'Generate accessibility tests for a URL based on the project folder. Analyzes the page (DOM + a11y), detects project framework (TypeScript, JavaScript, or Python, BDD, Page Object), and returns generated test files (feature, spec/step defs, page objects). Optionally write files to the project folder. On failure: run node node_modules/genat-mcp/run-genat.mjs (NOT run-genat.mjs in project root). Use webhook-genat-login in N8N_WEBHOOK_URL (never webhook-genat).',
|
|
68
68
|
inputSchema: {
|
|
69
69
|
url: z.string().describe('Page URL to analyze for accessibility (e.g. https://example.com)'),
|
|
70
70
|
parentProjectFolder: z
|
|
@@ -74,9 +74,37 @@ server.registerTool(
|
|
|
74
74
|
.boolean()
|
|
75
75
|
.optional()
|
|
76
76
|
.describe('If true, write generated files into the project folder (e.g. tests/accessibility/)'),
|
|
77
|
+
maxAssertions: z
|
|
78
|
+
.number()
|
|
79
|
+
.optional()
|
|
80
|
+
.describe('Override default assertion count (e.g. 25 for complex pages)'),
|
|
81
|
+
scopeToRegions: z
|
|
82
|
+
.array(z.string())
|
|
83
|
+
.optional()
|
|
84
|
+
.describe('Limit tests to specific regions (e.g. ["header", "main", "table"])'),
|
|
85
|
+
analyzeStates: z
|
|
86
|
+
.boolean()
|
|
87
|
+
.optional()
|
|
88
|
+
.describe('Enable interaction-state analysis (dropdowns, modals). Requires Phase 2.'),
|
|
89
|
+
loginUsername: z
|
|
90
|
+
.string()
|
|
91
|
+
.optional()
|
|
92
|
+
.describe('Login username (overrides .env when provided)'),
|
|
93
|
+
loginPassword: z
|
|
94
|
+
.string()
|
|
95
|
+
.optional()
|
|
96
|
+
.describe('Login password (overrides .env when provided)'),
|
|
97
|
+
loginUrl: z
|
|
98
|
+
.string()
|
|
99
|
+
.optional()
|
|
100
|
+
.describe('Login page URL if different from target URL'),
|
|
101
|
+
jiraNumber: z
|
|
102
|
+
.string()
|
|
103
|
+
.optional()
|
|
104
|
+
.describe('JIRA issue key (e.g. PROJ-123) for acceptance criteria'),
|
|
77
105
|
},
|
|
78
106
|
},
|
|
79
|
-
async ({ url, parentProjectFolder, writeToProject }) => {
|
|
107
|
+
async ({ url, parentProjectFolder, writeToProject, maxAssertions, scopeToRegions, analyzeStates, loginUsername, loginPassword, loginUrl, jiraNumber }) => {
|
|
80
108
|
if (!url || typeof url !== 'string') {
|
|
81
109
|
return {
|
|
82
110
|
content: [{ type: 'text', text: JSON.stringify({ error: 'Missing or invalid "url"' }) }],
|
|
@@ -107,6 +135,16 @@ server.registerTool(
|
|
|
107
135
|
try {
|
|
108
136
|
login = detectLogin(parentProjectFolder || '.') || {};
|
|
109
137
|
} catch (_) {}
|
|
138
|
+
|
|
139
|
+
// Tool params override .env
|
|
140
|
+
if (loginUsername != null && loginUsername !== '') login.username = loginUsername;
|
|
141
|
+
if (loginPassword != null && loginPassword !== '') login.password = loginPassword;
|
|
142
|
+
if (loginUrl != null && loginUrl !== '') login.loginUrl = loginUrl;
|
|
143
|
+
|
|
144
|
+
const maxAssertionsResolved = maxAssertions ?? (process.env.N8N_MAX_ASSERTIONS ? parseInt(process.env.N8N_MAX_ASSERTIONS, 10) : null);
|
|
145
|
+
const scopeToRegionsResolved = scopeToRegions?.length ? scopeToRegions : (process.env.N8N_SCOPE_TO_REGIONS ? process.env.N8N_SCOPE_TO_REGIONS.split(',').map((s) => s.trim()).filter(Boolean) : null);
|
|
146
|
+
const analyzeStatesResolved = analyzeStates || /^1|true|yes$/i.test(process.env.N8N_ANALYZE_STATES || '');
|
|
147
|
+
|
|
110
148
|
const body = {
|
|
111
149
|
url,
|
|
112
150
|
scriptType: framework.scriptType,
|
|
@@ -114,12 +152,18 @@ server.registerTool(
|
|
|
114
152
|
pageObject: framework.pageObject,
|
|
115
153
|
projectSummary: framework.projectSummary,
|
|
116
154
|
...(SERVICE_BASE_URL && { serviceBaseUrl: SERVICE_BASE_URL }),
|
|
117
|
-
...(
|
|
118
|
-
...(
|
|
119
|
-
...(
|
|
120
|
-
...(login.
|
|
121
|
-
|
|
122
|
-
|
|
155
|
+
...(maxAssertionsResolved != null && !isNaN(maxAssertionsResolved) && { maxAssertions: maxAssertionsResolved }),
|
|
156
|
+
...(scopeToRegionsResolved?.length && { scopeToRegions: scopeToRegionsResolved }),
|
|
157
|
+
...(analyzeStatesResolved && { analyzeStates: true }),
|
|
158
|
+
...(login.username && login.password && {
|
|
159
|
+
loginUsername: login.username,
|
|
160
|
+
loginPassword: login.password,
|
|
161
|
+
...(login.loginUrl && { loginUrl: login.loginUrl }),
|
|
162
|
+
...(login.usernameSelector && { loginUsernameSelector: login.usernameSelector }),
|
|
163
|
+
...(login.passwordSelector && { loginPasswordSelector: login.passwordSelector }),
|
|
164
|
+
...(login.submitSelector && { loginSubmitSelector: login.submitSelector }),
|
|
165
|
+
}),
|
|
166
|
+
...(jiraNumber && jiraNumber.trim() && { jiraNumber: jiraNumber.trim() }),
|
|
123
167
|
};
|
|
124
168
|
|
|
125
169
|
const useInsecureTls = N8N_INSECURE_TLS && N8N_WEBHOOK_URL.startsWith('https://');
|
|
@@ -134,6 +178,7 @@ server.registerTool(
|
|
|
134
178
|
? await insecureHttpsFetch(N8N_WEBHOOK_URL, fetchOpts)
|
|
135
179
|
: await fetch(N8N_WEBHOOK_URL, fetchOpts);
|
|
136
180
|
} catch (err) {
|
|
181
|
+
const loginUrl = N8N_WEBHOOK_URL.replace(/webhook-genat\/?$/, 'webhook-genat-login');
|
|
137
182
|
return {
|
|
138
183
|
content: [
|
|
139
184
|
{
|
|
@@ -142,7 +187,7 @@ server.registerTool(
|
|
|
142
187
|
error: 'Failed to call n8n webhook',
|
|
143
188
|
message: err instanceof Error ? err.message : String(err),
|
|
144
189
|
hint: 'Set N8N_WEBHOOK_URL to your n8n webhook URL. For self-signed/internal certs, set N8N_INSECURE_TLS=1',
|
|
145
|
-
workaround:
|
|
190
|
+
workaround: `Run: N8N_WEBHOOK_URL='${loginUrl}' N8N_INSECURE_TLS=1 node node_modules/genat-mcp/run-genat.mjs "<url>" . Use node_modules/genat-mcp/run-genat.mjs only (delete any run-genat.mjs in project root that uses webhook-genat).`,
|
|
146
191
|
}),
|
|
147
192
|
},
|
|
148
193
|
],
|
|
@@ -153,6 +198,7 @@ server.registerTool(
|
|
|
153
198
|
if (!response.ok) {
|
|
154
199
|
const text = await response.text();
|
|
155
200
|
const is404 = response.status === 404;
|
|
201
|
+
const loginUrl = N8N_WEBHOOK_URL.replace(/webhook-genat\/?$/, 'webhook-genat-login');
|
|
156
202
|
return {
|
|
157
203
|
content: [
|
|
158
204
|
{
|
|
@@ -161,7 +207,7 @@ server.registerTool(
|
|
|
161
207
|
error: `n8n returned ${response.status}`,
|
|
162
208
|
body: text.slice(0, 1000),
|
|
163
209
|
...(is404 && {
|
|
164
|
-
workaround:
|
|
210
|
+
workaround: `Run: N8N_WEBHOOK_URL='${loginUrl}' N8N_INSECURE_TLS=1 node node_modules/genat-mcp/run-genat.mjs "<url>" . Use node_modules/genat-mcp/run-genat.mjs only (delete any run-genat.mjs in project root that uses webhook-genat).`,
|
|
165
211
|
}),
|
|
166
212
|
}),
|
|
167
213
|
},
|
package/package.json
CHANGED
package/run-genat.mjs
CHANGED
|
@@ -10,6 +10,9 @@
|
|
|
10
10
|
* Env:
|
|
11
11
|
* N8N_WEBHOOK_URL - webhook URL (default: http://localhost:5678/webhook-test/webhook-genat-login)
|
|
12
12
|
* N8N_INSECURE_TLS - set to 1 for self-signed/internal certs
|
|
13
|
+
* N8N_MAX_ASSERTIONS - override assertion count (e.g. 25)
|
|
14
|
+
* N8N_SCOPE_TO_REGIONS - comma-separated regions (e.g. header,main,table)
|
|
15
|
+
* N8N_ANALYZE_STATES - set to 1 to analyze dropdown/combobox expanded states
|
|
13
16
|
*/
|
|
14
17
|
import https from 'node:https';
|
|
15
18
|
import { resolve } from 'node:path';
|
|
@@ -71,12 +74,21 @@ async function main() {
|
|
|
71
74
|
login = detectLogin(parentProjectFolder) || {};
|
|
72
75
|
} catch (_) {}
|
|
73
76
|
|
|
77
|
+
const maxAssertions = process.env.N8N_MAX_ASSERTIONS ? parseInt(process.env.N8N_MAX_ASSERTIONS, 10) : null;
|
|
78
|
+
const scopeToRegions = process.env.N8N_SCOPE_TO_REGIONS
|
|
79
|
+
? process.env.N8N_SCOPE_TO_REGIONS.split(',').map((s) => s.trim()).filter(Boolean)
|
|
80
|
+
: null;
|
|
81
|
+
const analyzeStates = /^1|true|yes$/i.test(process.env.N8N_ANALYZE_STATES || '');
|
|
82
|
+
|
|
74
83
|
const body = {
|
|
75
84
|
url,
|
|
76
85
|
scriptType: framework.scriptType,
|
|
77
86
|
bddFramework: framework.bddFramework,
|
|
78
87
|
pageObject: framework.pageObject,
|
|
79
88
|
projectSummary: framework.projectSummary,
|
|
89
|
+
...(maxAssertions != null && !isNaN(maxAssertions) && { maxAssertions }),
|
|
90
|
+
...(scopeToRegions?.length && { scopeToRegions }),
|
|
91
|
+
...(analyzeStates && { analyzeStates: true }),
|
|
80
92
|
...(login.username && { loginUsername: login.username }),
|
|
81
93
|
...(login.password && { loginPassword: login.password }),
|
|
82
94
|
...(login.loginUrl && { loginUrl: login.loginUrl }),
|