@qulib/mcp 0.4.0 → 0.4.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
|
@@ -31,6 +31,7 @@ Tools:
|
|
|
31
31
|
- **`explore_auth(url, timeoutMs?)`** — list all sign-in paths (OAuth, unknown SSO heuristics, forms, magic link) and what the agent must collect before `analyze_app`. Prefer this on unfamiliar apps.
|
|
32
32
|
- **`analyze_app`** — quality scan (optional form-login or storage-state auth). **Default payload is summary-first:** `summary`, `topGaps`, `costIntelligenceSummary`, `nextDeterministicChecks`, small previews. Set **`includeFullReport: true`** for the full `analyzeApp` result (all scenarios). Optional harness overrides: **`llmMaxOutputTokensPerCall`**, **`llmTokenBudget`** (legacy), **`testGenerationLimit`**, **`enableLlmScenarios`** (default true when omitted).
|
|
33
33
|
- **`detect_auth(url, timeoutMs?)`** — single-pattern auth guess with a short recommendation (lighter than `explore_auth`).
|
|
34
|
+
- **`qulib_score_automation(repoPath, includeFullDimensions?)`** — score a local automation repo across six dimensions (test coverage breadth, framework adoption, test-id hygiene, CI integration, auth test coverage, component test ratio). Returns an overall 0–100 score, maturity level (L1–L5), and top recommendations. Each dimension carries an **`applicability`** field (`applicable` / `not_applicable` / `unknown`); the overall score normalizes across applicable dimensions only so absent capabilities never get silent partial credit. **`repoPath`** must be an absolute path on the MCP host. Pass **`includeFullDimensions: true`** for per-dimension evidence and reasons.
|
|
34
35
|
|
|
35
36
|
Returns from `analyze_app`:
|
|
36
37
|
|
|
@@ -87,9 +88,11 @@ When the model sees **`unrecognizedButtons`**, it can ask the user to register a
|
|
|
87
88
|
|
|
88
89
|
| | Default (`includeFullReport` omitted or false) | `includeFullReport: true` |
|
|
89
90
|
|--|--|--|
|
|
90
|
-
| Size | Small: top gaps, cost summary, next checks | Full `gapAnalysis`
|
|
91
|
+
| Size | Small: top gaps, cost summary, next checks, `repoInventorySummary` (counts only) | Full `gapAnalysis` (all scenarios) and full `repoInventory` (test files, missing test IDs) |
|
|
91
92
|
| When to use | Routine agent turns, chat context limits | Deep dives, exporting full scenario JSON |
|
|
92
93
|
|
|
94
|
+
> **0.4.2 note:** The compact response now ships `repoInventorySummary` (route/test/missing-id counts plus framework verdict) instead of the full `repoInventory`. Agents that need the raw `testFiles` or `missingTestIds` arrays should pass `includeFullReport: true`.
|
|
95
|
+
|
|
93
96
|
Example (full):
|
|
94
97
|
|
|
95
98
|
```json
|
|
@@ -3,66 +3,59 @@ export declare function buildCompactAnalyzePayload(result: AnalyzeResult, includ
|
|
|
3
3
|
includeFullReport: boolean;
|
|
4
4
|
note: string;
|
|
5
5
|
detectedAuth?: {
|
|
6
|
-
type: "unknown" | "form-login" | "
|
|
6
|
+
type: "unknown" | "form-login" | "oauth" | "magic-link" | "none";
|
|
7
7
|
loginUrl: string | null;
|
|
8
|
-
hasAuth: boolean;
|
|
9
8
|
provider: string | null;
|
|
9
|
+
hasAuth: boolean;
|
|
10
10
|
observedSelectors: {
|
|
11
11
|
usernameSelector: string | null;
|
|
12
12
|
passwordSelector: string | null;
|
|
13
13
|
submitSelector: string | null;
|
|
14
14
|
} | null;
|
|
15
15
|
oauthButtons: {
|
|
16
|
-
provider: string;
|
|
17
16
|
text: string;
|
|
17
|
+
provider: string;
|
|
18
18
|
}[];
|
|
19
19
|
recommendation: string;
|
|
20
|
+
authOptions?: {
|
|
21
|
+
type: "unknown" | "form-login" | "oauth" | "oauth-unknown" | "form-multi" | "magic-link";
|
|
22
|
+
label: string;
|
|
23
|
+
id: string;
|
|
24
|
+
provider: string | null;
|
|
25
|
+
source: "built-in" | "user-local" | "heuristic";
|
|
26
|
+
automatable: boolean;
|
|
27
|
+
confidence: "high" | "medium" | "low";
|
|
28
|
+
requirements: {
|
|
29
|
+
method: "storage-state";
|
|
30
|
+
instruction: string;
|
|
31
|
+
} | {
|
|
32
|
+
method: "credentials";
|
|
33
|
+
fields: {
|
|
34
|
+
type: "password" | "text" | "email" | "select" | "checkbox";
|
|
35
|
+
name: string;
|
|
36
|
+
label: string;
|
|
37
|
+
observedOptions: string[];
|
|
38
|
+
}[];
|
|
39
|
+
} | {
|
|
40
|
+
method: "unknown";
|
|
41
|
+
instruction: string;
|
|
42
|
+
};
|
|
43
|
+
}[] | undefined;
|
|
20
44
|
} | undefined;
|
|
21
|
-
|
|
22
|
-
scannedAt: string;
|
|
23
|
-
routes: {
|
|
24
|
-
path: string;
|
|
25
|
-
method: "unknown" | "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
|
|
26
|
-
file: string;
|
|
27
|
-
}[];
|
|
28
|
-
repoPath: string;
|
|
29
|
-
testFiles: {
|
|
30
|
-
type: "playwright" | "cypress-e2e" | "cypress-component" | "jest" | "vitest" | "other";
|
|
31
|
-
file: string;
|
|
32
|
-
coveredPaths: string[];
|
|
33
|
-
}[];
|
|
34
|
-
missingTestIds: string[];
|
|
35
|
-
cypressStructure: {
|
|
36
|
-
detected: boolean;
|
|
37
|
-
hasCommandsFile: boolean;
|
|
38
|
-
existingE2eFiles: string[];
|
|
39
|
-
existingComponentFiles: string[];
|
|
40
|
-
e2eFolder?: string | undefined;
|
|
41
|
-
componentFolder?: string | undefined;
|
|
42
|
-
fixturesFolder?: string | undefined;
|
|
43
|
-
supportFolder?: string | undefined;
|
|
44
|
-
};
|
|
45
|
+
repoInventorySummary: {
|
|
45
46
|
framework?: {
|
|
46
|
-
confidence: "high" | "medium" | "low";
|
|
47
|
-
evidence: string[];
|
|
48
47
|
primary: "unknown" | "nextjs-app-router" | "nextjs-pages-router" | "express" | "remix" | "nuxt" | "sveltekit" | "astro" | "vite";
|
|
48
|
+
confidence: "high" | "medium" | "low";
|
|
49
49
|
testFrameworks: ("playwright" | "cypress-e2e" | "cypress-component" | "jest" | "vitest" | "other")[];
|
|
50
|
+
evidenceCount: number;
|
|
50
51
|
} | undefined;
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
recommendations: string[];
|
|
59
|
-
dimension: "test-coverage-breadth" | "framework-adoption" | "test-id-hygiene" | "ci-integration" | "auth-test-coverage" | "component-test-ratio";
|
|
60
|
-
score: number;
|
|
61
|
-
weight: number;
|
|
62
|
-
evidence: string[];
|
|
63
|
-
}[];
|
|
64
|
-
topRecommendations: string[];
|
|
65
|
-
} | undefined;
|
|
52
|
+
repoPath: string;
|
|
53
|
+
scannedAt: string;
|
|
54
|
+
routeCount: number;
|
|
55
|
+
testFileCount: number;
|
|
56
|
+
missingTestIdCount: number;
|
|
57
|
+
interactiveTsxFilesScanned: number | null;
|
|
58
|
+
cypressDetected: boolean;
|
|
66
59
|
} | null;
|
|
67
60
|
decisionLogPreview: {
|
|
68
61
|
timestamp: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compact-analyze-payload.d.ts","sourceRoot":"","sources":["../src/compact-analyze-payload.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAqBjD,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,aAAa,EAAE,iBAAiB,EAAE,OAAO
|
|
1
|
+
{"version":3,"file":"compact-analyze-payload.d.ts","sourceRoot":"","sources":["../src/compact-analyze-payload.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAqBjD,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,aAAa,EAAE,iBAAiB,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4BAuG+9d,CAAC;sBAA4C,CAAC;sBAA4C,CAAC;iBAAuC,CAAC;;;;;;;;;;;;;;;;;uBAAghB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;EAD9sf"}
|
|
@@ -33,6 +33,26 @@ export function buildCompactAnalyzePayload(result, includeFullReport) {
|
|
|
33
33
|
}
|
|
34
34
|
: null;
|
|
35
35
|
const ps = result.publicSurface;
|
|
36
|
+
const repo = result.repoInventory;
|
|
37
|
+
const repoInventorySummary = repo
|
|
38
|
+
? {
|
|
39
|
+
repoPath: repo.repoPath,
|
|
40
|
+
scannedAt: repo.scannedAt,
|
|
41
|
+
routeCount: repo.routes.length,
|
|
42
|
+
testFileCount: repo.testFiles.length,
|
|
43
|
+
missingTestIdCount: repo.missingTestIds.length,
|
|
44
|
+
interactiveTsxFilesScanned: repo.interactiveTsxFilesScanned ?? null,
|
|
45
|
+
cypressDetected: repo.cypressStructure.detected,
|
|
46
|
+
...(repo.framework && {
|
|
47
|
+
framework: {
|
|
48
|
+
primary: repo.framework.primary,
|
|
49
|
+
confidence: repo.framework.confidence,
|
|
50
|
+
testFrameworks: repo.framework.testFrameworks,
|
|
51
|
+
evidenceCount: repo.framework.evidence.length,
|
|
52
|
+
},
|
|
53
|
+
}),
|
|
54
|
+
}
|
|
55
|
+
: null;
|
|
36
56
|
return {
|
|
37
57
|
summary: {
|
|
38
58
|
status: result.status,
|
|
@@ -78,18 +98,18 @@ export function buildCompactAnalyzePayload(result, includeFullReport) {
|
|
|
78
98
|
pagesSkipped: result.routeInventory.pagesSkipped,
|
|
79
99
|
budgetExceeded: result.routeInventory.budgetExceeded,
|
|
80
100
|
},
|
|
81
|
-
...(
|
|
101
|
+
...(repo?.automationMaturity && {
|
|
82
102
|
automationMaturitySummary: {
|
|
83
|
-
overallScore:
|
|
84
|
-
level:
|
|
85
|
-
label:
|
|
86
|
-
topRecommendations:
|
|
103
|
+
overallScore: repo.automationMaturity.overallScore,
|
|
104
|
+
level: repo.automationMaturity.level,
|
|
105
|
+
label: repo.automationMaturity.label,
|
|
106
|
+
topRecommendations: repo.automationMaturity.topRecommendations,
|
|
87
107
|
},
|
|
88
108
|
}),
|
|
89
|
-
|
|
109
|
+
repoInventorySummary,
|
|
90
110
|
decisionLogPreview: result.decisionLog.slice(-8),
|
|
91
111
|
...(result.detectedAuth !== undefined && { detectedAuth: result.detectedAuth }),
|
|
92
112
|
includeFullReport: false,
|
|
93
|
-
note: 'Summary-first payload. Pass includeFullReport: true for full gapAnalysis (all scenarios and
|
|
113
|
+
note: 'Summary-first payload. Pass includeFullReport: true for the full gapAnalysis (all scenarios, generated tests) and the full repoInventory (test files, missing test IDs).',
|
|
94
114
|
};
|
|
95
115
|
}
|
package/dist/index.js
CHANGED
|
@@ -8,9 +8,12 @@
|
|
|
8
8
|
// TODO(@qulib/mcp): Evaluate tool-level permission modeling when MCP spec stabilizes.
|
|
9
9
|
// Today: all tools are equally trusted. Future: read-only tools (detect_auth, explore_auth)
|
|
10
10
|
// vs. write-capable tools (analyze_app with writeArtifacts) should carry different trust levels.
|
|
11
|
+
import { createRequire } from 'node:module';
|
|
11
12
|
import { isAbsolute, normalize, resolve } from 'node:path';
|
|
12
13
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
13
14
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
15
|
+
const requirePkg = createRequire(import.meta.url);
|
|
16
|
+
const pkg = requirePkg('../package.json');
|
|
14
17
|
import { analyzeApp, detectAuth, exploreAuth, scanRepo, computeAutomationMaturity, } from '@qulib/core';
|
|
15
18
|
import { z } from 'zod';
|
|
16
19
|
import { buildCompactAnalyzePayload } from './compact-analyze-payload.js';
|
|
@@ -39,6 +42,12 @@ const mcpProgressLog = {
|
|
|
39
42
|
error: (message) => log.error(message),
|
|
40
43
|
debug: (message) => log.debug(message),
|
|
41
44
|
};
|
|
45
|
+
// NOTE: MCP `auth` shape intentionally flattens the core `AuthConfigSchema` so an LLM
|
|
46
|
+
// can populate it without nested objects. We translate it back into core's nested
|
|
47
|
+
// `AuthConfig` (with `credentials: { username, password }` and `selectors: { ... }`)
|
|
48
|
+
// before passing it to `analyzeApp` below. If core's `AuthConfigSchema` changes, mirror
|
|
49
|
+
// the change here. Drift is allowed because the surfaces serve different consumers
|
|
50
|
+
// (LLM tool input vs internal harness contract), but the translation must stay 1:1.
|
|
42
51
|
const FormLoginMcpAuthSchema = z.object({
|
|
43
52
|
type: z.literal('form-login'),
|
|
44
53
|
loginUrl: z.string().url(),
|
|
@@ -83,7 +92,7 @@ function validateAbsoluteRepoPath(repoPath) {
|
|
|
83
92
|
}
|
|
84
93
|
const mcpServer = new McpServer({
|
|
85
94
|
name: 'qulib-mcp',
|
|
86
|
-
version:
|
|
95
|
+
version: pkg.version,
|
|
87
96
|
description: 'Qulib QA intelligence platform — gap analysis, auth exploration, and quality scoring for deployed web applications',
|
|
88
97
|
}, {
|
|
89
98
|
capabilities: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qulib/mcp",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"description": "MCP server for Qulib — AI-callable QA gap analysis",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Tapesh Nagarwal",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
36
|
-
"@qulib/core": "0.4.
|
|
36
|
+
"@qulib/core": "0.4.2",
|
|
37
37
|
"zod": "^3.23.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|