@qulib/mcp 0.2.2 → 0.3.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 +25 -1
- package/dist/compact-analyze-payload.d.ts +144 -0
- package/dist/compact-analyze-payload.d.ts.map +1 -0
- package/dist/compact-analyze-payload.js +87 -0
- package/dist/index.js +62 -20
- package/dist/logger.d.ts +7 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +29 -0
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
Tools:
|
|
8
8
|
|
|
9
9
|
- **`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.
|
|
10
|
-
- **`analyze_app
|
|
10
|
+
- **`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).
|
|
11
11
|
- **`detect_auth(url, timeoutMs?)`** — single-pattern auth guess with a short recommendation (lighter than `explore_auth`).
|
|
12
12
|
|
|
13
13
|
Returns from `analyze_app`:
|
|
@@ -61,6 +61,30 @@ When the model sees **`unrecognizedButtons`**, it can ask the user to register a
|
|
|
61
61
|
|
|
62
62
|
`qulib auth providers add --id <kebab-id> --label "..." --pattern "..."` — patterns are saved under **`~/.qulib/providers.json`** and merged with the built-in list on the next `explore_auth` / `explore-auth`. Nothing is auto-written without an explicit `providers add`.
|
|
63
63
|
|
|
64
|
+
## Compact vs full `analyze_app` response
|
|
65
|
+
|
|
66
|
+
| | Default (`includeFullReport` omitted or false) | `includeFullReport: true` |
|
|
67
|
+
|--|--|--|
|
|
68
|
+
| Size | Small: top gaps, cost summary, next checks | Full `gapAnalysis` with every scenario |
|
|
69
|
+
| When to use | Routine agent turns, chat context limits | Deep dives, exporting full scenario JSON |
|
|
70
|
+
|
|
71
|
+
Example (full):
|
|
72
|
+
|
|
73
|
+
```json
|
|
74
|
+
{ "url": "https://example.com", "includeFullReport": true }
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Example (tighter LLM envelope from MCP):
|
|
78
|
+
|
|
79
|
+
```json
|
|
80
|
+
{
|
|
81
|
+
"url": "https://example.com",
|
|
82
|
+
"llmMaxOutputTokensPerCall": 2048,
|
|
83
|
+
"testGenerationLimit": 5,
|
|
84
|
+
"enableLlmScenarios": true
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
64
88
|
## Example usage
|
|
65
89
|
|
|
66
90
|
Ask Claude:
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import type { AnalyzeResult } from '@qulib/core';
|
|
2
|
+
export declare function buildCompactAnalyzePayload(result: AnalyzeResult, includeFullReport: boolean): AnalyzeResult | {
|
|
3
|
+
includeFullReport: boolean;
|
|
4
|
+
note: string;
|
|
5
|
+
detectedAuth?: {
|
|
6
|
+
type: "unknown" | "form-login" | "none" | "oauth" | "magic-link";
|
|
7
|
+
loginUrl: string | null;
|
|
8
|
+
hasAuth: boolean;
|
|
9
|
+
provider: string | null;
|
|
10
|
+
observedSelectors: {
|
|
11
|
+
usernameSelector: string | null;
|
|
12
|
+
passwordSelector: string | null;
|
|
13
|
+
submitSelector: string | null;
|
|
14
|
+
} | null;
|
|
15
|
+
oauthButtons: {
|
|
16
|
+
provider: string;
|
|
17
|
+
text: string;
|
|
18
|
+
}[];
|
|
19
|
+
recommendation: string;
|
|
20
|
+
} | undefined;
|
|
21
|
+
summary: {
|
|
22
|
+
status: import("@qulib/core").AnalyzeStatus;
|
|
23
|
+
coverageScore: number | null;
|
|
24
|
+
releaseConfidence: number | null;
|
|
25
|
+
mode: "url-only" | "url-repo" | "auth-required";
|
|
26
|
+
coveragePagesScanned: number;
|
|
27
|
+
coverageBudgetExceeded: boolean;
|
|
28
|
+
coverageWarning: "auth-required" | "budget-exceeded" | "low-coverage" | "navigation-failures" | null;
|
|
29
|
+
gapCount: number;
|
|
30
|
+
scenarioCount: number;
|
|
31
|
+
generatedTestCount: number;
|
|
32
|
+
publicSurface: {
|
|
33
|
+
pageCount: number;
|
|
34
|
+
gapCount: number;
|
|
35
|
+
accessibilityViolationCount: number;
|
|
36
|
+
brokenLinkCount: number;
|
|
37
|
+
} | null;
|
|
38
|
+
};
|
|
39
|
+
topGaps: {
|
|
40
|
+
path: string;
|
|
41
|
+
category: "untested-route" | "a11y" | "console-error" | "broken-link" | "auth-surface" | "coverage";
|
|
42
|
+
severity: "high" | "medium" | "low" | "critical";
|
|
43
|
+
reason: string;
|
|
44
|
+
}[];
|
|
45
|
+
costIntelligenceSummary: {
|
|
46
|
+
maxOutputTokensPerLlmCall: number;
|
|
47
|
+
usageDataQuality: "none" | "actual" | "estimated" | "mixed";
|
|
48
|
+
totalInputTokens: number;
|
|
49
|
+
totalOutputTokens: number;
|
|
50
|
+
budgetWarningCount: number;
|
|
51
|
+
maturityLevel: number;
|
|
52
|
+
maturityLabel: string;
|
|
53
|
+
} | null;
|
|
54
|
+
costIntelligence: {
|
|
55
|
+
maxOutputTokensPerLlmCall: number;
|
|
56
|
+
budgetRole: "max-output-tokens-per-llm-call";
|
|
57
|
+
records: {
|
|
58
|
+
provider: string;
|
|
59
|
+
model: string;
|
|
60
|
+
inputTokens: number;
|
|
61
|
+
outputTokens: number;
|
|
62
|
+
operationType: "scenario-generation";
|
|
63
|
+
timestamp: string;
|
|
64
|
+
dataQuality: "none" | "actual" | "estimated" | "mixed";
|
|
65
|
+
estimatedCostUsd?: number | undefined;
|
|
66
|
+
promptHash?: string | undefined;
|
|
67
|
+
resultHash?: string | undefined;
|
|
68
|
+
notes?: string | undefined;
|
|
69
|
+
}[];
|
|
70
|
+
budgetWarnings: string[];
|
|
71
|
+
usageSummary: {
|
|
72
|
+
dataQuality: "none" | "actual" | "estimated" | "mixed";
|
|
73
|
+
totalInputTokens: number;
|
|
74
|
+
totalOutputTokens: number;
|
|
75
|
+
};
|
|
76
|
+
repeatedOperations: {
|
|
77
|
+
recommendation: string;
|
|
78
|
+
promptHash: string;
|
|
79
|
+
count: number;
|
|
80
|
+
}[];
|
|
81
|
+
deterministicMaturity: {
|
|
82
|
+
label: string;
|
|
83
|
+
level: number;
|
|
84
|
+
rationale: string;
|
|
85
|
+
ceilingNote?: string | undefined;
|
|
86
|
+
};
|
|
87
|
+
conversionRecommendations: string[];
|
|
88
|
+
} | null;
|
|
89
|
+
nextDeterministicChecks: string[];
|
|
90
|
+
gapAnalysisPreview: {
|
|
91
|
+
analyzedAt: string;
|
|
92
|
+
gapsSample: {
|
|
93
|
+
path: string;
|
|
94
|
+
id: string;
|
|
95
|
+
severity: "high" | "medium" | "low" | "critical";
|
|
96
|
+
reason: string;
|
|
97
|
+
category: "untested-route" | "a11y" | "console-error" | "broken-link" | "auth-surface" | "coverage";
|
|
98
|
+
recommendation?: string | undefined;
|
|
99
|
+
description?: string | undefined;
|
|
100
|
+
}[];
|
|
101
|
+
scenariosOmitted: number;
|
|
102
|
+
generatedTestsOmitted: number;
|
|
103
|
+
};
|
|
104
|
+
routeInventorySummary: {
|
|
105
|
+
scannedAt: string;
|
|
106
|
+
baseUrl: string;
|
|
107
|
+
routeCount: number;
|
|
108
|
+
pagesSkipped: number;
|
|
109
|
+
budgetExceeded: boolean;
|
|
110
|
+
};
|
|
111
|
+
repoInventory: {
|
|
112
|
+
scannedAt: string;
|
|
113
|
+
routes: {
|
|
114
|
+
path: string;
|
|
115
|
+
method: "unknown" | "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
|
|
116
|
+
file: string;
|
|
117
|
+
}[];
|
|
118
|
+
repoPath: string;
|
|
119
|
+
testFiles: {
|
|
120
|
+
type: "playwright" | "cypress-e2e" | "cypress-component" | "jest" | "vitest" | "other";
|
|
121
|
+
file: string;
|
|
122
|
+
coveredPaths: string[];
|
|
123
|
+
}[];
|
|
124
|
+
missingTestIds: string[];
|
|
125
|
+
cypressStructure: {
|
|
126
|
+
detected: boolean;
|
|
127
|
+
hasCommandsFile: boolean;
|
|
128
|
+
existingE2eFiles: string[];
|
|
129
|
+
existingComponentFiles: string[];
|
|
130
|
+
e2eFolder?: string | undefined;
|
|
131
|
+
componentFolder?: string | undefined;
|
|
132
|
+
fixturesFolder?: string | undefined;
|
|
133
|
+
supportFolder?: string | undefined;
|
|
134
|
+
};
|
|
135
|
+
} | null;
|
|
136
|
+
decisionLogPreview: {
|
|
137
|
+
timestamp: string;
|
|
138
|
+
reason: string;
|
|
139
|
+
phase: "observe" | "think" | "act" | "harness";
|
|
140
|
+
decision: string;
|
|
141
|
+
metadata?: Record<string, unknown> | undefined;
|
|
142
|
+
}[];
|
|
143
|
+
};
|
|
144
|
+
//# sourceMappingURL=compact-analyze-payload.d.ts.map
|
|
@@ -0,0 +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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4BA0E0igB,CAAC;sBAA4C,CAAC;sBAA4C,CAAC;iBAAuC,CAAC;;;;;;;;;;;;;;;;;uBAAghB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAAt7e,CAAC;2BAA6C,CAAC;0BAA4C,CAAC;yBAA2C,CAAC;;;;;;;;;;EAD3+C"}
|
|
@@ -0,0 +1,87 @@
|
|
|
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 buildCompactAnalyzePayload(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
|
+
return {
|
|
37
|
+
summary: {
|
|
38
|
+
status: result.status,
|
|
39
|
+
coverageScore: result.coverageScore,
|
|
40
|
+
releaseConfidence: g.releaseConfidence,
|
|
41
|
+
mode: g.mode,
|
|
42
|
+
coveragePagesScanned: g.coveragePagesScanned,
|
|
43
|
+
coverageBudgetExceeded: g.coverageBudgetExceeded,
|
|
44
|
+
coverageWarning: g.coverageWarning ?? null,
|
|
45
|
+
gapCount: g.gaps.length,
|
|
46
|
+
scenarioCount: g.scenarios.length,
|
|
47
|
+
generatedTestCount: g.generatedTests.length,
|
|
48
|
+
publicSurface: ps === null
|
|
49
|
+
? null
|
|
50
|
+
: {
|
|
51
|
+
pageCount: ps.pages.length,
|
|
52
|
+
gapCount: ps.gaps.length,
|
|
53
|
+
accessibilityViolationCount: ps.accessibilityViolations.length,
|
|
54
|
+
brokenLinkCount: ps.brokenLinks.length,
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
topGaps: top.map((x) => ({
|
|
58
|
+
path: x.path,
|
|
59
|
+
category: x.category,
|
|
60
|
+
severity: x.severity,
|
|
61
|
+
reason: x.reason,
|
|
62
|
+
})),
|
|
63
|
+
costIntelligenceSummary: costSummary,
|
|
64
|
+
costIntelligence: ci ?? null,
|
|
65
|
+
nextDeterministicChecks: ci
|
|
66
|
+
? nextDeterministicChecks(result.gaps, ci.conversionRecommendations)
|
|
67
|
+
: nextDeterministicChecks(result.gaps, []),
|
|
68
|
+
gapAnalysisPreview: {
|
|
69
|
+
analyzedAt: g.analyzedAt,
|
|
70
|
+
gapsSample: g.gaps.slice(0, 8),
|
|
71
|
+
scenariosOmitted: g.scenarios.length,
|
|
72
|
+
generatedTestsOmitted: g.generatedTests.length,
|
|
73
|
+
},
|
|
74
|
+
routeInventorySummary: {
|
|
75
|
+
scannedAt: result.routeInventory.scannedAt,
|
|
76
|
+
baseUrl: result.routeInventory.baseUrl,
|
|
77
|
+
routeCount: result.routeInventory.routes.length,
|
|
78
|
+
pagesSkipped: result.routeInventory.pagesSkipped,
|
|
79
|
+
budgetExceeded: result.routeInventory.budgetExceeded,
|
|
80
|
+
},
|
|
81
|
+
repoInventory: result.repoInventory,
|
|
82
|
+
decisionLogPreview: result.decisionLog.slice(-8),
|
|
83
|
+
...(result.detectedAuth !== undefined && { detectedAuth: result.detectedAuth }),
|
|
84
|
+
includeFullReport: false,
|
|
85
|
+
note: 'Summary-first payload. Pass includeFullReport: true for full gapAnalysis (all scenarios and generatedTests).',
|
|
86
|
+
};
|
|
87
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,14 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
|
4
4
|
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
5
5
|
import { analyzeApp, detectAuth, exploreAuth } from '@qulib/core';
|
|
6
6
|
import { z } from 'zod';
|
|
7
|
+
import { buildCompactAnalyzePayload } from './compact-analyze-payload.js';
|
|
8
|
+
import { log } from './logger.js';
|
|
9
|
+
const mcpProgressLog = {
|
|
10
|
+
info: (message) => log.info(message),
|
|
11
|
+
warn: (message) => log.warn(message),
|
|
12
|
+
error: (message) => log.error(message),
|
|
13
|
+
debug: (message) => log.debug(message),
|
|
14
|
+
};
|
|
7
15
|
const FormLoginMcpAuthSchema = z.object({
|
|
8
16
|
type: z.literal('form-login'),
|
|
9
17
|
loginUrl: z.string().url(),
|
|
@@ -23,6 +31,11 @@ const AnalyzeInputSchema = z.object({
|
|
|
23
31
|
maxPagesToScan: z.number().int().min(1).max(50).optional(),
|
|
24
32
|
timeoutMs: z.number().int().positive().optional(),
|
|
25
33
|
auth: z.discriminatedUnion('type', [FormLoginMcpAuthSchema, StorageStateMcpAuthSchema]).optional(),
|
|
34
|
+
includeFullReport: z.boolean().optional(),
|
|
35
|
+
llmTokenBudget: z.number().int().positive().optional(),
|
|
36
|
+
llmMaxOutputTokensPerCall: z.number().int().positive().optional(),
|
|
37
|
+
testGenerationLimit: z.number().int().positive().max(50).optional(),
|
|
38
|
+
enableLlmScenarios: z.boolean().optional(),
|
|
26
39
|
});
|
|
27
40
|
const server = new Server({
|
|
28
41
|
name: 'qulib-mcp',
|
|
@@ -48,7 +61,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
48
61
|
},
|
|
49
62
|
{
|
|
50
63
|
name: 'analyze_app',
|
|
51
|
-
description: 'Analyze a deployed web app for quality gaps.
|
|
64
|
+
description: 'Analyze a deployed web app for quality gaps. Default response is summary-first (top gaps, cost summary, next checks). Set includeFullReport for the full gapAnalysis. Optional llmMaxOutputTokensPerCall / llmTokenBudget (legacy), testGenerationLimit, enableLlmScenarios align with @qulib/core HarnessConfig.',
|
|
52
65
|
inputSchema: {
|
|
53
66
|
type: 'object',
|
|
54
67
|
properties: {
|
|
@@ -90,6 +103,23 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
90
103
|
},
|
|
91
104
|
],
|
|
92
105
|
},
|
|
106
|
+
includeFullReport: {
|
|
107
|
+
type: 'boolean',
|
|
108
|
+
description: 'When true, returns the full analyzeApp payload including all scenarios. Default false returns a summary-first shape.',
|
|
109
|
+
},
|
|
110
|
+
llmTokenBudget: {
|
|
111
|
+
type: 'number',
|
|
112
|
+
description: 'Legacy per-completion max output tokens (same as HarnessConfig.llmTokenBudget). Prefer llmMaxOutputTokensPerCall when both are set.',
|
|
113
|
+
},
|
|
114
|
+
llmMaxOutputTokensPerCall: {
|
|
115
|
+
type: 'number',
|
|
116
|
+
description: 'Optional override for per-completion max output tokens (maps to HarnessConfig.llmMaxOutputTokensPerCall).',
|
|
117
|
+
},
|
|
118
|
+
testGenerationLimit: { type: 'number', description: 'Max gaps fed into scenario generation (default 5).' },
|
|
119
|
+
enableLlmScenarios: {
|
|
120
|
+
type: 'boolean',
|
|
121
|
+
description: 'When false, never calls an LLM for scenarios (default true when omitted).',
|
|
122
|
+
},
|
|
93
123
|
},
|
|
94
124
|
required: ['url'],
|
|
95
125
|
},
|
|
@@ -116,7 +146,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
116
146
|
timeoutMs: z.number().int().positive().optional(),
|
|
117
147
|
})
|
|
118
148
|
.parse(request.params.arguments ?? {});
|
|
119
|
-
|
|
149
|
+
log.info(`explore_auth tool url=${url} timeoutMs=${timeoutMs ?? 20000}`);
|
|
150
|
+
const result = await exploreAuth(url, timeoutMs, mcpProgressLog);
|
|
151
|
+
log.info(`explore_auth tool done authRequired=${result.authRequired} paths=${result.authPaths.length}`);
|
|
120
152
|
return {
|
|
121
153
|
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
122
154
|
};
|
|
@@ -128,7 +160,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
128
160
|
timeoutMs: z.number().int().positive().optional(),
|
|
129
161
|
})
|
|
130
162
|
.parse(request.params.arguments ?? {});
|
|
131
|
-
|
|
163
|
+
log.info(`detect_auth tool url=${url} timeoutMs=${timeoutMs ?? 15000}`);
|
|
164
|
+
const result = await detectAuth(url, timeoutMs, mcpProgressLog);
|
|
165
|
+
const providerSummary = result.oauthButtons.length > 0
|
|
166
|
+
? result.oauthButtons.map((b) => b.provider).join(', ')
|
|
167
|
+
: result.provider ?? 'none';
|
|
168
|
+
log.info(`detect_auth tool done type=${result.type} providers=${providerSummary} automatable=${result.type === 'form-login'}`);
|
|
132
169
|
return {
|
|
133
170
|
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
134
171
|
};
|
|
@@ -157,31 +194,36 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
157
194
|
: input.auth?.type === 'storage-state'
|
|
158
195
|
? { type: 'storage-state', path: input.auth.path }
|
|
159
196
|
: undefined;
|
|
197
|
+
const harnessConfig = {
|
|
198
|
+
maxPagesToScan: input.maxPagesToScan ?? 10,
|
|
199
|
+
maxDepth: 3,
|
|
200
|
+
minPagesForConfidence: 3,
|
|
201
|
+
timeoutMs: input.timeoutMs ?? 30000,
|
|
202
|
+
retryCount: 0,
|
|
203
|
+
llmTokenBudget: input.llmTokenBudget ?? input.llmMaxOutputTokensPerCall ?? 4096,
|
|
204
|
+
llmMaxOutputTokensPerCall: input.llmMaxOutputTokensPerCall,
|
|
205
|
+
testGenerationLimit: input.testGenerationLimit ?? 5,
|
|
206
|
+
enableLlmScenarios: input.enableLlmScenarios !== false,
|
|
207
|
+
readOnlyMode: true,
|
|
208
|
+
requireHumanReview: false,
|
|
209
|
+
failOnConsoleError: false,
|
|
210
|
+
explorer: 'playwright',
|
|
211
|
+
defaultAdapter: 'playwright',
|
|
212
|
+
adapters: ['playwright'],
|
|
213
|
+
...(authConfig && { auth: authConfig }),
|
|
214
|
+
};
|
|
160
215
|
const result = await analyzeApp({
|
|
161
216
|
url: input.url,
|
|
162
217
|
writeArtifacts: false,
|
|
163
|
-
config:
|
|
164
|
-
|
|
165
|
-
maxDepth: 3,
|
|
166
|
-
minPagesForConfidence: 3,
|
|
167
|
-
timeoutMs: input.timeoutMs ?? 30000,
|
|
168
|
-
retryCount: 0,
|
|
169
|
-
llmTokenBudget: 1,
|
|
170
|
-
testGenerationLimit: 1,
|
|
171
|
-
readOnlyMode: true,
|
|
172
|
-
requireHumanReview: false,
|
|
173
|
-
failOnConsoleError: false,
|
|
174
|
-
explorer: 'playwright',
|
|
175
|
-
defaultAdapter: 'playwright',
|
|
176
|
-
adapters: ['playwright'],
|
|
177
|
-
...(authConfig && { auth: authConfig }),
|
|
178
|
-
},
|
|
218
|
+
config: harnessConfig,
|
|
219
|
+
progressLog: mcpProgressLog,
|
|
179
220
|
});
|
|
221
|
+
const payload = buildCompactAnalyzePayload(result, input.includeFullReport === true);
|
|
180
222
|
return {
|
|
181
223
|
content: [
|
|
182
224
|
{
|
|
183
225
|
type: 'text',
|
|
184
|
-
text: JSON.stringify(
|
|
226
|
+
text: JSON.stringify(payload, null, 2),
|
|
185
227
|
},
|
|
186
228
|
],
|
|
187
229
|
};
|
package/dist/logger.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAiBA,eAAO,MAAM,GAAG;kBACA,MAAM,GAAG,IAAI;kBAGb,MAAM,GAAG,IAAI;mBAGZ,MAAM,GAAG,IAAI;mBAGb,MAAM,GAAG,IAAI;CAK7B,CAAC"}
|
package/dist/logger.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
function pad2(n) {
|
|
2
|
+
return String(n).padStart(2, '0');
|
|
3
|
+
}
|
|
4
|
+
function wallTime() {
|
|
5
|
+
const d = new Date();
|
|
6
|
+
return `${pad2(d.getHours())}:${pad2(d.getMinutes())}:${pad2(d.getSeconds())}`;
|
|
7
|
+
}
|
|
8
|
+
function isDebugEnabled() {
|
|
9
|
+
return process.env.QULIB_DEBUG === '1';
|
|
10
|
+
}
|
|
11
|
+
function emit(level, message) {
|
|
12
|
+
process.stderr.write(`[qulib ${wallTime()}] ${level} ${message}\n`);
|
|
13
|
+
}
|
|
14
|
+
export const log = {
|
|
15
|
+
info(message) {
|
|
16
|
+
emit('INFO', message);
|
|
17
|
+
},
|
|
18
|
+
warn(message) {
|
|
19
|
+
emit('WARN', message);
|
|
20
|
+
},
|
|
21
|
+
error(message) {
|
|
22
|
+
emit('ERROR', message);
|
|
23
|
+
},
|
|
24
|
+
debug(message) {
|
|
25
|
+
if (isDebugEnabled()) {
|
|
26
|
+
emit('DEBUG', message);
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qulib/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "MCP server for Qulib — AI-callable QA gap analysis",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Tapesh Nagarwal",
|
|
@@ -27,12 +27,13 @@
|
|
|
27
27
|
"README.md"
|
|
28
28
|
],
|
|
29
29
|
"scripts": {
|
|
30
|
-
"build": "tsc && chmod +x dist/index.js",
|
|
31
|
-
"dev": "tsx src/index.ts"
|
|
30
|
+
"build": "npm --prefix ../.. run build -w @qulib/core && tsc && chmod +x dist/index.js",
|
|
31
|
+
"dev": "tsx src/index.ts",
|
|
32
|
+
"test": "node --import tsx/esm --test src/compact-analyze-payload.test.ts"
|
|
32
33
|
},
|
|
33
34
|
"dependencies": {
|
|
34
|
-
"@qulib/core": "0.2.2",
|
|
35
35
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
36
|
+
"@qulib/core": "0.3.0",
|
|
36
37
|
"zod": "^3.23.0"
|
|
37
38
|
},
|
|
38
39
|
"devDependencies": {
|