@ltorrey/forgeqa-mcp 1.0.1 → 1.1.0
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/LICENSE.md +13 -0
- package/README.md +81 -19
- package/dist/auth.d.ts +1 -3
- package/dist/auth.js +6 -6
- package/dist/auth.js.map +1 -1
- package/dist/core.d.ts +66 -0
- package/dist/core.js +164 -0
- package/dist/core.js.map +1 -0
- package/dist/index.js +22 -164
- package/dist/index.js.map +1 -1
- package/package.json +16 -12
- package/smithery.yaml +1 -1
package/LICENSE.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# ForgeQA AI Commercial License
|
|
2
|
+
|
|
3
|
+
Copyright 2026 Lajuane Torrey. All rights reserved.
|
|
4
|
+
|
|
5
|
+
ForgeQA AI is commercial software. A valid license purchased through the official
|
|
6
|
+
ForgeQA AI Gumroad product page is required to use the software. The license key
|
|
7
|
+
may not be shared, published, resold, sublicensed, or used to provide unauthorized
|
|
8
|
+
access to the software.
|
|
9
|
+
|
|
10
|
+
The software is provided as decision-support and testing infrastructure without
|
|
11
|
+
warranties of uninterrupted operation, defect-free output, or suitability for a
|
|
12
|
+
particular purpose. Users are responsible for reviewing generated tests and for
|
|
13
|
+
testing only applications and environments they are authorized to access.
|
package/README.md
CHANGED
|
@@ -1,29 +1,36 @@
|
|
|
1
|
-
# ForgeQA
|
|
1
|
+
# ForgeQA AI
|
|
2
2
|
|
|
3
|
-
ForgeQA
|
|
3
|
+
ForgeQA AI connects Claude Desktop or Cursor to a licensed, locally run
|
|
4
|
+
Playwright QA server. It opens supplied URLs in fresh headless Chromium sessions,
|
|
5
|
+
returns semantic accessibility snapshots, executes role-based actions and
|
|
6
|
+
assertions, and compiles reviewed flows into Playwright specifications.
|
|
4
7
|
|
|
5
8
|
## Requirements
|
|
6
9
|
|
|
7
10
|
- Node.js 20 or newer
|
|
8
|
-
- A ForgeQA AI license purchased from
|
|
11
|
+
- A ForgeQA AI license purchased from the
|
|
12
|
+
[official Gumroad page](https://lajuanetorrey.gumroad.com/l/forgeqa-ai)
|
|
9
13
|
- Chromium for Playwright
|
|
14
|
+
- Internet access on first installation, at startup for Gumroad license
|
|
15
|
+
verification, and whenever the target application requires network access
|
|
10
16
|
|
|
11
|
-
Install the browser runtime once:
|
|
17
|
+
Install the matching browser runtime once:
|
|
12
18
|
|
|
13
19
|
```bash
|
|
14
|
-
npx -y playwright install chromium
|
|
20
|
+
npx -y playwright@1.63.0 install chromium
|
|
15
21
|
```
|
|
16
22
|
|
|
17
23
|
## Claude Desktop setup
|
|
18
24
|
|
|
19
|
-
Add
|
|
25
|
+
Add this entry to `claude_desktop_config.json`, replacing the placeholder with
|
|
26
|
+
the license key from the Gumroad receipt:
|
|
20
27
|
|
|
21
28
|
```json
|
|
22
29
|
{
|
|
23
30
|
"mcpServers": {
|
|
24
31
|
"ForgeQA": {
|
|
25
32
|
"command": "npx",
|
|
26
|
-
"args": ["-y", "@ltorrey/forgeqa-mcp"],
|
|
33
|
+
"args": ["-y", "@ltorrey/forgeqa-mcp@1.1.0"],
|
|
27
34
|
"env": {
|
|
28
35
|
"GUMROAD_LICENSE_KEY": "PASTE_YOUR_GUMROAD_LICENSE_KEY_HERE"
|
|
29
36
|
}
|
|
@@ -32,28 +39,83 @@ Add the following entry to `claude_desktop_config.json`, replacing the placehold
|
|
|
32
39
|
}
|
|
33
40
|
```
|
|
34
41
|
|
|
35
|
-
|
|
42
|
+
Save the file, completely quit Claude Desktop, and reopen it.
|
|
43
|
+
|
|
44
|
+
## Cursor setup
|
|
45
|
+
|
|
46
|
+
Use either of Cursor's documented configuration locations:
|
|
47
|
+
|
|
48
|
+
- Global: `~/.cursor/mcp.json`
|
|
49
|
+
- Project-specific: `<project-root>/.cursor/mcp.json`
|
|
50
|
+
|
|
51
|
+
Add the same configuration:
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
{
|
|
55
|
+
"mcpServers": {
|
|
56
|
+
"ForgeQA": {
|
|
57
|
+
"command": "npx",
|
|
58
|
+
"args": ["-y", "@ltorrey/forgeqa-mcp@1.1.0"],
|
|
59
|
+
"env": {
|
|
60
|
+
"GUMROAD_LICENSE_KEY": "PASTE_YOUR_GUMROAD_LICENSE_KEY_HERE"
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Restart Cursor after saving the file. See the
|
|
68
|
+
[official Cursor MCP documentation](https://cursor.com/docs/mcp) for current
|
|
69
|
+
configuration options.
|
|
36
70
|
|
|
37
71
|
## Tools
|
|
38
72
|
|
|
39
|
-
- `scan_dom_accessibility`
|
|
40
|
-
|
|
41
|
-
- `
|
|
73
|
+
- `scan_dom_accessibility` opens a supplied URL in a fresh headless Chromium
|
|
74
|
+
session and returns its accessibility tree.
|
|
75
|
+
- `dry_run_qa_flow` executes role-based actions and outcome assertions, returning
|
|
76
|
+
a step-by-step pass/fail diagnostic.
|
|
77
|
+
- `compile_playwright_spec` writes reviewed actions and assertions to a reusable
|
|
78
|
+
Playwright specification in the local `tests/` directory.
|
|
42
79
|
|
|
43
|
-
|
|
80
|
+
Supported interactions are click, fill, check, select option, and key press.
|
|
81
|
+
Supported assertions cover visibility, hidden state, text, input value, checked
|
|
82
|
+
state, and URL outcomes.
|
|
44
83
|
|
|
45
|
-
|
|
84
|
+
Each scan or dry run starts in a fresh browser context. A complete login flow can
|
|
85
|
+
be tested within one dry run, but browser sessions are not shared between tool calls.
|
|
46
86
|
|
|
47
|
-
|
|
87
|
+
## Running generated specifications
|
|
88
|
+
|
|
89
|
+
In the project where the specification was written:
|
|
48
90
|
|
|
49
91
|
```bash
|
|
50
|
-
npm install --save-dev playwright
|
|
51
|
-
npx playwright install chromium
|
|
92
|
+
npm install --save-dev playwright@1.63.0
|
|
93
|
+
npx playwright@1.63.0 install chromium
|
|
52
94
|
npx playwright test tests/example.spec.ts
|
|
53
95
|
```
|
|
54
96
|
|
|
55
|
-
|
|
97
|
+
Generated specifications never overwrite an existing file.
|
|
98
|
+
|
|
99
|
+
## Privacy and safety
|
|
100
|
+
|
|
101
|
+
ForgeQA AI contains no product telemetry. At startup it sends the Gumroad product
|
|
102
|
+
ID and license key to Gumroad for validation. Test inputs and browser activity run
|
|
103
|
+
locally, while target websites receive ordinary browser requests. License keys are
|
|
104
|
+
never logged or persisted by ForgeQA AI.
|
|
105
|
+
|
|
106
|
+
`dry_run_qa_flow` performs real browser interactions. Use it only on applications
|
|
107
|
+
and environments you are authorized to test. Prefer staging systems and disposable
|
|
108
|
+
test data for flows that submit forms or change server-side state. Review generated
|
|
109
|
+
specifications before running them.
|
|
110
|
+
|
|
111
|
+
## Licensing, updates, support, and refunds
|
|
56
112
|
|
|
57
|
-
|
|
113
|
+
- Each purchase delivers a unique license key. Do not share or publish it.
|
|
114
|
+
- Examples pin a package version so updates cannot silently change behavior.
|
|
115
|
+
- Review release notes and update the version in the MCP configuration deliberately.
|
|
116
|
+
- For support, reply to the Gumroad receipt or use the creator contact on the
|
|
117
|
+
product page. Never send a full license key in a support message.
|
|
118
|
+
- Sales are final. Refunds are not offered except where required by applicable law
|
|
119
|
+
or Gumroad's mandatory policies.
|
|
58
120
|
|
|
59
|
-
|
|
121
|
+
Copyright 2026 Lajuane Torrey. All rights reserved.
|
package/dist/auth.d.ts
CHANGED
package/dist/auth.js
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import fetch from "node-fetch";
|
|
2
|
-
/**
|
|
3
|
-
* Verifies a Gumroad license without ever logging or persisting the key.
|
|
4
|
-
*/
|
|
2
|
+
/** Verifies a Gumroad license without logging or persisting the key. */
|
|
5
3
|
export async function verifyGumroadLicense(licenseKey, productId) {
|
|
6
4
|
const body = new URLSearchParams({
|
|
7
5
|
product_id: productId,
|
|
@@ -10,11 +8,13 @@ export async function verifyGumroadLicense(licenseKey, productId) {
|
|
|
10
8
|
});
|
|
11
9
|
const response = await fetch("https://api.gumroad.com/v2/licenses/verify", {
|
|
12
10
|
method: "POST",
|
|
13
|
-
headers: {
|
|
14
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
15
|
-
},
|
|
11
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
16
12
|
body,
|
|
13
|
+
signal: AbortSignal.timeout(15_000),
|
|
17
14
|
});
|
|
15
|
+
if (!response.ok) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
18
|
const data = (await response.json());
|
|
19
19
|
return (data.success === true &&
|
|
20
20
|
data.purchase !== undefined &&
|
package/dist/auth.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,YAAY,CAAC;AAW/B
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,YAAY,CAAC;AAW/B,wEAAwE;AACxE,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,UAAkB,EAClB,SAAiB;IAEjB,MAAM,IAAI,GAAG,IAAI,eAAe,CAAC;QAC/B,UAAU,EAAE,SAAS;QACrB,WAAW,EAAE,UAAU;QACvB,oBAAoB,EAAE,OAAO;KAC9B,CAAC,CAAC;IAEH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,4CAA4C,EAAE;QACzE,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,EAAE,cAAc,EAAE,mCAAmC,EAAE;QAChE,IAAI;QACJ,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC;KACpC,CAAC,CAAC;IAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAA2B,CAAC;IAC/D,OAAO,CACL,IAAI,CAAC,OAAO,KAAK,IAAI;QACrB,IAAI,CAAC,QAAQ,KAAK,SAAS;QAC3B,IAAI,CAAC,QAAQ,CAAC,QAAQ,KAAK,IAAI;QAC/B,IAAI,CAAC,QAAQ,CAAC,QAAQ,KAAK,IAAI;QAC/B,IAAI,CAAC,QAAQ,CAAC,YAAY,KAAK,IAAI,CACpC,CAAC;AACJ,CAAC"}
|
package/dist/core.d.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { Page } from "playwright";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
export declare const webUrlSchema: z.ZodString;
|
|
4
|
+
export declare const qaActionSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
5
|
+
role: z.ZodString;
|
|
6
|
+
name: z.ZodString;
|
|
7
|
+
type: z.ZodLiteral<"click">;
|
|
8
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
9
|
+
role: z.ZodString;
|
|
10
|
+
name: z.ZodString;
|
|
11
|
+
type: z.ZodLiteral<"fill">;
|
|
12
|
+
value: z.ZodString;
|
|
13
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
14
|
+
role: z.ZodString;
|
|
15
|
+
name: z.ZodString;
|
|
16
|
+
type: z.ZodLiteral<"check">;
|
|
17
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
18
|
+
role: z.ZodString;
|
|
19
|
+
name: z.ZodString;
|
|
20
|
+
type: z.ZodLiteral<"select_option">;
|
|
21
|
+
value: z.ZodString;
|
|
22
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
23
|
+
role: z.ZodString;
|
|
24
|
+
name: z.ZodString;
|
|
25
|
+
type: z.ZodLiteral<"press">;
|
|
26
|
+
key: z.ZodString;
|
|
27
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
28
|
+
role: z.ZodString;
|
|
29
|
+
name: z.ZodString;
|
|
30
|
+
type: z.ZodLiteral<"expect_visible">;
|
|
31
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
32
|
+
role: z.ZodString;
|
|
33
|
+
name: z.ZodString;
|
|
34
|
+
type: z.ZodLiteral<"expect_hidden">;
|
|
35
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
36
|
+
role: z.ZodString;
|
|
37
|
+
name: z.ZodString;
|
|
38
|
+
type: z.ZodLiteral<"expect_text">;
|
|
39
|
+
expected: z.ZodString;
|
|
40
|
+
match: z.ZodDefault<z.ZodEnum<{
|
|
41
|
+
contains: "contains";
|
|
42
|
+
equals: "equals";
|
|
43
|
+
}>>;
|
|
44
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
45
|
+
role: z.ZodString;
|
|
46
|
+
name: z.ZodString;
|
|
47
|
+
type: z.ZodLiteral<"expect_value">;
|
|
48
|
+
expected: z.ZodString;
|
|
49
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
50
|
+
role: z.ZodString;
|
|
51
|
+
name: z.ZodString;
|
|
52
|
+
type: z.ZodLiteral<"expect_checked">;
|
|
53
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
54
|
+
type: z.ZodLiteral<"expect_url">;
|
|
55
|
+
expected: z.ZodString;
|
|
56
|
+
match: z.ZodDefault<z.ZodEnum<{
|
|
57
|
+
contains: "contains";
|
|
58
|
+
equals: "equals";
|
|
59
|
+
}>>;
|
|
60
|
+
}, z.core.$strip>], "type">;
|
|
61
|
+
export type QaAction = z.infer<typeof qaActionSchema>;
|
|
62
|
+
export declare function executeAction(page: Page, action: QaAction): Promise<void>;
|
|
63
|
+
export declare function actionDescription(action: QaAction): string;
|
|
64
|
+
export declare function compileAction(action: QaAction): string;
|
|
65
|
+
export declare function safeSpecName(testName: string): string;
|
|
66
|
+
export declare function compileSpecSource(testName: string, targetUrl: string, actions: QaAction[]): string;
|
package/dist/core.js
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const webUrlSchema = z
|
|
3
|
+
.string()
|
|
4
|
+
.url()
|
|
5
|
+
.refine((value) => ["http:", "https:"].includes(new URL(value).protocol), {
|
|
6
|
+
message: "URL must use http or https",
|
|
7
|
+
});
|
|
8
|
+
const locatorFields = {
|
|
9
|
+
role: z.string().min(1).describe("ARIA role, such as button or textbox"),
|
|
10
|
+
name: z.string().min(1).describe("Accessible name of the target element"),
|
|
11
|
+
};
|
|
12
|
+
const matchMode = z.enum(["contains", "equals"]).default("contains");
|
|
13
|
+
export const qaActionSchema = z.discriminatedUnion("type", [
|
|
14
|
+
z.object({ type: z.literal("click"), ...locatorFields }),
|
|
15
|
+
z.object({ type: z.literal("fill"), ...locatorFields, value: z.string() }),
|
|
16
|
+
z.object({ type: z.literal("check"), ...locatorFields }),
|
|
17
|
+
z.object({
|
|
18
|
+
type: z.literal("select_option"),
|
|
19
|
+
...locatorFields,
|
|
20
|
+
value: z.string(),
|
|
21
|
+
}),
|
|
22
|
+
z.object({
|
|
23
|
+
type: z.literal("press"),
|
|
24
|
+
...locatorFields,
|
|
25
|
+
key: z.string().min(1),
|
|
26
|
+
}),
|
|
27
|
+
z.object({ type: z.literal("expect_visible"), ...locatorFields }),
|
|
28
|
+
z.object({ type: z.literal("expect_hidden"), ...locatorFields }),
|
|
29
|
+
z.object({
|
|
30
|
+
type: z.literal("expect_text"),
|
|
31
|
+
...locatorFields,
|
|
32
|
+
expected: z.string(),
|
|
33
|
+
match: matchMode,
|
|
34
|
+
}),
|
|
35
|
+
z.object({
|
|
36
|
+
type: z.literal("expect_value"),
|
|
37
|
+
...locatorFields,
|
|
38
|
+
expected: z.string(),
|
|
39
|
+
}),
|
|
40
|
+
z.object({ type: z.literal("expect_checked"), ...locatorFields }),
|
|
41
|
+
z.object({
|
|
42
|
+
type: z.literal("expect_url"),
|
|
43
|
+
expected: webUrlSchema,
|
|
44
|
+
match: matchMode,
|
|
45
|
+
}),
|
|
46
|
+
]);
|
|
47
|
+
function locatorFor(page, action) {
|
|
48
|
+
return page.getByRole(action.role, { name: action.name });
|
|
49
|
+
}
|
|
50
|
+
function assertEqual(actual, expected, label) {
|
|
51
|
+
if (actual !== expected) {
|
|
52
|
+
throw new Error(`${label} mismatch: expected ${JSON.stringify(expected)}, received ${JSON.stringify(actual)}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function assertContains(actual, expected, label) {
|
|
56
|
+
if (!actual.includes(expected)) {
|
|
57
|
+
throw new Error(`${label} mismatch: expected ${JSON.stringify(actual)} to contain ${JSON.stringify(expected)}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
export async function executeAction(page, action) {
|
|
61
|
+
if (action.type === "expect_url") {
|
|
62
|
+
const actual = page.url();
|
|
63
|
+
if (action.match === "equals")
|
|
64
|
+
assertEqual(actual, action.expected, "URL");
|
|
65
|
+
else
|
|
66
|
+
assertContains(actual, action.expected, "URL");
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const locator = locatorFor(page, action);
|
|
70
|
+
switch (action.type) {
|
|
71
|
+
case "click":
|
|
72
|
+
await locator.click();
|
|
73
|
+
return;
|
|
74
|
+
case "fill":
|
|
75
|
+
await locator.fill(action.value);
|
|
76
|
+
return;
|
|
77
|
+
case "check":
|
|
78
|
+
await locator.check();
|
|
79
|
+
return;
|
|
80
|
+
case "select_option":
|
|
81
|
+
await locator.selectOption(action.value);
|
|
82
|
+
return;
|
|
83
|
+
case "press":
|
|
84
|
+
await locator.press(action.key);
|
|
85
|
+
return;
|
|
86
|
+
case "expect_visible":
|
|
87
|
+
await locator.waitFor({ state: "visible" });
|
|
88
|
+
return;
|
|
89
|
+
case "expect_hidden":
|
|
90
|
+
await locator.waitFor({ state: "hidden" });
|
|
91
|
+
return;
|
|
92
|
+
case "expect_text": {
|
|
93
|
+
const actual = (await locator.textContent()) ?? "";
|
|
94
|
+
if (action.match === "equals")
|
|
95
|
+
assertEqual(actual, action.expected, "Text");
|
|
96
|
+
else
|
|
97
|
+
assertContains(actual, action.expected, "Text");
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
case "expect_value":
|
|
101
|
+
assertEqual(await locator.inputValue(), action.expected, "Value");
|
|
102
|
+
return;
|
|
103
|
+
case "expect_checked":
|
|
104
|
+
if (!(await locator.isChecked()))
|
|
105
|
+
throw new Error("Expected element to be checked");
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
export function actionDescription(action) {
|
|
110
|
+
if (action.type === "expect_url") {
|
|
111
|
+
return `${action.type} ${action.match} ${JSON.stringify(action.expected)}`;
|
|
112
|
+
}
|
|
113
|
+
return `${action.type} ${action.role} ${JSON.stringify(action.name)}`;
|
|
114
|
+
}
|
|
115
|
+
function compiledLocator(action) {
|
|
116
|
+
return `page.getByRole(${JSON.stringify(action.role)} as any, { name: ${JSON.stringify(action.name)} })`;
|
|
117
|
+
}
|
|
118
|
+
export function compileAction(action) {
|
|
119
|
+
if (action.type === "expect_url") {
|
|
120
|
+
return action.match === "equals"
|
|
121
|
+
? ` expect(page.url()).toBe(${JSON.stringify(action.expected)});`
|
|
122
|
+
: ` expect(page.url()).toContain(${JSON.stringify(action.expected)});`;
|
|
123
|
+
}
|
|
124
|
+
const locator = compiledLocator(action);
|
|
125
|
+
switch (action.type) {
|
|
126
|
+
case "click":
|
|
127
|
+
return ` await ${locator}.click();`;
|
|
128
|
+
case "fill":
|
|
129
|
+
return ` await ${locator}.fill(${JSON.stringify(action.value)});`;
|
|
130
|
+
case "check":
|
|
131
|
+
return ` await ${locator}.check();`;
|
|
132
|
+
case "select_option":
|
|
133
|
+
return ` await ${locator}.selectOption(${JSON.stringify(action.value)});`;
|
|
134
|
+
case "press":
|
|
135
|
+
return ` await ${locator}.press(${JSON.stringify(action.key)});`;
|
|
136
|
+
case "expect_visible":
|
|
137
|
+
return ` await expect(${locator}).toBeVisible();`;
|
|
138
|
+
case "expect_hidden":
|
|
139
|
+
return ` await expect(${locator}).toBeHidden();`;
|
|
140
|
+
case "expect_text":
|
|
141
|
+
return action.match === "equals"
|
|
142
|
+
? ` await expect(${locator}).toHaveText(${JSON.stringify(action.expected)});`
|
|
143
|
+
: ` await expect(${locator}).toContainText(${JSON.stringify(action.expected)});`;
|
|
144
|
+
case "expect_value":
|
|
145
|
+
return ` await expect(${locator}).toHaveValue(${JSON.stringify(action.expected)});`;
|
|
146
|
+
case "expect_checked":
|
|
147
|
+
return ` await expect(${locator}).toBeChecked();`;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
export function safeSpecName(testName) {
|
|
151
|
+
const slug = testName
|
|
152
|
+
.normalize("NFKD")
|
|
153
|
+
.replace(/[\u0300-\u036f]/g, "")
|
|
154
|
+
.toLowerCase()
|
|
155
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
156
|
+
.replace(/^-+|-+$/g, "")
|
|
157
|
+
.slice(0, 80);
|
|
158
|
+
return slug || "forgeqa-generated";
|
|
159
|
+
}
|
|
160
|
+
export function compileSpecSource(testName, targetUrl, actions) {
|
|
161
|
+
const actionLines = actions.map(compileAction).join("\n");
|
|
162
|
+
return `import { expect, test } from "playwright/test";\n\ntest(${JSON.stringify(testName)}, async ({ page }) => {\n await page.goto(${JSON.stringify(targetUrl)});\n${actionLines}\n});\n`;
|
|
163
|
+
}
|
|
164
|
+
//# sourceMappingURL=core.js.map
|
package/dist/core.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"core.js","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -6,124 +6,26 @@ import path from "node:path";
|
|
|
6
6
|
import { chromium } from "playwright";
|
|
7
7
|
import { z } from "zod";
|
|
8
8
|
import { verifyGumroadLicense } from "./auth.js";
|
|
9
|
+
import { actionDescription, compileSpecSource, executeAction, qaActionSchema, safeSpecName, webUrlSchema, } from "./core.js";
|
|
9
10
|
const GUMROAD_PRODUCT_ID = "ZFFt_s2vgLi2UF_E3t3o6A==";
|
|
10
11
|
const NAVIGATION_TIMEOUT_MS = 30_000;
|
|
11
|
-
const
|
|
12
|
-
.string()
|
|
13
|
-
.url()
|
|
14
|
-
.refine((value) => ["http:", "https:"].includes(new URL(value).protocol), {
|
|
15
|
-
message: "URL must use http or https",
|
|
16
|
-
});
|
|
17
|
-
const locatorFields = {
|
|
18
|
-
role: z.string().min(1).describe("ARIA role, such as button or textbox"),
|
|
19
|
-
name: z.string().min(1).describe("Accessible name of the target element"),
|
|
20
|
-
};
|
|
21
|
-
const qaActionSchema = z.discriminatedUnion("type", [
|
|
22
|
-
z.object({
|
|
23
|
-
type: z.literal("click"),
|
|
24
|
-
...locatorFields,
|
|
25
|
-
}),
|
|
26
|
-
z.object({
|
|
27
|
-
type: z.literal("fill"),
|
|
28
|
-
...locatorFields,
|
|
29
|
-
value: z.string(),
|
|
30
|
-
}),
|
|
31
|
-
z.object({
|
|
32
|
-
type: z.literal("check"),
|
|
33
|
-
...locatorFields,
|
|
34
|
-
}),
|
|
35
|
-
z.object({
|
|
36
|
-
type: z.literal("select_option"),
|
|
37
|
-
...locatorFields,
|
|
38
|
-
value: z.string(),
|
|
39
|
-
}),
|
|
40
|
-
z.object({
|
|
41
|
-
type: z.literal("press"),
|
|
42
|
-
...locatorFields,
|
|
43
|
-
key: z.string().min(1),
|
|
44
|
-
}),
|
|
45
|
-
]);
|
|
46
|
-
const server = new McpServer({
|
|
47
|
-
name: "ForgeQA-MCP",
|
|
48
|
-
version: "1.0.1",
|
|
49
|
-
});
|
|
12
|
+
const server = new McpServer({ name: "ForgeQA-MCP", version: "1.1.0" });
|
|
50
13
|
function errorMessage(error) {
|
|
51
14
|
return error instanceof Error ? error.message : String(error);
|
|
52
15
|
}
|
|
53
|
-
function locatorFor(page, action) {
|
|
54
|
-
return page.getByRole(action.role, { name: action.name });
|
|
55
|
-
}
|
|
56
|
-
async function executeAction(page, action) {
|
|
57
|
-
const locator = locatorFor(page, action);
|
|
58
|
-
switch (action.type) {
|
|
59
|
-
case "click":
|
|
60
|
-
await locator.click();
|
|
61
|
-
return;
|
|
62
|
-
case "fill":
|
|
63
|
-
await locator.fill(action.value);
|
|
64
|
-
return;
|
|
65
|
-
case "check":
|
|
66
|
-
await locator.check();
|
|
67
|
-
return;
|
|
68
|
-
case "select_option":
|
|
69
|
-
await locator.selectOption(action.value);
|
|
70
|
-
return;
|
|
71
|
-
case "press":
|
|
72
|
-
await locator.press(action.key);
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
16
|
async function captureAccessibilitySnapshot(page) {
|
|
77
|
-
// Playwright <=1.49 exposed page.accessibility.snapshot(). Use it when a
|
|
78
|
-
// compatible runtime provides it, then fall back to the current structured API.
|
|
79
17
|
const legacyPage = page;
|
|
80
18
|
if (legacyPage.accessibility?.snapshot) {
|
|
81
19
|
return legacyPage.accessibility.snapshot();
|
|
82
20
|
}
|
|
83
21
|
return page.ariaSnapshotJSON({ mode: "ai" });
|
|
84
22
|
}
|
|
85
|
-
function actionDescription(action) {
|
|
86
|
-
return `${action.type} ${action.role} \"${action.name}\"`;
|
|
87
|
-
}
|
|
88
|
-
function compileAction(action) {
|
|
89
|
-
const locator = `page.getByRole(${JSON.stringify(action.role)} as any, { name: ${JSON.stringify(action.name)} })`;
|
|
90
|
-
switch (action.type) {
|
|
91
|
-
case "click":
|
|
92
|
-
return ` await ${locator}.click();`;
|
|
93
|
-
case "fill":
|
|
94
|
-
return ` await ${locator}.fill(${JSON.stringify(action.value)});`;
|
|
95
|
-
case "check":
|
|
96
|
-
return ` await ${locator}.check();`;
|
|
97
|
-
case "select_option":
|
|
98
|
-
return ` await ${locator}.selectOption(${JSON.stringify(action.value)});`;
|
|
99
|
-
case "press":
|
|
100
|
-
return ` await ${locator}.press(${JSON.stringify(action.key)});`;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
function safeSpecName(testName) {
|
|
104
|
-
const slug = testName
|
|
105
|
-
.normalize("NFKD")
|
|
106
|
-
.replace(/[\u0300-\u036f]/g, "")
|
|
107
|
-
.toLowerCase()
|
|
108
|
-
.replace(/[^a-z0-9]+/g, "-")
|
|
109
|
-
.replace(/^-+|-+$/g, "")
|
|
110
|
-
.slice(0, 80);
|
|
111
|
-
return slug || "forgeqa-generated";
|
|
112
|
-
}
|
|
113
23
|
server.registerTool("scan_dom_accessibility", {
|
|
114
24
|
title: "Scan DOM accessibility",
|
|
115
|
-
description: "
|
|
116
|
-
inputSchema: {
|
|
117
|
-
|
|
118
|
-
},
|
|
119
|
-
outputSchema: {
|
|
120
|
-
url: z.string(),
|
|
121
|
-
snapshot: z.unknown(),
|
|
122
|
-
},
|
|
123
|
-
annotations: {
|
|
124
|
-
readOnlyHint: true,
|
|
125
|
-
openWorldHint: true,
|
|
126
|
-
},
|
|
25
|
+
description: "Open a supplied URL in a fresh headless Chromium session and return its accessibility tree as structured content.",
|
|
26
|
+
inputSchema: { url: webUrlSchema.describe("Page URL to scan") },
|
|
27
|
+
outputSchema: { url: z.string(), snapshot: z.unknown() },
|
|
28
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
127
29
|
}, async ({ url }) => {
|
|
128
30
|
const browser = await chromium.launch({ headless: true });
|
|
129
31
|
try {
|
|
@@ -134,24 +36,14 @@ server.registerTool("scan_dom_accessibility", {
|
|
|
134
36
|
});
|
|
135
37
|
const snapshot = await captureAccessibilitySnapshot(page);
|
|
136
38
|
return {
|
|
137
|
-
content: [
|
|
138
|
-
{
|
|
139
|
-
type: "text",
|
|
140
|
-
text: `Accessibility snapshot captured for ${url}.`,
|
|
141
|
-
},
|
|
142
|
-
],
|
|
39
|
+
content: [{ type: "text", text: `Accessibility snapshot captured for ${url}.` }],
|
|
143
40
|
structuredContent: { url, snapshot },
|
|
144
41
|
};
|
|
145
42
|
}
|
|
146
43
|
catch (error) {
|
|
147
44
|
return {
|
|
148
45
|
isError: true,
|
|
149
|
-
content: [
|
|
150
|
-
{
|
|
151
|
-
type: "text",
|
|
152
|
-
text: `Accessibility scan failed: ${errorMessage(error)}`,
|
|
153
|
-
},
|
|
154
|
-
],
|
|
46
|
+
content: [{ type: "text", text: `Accessibility scan failed: ${errorMessage(error)}` }],
|
|
155
47
|
};
|
|
156
48
|
}
|
|
157
49
|
finally {
|
|
@@ -160,7 +52,7 @@ server.registerTool("scan_dom_accessibility", {
|
|
|
160
52
|
});
|
|
161
53
|
server.registerTool("dry_run_qa_flow", {
|
|
162
54
|
title: "Dry-run QA flow",
|
|
163
|
-
description: "Run role-based browser actions and return a step-by-step pass/fail report.",
|
|
55
|
+
description: "Run role-based browser actions and assertions in a fresh headless Chromium session and return a step-by-step pass/fail report.",
|
|
164
56
|
inputSchema: {
|
|
165
57
|
url: webUrlSchema.describe("Starting page URL"),
|
|
166
58
|
actions: z.array(qaActionSchema).min(1),
|
|
@@ -175,10 +67,7 @@ server.registerTool("dry_run_qa_flow", {
|
|
|
175
67
|
message: z.string(),
|
|
176
68
|
})),
|
|
177
69
|
},
|
|
178
|
-
annotations: {
|
|
179
|
-
readOnlyHint: false,
|
|
180
|
-
openWorldHint: true,
|
|
181
|
-
},
|
|
70
|
+
annotations: { readOnlyHint: false, openWorldHint: true },
|
|
182
71
|
}, async ({ url, actions }) => {
|
|
183
72
|
const browser = await chromium.launch({ headless: true });
|
|
184
73
|
const logs = [];
|
|
@@ -220,7 +109,7 @@ server.registerTool("dry_run_qa_flow", {
|
|
|
220
109
|
action: actionDescription(action),
|
|
221
110
|
status: "passed",
|
|
222
111
|
duration_ms: Date.now() - started,
|
|
223
|
-
message: "Action completed",
|
|
112
|
+
message: "Action or assertion completed",
|
|
224
113
|
});
|
|
225
114
|
}
|
|
226
115
|
catch (error) {
|
|
@@ -232,35 +121,20 @@ server.registerTool("dry_run_qa_flow", {
|
|
|
232
121
|
message: errorMessage(error),
|
|
233
122
|
});
|
|
234
123
|
return {
|
|
235
|
-
content: [
|
|
236
|
-
{
|
|
237
|
-
type: "text",
|
|
238
|
-
text: `QA flow failed at step ${index + 1}.`,
|
|
239
|
-
},
|
|
240
|
-
],
|
|
124
|
+
content: [{ type: "text", text: `QA flow failed at step ${index + 1}.` }],
|
|
241
125
|
structuredContent: { passed: false, logs },
|
|
242
126
|
};
|
|
243
127
|
}
|
|
244
128
|
}
|
|
245
129
|
return {
|
|
246
|
-
content: [
|
|
247
|
-
{
|
|
248
|
-
type: "text",
|
|
249
|
-
text: `QA flow passed ${actions.length} action(s).`,
|
|
250
|
-
},
|
|
251
|
-
],
|
|
130
|
+
content: [{ type: "text", text: `QA flow passed ${actions.length} step(s).` }],
|
|
252
131
|
structuredContent: { passed: true, logs },
|
|
253
132
|
};
|
|
254
133
|
}
|
|
255
134
|
catch (error) {
|
|
256
135
|
return {
|
|
257
136
|
isError: true,
|
|
258
|
-
content: [
|
|
259
|
-
{
|
|
260
|
-
type: "text",
|
|
261
|
-
text: `QA flow could not run: ${errorMessage(error)}`,
|
|
262
|
-
},
|
|
263
|
-
],
|
|
137
|
+
content: [{ type: "text", text: `QA flow could not run: ${errorMessage(error)}` }],
|
|
264
138
|
};
|
|
265
139
|
}
|
|
266
140
|
finally {
|
|
@@ -269,7 +143,7 @@ server.registerTool("dry_run_qa_flow", {
|
|
|
269
143
|
});
|
|
270
144
|
server.registerTool("compile_playwright_spec", {
|
|
271
145
|
title: "Compile Playwright spec",
|
|
272
|
-
description: "Write validated role-based actions to a Playwright spec in the local tests directory.",
|
|
146
|
+
description: "Write validated role-based browser actions and assertions to a Playwright spec in the local tests directory.",
|
|
273
147
|
inputSchema: {
|
|
274
148
|
test_name: z.string().min(1),
|
|
275
149
|
target_url: webUrlSchema,
|
|
@@ -283,27 +157,18 @@ server.registerTool("compile_playwright_spec", {
|
|
|
283
157
|
annotations: {
|
|
284
158
|
readOnlyHint: false,
|
|
285
159
|
destructiveHint: false,
|
|
286
|
-
idempotentHint:
|
|
160
|
+
idempotentHint: false,
|
|
287
161
|
openWorldHint: false,
|
|
288
162
|
},
|
|
289
163
|
}, async ({ test_name, target_url, validated_actions }) => {
|
|
290
164
|
try {
|
|
291
165
|
const testsDirectory = path.resolve(process.cwd(), "tests");
|
|
292
166
|
const filePath = path.join(testsDirectory, `${safeSpecName(test_name)}.spec.ts`);
|
|
293
|
-
const
|
|
294
|
-
const source = `import { test } from "playwright/test";\n\ntest(${JSON.stringify(test_name)}, async ({ page }) => {\n await page.goto(${JSON.stringify(target_url)});\n${actionLines}\n});\n`;
|
|
167
|
+
const source = compileSpecSource(test_name, target_url, validated_actions);
|
|
295
168
|
await mkdir(testsDirectory, { recursive: true });
|
|
296
|
-
await writeFile(filePath, source, {
|
|
297
|
-
encoding: "utf8",
|
|
298
|
-
flag: "wx",
|
|
299
|
-
});
|
|
169
|
+
await writeFile(filePath, source, { encoding: "utf8", flag: "wx" });
|
|
300
170
|
return {
|
|
301
|
-
content: [
|
|
302
|
-
{
|
|
303
|
-
type: "text",
|
|
304
|
-
text: `Created Playwright spec at ${filePath}.`,
|
|
305
|
-
},
|
|
306
|
-
],
|
|
171
|
+
content: [{ type: "text", text: `Created Playwright spec at ${filePath}.` }],
|
|
307
172
|
structuredContent: {
|
|
308
173
|
success: true,
|
|
309
174
|
file_path: filePath,
|
|
@@ -314,24 +179,17 @@ server.registerTool("compile_playwright_spec", {
|
|
|
314
179
|
catch (error) {
|
|
315
180
|
return {
|
|
316
181
|
isError: true,
|
|
317
|
-
content: [
|
|
318
|
-
{
|
|
319
|
-
type: "text",
|
|
320
|
-
text: `Could not create Playwright spec: ${errorMessage(error)}`,
|
|
321
|
-
},
|
|
322
|
-
],
|
|
182
|
+
content: [{ type: "text", text: `Could not create Playwright spec: ${errorMessage(error)}` }],
|
|
323
183
|
};
|
|
324
184
|
}
|
|
325
185
|
});
|
|
326
186
|
async function main() {
|
|
327
187
|
const licenseKey = process.env.GUMROAD_LICENSE_KEY;
|
|
328
|
-
if (!licenseKey)
|
|
188
|
+
if (!licenseKey)
|
|
329
189
|
throw new Error("GUMROAD_LICENSE_KEY is required");
|
|
330
|
-
}
|
|
331
190
|
const licensed = await verifyGumroadLicense(licenseKey, GUMROAD_PRODUCT_ID);
|
|
332
|
-
if (!licensed)
|
|
191
|
+
if (!licensed)
|
|
333
192
|
throw new Error("Gumroad license verification failed");
|
|
334
|
-
}
|
|
335
193
|
const transport = new StdioServerTransport();
|
|
336
194
|
await server.connect(transport);
|
|
337
195
|
console.error("ForgeQA-MCP is running on stdio");
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ltorrey/forgeqa-mcp",
|
|
3
|
-
"version": "1.0
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Licensed local MCP server for semantic Playwright E2E testing",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"bin": {
|
|
@@ -10,10 +10,14 @@
|
|
|
10
10
|
"files": [
|
|
11
11
|
"dist",
|
|
12
12
|
"smithery.yaml",
|
|
13
|
-
"README.md"
|
|
13
|
+
"README.md",
|
|
14
|
+
"LICENSE.md"
|
|
14
15
|
],
|
|
15
16
|
"scripts": {
|
|
16
|
-
"build": "tsc"
|
|
17
|
+
"build": "tsc",
|
|
18
|
+
"typecheck": "tsc --noEmit",
|
|
19
|
+
"test": "npm run build && node --test test/*.test.mjs",
|
|
20
|
+
"prepack": "npm test"
|
|
17
21
|
},
|
|
18
22
|
"engines": {
|
|
19
23
|
"node": ">=20"
|
|
@@ -23,21 +27,21 @@
|
|
|
23
27
|
"playwright",
|
|
24
28
|
"qa",
|
|
25
29
|
"e2e-testing",
|
|
30
|
+
"accessibility-tree",
|
|
26
31
|
"claude",
|
|
27
32
|
"cursor"
|
|
28
33
|
],
|
|
29
34
|
"author": "Lajuane Torrey",
|
|
30
35
|
"homepage": "https://lajuanetorrey.gumroad.com/l/forgeqa-ai",
|
|
31
|
-
"license": "
|
|
36
|
+
"license": "SEE LICENSE IN LICENSE.md",
|
|
32
37
|
"dependencies": {
|
|
33
|
-
"@modelcontextprotocol/sdk": "
|
|
34
|
-
"node-fetch": "
|
|
35
|
-
"playwright": "
|
|
36
|
-
"zod": "
|
|
38
|
+
"@modelcontextprotocol/sdk": "1.30.0",
|
|
39
|
+
"node-fetch": "3.3.2",
|
|
40
|
+
"playwright": "1.63.0",
|
|
41
|
+
"zod": "4.6.4"
|
|
37
42
|
},
|
|
38
43
|
"devDependencies": {
|
|
39
|
-
"@types/node": "
|
|
40
|
-
"
|
|
41
|
-
"typescript": "^7.0.2"
|
|
44
|
+
"@types/node": "22.20.2",
|
|
45
|
+
"typescript": "7.0.2"
|
|
42
46
|
}
|
|
43
47
|
}
|