@qulib/mcp 0.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/README.md +64 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +122 -0
- package/package.json +51 -0
package/README.md
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# @qulib/mcp
|
|
2
|
+
|
|
3
|
+
**@qulib/mcp** is an MCP server that exposes Qulib so AI clients can analyze a deployed URL for release confidence, accessibility, broken links, console noise, and prioritized gaps (CLI entry `qulib-mcp`).
|
|
4
|
+
|
|
5
|
+
## What it does
|
|
6
|
+
|
|
7
|
+
One tool: `analyze_app(url, auth?)`
|
|
8
|
+
|
|
9
|
+
Returns:
|
|
10
|
+
|
|
11
|
+
- Release confidence score (0-100)
|
|
12
|
+
- Accessibility violations (axe-core, WCAG 2 A/AA)
|
|
13
|
+
- Broken links
|
|
14
|
+
- Console errors and coverage warnings
|
|
15
|
+
- Prioritized gaps with severity
|
|
16
|
+
|
|
17
|
+
Supports optional form-login auth for scanning authenticated pages.
|
|
18
|
+
|
|
19
|
+
## Install for Claude Code
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
claude mcp add qulib --scope user npx -y @qulib/mcp
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Install for Claude Desktop / Cursor
|
|
26
|
+
|
|
27
|
+
Add this under `mcpServers` in `claude_desktop_config.json` (Claude Desktop) or your editor MCP settings (Cursor), adjusting paths if your client uses a different layout:
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"mcpServers": {
|
|
32
|
+
"qulib": {
|
|
33
|
+
"command": "npx",
|
|
34
|
+
"args": ["-y", "@qulib/mcp"]
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Example usage
|
|
41
|
+
|
|
42
|
+
Ask Claude:
|
|
43
|
+
|
|
44
|
+
> "Use Qulib to analyze https://example.com and tell me if it's ready to ship."
|
|
45
|
+
|
|
46
|
+
Claude will call `analyze_app({ url: "https://example.com" })` and reason about the result.
|
|
47
|
+
|
|
48
|
+
## Authenticated scanning
|
|
49
|
+
|
|
50
|
+
> "Use Qulib to scan my staging app at https://staging.example.com. Log in as user@example.com with password Test123, the login form is at /login with selectors [data-testid='email'], [data-testid='password'], and [data-testid='submit']."
|
|
51
|
+
|
|
52
|
+
Claude will pass auth credentials to the tool; Qulib signs in, then scans.
|
|
53
|
+
|
|
54
|
+
## Known limitations
|
|
55
|
+
|
|
56
|
+
In **v0.1.0**, link discovery and route expansion from the entry URL are **shallow** compared to full multi-site crawling. Broader multi-page coverage is planned for **0.1.1**; treat low page counts in the output as a signal that the scan may not represent the whole app yet.
|
|
57
|
+
|
|
58
|
+
## Repository
|
|
59
|
+
|
|
60
|
+
Source and issues: **[github.com/TapeshN/qulib](https://github.com/TapeshN/qulib)**.
|
|
61
|
+
|
|
62
|
+
## License
|
|
63
|
+
|
|
64
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
3
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
+
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
5
|
+
import { analyzeApp } from '@qulib/core';
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
const AnalyzeInputSchema = z.object({
|
|
8
|
+
url: z.string().url(),
|
|
9
|
+
maxPagesToScan: z.number().int().min(1).max(50).optional(),
|
|
10
|
+
timeoutMs: z.number().int().positive().optional(),
|
|
11
|
+
auth: z
|
|
12
|
+
.object({
|
|
13
|
+
type: z.literal('form-login'),
|
|
14
|
+
loginUrl: z.string().url(),
|
|
15
|
+
username: z.string(),
|
|
16
|
+
password: z.string(),
|
|
17
|
+
usernameSelector: z.string(),
|
|
18
|
+
passwordSelector: z.string(),
|
|
19
|
+
submitSelector: z.string(),
|
|
20
|
+
successUrlContains: z.string().optional(),
|
|
21
|
+
})
|
|
22
|
+
.optional(),
|
|
23
|
+
});
|
|
24
|
+
const server = new Server({
|
|
25
|
+
name: 'qulib-mcp',
|
|
26
|
+
version: '0.1.0',
|
|
27
|
+
}, {
|
|
28
|
+
capabilities: {
|
|
29
|
+
tools: {},
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
33
|
+
tools: [
|
|
34
|
+
{
|
|
35
|
+
name: 'analyze_app',
|
|
36
|
+
description: 'Analyze a deployed web app for quality gaps. Returns a release confidence score (0-100), accessibility violations, broken links, and prioritized risks. Supports optional form-login authentication.',
|
|
37
|
+
inputSchema: {
|
|
38
|
+
type: 'object',
|
|
39
|
+
properties: {
|
|
40
|
+
url: { type: 'string', description: 'Full URL of the deployed app' },
|
|
41
|
+
maxPagesToScan: { type: 'number', description: 'Max pages to crawl (default 10)' },
|
|
42
|
+
timeoutMs: { type: 'number', description: 'Per-page timeout in milliseconds (default 30000)' },
|
|
43
|
+
auth: {
|
|
44
|
+
type: 'object',
|
|
45
|
+
description: 'Optional form-login auth',
|
|
46
|
+
properties: {
|
|
47
|
+
type: { type: 'string', enum: ['form-login'] },
|
|
48
|
+
loginUrl: { type: 'string' },
|
|
49
|
+
username: { type: 'string' },
|
|
50
|
+
password: { type: 'string' },
|
|
51
|
+
usernameSelector: { type: 'string' },
|
|
52
|
+
passwordSelector: { type: 'string' },
|
|
53
|
+
submitSelector: { type: 'string' },
|
|
54
|
+
successUrlContains: { type: 'string' },
|
|
55
|
+
},
|
|
56
|
+
required: [
|
|
57
|
+
'type',
|
|
58
|
+
'loginUrl',
|
|
59
|
+
'username',
|
|
60
|
+
'password',
|
|
61
|
+
'usernameSelector',
|
|
62
|
+
'passwordSelector',
|
|
63
|
+
'submitSelector',
|
|
64
|
+
],
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
required: ['url'],
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
}));
|
|
72
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
73
|
+
if (request.params.name !== 'analyze_app') {
|
|
74
|
+
throw new Error(`Unknown tool: ${request.params.name}`);
|
|
75
|
+
}
|
|
76
|
+
const input = AnalyzeInputSchema.parse(request.params.arguments ?? {});
|
|
77
|
+
const successIndicator = input.auth?.successUrlContains !== undefined && input.auth.successUrlContains !== ''
|
|
78
|
+
? { urlContains: input.auth.successUrlContains }
|
|
79
|
+
: {};
|
|
80
|
+
const result = await analyzeApp({
|
|
81
|
+
url: input.url,
|
|
82
|
+
writeArtifacts: false,
|
|
83
|
+
config: {
|
|
84
|
+
maxPagesToScan: input.maxPagesToScan ?? 10,
|
|
85
|
+
maxDepth: 3,
|
|
86
|
+
minPagesForConfidence: 3,
|
|
87
|
+
timeoutMs: input.timeoutMs ?? 30000,
|
|
88
|
+
retryCount: 0,
|
|
89
|
+
llmTokenBudget: 1,
|
|
90
|
+
testGenerationLimit: 1,
|
|
91
|
+
readOnlyMode: true,
|
|
92
|
+
requireHumanReview: false,
|
|
93
|
+
failOnConsoleError: false,
|
|
94
|
+
explorer: 'playwright',
|
|
95
|
+
defaultAdapter: 'playwright',
|
|
96
|
+
adapters: ['playwright'],
|
|
97
|
+
...(input.auth && {
|
|
98
|
+
auth: {
|
|
99
|
+
type: 'form-login',
|
|
100
|
+
loginUrl: input.auth.loginUrl,
|
|
101
|
+
credentials: { username: input.auth.username, password: input.auth.password },
|
|
102
|
+
selectors: {
|
|
103
|
+
username: input.auth.usernameSelector,
|
|
104
|
+
password: input.auth.passwordSelector,
|
|
105
|
+
submit: input.auth.submitSelector,
|
|
106
|
+
},
|
|
107
|
+
successIndicator,
|
|
108
|
+
},
|
|
109
|
+
}),
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
return {
|
|
113
|
+
content: [
|
|
114
|
+
{
|
|
115
|
+
type: 'text',
|
|
116
|
+
text: JSON.stringify(result, null, 2),
|
|
117
|
+
},
|
|
118
|
+
],
|
|
119
|
+
};
|
|
120
|
+
});
|
|
121
|
+
const transport = new StdioServerTransport();
|
|
122
|
+
await server.connect(transport);
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@qulib/mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server for Qulib — AI-callable QA gap analysis",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Tapesh Nagarwal",
|
|
7
|
+
"homepage": "https://github.com/TapeshN/qulib#readme",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/TapeshN/qulib.git",
|
|
11
|
+
"directory": "packages/mcp"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/TapeshN/qulib/issues"
|
|
15
|
+
},
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public"
|
|
18
|
+
},
|
|
19
|
+
"type": "module",
|
|
20
|
+
"main": "./dist/index.js",
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"bin": {
|
|
23
|
+
"qulib-mcp": "./dist/index.js"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist",
|
|
27
|
+
"README.md"
|
|
28
|
+
],
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "tsc && chmod +x dist/index.js",
|
|
31
|
+
"dev": "tsx src/index.ts"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@qulib/core": "0.1.0",
|
|
35
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
36
|
+
"zod": "^3.23.0"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/node": "^20.0.0",
|
|
40
|
+
"tsx": "^4.11.0",
|
|
41
|
+
"typescript": "^5.4.0"
|
|
42
|
+
},
|
|
43
|
+
"keywords": [
|
|
44
|
+
"mcp",
|
|
45
|
+
"qa",
|
|
46
|
+
"quality",
|
|
47
|
+
"accessibility",
|
|
48
|
+
"release-confidence",
|
|
49
|
+
"ai"
|
|
50
|
+
]
|
|
51
|
+
}
|