@qulib/mcp 0.8.2 → 0.9.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 +38 -12
- package/dist/index.js +126 -4
- package/package.json +8 -5
package/README.md
CHANGED
|
@@ -43,22 +43,48 @@ For verbose server-side stderr logs while troubleshooting host wiring, add:
|
|
|
43
43
|
}
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
-
##
|
|
46
|
+
## MCP tools
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
| Tool | Purpose |
|
|
49
|
+
|---|---|
|
|
50
|
+
| **`qulib_score_confidence`** | **Flagship.** Fuses evidence from `analyze_app`, `qulib_score_automation`, and `qulib_score_api` into one verdict: **ship / caution / hold / block** with a 0–100 confidence score, L1–L5 level, per-source contributions, honesty notes, and recommended next checks. Pass `url` and/or `repoPath`. |
|
|
51
|
+
| `analyze_app` | Live-app quality scan: release confidence (0–100), axe-core a11y, broken links, console errors, prioritized gaps. Default payload is summary-first; pass `includeFullReport: true` for all scenarios. Optional form-login / storage-state auth. |
|
|
52
|
+
| `qulib_score_automation` | Score a local repo's test-automation maturity across six dimensions (test coverage breadth, framework adoption, test-id hygiene, CI integration, auth test coverage, component test ratio) — plus a conditional 7th dimension (API coverage) when API endpoints are detected. Returns overall 0–100, level (L1–L5), and top recommendations. Each dimension carries `applicability`; score normalizes over applicable dimensions only. |
|
|
53
|
+
| `qulib_score_api` | Discover API endpoints in a repo and score their test coverage. Tier1=OpenAPI specs, Tier2=framework routes (Next.js, Express, Fastify, NestJS), Tier3=heuristic opt-in (tRPC). Returns an api-test-coverage dimension score with per-endpoint evidence. |
|
|
54
|
+
| `qulib_scaffold_tests` | Generate a ready-to-run test scaffold (Cypress or Playwright config + spec files) by crawling a deployed URL. Returns `generatedTests` and `projectConfig` so an agent can write files directly. Pass `recipes` (e.g. `["auth","a11y"]`) to append proven test patterns. |
|
|
55
|
+
| `explore_auth` | List all sign-in paths (OAuth, SSO, forms, magic link) and what the agent must collect before `analyze_app`. Prefer on unfamiliar apps. |
|
|
56
|
+
| `detect_auth` | Single-pass auth pattern guess with a recommendation. Lighter than `explore_auth`. |
|
|
49
57
|
|
|
50
|
-
|
|
51
|
-
- **`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).
|
|
52
|
-
- **`detect_auth(url, timeoutMs?)`** — single-pattern auth guess with a short recommendation (lighter than `explore_auth`).
|
|
53
|
-
- **`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.
|
|
58
|
+
**Example — flagship confidence call:**
|
|
54
59
|
|
|
55
|
-
|
|
60
|
+
```
|
|
61
|
+
qulib_score_confidence({ url: "https://example.com", repoPath: "/path/to/repo" })
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Returns a verdict like:
|
|
65
|
+
|
|
66
|
+
```json
|
|
67
|
+
{
|
|
68
|
+
"releaseConfidence": {
|
|
69
|
+
"verdict": "caution",
|
|
70
|
+
"confidenceScore": 54,
|
|
71
|
+
"level": 3,
|
|
72
|
+
"label": "Moderate confidence — proceed with known risks",
|
|
73
|
+
"topRisks": ["Low crawl coverage (2 pages)", "No CI integration detected"],
|
|
74
|
+
"recommendedNextChecks": ["Add CI pipeline", "Increase crawl depth"],
|
|
75
|
+
"honestyNotes": ["API coverage: not_applicable (no API endpoints found — excluded from score)"]
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### `analyze_app` detail
|
|
81
|
+
|
|
82
|
+
- **Default payload:** `summary`, `topGaps`, `costIntelligenceSummary`, `nextDeterministicChecks`, small previews.
|
|
83
|
+
- **`includeFullReport: true`** — full `gapAnalysis` (all scenarios) and full `repoInventory`.
|
|
84
|
+
- **`agentSummary: true`** — compact gate-decision payload (`pass`/`warn`/`fail`) for CI orchestrators.
|
|
85
|
+
- Optional harness overrides: **`llmMaxOutputTokensPerCall`**, **`llmTokenBudget`** (legacy), **`testGenerationLimit`**, **`enableLlmScenarios`**.
|
|
56
86
|
|
|
57
|
-
|
|
58
|
-
- Accessibility violations (axe-core, WCAG 2 A/AA)
|
|
59
|
-
- Broken links
|
|
60
|
-
- Console errors and coverage warnings
|
|
61
|
-
- Prioritized gaps with severity
|
|
87
|
+
Returns: release confidence score (0–100), accessibility violations (axe-core, WCAG 2 A/AA), broken links, console errors and coverage warnings, prioritized gaps with severity.
|
|
62
88
|
|
|
63
89
|
Supports optional form-login auth for scanning authenticated pages. If auth is required but not configured, the scan can stop early with `mode: auth-required` and guidance in `detectedAuth` / the decision log.
|
|
64
90
|
|
package/dist/index.js
CHANGED
|
@@ -14,7 +14,8 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
|
14
14
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
15
15
|
const requirePkg = createRequire(import.meta.url);
|
|
16
16
|
const pkg = requirePkg('../package.json');
|
|
17
|
-
import { analyzeApp, detectAuth, exploreAuth, scanRepo, computeAutomationMaturity, scaffoldTests, discoverApiSurfaceWithRepo, computeApiCoverage, } from '@qulib/core';
|
|
17
|
+
import { analyzeApp, detectAuth, exploreAuth, scanRepo, computeAutomationMaturity, scaffoldTests, discoverApiSurfaceWithRepo, computeApiCoverage, computeReleaseConfidence, buildConfidenceInputFromQulib, } from '@qulib/core';
|
|
18
|
+
import { RecipeIdSchema } from '@qulib/core';
|
|
18
19
|
import { z } from 'zod';
|
|
19
20
|
import { buildAnalyzeAppMcpPayload } from './analyze-app-mcp-payload.js';
|
|
20
21
|
import { log } from './logger.js';
|
|
@@ -274,18 +275,31 @@ const ScaffoldTestsInputSchema = z.object({
|
|
|
274
275
|
.max(20)
|
|
275
276
|
.optional()
|
|
276
277
|
.describe('Max pages to crawl when running analyze_app internally. Default: 10'),
|
|
278
|
+
recipes: z
|
|
279
|
+
.array(RecipeIdSchema)
|
|
280
|
+
.optional()
|
|
281
|
+
.describe('Optional list of reusable test-pattern recipes to append to the scaffold. ' +
|
|
282
|
+
'Each recipe adds proven NQ-2/CaseLoom-derived scenarios: ' +
|
|
283
|
+
'"auth" = login/logout/protected-route flows; ' +
|
|
284
|
+
'"a11y" = heading/landmark/title accessibility checks; ' +
|
|
285
|
+
'"nav" = deep-link/browser-back/404 handling; ' +
|
|
286
|
+
'"seed" = data-seeding/state-reset helpers. ' +
|
|
287
|
+
'Recipe scenarios are APPENDED to crawl-derived scenarios — they never replace them. ' +
|
|
288
|
+
'Example: ["auth", "a11y"] adds 6 ready-to-run test scenarios.'),
|
|
277
289
|
});
|
|
278
290
|
mcpServer.registerTool('qulib_scaffold_tests', {
|
|
279
|
-
description: 'Generate a ready-to-run test scaffold for a deployed web app. Crawls the URL, identifies quality gaps and user flows, then produces framework-specific test files (Cypress or Playwright) plus the project config (cypress.config.ts or playwright.config.ts) and package.json deps. Returns generatedTests (array of {filename, code, outputPath}) and projectConfig so an agent can write the files directly to a repo without any manual test-writing.',
|
|
291
|
+
description: 'Generate a ready-to-run test scaffold for a deployed web app. Crawls the URL, identifies quality gaps and user flows, then produces framework-specific test files (Cypress or Playwright) plus the project config (cypress.config.ts or playwright.config.ts) and package.json deps. Returns generatedTests (array of {filename, code, outputPath}) and projectConfig so an agent can write the files directly to a repo without any manual test-writing. Optionally pass recipes (e.g. ["auth","a11y"]) to append proven NQ-2/CaseLoom-derived test patterns for common flows — auth adds login/logout/protected-route tests, a11y adds heading/landmark/title checks, nav adds deep-link/404 tests, seed adds state-reset helpers.',
|
|
280
292
|
inputSchema: ScaffoldTestsInputSchema,
|
|
281
|
-
}, async ({ url, framework, maxPagesToScan }) => {
|
|
293
|
+
}, async ({ url, framework, maxPagesToScan, recipes }) => {
|
|
282
294
|
try {
|
|
283
|
-
|
|
295
|
+
const recipesLog = recipes && recipes.length > 0 ? ` recipes=[${recipes.join(',')}]` : '';
|
|
296
|
+
log.info(`qulib_scaffold_tests url=${url} framework=${framework ?? 'cypress-e2e'} maxPagesToScan=${maxPagesToScan ?? 10}${recipesLog}`);
|
|
284
297
|
const result = await scaffoldTests(url, {
|
|
285
298
|
framework: framework ?? 'cypress-e2e',
|
|
286
299
|
maxPagesToScan: maxPagesToScan ?? 10,
|
|
287
300
|
progressLog: mcpProgressLog,
|
|
288
301
|
telemetry: telemetrySink,
|
|
302
|
+
...(recipes && recipes.length > 0 && { recipes }),
|
|
289
303
|
});
|
|
290
304
|
return {
|
|
291
305
|
content: [
|
|
@@ -359,5 +373,113 @@ mcpServer.registerTool('qulib_score_api', {
|
|
|
359
373
|
return toolError('QULIB_API_SCORE_FAILED', msg, err instanceof Error ? err.stack : undefined);
|
|
360
374
|
}
|
|
361
375
|
});
|
|
376
|
+
// ---------------------------------------------------------------------------
|
|
377
|
+
// qulib_score_confidence — P3 Release Confidence Aggregator
|
|
378
|
+
// Composes existing collectors (analyze_app, qulib_score_automation,
|
|
379
|
+
// qulib_score_api) into one fused Release Confidence verdict. Honors the
|
|
380
|
+
// tool-explosion guardrail by composing, not fanning out (index.ts lines 4–10).
|
|
381
|
+
// ---------------------------------------------------------------------------
|
|
382
|
+
const ScoreConfidenceInputSchema = z.object({
|
|
383
|
+
url: z.string().url().optional().describe('URL of the deployed app to analyze (runs analyze_app if provided)'),
|
|
384
|
+
repoPath: z
|
|
385
|
+
.string()
|
|
386
|
+
.optional()
|
|
387
|
+
.describe('Absolute path to the repository (runs qulib_score_automation + qulib_score_api if provided)'),
|
|
388
|
+
includeViews: z
|
|
389
|
+
.object({
|
|
390
|
+
replay: z.boolean().optional().describe('Include the Replay provenance trace in the response'),
|
|
391
|
+
})
|
|
392
|
+
.optional()
|
|
393
|
+
.describe('Optional projection flags — which views to include beyond the Release Confidence view'),
|
|
394
|
+
subject: z
|
|
395
|
+
.object({
|
|
396
|
+
kind: z.enum(['release', 'pr', 'deploy', 'app', 'repo']).optional(),
|
|
397
|
+
ref: z.string().optional(),
|
|
398
|
+
tenantId: z.string().optional(),
|
|
399
|
+
})
|
|
400
|
+
.optional()
|
|
401
|
+
.describe('Subject metadata for the confidence verdict; defaults are inferred from url/repoPath'),
|
|
402
|
+
});
|
|
403
|
+
mcpServer.registerTool('qulib_score_confidence', {
|
|
404
|
+
description: 'Compute a fused Release Confidence verdict by composing qulib evidence collectors. ' +
|
|
405
|
+
'Given a URL and/or repo path, runs analyze_app / qulib_score_automation / qulib_score_api as applicable, ' +
|
|
406
|
+
'then fuses the signals into one verdict (ship | caution | hold | block) with a 0–100 confidence score, ' +
|
|
407
|
+
'L1–L5 level, per-source contributions, honesty notes for any excluded/unknown source, and recommended next checks. ' +
|
|
408
|
+
'Returns the Release Confidence view. Pass includeViews.replay for the full provenance trace.',
|
|
409
|
+
inputSchema: ScoreConfidenceInputSchema,
|
|
410
|
+
}, async ({ url, repoPath, includeViews, subject }) => {
|
|
411
|
+
try {
|
|
412
|
+
const subjectRef = subject?.ref ?? url ?? repoPath ?? 'unknown';
|
|
413
|
+
const subjectKind = subject?.kind ?? (url && repoPath ? 'release' : url ? 'app' : 'repo');
|
|
414
|
+
const tenantId = subject?.tenantId ?? 'default';
|
|
415
|
+
const confidenceSubject = { kind: subjectKind, ref: subjectRef, tenantId };
|
|
416
|
+
// Collect evidence from whichever collectors apply.
|
|
417
|
+
let analyzeResult;
|
|
418
|
+
let maturityResult;
|
|
419
|
+
let apiCoverageResult;
|
|
420
|
+
if (url) {
|
|
421
|
+
log.info(`qulib_score_confidence: running analyze_app url=${url}`);
|
|
422
|
+
const harnessConfig = {
|
|
423
|
+
maxPagesToScan: 10,
|
|
424
|
+
maxDepth: 3,
|
|
425
|
+
minPagesForConfidence: 3,
|
|
426
|
+
timeoutMs: 30000,
|
|
427
|
+
retryCount: 0,
|
|
428
|
+
llmTokenBudget: 4096,
|
|
429
|
+
testGenerationLimit: 5,
|
|
430
|
+
enableLlmScenarios: false,
|
|
431
|
+
readOnlyMode: true,
|
|
432
|
+
requireHumanReview: false,
|
|
433
|
+
failOnConsoleError: false,
|
|
434
|
+
explorer: 'playwright',
|
|
435
|
+
defaultAdapter: 'playwright',
|
|
436
|
+
adapters: ['playwright'],
|
|
437
|
+
};
|
|
438
|
+
analyzeResult = await analyzeApp({
|
|
439
|
+
url,
|
|
440
|
+
writeArtifacts: false,
|
|
441
|
+
config: harnessConfig,
|
|
442
|
+
progressLog: mcpProgressLog,
|
|
443
|
+
telemetry: telemetrySink,
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
if (repoPath) {
|
|
447
|
+
const abs = validateAbsoluteRepoPath(repoPath);
|
|
448
|
+
log.info(`qulib_score_confidence: running qulib_score_automation + qulib_score_api repoPath=${abs}`);
|
|
449
|
+
const repo = await scanRepo(abs);
|
|
450
|
+
maturityResult = computeAutomationMaturity(repo);
|
|
451
|
+
const apiSurface = await discoverApiSurfaceWithRepo(abs, repo, { enableTier3: false });
|
|
452
|
+
apiCoverageResult = computeApiCoverage(repo, apiSurface);
|
|
453
|
+
}
|
|
454
|
+
// Build the evidence bundle from qulib's own collectors.
|
|
455
|
+
const confidenceInput = buildConfidenceInputFromQulib({
|
|
456
|
+
analyze: analyzeResult,
|
|
457
|
+
maturity: maturityResult,
|
|
458
|
+
apiCoverage: apiCoverageResult,
|
|
459
|
+
subject: confidenceSubject,
|
|
460
|
+
});
|
|
461
|
+
// Run the pure scorer.
|
|
462
|
+
const rc = computeReleaseConfidence(confidenceInput);
|
|
463
|
+
// Build the response payload (Release Confidence view is always included).
|
|
464
|
+
const payload = { releaseConfidence: rc };
|
|
465
|
+
if (includeViews?.replay) {
|
|
466
|
+
const { buildReplay } = await import('@qulib/core');
|
|
467
|
+
payload['replay'] = buildReplay(confidenceInput, rc);
|
|
468
|
+
}
|
|
469
|
+
log.info(`qulib_score_confidence done verdict=${rc.verdict} confidenceScore=${rc.confidenceScore ?? 'null'} ` +
|
|
470
|
+
`level=${rc.level} evidenceSources=${confidenceInput.evidence.length}`);
|
|
471
|
+
return {
|
|
472
|
+
content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }],
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
catch (err) {
|
|
476
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
477
|
+
if (msg.includes('repoPath must')) {
|
|
478
|
+
return toolError('QULIB_INPUT_INVALID', msg, undefined);
|
|
479
|
+
}
|
|
480
|
+
log.error(`qulib_score_confidence failed: ${msg}`);
|
|
481
|
+
return toolError('QULIB_CONFIDENCE_FAILED', msg, err instanceof Error ? err.stack : undefined);
|
|
482
|
+
}
|
|
483
|
+
});
|
|
362
484
|
const transport = new StdioServerTransport();
|
|
363
485
|
await mcpServer.connect(transport);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qulib/mcp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "MCP server for Qulib — AI-callable
|
|
3
|
+
"version": "0.9.0",
|
|
4
|
+
"description": "MCP server for Qulib — AI-callable release confidence. Seven tools: fused verdict, live-app scan, automation maturity, API coverage, test scaffold, and auth tools.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Tapesh Nagarwal",
|
|
7
7
|
"homepage": "https://github.com/TapeshN/qulib#readme",
|
|
@@ -28,12 +28,13 @@
|
|
|
28
28
|
],
|
|
29
29
|
"scripts": {
|
|
30
30
|
"build": "npm --prefix ../.. run build -w @qulib/core && tsc && chmod +x dist/index.js",
|
|
31
|
+
"prepublishOnly": "npm run build",
|
|
31
32
|
"dev": "tsx src/index.ts",
|
|
32
|
-
"test": "node --import tsx/esm --test src/__tests__/summarize-analyze-result.test.ts src/__tests__/analyze-app-mcp-payload.test.ts"
|
|
33
|
+
"test": "node --import tsx/esm --test src/__tests__/summarize-analyze-result.test.ts src/__tests__/analyze-app-mcp-payload.test.ts src/__tests__/score-confidence-mcp.test.ts"
|
|
33
34
|
},
|
|
34
35
|
"dependencies": {
|
|
35
36
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
36
|
-
"@qulib/core": "0.
|
|
37
|
+
"@qulib/core": "0.9.0",
|
|
37
38
|
"zod": "^3.23.0"
|
|
38
39
|
},
|
|
39
40
|
"devDependencies": {
|
|
@@ -43,10 +44,12 @@
|
|
|
43
44
|
},
|
|
44
45
|
"keywords": [
|
|
45
46
|
"mcp",
|
|
47
|
+
"release-confidence",
|
|
46
48
|
"qa",
|
|
47
49
|
"quality",
|
|
50
|
+
"ship-verdict",
|
|
51
|
+
"automation-maturity",
|
|
48
52
|
"accessibility",
|
|
49
|
-
"release-confidence",
|
|
50
53
|
"ai"
|
|
51
54
|
]
|
|
52
55
|
}
|