@qulib/mcp 0.6.0 → 0.8.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/dist/index.js CHANGED
@@ -14,7 +14,7 @@ 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, } from '@qulib/core';
17
+ import { analyzeApp, detectAuth, exploreAuth, scanRepo, computeAutomationMaturity, scaffoldTests, discoverApiSurfaceWithRepo, computeApiCoverage, } from '@qulib/core';
18
18
  import { z } from 'zod';
19
19
  import { buildAnalyzeAppMcpPayload } from './analyze-app-mcp-payload.js';
20
20
  import { log } from './logger.js';
@@ -261,5 +261,103 @@ mcpServer.registerTool('qulib_score_automation', {
261
261
  return toolError('QULIB_REPO_SCORE_FAILED', msg, err instanceof Error ? err.stack : undefined);
262
262
  }
263
263
  });
264
+ const ScaffoldTestsInputSchema = z.object({
265
+ url: z.string().url().describe('URL of the deployed web app to scaffold tests for'),
266
+ framework: z
267
+ .enum(['cypress-e2e', 'playwright'])
268
+ .optional()
269
+ .describe('Test framework to generate. Default: cypress-e2e'),
270
+ maxPagesToScan: z
271
+ .number()
272
+ .int()
273
+ .min(1)
274
+ .max(20)
275
+ .optional()
276
+ .describe('Max pages to crawl when running analyze_app internally. Default: 10'),
277
+ });
278
+ 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.',
280
+ inputSchema: ScaffoldTestsInputSchema,
281
+ }, async ({ url, framework, maxPagesToScan }) => {
282
+ try {
283
+ log.info(`qulib_scaffold_tests url=${url} framework=${framework ?? 'cypress-e2e'} maxPagesToScan=${maxPagesToScan ?? 10}`);
284
+ const result = await scaffoldTests(url, {
285
+ framework: framework ?? 'cypress-e2e',
286
+ maxPagesToScan: maxPagesToScan ?? 10,
287
+ progressLog: mcpProgressLog,
288
+ telemetry: telemetrySink,
289
+ });
290
+ return {
291
+ content: [
292
+ {
293
+ type: 'text',
294
+ text: JSON.stringify({
295
+ url: result.url,
296
+ framework: result.framework,
297
+ scenarioCount: result.scenarios.length,
298
+ testCount: result.generatedTests.length,
299
+ generatedTests: result.generatedTests,
300
+ projectConfig: result.projectConfig,
301
+ }, null, 2),
302
+ },
303
+ ],
304
+ };
305
+ }
306
+ catch (err) {
307
+ const msg = err instanceof Error ? err.message : String(err);
308
+ log.error(`qulib_scaffold_tests failed: ${msg}`);
309
+ return toolError('QULIB_SCAFFOLD_FAILED', msg, err instanceof Error ? err.stack : undefined);
310
+ }
311
+ });
312
+ const ScoreApiInputSchema = z.object({
313
+ repoPath: z.string().describe('Absolute path to the repository on the MCP host filesystem'),
314
+ enableTier3: z
315
+ .boolean()
316
+ .optional()
317
+ .describe('Enable Tier3 heuristic discovery (currently: tRPC router definitions). Default false. Tier1=OpenAPI specs, Tier2=framework routes (Next.js, Express, Fastify, NestJS), Tier3=heuristic.'),
318
+ includeEndpointDetail: z
319
+ .boolean()
320
+ .optional()
321
+ .describe('When true, includes per-endpoint coverage detail in the response. Default false.'),
322
+ });
323
+ mcpServer.registerTool('qulib_score_api', {
324
+ description: 'Discover API endpoints in a repository and score their test coverage. Returns an api-test-coverage dimension score (0–100) with per-endpoint contextual evidence — which endpoints are covered by tests and which are not. Discovery is evidence-only: Tier1=OpenAPI/Swagger specs, Tier2=framework routes (Next.js App-Router route.ts exports, Pages API routes, Express, Fastify, Hono, NestJS decorators), Tier3=heuristic opt-in (tRPC). Never fabricates endpoints. Returns "not_applicable" when no API endpoints are found.',
325
+ inputSchema: ScoreApiInputSchema,
326
+ }, async ({ repoPath, enableTier3, includeEndpointDetail }) => {
327
+ try {
328
+ const abs = validateAbsoluteRepoPath(repoPath);
329
+ log.info(`qulib_score_api repoPath=${abs} enableTier3=${enableTier3 ?? false}`);
330
+ const repo = await scanRepo(abs);
331
+ const apiSurfaceResult = await discoverApiSurfaceWithRepo(abs, repo, {
332
+ enableTier3: enableTier3 ?? false,
333
+ });
334
+ const coverageResult = computeApiCoverage(repo, apiSurfaceResult);
335
+ const payload = {
336
+ repoPath: abs,
337
+ computedAt: new Date().toISOString(),
338
+ endpointsDiscovered: apiSurfaceResult.endpoints.length,
339
+ openApiSpecsFound: apiSurfaceResult.openApiSpecsFound,
340
+ tier3Enabled: apiSurfaceResult.tier3Enabled,
341
+ dimension: coverageResult.dimension,
342
+ untestedHighSeverityCount: coverageResult.untestedHighSeverityCount,
343
+ untestedMediumSeverityCount: coverageResult.untestedMediumSeverityCount,
344
+ };
345
+ if (includeEndpointDetail === true) {
346
+ payload['endpointCoverage'] = coverageResult.endpointCoverage;
347
+ }
348
+ log.info(`qulib_score_api done endpoints=${apiSurfaceResult.endpoints.length} score=${coverageResult.dimension.score} applicability=${coverageResult.dimension.applicability ?? 'applicable'}`);
349
+ return {
350
+ content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }],
351
+ };
352
+ }
353
+ catch (err) {
354
+ const msg = err instanceof Error ? err.message : String(err);
355
+ if (msg.includes('repoPath must')) {
356
+ return toolError('QULIB_INPUT_INVALID', msg, undefined);
357
+ }
358
+ log.error(`qulib_score_api failed: ${msg}`);
359
+ return toolError('QULIB_API_SCORE_FAILED', msg, err instanceof Error ? err.stack : undefined);
360
+ }
361
+ });
264
362
  const transport = new StdioServerTransport();
265
363
  await mcpServer.connect(transport);
@@ -71,7 +71,7 @@ export declare function summarizeAnalyzeResult(result: AnalyzeResult, includeFul
71
71
  topRecommendations: string[];
72
72
  dimensions: {
73
73
  guidance?: string | undefined;
74
- dimension: "test-coverage-breadth" | "framework-adoption" | "test-id-hygiene" | "ci-integration" | "auth-test-coverage" | "component-test-ratio";
74
+ dimension: "test-coverage-breadth" | "framework-adoption" | "test-id-hygiene" | "ci-integration" | "auth-test-coverage" | "component-test-ratio" | "api-test-coverage";
75
75
  score: number;
76
76
  applicability: "unknown" | "applicable" | "not_applicable";
77
77
  }[];
@@ -96,7 +96,7 @@ export declare function summarizeAnalyzeResult(result: AnalyzeResult, includeFul
96
96
  };
97
97
  topGaps: {
98
98
  path: string;
99
- category: "untested-route" | "a11y" | "console-error" | "broken-link" | "auth-surface" | "coverage";
99
+ category: "untested-route" | "a11y" | "console-error" | "broken-link" | "auth-surface" | "coverage" | "untested-api-endpoint";
100
100
  severity: "critical" | "high" | "medium" | "low";
101
101
  reason: string;
102
102
  }[];
@@ -152,7 +152,7 @@ export declare function summarizeAnalyzeResult(result: AnalyzeResult, includeFul
152
152
  id: string;
153
153
  severity: "critical" | "high" | "medium" | "low";
154
154
  reason: string;
155
- category: "untested-route" | "a11y" | "console-error" | "broken-link" | "auth-surface" | "coverage";
155
+ category: "untested-route" | "a11y" | "console-error" | "broken-link" | "auth-surface" | "coverage" | "untested-api-endpoint";
156
156
  recommendation?: string | undefined;
157
157
  description?: string | undefined;
158
158
  }[];
@@ -1 +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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4BA6G0td,CAAC;sBAA4C,CAAC;sBAA4C,CAAC;iBAAuC,CAAC;;;;;;;;;;;;;;;;;uBAAghB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;EADr8e"}
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4BA6G84d,CAAC;sBAA4C,CAAC;sBAA4C,CAAC;iBAAuC,CAAC;;;;;;;;;;;;;;;;;uBAAghB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;EADznf"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qulib/mcp",
3
- "version": "0.6.0",
3
+ "version": "0.8.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.6.0",
36
+ "@qulib/core": "0.8.2",
37
37
  "zod": "^3.23.0"
38
38
  },
39
39
  "devDependencies": {