@qulib/mcp 0.2.1 → 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 CHANGED
@@ -6,8 +6,9 @@
6
6
 
7
7
  Tools:
8
8
 
9
- - **`analyze_app(url, auth?)`** — full quality scan (optional form-login auth).
10
- - **`detect_auth(url, timeoutMs?)`** detect whether the site uses form login, OAuth, magic link, etc., and get a plain-language recommendation (including when to use manual `qulib auth init` and storage state).
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`** — 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
+ - **`detect_auth(url, timeoutMs?)`** — single-pattern auth guess with a short recommendation (lighter than `explore_auth`).
11
12
 
12
13
  Returns from `analyze_app`:
13
14
 
@@ -52,6 +53,38 @@ This is a one-time step. You'll only need to do it again if Playwright's browser
52
53
 
53
54
  If you skip this step, the first tool call will return a clear error telling you to run the command.
54
55
 
56
+ ## Agentic auth exploration (`explore_auth`)
57
+
58
+ On unfamiliar apps, call **`explore_auth`** before **`analyze_app`**. The response lists each sign-in path (curated public OAuth/SSO, password forms, magic-link wording, and **heuristic** unknown buttons such as tenant-specific SSO). Each path includes **`requirements`** (e.g. storage-state vs credentials) and **`suggestedAgentBehavior`**.
59
+
60
+ When the model sees **`unrecognizedButtons`**, it can ask the user to register a label on the **MCP host** with the CLI:
61
+
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
+
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
+
55
88
  ## Example usage
56
89
 
57
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
@@ -2,8 +2,16 @@
2
2
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
3
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
4
  import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
5
- import { analyzeApp, detectAuth } from '@qulib/core';
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',
@@ -34,9 +47,21 @@ const server = new Server({
34
47
  });
35
48
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
36
49
  tools: [
50
+ {
51
+ name: 'explore_auth',
52
+ description: 'Use this BEFORE analyze_app when scanning unfamiliar apps. Returns all detected sign-in paths with per-path requirements describing what credentials or actions the agent must collect from the user before calling analyze_app. Combines built-in OAuth/SSO labels, user-local patterns from ~/.qulib/providers.json, and heuristic unknown buttons.',
53
+ inputSchema: {
54
+ type: 'object',
55
+ properties: {
56
+ url: { type: 'string', description: 'Full URL of the deployed app or login page' },
57
+ timeoutMs: { type: 'number', description: 'Navigation timeout in milliseconds (default 20000)' },
58
+ },
59
+ required: ['url'],
60
+ },
61
+ },
37
62
  {
38
63
  name: 'analyze_app',
39
- description: 'Analyze a deployed web app for quality gaps. Returns a release confidence score (0-100), accessibility violations, broken links, and prioritized risks. Supports optional form-login or storage-state (Playwright) authentication.',
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.',
40
65
  inputSchema: {
41
66
  type: 'object',
42
67
  properties: {
@@ -78,6 +103,23 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
78
103
  },
79
104
  ],
80
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
+ },
81
123
  },
82
124
  required: ['url'],
83
125
  },
@@ -97,6 +139,20 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
97
139
  ],
98
140
  }));
99
141
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
142
+ if (request.params.name === 'explore_auth') {
143
+ const { url, timeoutMs } = z
144
+ .object({
145
+ url: z.string().url(),
146
+ timeoutMs: z.number().int().positive().optional(),
147
+ })
148
+ .parse(request.params.arguments ?? {});
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}`);
152
+ return {
153
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
154
+ };
155
+ }
100
156
  if (request.params.name === 'detect_auth') {
101
157
  const { url, timeoutMs } = z
102
158
  .object({
@@ -104,7 +160,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
104
160
  timeoutMs: z.number().int().positive().optional(),
105
161
  })
106
162
  .parse(request.params.arguments ?? {});
107
- const result = await detectAuth(url, timeoutMs);
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'}`);
108
169
  return {
109
170
  content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
110
171
  };
@@ -133,31 +194,36 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
133
194
  : input.auth?.type === 'storage-state'
134
195
  ? { type: 'storage-state', path: input.auth.path }
135
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
+ };
136
215
  const result = await analyzeApp({
137
216
  url: input.url,
138
217
  writeArtifacts: false,
139
- config: {
140
- maxPagesToScan: input.maxPagesToScan ?? 10,
141
- maxDepth: 3,
142
- minPagesForConfidence: 3,
143
- timeoutMs: input.timeoutMs ?? 30000,
144
- retryCount: 0,
145
- llmTokenBudget: 1,
146
- testGenerationLimit: 1,
147
- readOnlyMode: true,
148
- requireHumanReview: false,
149
- failOnConsoleError: false,
150
- explorer: 'playwright',
151
- defaultAdapter: 'playwright',
152
- adapters: ['playwright'],
153
- ...(authConfig && { auth: authConfig }),
154
- },
218
+ config: harnessConfig,
219
+ progressLog: mcpProgressLog,
155
220
  });
221
+ const payload = buildCompactAnalyzePayload(result, input.includeFullReport === true);
156
222
  return {
157
223
  content: [
158
224
  {
159
225
  type: 'text',
160
- text: JSON.stringify(result, null, 2),
226
+ text: JSON.stringify(payload, null, 2),
161
227
  },
162
228
  ],
163
229
  };
@@ -0,0 +1,7 @@
1
+ export declare const log: {
2
+ info(message: string): void;
3
+ warn(message: string): void;
4
+ error(message: string): void;
5
+ debug(message: string): void;
6
+ };
7
+ //# sourceMappingURL=logger.d.ts.map
@@ -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.2.1",
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.1",
35
35
  "@modelcontextprotocol/sdk": "^1.0.0",
36
+ "@qulib/core": "0.3.0",
36
37
  "zod": "^3.23.0"
37
38
  },
38
39
  "devDependencies": {