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 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 the [GenAT workflow](../workflows/genat-accessibility-tests-with-login.json) imported and activated (webhook path `webhook-test/webhook-genat-login`)
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
- **Important:** Use `webhook-genat-login` (not `webhook-genat`). Import and activate [genat-accessibility-tests-with-login.json](../workflows/genat-accessibility-tests-with-login.json) in n8n.
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.1.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: use node node_modules/genat-mcp/run-genat.mjs (webhook-genat-login). Do not create run-genat.mjs with webhook-genat.',
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
- ...(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 }),
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: 'Run: N8N_WEBHOOK_URL=\'https://your-n8n/webhook-test/webhook-genat-login\' N8N_INSECURE_TLS=1 node node_modules/genat-mcp/run-genat.mjs "<url>" . Use webhook-genat-login (not webhook-genat). Do not create run-genat.mjs with webhook-genat.',
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: 'Use webhook-genat-login (not webhook-genat). Run: N8N_WEBHOOK_URL=\'https://your-n8n/webhook-test/webhook-genat-login\' N8N_INSECURE_TLS=1 node node_modules/genat-mcp/run-genat.mjs "<url>" . Do not create run-genat.mjs with webhook-genat.',
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genat-mcp",
3
- "version": "2.1.2",
3
+ "version": "2.2.2",
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",
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 }),