@askqa/mcp 1.0.8 → 1.1.1
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/.claude-plugin/plugin.json +14 -2
- package/README.md +16 -0
- package/package.json +1 -1
- package/server.js +81 -7
- package/skills/setup-mcp/SKILL.md +127 -0
- package/skills/setup-slack/SKILL.md +5 -0
- package/skills/shopify-add-to-cart/SKILL.md +5 -0
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "askqa",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "AskQA skills — set up notifications and monitoring for your websites"
|
|
3
|
+
"version": "1.1.1",
|
|
4
|
+
"description": "AskQA skills — set up notifications and monitoring for your websites",
|
|
5
|
+
"mcpServers": {
|
|
6
|
+
"askqa": {
|
|
7
|
+
"command": "node",
|
|
8
|
+
"args": [
|
|
9
|
+
"${CLAUDE_PLUGIN_ROOT}/server.js"
|
|
10
|
+
],
|
|
11
|
+
"env": {
|
|
12
|
+
"AUTOQA_API_URL": "",
|
|
13
|
+
"AUTOQA_API_KEY": ""
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
5
17
|
}
|
package/README.md
CHANGED
|
@@ -73,3 +73,19 @@ The AI will use `screenshot_url` to inspect the page, write custom Playwright co
|
|
|
73
73
|
> "Why is my checkout test failing?"
|
|
74
74
|
|
|
75
75
|
The AI will call `get_test_results` and `get_test_screenshots` to analyze the failure.
|
|
76
|
+
|
|
77
|
+
## Privacy Policy
|
|
78
|
+
|
|
79
|
+
This extension only makes HTTPS requests to the AskQA API. It does not access files on your computer or collect analytics.
|
|
80
|
+
|
|
81
|
+
For full details, see the [Privacy Policy](https://askqa.ai/privacy).
|
|
82
|
+
|
|
83
|
+
## Support
|
|
84
|
+
|
|
85
|
+
- **Website**: [askqa.ai](https://askqa.ai)
|
|
86
|
+
- **Issues**: [GitHub Issues](https://github.com/askqa-ai/askqa-plugins/issues)
|
|
87
|
+
- **Email**: support@askqa.ai
|
|
88
|
+
|
|
89
|
+
## License
|
|
90
|
+
|
|
91
|
+
MIT
|
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -97,10 +97,11 @@ async function fetchScreenshot(url) {
|
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
function buildTestRunText(testRun) {
|
|
100
|
+
function buildTestRunText(testRun, testName) {
|
|
101
101
|
const icon = testRun.status === "completed" ? "✓" : testRun.status === "failed" ? "✗" : "…";
|
|
102
|
+
const testLabel = testName ? `${testName} (#${testRun.test_id})` : `${testRun.test_id}`;
|
|
102
103
|
const lines = [
|
|
103
|
-
`${icon} Run #${testRun.id} |
|
|
104
|
+
`${icon} Run #${testRun.id} | ${testLabel} | ${testRun.trigger_type} | ${testRun.status}`,
|
|
104
105
|
];
|
|
105
106
|
|
|
106
107
|
if (testRun.result?.durationMs) {
|
|
@@ -179,6 +180,7 @@ server.registerTool(
|
|
|
179
180
|
"list_templates",
|
|
180
181
|
{
|
|
181
182
|
description: "List available test templates. Returns template IDs, names, descriptions, and steps.",
|
|
183
|
+
readOnlyHint: true,
|
|
182
184
|
},
|
|
183
185
|
async () => {
|
|
184
186
|
try {
|
|
@@ -194,20 +196,27 @@ server.registerTool(
|
|
|
194
196
|
"create_test",
|
|
195
197
|
{
|
|
196
198
|
description: "Create a saved test. Use template_id for built-in templates, or code for custom Playwright tests. Provide one or the other, not both.",
|
|
199
|
+
destructiveHint: true,
|
|
197
200
|
inputSchema: {
|
|
198
201
|
name: z.string().describe("A name for this test (e.g. 'Homepage health check')"),
|
|
199
202
|
url: z.string().describe("The target URL to test (e.g. 'https://example.com')"),
|
|
200
203
|
template_id: z.string().optional().describe("Template ID from list_templates (e.g. 'quick-checks'). Omit if using code."),
|
|
201
204
|
params: z.record(z.string()).optional().describe("Optional template parameters"),
|
|
202
|
-
code: z.string().optional().describe("Custom Playwright test code. Must define an async function test({ page, step, log }). Omit if using template_id."),
|
|
205
|
+
code: z.string().optional().describe("Custom Playwright test code. Must define an async function test({ page, step, log, secrets }). Omit if using template_id."),
|
|
206
|
+
secrets: z.record(z.string()).optional().describe("Optional key-value secrets (e.g. { email: '...', password: '...' } or { api_key: '...' }). Encrypted at rest, never returned in API responses."),
|
|
207
|
+
headers: z.record(z.string()).optional().describe("Optional HTTP headers injected into requests to the target domain (e.g. { 'X-Test-Secret': 'abc' })"),
|
|
208
|
+
enable_test_mode: z.boolean().optional().describe("Send X-AskQA-Secret header to the target site, enabling test mode on sites that support it (default: true)"),
|
|
203
209
|
},
|
|
204
210
|
},
|
|
205
|
-
async ({ name, url, template_id, params, code }) => {
|
|
211
|
+
async ({ name, url, template_id, params, code, secrets, headers, enable_test_mode }) => {
|
|
206
212
|
try {
|
|
207
213
|
const body = { name, url };
|
|
208
214
|
if (template_id) body.template_id = template_id;
|
|
209
215
|
if (params) body.params = params;
|
|
210
216
|
if (code) body.code = code;
|
|
217
|
+
if (secrets) body.secrets = secrets;
|
|
218
|
+
if (headers) body.headers = headers;
|
|
219
|
+
if (enable_test_mode !== undefined) body.enable_test_mode = enable_test_mode;
|
|
211
220
|
const test = await apiPost("/api/tests/create", body);
|
|
212
221
|
return { content: [{ type: "text", text: JSON.stringify(test, null, 2) }] };
|
|
213
222
|
} catch (err) {
|
|
@@ -220,6 +229,7 @@ server.registerTool(
|
|
|
220
229
|
"screenshot_url",
|
|
221
230
|
{
|
|
222
231
|
description: "Take a screenshot of a URL and extract page structure (links, buttons, inputs, headings with selectors). Use this BEFORE writing custom test code to see the page layout and discover available selectors.",
|
|
232
|
+
readOnlyHint: true,
|
|
223
233
|
inputSchema: {
|
|
224
234
|
url: z.string().describe("The URL to screenshot (e.g. 'https://example.com')"),
|
|
225
235
|
},
|
|
@@ -280,6 +290,7 @@ server.registerTool(
|
|
|
280
290
|
"validate_test",
|
|
281
291
|
{
|
|
282
292
|
description: "Dry-run custom Playwright test code against a URL. Returns execution results, screenshots, and page structure for debugging. Steps continue even on failure to maximize debug signal. Use this to iterate on code before calling create_test.",
|
|
293
|
+
readOnlyHint: true,
|
|
283
294
|
inputSchema: {
|
|
284
295
|
code: z.string().describe("Custom Playwright test code. Must define an async function test({ page, step, log })."),
|
|
285
296
|
url: z.string().describe("The target URL to test against (e.g. 'https://example.com')"),
|
|
@@ -349,6 +360,7 @@ server.registerTool(
|
|
|
349
360
|
"list_tests",
|
|
350
361
|
{
|
|
351
362
|
description: "List all saved tests for the current organization. Returns id, name, url, template_id, status, and last_run summary (id, status, completed_at). This is the best starting point when checking if a feature or site is working — find the relevant test, then use get_test_results to see details.",
|
|
363
|
+
readOnlyHint: true,
|
|
352
364
|
},
|
|
353
365
|
async () => {
|
|
354
366
|
try {
|
|
@@ -364,6 +376,7 @@ server.registerTool(
|
|
|
364
376
|
"get_test",
|
|
365
377
|
{
|
|
366
378
|
description: "Get full details of a test by ID, including code for custom tests. Use list_tests to find the test ID first.",
|
|
379
|
+
readOnlyHint: true,
|
|
367
380
|
inputSchema: {
|
|
368
381
|
test_id: z.coerce.number().describe("The test ID (from list_tests or create_test)"),
|
|
369
382
|
},
|
|
@@ -382,6 +395,7 @@ server.registerTool(
|
|
|
382
395
|
"update_test",
|
|
383
396
|
{
|
|
384
397
|
description: "Update an existing test's name, URL, code, or other properties. Only provided fields are changed.",
|
|
398
|
+
destructiveHint: true,
|
|
385
399
|
inputSchema: {
|
|
386
400
|
test_id: z.coerce.number().describe("The test ID to update (from list_tests or get_test)"),
|
|
387
401
|
name: z.string().optional().describe("New test name"),
|
|
@@ -389,9 +403,12 @@ server.registerTool(
|
|
|
389
403
|
code: z.string().optional().describe("Updated custom Playwright test code"),
|
|
390
404
|
template_id: z.string().optional().describe("Updated template ID"),
|
|
391
405
|
params: z.record(z.string()).optional().describe("Updated template parameters"),
|
|
406
|
+
secrets: z.record(z.string()).nullable().optional().describe("Updated secrets (pass null to clear)"),
|
|
407
|
+
headers: z.record(z.string()).nullable().optional().describe("Updated HTTP headers (pass null to clear)"),
|
|
408
|
+
enable_test_mode: z.boolean().optional().describe("Send X-AskQA-Secret header to the target site, enabling test mode on sites that support it (default: true)"),
|
|
392
409
|
},
|
|
393
410
|
},
|
|
394
|
-
async ({ test_id, name, url, code, template_id, params }) => {
|
|
411
|
+
async ({ test_id, name, url, code, template_id, params, secrets, headers, enable_test_mode }) => {
|
|
395
412
|
try {
|
|
396
413
|
const body = {};
|
|
397
414
|
if (name !== undefined) body.name = name;
|
|
@@ -399,6 +416,9 @@ server.registerTool(
|
|
|
399
416
|
if (code !== undefined) body.code = code;
|
|
400
417
|
if (template_id !== undefined) body.template_id = template_id;
|
|
401
418
|
if (params !== undefined) body.params = params;
|
|
419
|
+
if (secrets !== undefined) body.secrets = secrets;
|
|
420
|
+
if (headers !== undefined) body.headers = headers;
|
|
421
|
+
if (enable_test_mode !== undefined) body.enable_test_mode = enable_test_mode;
|
|
402
422
|
const test = await apiPatch(`/api/tests/${test_id}`, body);
|
|
403
423
|
return { content: [{ type: "text", text: JSON.stringify(test, null, 2) }] };
|
|
404
424
|
} catch (err) {
|
|
@@ -411,6 +431,7 @@ server.registerTool(
|
|
|
411
431
|
"delete_test",
|
|
412
432
|
{
|
|
413
433
|
description: "Permanently delete a test and its associated schedules. IMPORTANT: Always call this first WITHOUT confirm to see what will be deleted, show that to the user, and only call again with confirm=true after the user explicitly agrees.",
|
|
434
|
+
destructiveHint: true,
|
|
414
435
|
inputSchema: {
|
|
415
436
|
test_id: z.coerce.number().describe("The test ID to delete (from list_tests)"),
|
|
416
437
|
confirm: z.boolean().optional().describe("Set to true to actually delete. Omit or false to preview what will be deleted."),
|
|
@@ -461,16 +482,18 @@ server.registerTool(
|
|
|
461
482
|
"run_test",
|
|
462
483
|
{
|
|
463
484
|
description: "Run a saved test by ID. Waits for the test to finish and returns full results with step details.",
|
|
485
|
+
destructiveHint: true,
|
|
464
486
|
inputSchema: {
|
|
465
487
|
test_id: z.coerce.number().describe("The test ID to run (from create_test or list_tests)"),
|
|
466
488
|
},
|
|
467
489
|
},
|
|
468
490
|
async ({ test_id }) => {
|
|
469
491
|
try {
|
|
492
|
+
const test = await apiGet(`/api/tests/${test_id}`);
|
|
470
493
|
const { test_run_id } = await apiPost(`/api/tests/${test_id}/run`, {});
|
|
471
494
|
console.error(`Started test run ${test_run_id} for test ${test_id}`);
|
|
472
495
|
const testRun = await pollTestRun(test_run_id);
|
|
473
|
-
const text = buildTestRunText(testRun);
|
|
496
|
+
const text = buildTestRunText(testRun, test.name);
|
|
474
497
|
return { content: [{ type: "text", text }] };
|
|
475
498
|
} catch (err) {
|
|
476
499
|
return { content: [{ type: "text", text: `Error: ${err.message}` }], isError: true };
|
|
@@ -482,6 +505,7 @@ server.registerTool(
|
|
|
482
505
|
"get_test_screenshots",
|
|
483
506
|
{
|
|
484
507
|
description: "Get screenshots from a test run. Returns only images. Use after run_test or get_test_results to view screenshots.",
|
|
508
|
+
readOnlyHint: true,
|
|
485
509
|
inputSchema: {
|
|
486
510
|
test_run_id: z.coerce.number().describe("The test run ID (from run_test or get_test_results)"),
|
|
487
511
|
},
|
|
@@ -507,6 +531,7 @@ server.registerTool(
|
|
|
507
531
|
"schedule_test",
|
|
508
532
|
{
|
|
509
533
|
description: "Create a recurring schedule for a saved test. The test will run automatically at the specified interval.",
|
|
534
|
+
destructiveHint: true,
|
|
510
535
|
inputSchema: {
|
|
511
536
|
test_id: z.coerce.number().describe("The test ID to schedule (from create_test or list_tests)"),
|
|
512
537
|
interval: z.enum(["every_minute", "hourly", "every_6_hours", "every_12_hours", "daily", "weekly"])
|
|
@@ -534,6 +559,7 @@ server.registerTool(
|
|
|
534
559
|
"list_schedules",
|
|
535
560
|
{
|
|
536
561
|
description: "List all test schedules for the current organization, including last run status and next run time.",
|
|
562
|
+
readOnlyHint: true,
|
|
537
563
|
},
|
|
538
564
|
async () => {
|
|
539
565
|
try {
|
|
@@ -563,6 +589,7 @@ server.registerTool(
|
|
|
563
589
|
"update_schedule",
|
|
564
590
|
{
|
|
565
591
|
description: "Pause or resume a test schedule.",
|
|
592
|
+
destructiveHint: true,
|
|
566
593
|
inputSchema: {
|
|
567
594
|
schedule_id: z.coerce.number().describe("The schedule ID (from list_schedules)"),
|
|
568
595
|
enabled: z.boolean().describe("true to resume, false to pause"),
|
|
@@ -588,6 +615,7 @@ server.registerTool(
|
|
|
588
615
|
"delete_schedule",
|
|
589
616
|
{
|
|
590
617
|
description: "Permanently delete a test schedule. Historical run results are preserved.",
|
|
618
|
+
destructiveHint: true,
|
|
591
619
|
inputSchema: {
|
|
592
620
|
schedule_id: z.coerce.number().describe("The schedule ID to delete (from list_schedules)"),
|
|
593
621
|
},
|
|
@@ -606,6 +634,7 @@ server.registerTool(
|
|
|
606
634
|
"get_test_results",
|
|
607
635
|
{
|
|
608
636
|
description: "Get recent test run results with step-by-step details. Use this to answer questions like 'is X working?' — filter by test_id to see the latest runs for a specific test. Shows status, timing, step pass/fail, errors, and screenshot links.",
|
|
637
|
+
readOnlyHint: true,
|
|
609
638
|
inputSchema: {
|
|
610
639
|
test_id: z.coerce.number().optional().describe("Filter by test ID (optional — omit to see all runs)"),
|
|
611
640
|
limit: z.coerce.number().optional().describe("Max results to return (default: 10)"),
|
|
@@ -620,9 +649,18 @@ server.registerTool(
|
|
|
620
649
|
if (!data.test_runs.length) {
|
|
621
650
|
return { content: [{ type: "text", text: "No test runs found." }] };
|
|
622
651
|
}
|
|
652
|
+
|
|
653
|
+
// Build test name map
|
|
654
|
+
const testIds = [...new Set(data.test_runs.map((r) => r.test_id))];
|
|
655
|
+
const nameMap = {};
|
|
656
|
+
const testsData = await apiGet("/api/tests/list");
|
|
657
|
+
for (const t of testsData.tests) {
|
|
658
|
+
if (testIds.includes(t.id)) nameMap[t.id] = t.name;
|
|
659
|
+
}
|
|
660
|
+
|
|
623
661
|
const lines = [];
|
|
624
662
|
for (const run of data.test_runs) {
|
|
625
|
-
lines.push(buildTestRunText(run));
|
|
663
|
+
lines.push(buildTestRunText(run, nameMap[run.test_id]));
|
|
626
664
|
lines.push("");
|
|
627
665
|
}
|
|
628
666
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
@@ -638,6 +676,7 @@ server.registerTool(
|
|
|
638
676
|
"add_notification_channel",
|
|
639
677
|
{
|
|
640
678
|
description: "Add a notification channel to receive alerts when scheduled tests fail. Supports email, Telegram, and Slack. For Telegram, get a chat ID from https://t.me/userinfobot. For Slack, create an Incoming Webhook in your Slack workspace settings.",
|
|
679
|
+
destructiveHint: true,
|
|
641
680
|
inputSchema: {
|
|
642
681
|
channel_type: z.enum(["email", "telegram", "slack"]).describe("The notification channel type"),
|
|
643
682
|
email_address: z.string().optional().describe("Email address for email channels (required when channel_type is email)"),
|
|
@@ -696,6 +735,7 @@ server.registerTool(
|
|
|
696
735
|
"list_notification_channels",
|
|
697
736
|
{
|
|
698
737
|
description: "List all notification channels configured for the current organization.",
|
|
738
|
+
readOnlyHint: true,
|
|
699
739
|
},
|
|
700
740
|
async () => {
|
|
701
741
|
try {
|
|
@@ -727,6 +767,7 @@ server.registerTool(
|
|
|
727
767
|
"remove_notification_channel",
|
|
728
768
|
{
|
|
729
769
|
description: "Remove a notification channel. Use list_notification_channels to find the channel ID.",
|
|
770
|
+
destructiveHint: true,
|
|
730
771
|
inputSchema: {
|
|
731
772
|
channel_id: z.coerce.number().describe("The channel ID to remove (from list_notification_channels)"),
|
|
732
773
|
},
|
|
@@ -745,6 +786,7 @@ server.registerTool(
|
|
|
745
786
|
"test_notification_channel",
|
|
746
787
|
{
|
|
747
788
|
description: "Send a test notification to verify a channel is working correctly.",
|
|
789
|
+
destructiveHint: true,
|
|
748
790
|
inputSchema: {
|
|
749
791
|
channel_id: z.coerce.number().describe("The channel ID to test (from list_notification_channels)"),
|
|
750
792
|
},
|
|
@@ -759,6 +801,38 @@ server.registerTool(
|
|
|
759
801
|
}
|
|
760
802
|
);
|
|
761
803
|
|
|
804
|
+
server.registerTool(
|
|
805
|
+
"get_org_secret",
|
|
806
|
+
{
|
|
807
|
+
description: "Get the org's X-AskQA-Secret header value. This token is automatically sent with every test request so your backend can detect monitoring traffic and enable test mode (e.g. Stripe sandbox).",
|
|
808
|
+
readOnlyHint: true,
|
|
809
|
+
},
|
|
810
|
+
async () => {
|
|
811
|
+
try {
|
|
812
|
+
const data = await apiGet("/api/org/secret");
|
|
813
|
+
return { content: [{ type: "text", text: `Your X-AskQA-Secret header value: ${data.askqa_secret}\n\nThis is sent automatically with every test request to your target domain. Configure your backend to check for this header to enable test mode.` }] };
|
|
814
|
+
} catch (err) {
|
|
815
|
+
return { content: [{ type: "text", text: `Error: ${err.message}` }], isError: true };
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
);
|
|
819
|
+
|
|
820
|
+
server.registerTool(
|
|
821
|
+
"rotate_org_secret",
|
|
822
|
+
{
|
|
823
|
+
description: "Generate a new X-AskQA-Secret header value. The old value will stop working immediately — update your backend configuration.",
|
|
824
|
+
destructiveHint: true,
|
|
825
|
+
},
|
|
826
|
+
async () => {
|
|
827
|
+
try {
|
|
828
|
+
const data = await apiPost("/api/org/secret/rotate", {});
|
|
829
|
+
return { content: [{ type: "text", text: `New X-AskQA-Secret header value: ${data.askqa_secret}\n\nUpdate your backend configuration to use this new value. The old value no longer works.` }] };
|
|
830
|
+
} catch (err) {
|
|
831
|
+
return { content: [{ type: "text", text: `Error: ${err.message}` }], isError: true };
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
);
|
|
835
|
+
|
|
762
836
|
const transport = new StdioServerTransport();
|
|
763
837
|
await server.connect(transport);
|
|
764
838
|
console.error("AskQA MCP server running on stdio");
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: setup-mcp
|
|
3
|
+
description: Configure AskQA MCP server with API key
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Set Up AskQA MCP Server
|
|
7
|
+
|
|
8
|
+
Use this skill to automatically configure the AskQA MCP server in your Claude settings.
|
|
9
|
+
|
|
10
|
+
## Step 1: Get API Key
|
|
11
|
+
|
|
12
|
+
Ask the user if they already have an AskQA API key. If not, direct them to:
|
|
13
|
+
- Sign up at https://askqa.ai
|
|
14
|
+
- Get their API key from https://askqa.ai/account
|
|
15
|
+
|
|
16
|
+
Once they have the key, ask them to provide it.
|
|
17
|
+
|
|
18
|
+
## Step 2: Detect Environment
|
|
19
|
+
|
|
20
|
+
Check if the user is in a git repository by looking for a `.git` directory in the current working directory or parent directories.
|
|
21
|
+
|
|
22
|
+
Use the Bash tool:
|
|
23
|
+
```bash
|
|
24
|
+
git rev-parse --git-dir 2>/dev/null
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
If this succeeds, we're in a git repo. If it fails, we're in Desktop/global mode.
|
|
28
|
+
|
|
29
|
+
## Step 3: Determine Config Location
|
|
30
|
+
|
|
31
|
+
**If in a git repository:**
|
|
32
|
+
- Config location: `.mcp.json` in the project root (project-specific)
|
|
33
|
+
- This configuration will only apply when working in this repository
|
|
34
|
+
|
|
35
|
+
**If not in a git repository (Desktop mode):**
|
|
36
|
+
- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
37
|
+
- Windows: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
38
|
+
- Linux: `~/.config/Claude/claude_desktop_config.json`
|
|
39
|
+
- This configuration will apply globally for the user
|
|
40
|
+
|
|
41
|
+
Tell the user which location will be used.
|
|
42
|
+
|
|
43
|
+
## Step 4: Read Existing Config
|
|
44
|
+
|
|
45
|
+
Use the Read tool to check if the config file exists and read its contents.
|
|
46
|
+
|
|
47
|
+
If the file doesn't exist, start with an empty config:
|
|
48
|
+
```json
|
|
49
|
+
{
|
|
50
|
+
"mcpServers": {}
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
If the file exists, parse it as JSON.
|
|
55
|
+
|
|
56
|
+
## Step 5: Update Config
|
|
57
|
+
|
|
58
|
+
Add or update the `askqa` MCP server entry:
|
|
59
|
+
|
|
60
|
+
```json
|
|
61
|
+
{
|
|
62
|
+
"mcpServers": {
|
|
63
|
+
"askqa": {
|
|
64
|
+
"command": "node",
|
|
65
|
+
"args": ["${CLAUDE_PLUGIN_ROOT}/askqa/server.js"],
|
|
66
|
+
"env": {
|
|
67
|
+
"AUTOQA_API_URL": "https://api.askqa.ai",
|
|
68
|
+
"AUTOQA_API_KEY": "aq_..."
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**Important notes:**
|
|
76
|
+
- Use `${CLAUDE_PLUGIN_ROOT}` variable - it resolves to the plugin installation directory
|
|
77
|
+
- Replace `"aq_..."` with the actual API key the user provided
|
|
78
|
+
- Preserve any other MCP servers already configured
|
|
79
|
+
- If `askqa` already exists, update it with the new API key
|
|
80
|
+
|
|
81
|
+
## Step 6: Write Config
|
|
82
|
+
|
|
83
|
+
Use the Write tool to save the updated config back to the file.
|
|
84
|
+
|
|
85
|
+
**For project config (`.mcp.json`):**
|
|
86
|
+
- Write the config file to the project root
|
|
87
|
+
|
|
88
|
+
**For Desktop config:**
|
|
89
|
+
- Write directly to the Desktop config file location
|
|
90
|
+
|
|
91
|
+
## Step 7: Confirm
|
|
92
|
+
|
|
93
|
+
After writing the config, confirm with the user:
|
|
94
|
+
|
|
95
|
+
**If project config:**
|
|
96
|
+
```
|
|
97
|
+
✓ AskQA MCP server configured in .mcp.json
|
|
98
|
+
|
|
99
|
+
The MCP server is now available in this repository. Restart Claude Code to load it.
|
|
100
|
+
|
|
101
|
+
Try these commands:
|
|
102
|
+
- list_tests - List your saved tests
|
|
103
|
+
- create_test - Create a new test
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**If Desktop config:**
|
|
107
|
+
```
|
|
108
|
+
✓ AskQA MCP server configured globally
|
|
109
|
+
|
|
110
|
+
The MCP server is now available in Claude Desktop. Restart Claude Desktop to load it.
|
|
111
|
+
|
|
112
|
+
You can now use AskQA tools in any conversation.
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Error Handling
|
|
116
|
+
|
|
117
|
+
If any step fails:
|
|
118
|
+
- **Permission error**: Tell user they may need to run with appropriate permissions
|
|
119
|
+
- **Invalid API key format**: Check that it starts with `aq_`
|
|
120
|
+
- **Invalid JSON**: If existing config is malformed, warn user and ask if they want to replace it
|
|
121
|
+
|
|
122
|
+
## Additional Tips
|
|
123
|
+
|
|
124
|
+
After successful setup, remind the user:
|
|
125
|
+
- They can verify the setup by restarting and running `list_tests`
|
|
126
|
+
- They can update the API key anytime by running this skill again
|
|
127
|
+
- Project configs (`.mcp.json`) can be committed to git for team sharing (but shouldn't include the API key - use environment variables instead for team setups)
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: setup-slack
|
|
3
|
+
description: Configure Slack notifications for AskQA test failures
|
|
4
|
+
---
|
|
5
|
+
|
|
1
6
|
# Set Up Slack Notifications
|
|
2
7
|
|
|
3
8
|
Use this skill to configure Slack notifications for AskQA test failures. When a scheduled test fails, AskQA will post a message to your Slack channel with the test name, failed steps, and a link to the full results.
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: shopify-add-to-cart
|
|
3
|
+
description: Set up automated monitoring for a Shopify store's add-to-cart flow
|
|
4
|
+
---
|
|
5
|
+
|
|
1
6
|
# Monitor Shopify Add to Cart
|
|
2
7
|
|
|
3
8
|
Use this skill to set up automated monitoring for a Shopify store's add-to-cart flow. When the button breaks — due to an app update, theme change, or platform rollout — AskQA catches it within minutes and sends an alert with a screenshot.
|