@askqa/mcp 1.0.8 → 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/.claude-plugin/plugin.json +14 -2
- package/README.md +16 -0
- package/package.json +1 -1
- package/server.js +77 -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.0
|
|
4
|
-
"description": "AskQA skills — set up notifications and monitoring for your websites"
|
|
3
|
+
"version": "1.1.0",
|
|
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,25 @@ 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' })"),
|
|
203
208
|
},
|
|
204
209
|
},
|
|
205
|
-
async ({ name, url, template_id, params, code }) => {
|
|
210
|
+
async ({ name, url, template_id, params, code, secrets, headers }) => {
|
|
206
211
|
try {
|
|
207
212
|
const body = { name, url };
|
|
208
213
|
if (template_id) body.template_id = template_id;
|
|
209
214
|
if (params) body.params = params;
|
|
210
215
|
if (code) body.code = code;
|
|
216
|
+
if (secrets) body.secrets = secrets;
|
|
217
|
+
if (headers) body.headers = headers;
|
|
211
218
|
const test = await apiPost("/api/tests/create", body);
|
|
212
219
|
return { content: [{ type: "text", text: JSON.stringify(test, null, 2) }] };
|
|
213
220
|
} catch (err) {
|
|
@@ -220,6 +227,7 @@ server.registerTool(
|
|
|
220
227
|
"screenshot_url",
|
|
221
228
|
{
|
|
222
229
|
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.",
|
|
230
|
+
readOnlyHint: true,
|
|
223
231
|
inputSchema: {
|
|
224
232
|
url: z.string().describe("The URL to screenshot (e.g. 'https://example.com')"),
|
|
225
233
|
},
|
|
@@ -280,6 +288,7 @@ server.registerTool(
|
|
|
280
288
|
"validate_test",
|
|
281
289
|
{
|
|
282
290
|
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.",
|
|
291
|
+
readOnlyHint: true,
|
|
283
292
|
inputSchema: {
|
|
284
293
|
code: z.string().describe("Custom Playwright test code. Must define an async function test({ page, step, log })."),
|
|
285
294
|
url: z.string().describe("The target URL to test against (e.g. 'https://example.com')"),
|
|
@@ -349,6 +358,7 @@ server.registerTool(
|
|
|
349
358
|
"list_tests",
|
|
350
359
|
{
|
|
351
360
|
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.",
|
|
361
|
+
readOnlyHint: true,
|
|
352
362
|
},
|
|
353
363
|
async () => {
|
|
354
364
|
try {
|
|
@@ -364,6 +374,7 @@ server.registerTool(
|
|
|
364
374
|
"get_test",
|
|
365
375
|
{
|
|
366
376
|
description: "Get full details of a test by ID, including code for custom tests. Use list_tests to find the test ID first.",
|
|
377
|
+
readOnlyHint: true,
|
|
367
378
|
inputSchema: {
|
|
368
379
|
test_id: z.coerce.number().describe("The test ID (from list_tests or create_test)"),
|
|
369
380
|
},
|
|
@@ -382,6 +393,7 @@ server.registerTool(
|
|
|
382
393
|
"update_test",
|
|
383
394
|
{
|
|
384
395
|
description: "Update an existing test's name, URL, code, or other properties. Only provided fields are changed.",
|
|
396
|
+
destructiveHint: true,
|
|
385
397
|
inputSchema: {
|
|
386
398
|
test_id: z.coerce.number().describe("The test ID to update (from list_tests or get_test)"),
|
|
387
399
|
name: z.string().optional().describe("New test name"),
|
|
@@ -389,9 +401,11 @@ server.registerTool(
|
|
|
389
401
|
code: z.string().optional().describe("Updated custom Playwright test code"),
|
|
390
402
|
template_id: z.string().optional().describe("Updated template ID"),
|
|
391
403
|
params: z.record(z.string()).optional().describe("Updated template parameters"),
|
|
404
|
+
secrets: z.record(z.string()).nullable().optional().describe("Updated secrets (pass null to clear)"),
|
|
405
|
+
headers: z.record(z.string()).nullable().optional().describe("Updated HTTP headers (pass null to clear)"),
|
|
392
406
|
},
|
|
393
407
|
},
|
|
394
|
-
async ({ test_id, name, url, code, template_id, params }) => {
|
|
408
|
+
async ({ test_id, name, url, code, template_id, params, secrets, headers }) => {
|
|
395
409
|
try {
|
|
396
410
|
const body = {};
|
|
397
411
|
if (name !== undefined) body.name = name;
|
|
@@ -399,6 +413,8 @@ server.registerTool(
|
|
|
399
413
|
if (code !== undefined) body.code = code;
|
|
400
414
|
if (template_id !== undefined) body.template_id = template_id;
|
|
401
415
|
if (params !== undefined) body.params = params;
|
|
416
|
+
if (secrets !== undefined) body.secrets = secrets;
|
|
417
|
+
if (headers !== undefined) body.headers = headers;
|
|
402
418
|
const test = await apiPatch(`/api/tests/${test_id}`, body);
|
|
403
419
|
return { content: [{ type: "text", text: JSON.stringify(test, null, 2) }] };
|
|
404
420
|
} catch (err) {
|
|
@@ -411,6 +427,7 @@ server.registerTool(
|
|
|
411
427
|
"delete_test",
|
|
412
428
|
{
|
|
413
429
|
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.",
|
|
430
|
+
destructiveHint: true,
|
|
414
431
|
inputSchema: {
|
|
415
432
|
test_id: z.coerce.number().describe("The test ID to delete (from list_tests)"),
|
|
416
433
|
confirm: z.boolean().optional().describe("Set to true to actually delete. Omit or false to preview what will be deleted."),
|
|
@@ -461,16 +478,18 @@ server.registerTool(
|
|
|
461
478
|
"run_test",
|
|
462
479
|
{
|
|
463
480
|
description: "Run a saved test by ID. Waits for the test to finish and returns full results with step details.",
|
|
481
|
+
destructiveHint: true,
|
|
464
482
|
inputSchema: {
|
|
465
483
|
test_id: z.coerce.number().describe("The test ID to run (from create_test or list_tests)"),
|
|
466
484
|
},
|
|
467
485
|
},
|
|
468
486
|
async ({ test_id }) => {
|
|
469
487
|
try {
|
|
488
|
+
const test = await apiGet(`/api/tests/${test_id}`);
|
|
470
489
|
const { test_run_id } = await apiPost(`/api/tests/${test_id}/run`, {});
|
|
471
490
|
console.error(`Started test run ${test_run_id} for test ${test_id}`);
|
|
472
491
|
const testRun = await pollTestRun(test_run_id);
|
|
473
|
-
const text = buildTestRunText(testRun);
|
|
492
|
+
const text = buildTestRunText(testRun, test.name);
|
|
474
493
|
return { content: [{ type: "text", text }] };
|
|
475
494
|
} catch (err) {
|
|
476
495
|
return { content: [{ type: "text", text: `Error: ${err.message}` }], isError: true };
|
|
@@ -482,6 +501,7 @@ server.registerTool(
|
|
|
482
501
|
"get_test_screenshots",
|
|
483
502
|
{
|
|
484
503
|
description: "Get screenshots from a test run. Returns only images. Use after run_test or get_test_results to view screenshots.",
|
|
504
|
+
readOnlyHint: true,
|
|
485
505
|
inputSchema: {
|
|
486
506
|
test_run_id: z.coerce.number().describe("The test run ID (from run_test or get_test_results)"),
|
|
487
507
|
},
|
|
@@ -507,6 +527,7 @@ server.registerTool(
|
|
|
507
527
|
"schedule_test",
|
|
508
528
|
{
|
|
509
529
|
description: "Create a recurring schedule for a saved test. The test will run automatically at the specified interval.",
|
|
530
|
+
destructiveHint: true,
|
|
510
531
|
inputSchema: {
|
|
511
532
|
test_id: z.coerce.number().describe("The test ID to schedule (from create_test or list_tests)"),
|
|
512
533
|
interval: z.enum(["every_minute", "hourly", "every_6_hours", "every_12_hours", "daily", "weekly"])
|
|
@@ -534,6 +555,7 @@ server.registerTool(
|
|
|
534
555
|
"list_schedules",
|
|
535
556
|
{
|
|
536
557
|
description: "List all test schedules for the current organization, including last run status and next run time.",
|
|
558
|
+
readOnlyHint: true,
|
|
537
559
|
},
|
|
538
560
|
async () => {
|
|
539
561
|
try {
|
|
@@ -563,6 +585,7 @@ server.registerTool(
|
|
|
563
585
|
"update_schedule",
|
|
564
586
|
{
|
|
565
587
|
description: "Pause or resume a test schedule.",
|
|
588
|
+
destructiveHint: true,
|
|
566
589
|
inputSchema: {
|
|
567
590
|
schedule_id: z.coerce.number().describe("The schedule ID (from list_schedules)"),
|
|
568
591
|
enabled: z.boolean().describe("true to resume, false to pause"),
|
|
@@ -588,6 +611,7 @@ server.registerTool(
|
|
|
588
611
|
"delete_schedule",
|
|
589
612
|
{
|
|
590
613
|
description: "Permanently delete a test schedule. Historical run results are preserved.",
|
|
614
|
+
destructiveHint: true,
|
|
591
615
|
inputSchema: {
|
|
592
616
|
schedule_id: z.coerce.number().describe("The schedule ID to delete (from list_schedules)"),
|
|
593
617
|
},
|
|
@@ -606,6 +630,7 @@ server.registerTool(
|
|
|
606
630
|
"get_test_results",
|
|
607
631
|
{
|
|
608
632
|
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.",
|
|
633
|
+
readOnlyHint: true,
|
|
609
634
|
inputSchema: {
|
|
610
635
|
test_id: z.coerce.number().optional().describe("Filter by test ID (optional — omit to see all runs)"),
|
|
611
636
|
limit: z.coerce.number().optional().describe("Max results to return (default: 10)"),
|
|
@@ -620,9 +645,18 @@ server.registerTool(
|
|
|
620
645
|
if (!data.test_runs.length) {
|
|
621
646
|
return { content: [{ type: "text", text: "No test runs found." }] };
|
|
622
647
|
}
|
|
648
|
+
|
|
649
|
+
// Build test name map
|
|
650
|
+
const testIds = [...new Set(data.test_runs.map((r) => r.test_id))];
|
|
651
|
+
const nameMap = {};
|
|
652
|
+
const testsData = await apiGet("/api/tests/list");
|
|
653
|
+
for (const t of testsData.tests) {
|
|
654
|
+
if (testIds.includes(t.id)) nameMap[t.id] = t.name;
|
|
655
|
+
}
|
|
656
|
+
|
|
623
657
|
const lines = [];
|
|
624
658
|
for (const run of data.test_runs) {
|
|
625
|
-
lines.push(buildTestRunText(run));
|
|
659
|
+
lines.push(buildTestRunText(run, nameMap[run.test_id]));
|
|
626
660
|
lines.push("");
|
|
627
661
|
}
|
|
628
662
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
@@ -638,6 +672,7 @@ server.registerTool(
|
|
|
638
672
|
"add_notification_channel",
|
|
639
673
|
{
|
|
640
674
|
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.",
|
|
675
|
+
destructiveHint: true,
|
|
641
676
|
inputSchema: {
|
|
642
677
|
channel_type: z.enum(["email", "telegram", "slack"]).describe("The notification channel type"),
|
|
643
678
|
email_address: z.string().optional().describe("Email address for email channels (required when channel_type is email)"),
|
|
@@ -696,6 +731,7 @@ server.registerTool(
|
|
|
696
731
|
"list_notification_channels",
|
|
697
732
|
{
|
|
698
733
|
description: "List all notification channels configured for the current organization.",
|
|
734
|
+
readOnlyHint: true,
|
|
699
735
|
},
|
|
700
736
|
async () => {
|
|
701
737
|
try {
|
|
@@ -727,6 +763,7 @@ server.registerTool(
|
|
|
727
763
|
"remove_notification_channel",
|
|
728
764
|
{
|
|
729
765
|
description: "Remove a notification channel. Use list_notification_channels to find the channel ID.",
|
|
766
|
+
destructiveHint: true,
|
|
730
767
|
inputSchema: {
|
|
731
768
|
channel_id: z.coerce.number().describe("The channel ID to remove (from list_notification_channels)"),
|
|
732
769
|
},
|
|
@@ -745,6 +782,7 @@ server.registerTool(
|
|
|
745
782
|
"test_notification_channel",
|
|
746
783
|
{
|
|
747
784
|
description: "Send a test notification to verify a channel is working correctly.",
|
|
785
|
+
destructiveHint: true,
|
|
748
786
|
inputSchema: {
|
|
749
787
|
channel_id: z.coerce.number().describe("The channel ID to test (from list_notification_channels)"),
|
|
750
788
|
},
|
|
@@ -759,6 +797,38 @@ server.registerTool(
|
|
|
759
797
|
}
|
|
760
798
|
);
|
|
761
799
|
|
|
800
|
+
server.registerTool(
|
|
801
|
+
"get_org_secret",
|
|
802
|
+
{
|
|
803
|
+
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).",
|
|
804
|
+
readOnlyHint: true,
|
|
805
|
+
},
|
|
806
|
+
async () => {
|
|
807
|
+
try {
|
|
808
|
+
const data = await apiGet("/api/org/secret");
|
|
809
|
+
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.` }] };
|
|
810
|
+
} catch (err) {
|
|
811
|
+
return { content: [{ type: "text", text: `Error: ${err.message}` }], isError: true };
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
);
|
|
815
|
+
|
|
816
|
+
server.registerTool(
|
|
817
|
+
"rotate_org_secret",
|
|
818
|
+
{
|
|
819
|
+
description: "Generate a new X-AskQA-Secret header value. The old value will stop working immediately — update your backend configuration.",
|
|
820
|
+
destructiveHint: true,
|
|
821
|
+
},
|
|
822
|
+
async () => {
|
|
823
|
+
try {
|
|
824
|
+
const data = await apiPost("/api/org/secret/rotate", {});
|
|
825
|
+
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.` }] };
|
|
826
|
+
} catch (err) {
|
|
827
|
+
return { content: [{ type: "text", text: `Error: ${err.message}` }], isError: true };
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
);
|
|
831
|
+
|
|
762
832
|
const transport = new StdioServerTransport();
|
|
763
833
|
await server.connect(transport);
|
|
764
834
|
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.
|