@qulib/mcp 0.4.1 → 0.4.3

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` with every scenario |
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
@@ -42,51 +42,20 @@ export declare function buildCompactAnalyzePayload(result: AnalyzeResult, includ
42
42
  };
43
43
  }[] | undefined;
44
44
  } | undefined;
45
- repoInventory: {
46
- scannedAt: string;
47
- routes: {
48
- path: string;
49
- method: "unknown" | "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
50
- file: string;
51
- }[];
52
- repoPath: string;
53
- testFiles: {
54
- type: "playwright" | "cypress-e2e" | "cypress-component" | "jest" | "vitest" | "other";
55
- file: string;
56
- coveredPaths: string[];
57
- }[];
58
- missingTestIds: string[];
59
- cypressStructure: {
60
- detected: boolean;
61
- hasCommandsFile: boolean;
62
- existingE2eFiles: string[];
63
- existingComponentFiles: string[];
64
- e2eFolder?: string | undefined;
65
- componentFolder?: string | undefined;
66
- fixturesFolder?: string | undefined;
67
- supportFolder?: string | undefined;
68
- };
45
+ repoInventorySummary: {
69
46
  framework?: {
70
- confidence: "high" | "medium" | "low";
71
- evidence: string[];
72
47
  primary: "unknown" | "nextjs-app-router" | "nextjs-pages-router" | "express" | "remix" | "nuxt" | "sveltekit" | "astro" | "vite";
48
+ confidence: "high" | "medium" | "low";
73
49
  testFrameworks: ("playwright" | "cypress-e2e" | "cypress-component" | "jest" | "vitest" | "other")[];
50
+ evidenceCount: number;
74
51
  } | undefined;
75
- automationMaturity?: {
76
- label: string;
77
- level: number;
78
- computedAt: string;
79
- repoPath: string;
80
- overallScore: number;
81
- dimensions: {
82
- recommendations: string[];
83
- dimension: "test-coverage-breadth" | "framework-adoption" | "test-id-hygiene" | "ci-integration" | "auth-test-coverage" | "component-test-ratio";
84
- score: number;
85
- weight: number;
86
- evidence: string[];
87
- }[];
88
- topRecommendations: string[];
89
- } | undefined;
52
+ repoPath: string;
53
+ scannedAt: string;
54
+ routeCount: number;
55
+ testFileCount: number;
56
+ missingTestIdCount: number;
57
+ interactiveTsxFilesScanned: number | null;
58
+ cypressDetected: boolean;
90
59
  } | null;
91
60
  decisionLogPreview: {
92
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAkFs3L,CAAC;2BAA6C,CAAC;0BAA4C,CAAC;yBAA2C,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4BAAkqT,CAAC;sBAA4C,CAAC;sBAA4C,CAAC;iBAAuC,CAAC;;;;;;;;;;;;;;;;;uBAAghB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;EAD/4gB"}
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
- ...(result.repoInventory?.automationMaturity && {
101
+ ...(repo?.automationMaturity && {
82
102
  automationMaturitySummary: {
83
- overallScore: result.repoInventory.automationMaturity.overallScore,
84
- level: result.repoInventory.automationMaturity.level,
85
- label: result.repoInventory.automationMaturity.label,
86
- topRecommendations: result.repoInventory.automationMaturity.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
- repoInventory: result.repoInventory,
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 generatedTests).',
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,12 +8,15 @@
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
- import { buildCompactAnalyzePayload } from './compact-analyze-payload.js';
19
+ import { summarizeAnalyzeResult } from './summarize-analyze-result.js';
17
20
  import { log } from './logger.js';
18
21
  function toolError(code, message, detail) {
19
22
  return {
@@ -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: '0.4.1',
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: {
@@ -193,7 +202,7 @@ mcpServer.registerTool('analyze_app', {
193
202
  progressLog: mcpProgressLog,
194
203
  telemetry: telemetrySink,
195
204
  });
196
- const payload = buildCompactAnalyzePayload(result, input.includeFullReport === true);
205
+ const payload = summarizeAnalyzeResult(result, input.includeFullReport === true);
197
206
  return {
198
207
  content: [
199
208
  {
@@ -0,0 +1,164 @@
1
+ import type { AnalyzeResult } from '@qulib/core';
2
+ export declare function summarizeAnalyzeResult(result: AnalyzeResult, includeFullReport: boolean): AnalyzeResult | {
3
+ includeFullReport: boolean;
4
+ note: string;
5
+ detectedAuth?: {
6
+ type: "unknown" | "form-login" | "oauth" | "magic-link" | "none";
7
+ loginUrl: string | null;
8
+ provider: string | null;
9
+ hasAuth: boolean;
10
+ observedSelectors: {
11
+ usernameSelector: string | null;
12
+ passwordSelector: string | null;
13
+ submitSelector: string | null;
14
+ } | null;
15
+ oauthButtons: {
16
+ text: string;
17
+ provider: string;
18
+ }[];
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;
44
+ } | undefined;
45
+ repoInventorySummary: {
46
+ framework?: {
47
+ primary: "unknown" | "nextjs-app-router" | "nextjs-pages-router" | "express" | "remix" | "nuxt" | "sveltekit" | "astro" | "vite";
48
+ confidence: "high" | "medium" | "low";
49
+ testFrameworks: ("playwright" | "cypress-e2e" | "cypress-component" | "jest" | "vitest" | "other")[];
50
+ evidenceCount: number;
51
+ } | undefined;
52
+ repoPath: string;
53
+ scannedAt: string;
54
+ routeCount: number;
55
+ testFileCount: number;
56
+ missingTestIdCount: number;
57
+ interactiveTsxFilesScanned: number | null;
58
+ cypressDetected: boolean;
59
+ } | null;
60
+ decisionLogPreview: {
61
+ timestamp: string;
62
+ reason: string;
63
+ phase: "observe" | "think" | "act" | "harness";
64
+ decision: string;
65
+ metadata?: Record<string, unknown> | undefined;
66
+ }[];
67
+ automationMaturitySummary?: {
68
+ overallScore: number;
69
+ level: number;
70
+ label: string;
71
+ topRecommendations: string[];
72
+ } | undefined;
73
+ summary: {
74
+ status: import("@qulib/core").AnalyzeStatus;
75
+ coverageScore: number | null;
76
+ releaseConfidence: number | null;
77
+ mode: "url-only" | "url-repo" | "auth-required";
78
+ coveragePagesScanned: number;
79
+ coverageBudgetExceeded: boolean;
80
+ coverageWarning: "auth-required" | "budget-exceeded" | "low-coverage" | "navigation-failures" | null;
81
+ gapCount: number;
82
+ scenarioCount: number;
83
+ generatedTestCount: number;
84
+ publicSurface: {
85
+ pageCount: number;
86
+ gapCount: number;
87
+ accessibilityViolationCount: number;
88
+ brokenLinkCount: number;
89
+ } | null;
90
+ };
91
+ topGaps: {
92
+ path: string;
93
+ category: "untested-route" | "a11y" | "console-error" | "broken-link" | "auth-surface" | "coverage";
94
+ severity: "critical" | "high" | "medium" | "low";
95
+ reason: string;
96
+ }[];
97
+ costIntelligenceSummary: {
98
+ maxOutputTokensPerLlmCall: number;
99
+ usageDataQuality: "none" | "actual" | "estimated" | "mixed";
100
+ totalInputTokens: number;
101
+ totalOutputTokens: number;
102
+ budgetWarningCount: number;
103
+ maturityLevel: number;
104
+ maturityLabel: string;
105
+ } | null;
106
+ costIntelligence: {
107
+ maxOutputTokensPerLlmCall: number;
108
+ budgetRole: "max-output-tokens-per-llm-call";
109
+ records: {
110
+ provider: string;
111
+ model: string;
112
+ inputTokens: number;
113
+ outputTokens: number;
114
+ operationType: "scenario-generation";
115
+ timestamp: string;
116
+ dataQuality: "none" | "actual" | "estimated" | "mixed";
117
+ estimatedCostUsd?: number | undefined;
118
+ promptHash?: string | undefined;
119
+ resultHash?: string | undefined;
120
+ notes?: string | undefined;
121
+ }[];
122
+ budgetWarnings: string[];
123
+ usageSummary: {
124
+ dataQuality: "none" | "actual" | "estimated" | "mixed";
125
+ totalInputTokens: number;
126
+ totalOutputTokens: number;
127
+ };
128
+ repeatedOperations: {
129
+ recommendation: string;
130
+ promptHash: string;
131
+ count: number;
132
+ }[];
133
+ deterministicMaturity: {
134
+ label: string;
135
+ level: number;
136
+ rationale: string;
137
+ ceilingNote?: string | undefined;
138
+ };
139
+ conversionRecommendations: string[];
140
+ } | null;
141
+ nextDeterministicChecks: string[];
142
+ gapAnalysisPreview: {
143
+ analyzedAt: string;
144
+ gapsSample: {
145
+ path: string;
146
+ id: string;
147
+ severity: "critical" | "high" | "medium" | "low";
148
+ reason: string;
149
+ category: "untested-route" | "a11y" | "console-error" | "broken-link" | "auth-surface" | "coverage";
150
+ recommendation?: string | undefined;
151
+ description?: string | undefined;
152
+ }[];
153
+ scenariosOmitted: number;
154
+ generatedTestsOmitted: number;
155
+ };
156
+ routeInventorySummary: {
157
+ scannedAt: string;
158
+ baseUrl: string;
159
+ routeCount: number;
160
+ pagesSkipped: number;
161
+ budgetExceeded: boolean;
162
+ };
163
+ };
164
+ //# sourceMappingURL=summarize-analyze-result.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"summarize-analyze-result.d.ts","sourceRoot":"","sources":["../src/summarize-analyze-result.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAqBjD,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,aAAa,EAAE,iBAAiB,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4BAuGu+d,CAAC;sBAA4C,CAAC;sBAA4C,CAAC;iBAAuC,CAAC;;;;;;;;;;;;;;;;;uBAAghB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;EADltf"}
@@ -0,0 +1,115 @@
1
+ const severityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
2
+ function topGapsBySeverity(gaps, limit) {
3
+ return [...gaps].sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]).slice(0, limit);
4
+ }
5
+ function nextDeterministicChecks(gaps, conversion) {
6
+ const out = [];
7
+ const byCat = new Map();
8
+ for (const g of gaps) {
9
+ byCat.set(g.category, (byCat.get(g.category) ?? 0) + 1);
10
+ }
11
+ for (const [cat, n] of [...byCat.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3)) {
12
+ out.push(`Add or tighten deterministic coverage for **${cat}** (${n} gap(s) in this scan).`);
13
+ }
14
+ out.push(...conversion.slice(0, 2));
15
+ return out.slice(0, 5);
16
+ }
17
+ export function summarizeAnalyzeResult(result, includeFullReport) {
18
+ if (includeFullReport) {
19
+ return result;
20
+ }
21
+ const g = result.gapAnalysis;
22
+ const ci = g.costIntelligence;
23
+ const top = topGapsBySeverity(result.gaps, 5);
24
+ const costSummary = ci
25
+ ? {
26
+ maxOutputTokensPerLlmCall: ci.maxOutputTokensPerLlmCall,
27
+ usageDataQuality: ci.usageSummary.dataQuality,
28
+ totalInputTokens: ci.usageSummary.totalInputTokens,
29
+ totalOutputTokens: ci.usageSummary.totalOutputTokens,
30
+ budgetWarningCount: ci.budgetWarnings.length,
31
+ maturityLevel: ci.deterministicMaturity.level,
32
+ maturityLabel: ci.deterministicMaturity.label,
33
+ }
34
+ : null;
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;
56
+ return {
57
+ summary: {
58
+ status: result.status,
59
+ coverageScore: result.coverageScore,
60
+ releaseConfidence: g.releaseConfidence,
61
+ mode: g.mode,
62
+ coveragePagesScanned: g.coveragePagesScanned,
63
+ coverageBudgetExceeded: g.coverageBudgetExceeded,
64
+ coverageWarning: g.coverageWarning ?? null,
65
+ gapCount: g.gaps.length,
66
+ scenarioCount: g.scenarios.length,
67
+ generatedTestCount: g.generatedTests.length,
68
+ publicSurface: ps === null
69
+ ? null
70
+ : {
71
+ pageCount: ps.pages.length,
72
+ gapCount: ps.gaps.length,
73
+ accessibilityViolationCount: ps.accessibilityViolations.length,
74
+ brokenLinkCount: ps.brokenLinks.length,
75
+ },
76
+ },
77
+ topGaps: top.map((x) => ({
78
+ path: x.path,
79
+ category: x.category,
80
+ severity: x.severity,
81
+ reason: x.reason,
82
+ })),
83
+ costIntelligenceSummary: costSummary,
84
+ costIntelligence: ci ?? null,
85
+ nextDeterministicChecks: ci
86
+ ? nextDeterministicChecks(result.gaps, ci.conversionRecommendations)
87
+ : nextDeterministicChecks(result.gaps, []),
88
+ gapAnalysisPreview: {
89
+ analyzedAt: g.analyzedAt,
90
+ gapsSample: g.gaps.slice(0, 8),
91
+ scenariosOmitted: g.scenarios.length,
92
+ generatedTestsOmitted: g.generatedTests.length,
93
+ },
94
+ routeInventorySummary: {
95
+ scannedAt: result.routeInventory.scannedAt,
96
+ baseUrl: result.routeInventory.baseUrl,
97
+ routeCount: result.routeInventory.routes.length,
98
+ pagesSkipped: result.routeInventory.pagesSkipped,
99
+ budgetExceeded: result.routeInventory.budgetExceeded,
100
+ },
101
+ ...(repo?.automationMaturity && {
102
+ automationMaturitySummary: {
103
+ overallScore: repo.automationMaturity.overallScore,
104
+ level: repo.automationMaturity.level,
105
+ label: repo.automationMaturity.label,
106
+ topRecommendations: repo.automationMaturity.topRecommendations,
107
+ },
108
+ }),
109
+ repoInventorySummary,
110
+ decisionLogPreview: result.decisionLog.slice(-8),
111
+ ...(result.detectedAuth !== undefined && { detectedAuth: result.detectedAuth }),
112
+ includeFullReport: false,
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).',
114
+ };
115
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qulib/mcp",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
4
4
  "description": "MCP server for Qulib — AI-callable QA gap analysis",
5
5
  "license": "MIT",
6
6
  "author": "Tapesh Nagarwal",
@@ -29,11 +29,11 @@
29
29
  "scripts": {
30
30
  "build": "npm --prefix ../.. run build -w @qulib/core && tsc && chmod +x dist/index.js",
31
31
  "dev": "tsx src/index.ts",
32
- "test": "node --import tsx/esm --test src/compact-analyze-payload.test.ts"
32
+ "test": "node --import tsx/esm --test src/__tests__/summarize-analyze-result.test.ts"
33
33
  },
34
34
  "dependencies": {
35
35
  "@modelcontextprotocol/sdk": "^1.0.0",
36
- "@qulib/core": "0.4.1",
36
+ "@qulib/core": "0.4.3",
37
37
  "zod": "^3.23.0"
38
38
  },
39
39
  "devDependencies": {