@slbdn/mcp-tester 1.4.1 → 1.5.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/CHANGELOG.md +96 -0
- package/README.md +49 -0
- package/dist/ai/provider.d.ts +145 -0
- package/dist/ai/provider.d.ts.map +1 -0
- package/dist/ai/provider.js +428 -0
- package/dist/ai/provider.js.map +1 -0
- package/dist/cli/index.js +260 -151
- package/dist/cli/index.js.map +1 -1
- package/dist/client/MCPClient.d.ts +16 -2
- package/dist/client/MCPClient.d.ts.map +1 -1
- package/dist/client/MCPClient.js +77 -20
- package/dist/client/MCPClient.js.map +1 -1
- package/dist/generate-cases.d.ts +113 -0
- package/dist/generate-cases.d.ts.map +1 -0
- package/dist/generate-cases.js +565 -0
- package/dist/generate-cases.js.map +1 -0
- package/dist/generate-tests.d.ts +106 -0
- package/dist/generate-tests.d.ts.map +1 -0
- package/dist/generate-tests.js +441 -0
- package/dist/generate-tests.js.map +1 -0
- package/dist/generate-types.d.ts +86 -0
- package/dist/generate-types.d.ts.map +1 -0
- package/dist/generate-types.js +484 -0
- package/dist/generate-types.js.map +1 -0
- package/dist/index.d.ts +8 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/matchers.d.ts +31 -21
- package/dist/matchers.d.ts.map +1 -1
- package/dist/matchers.js.map +1 -1
- package/dist/templates/full-stack/.github/workflows/test.yml +34 -0
- package/dist/templates/full-stack/README.md +73 -0
- package/dist/templates/full-stack/examples/mock-server.js +115 -0
- package/dist/templates/full-stack/jest.config.js +39 -0
- package/dist/templates/full-stack/package.json +31 -0
- package/dist/templates/full-stack/scripts/generate-tests.ts +32 -0
- package/dist/templates/full-stack/scripts/generate-types.ts +28 -0
- package/dist/templates/full-stack/src/server.ts +78 -0
- package/dist/templates/full-stack/tests/helpers.ts +29 -0
- package/dist/templates/full-stack/tests/parallel.test.ts +44 -0
- package/dist/templates/full-stack/tests/server.test.ts +36 -0
- package/dist/templates/full-stack/tests/tools.test.ts +61 -0
- package/dist/templates/full-stack/tsconfig.json +19 -0
- package/dist/templates/minimal-jest/README.md +42 -0
- package/dist/templates/minimal-jest/examples/mock-server.js +62 -0
- package/dist/templates/minimal-jest/jest.config.js +24 -0
- package/dist/templates/minimal-jest/package.json +24 -0
- package/dist/templates/minimal-jest/tests/server.test.ts +64 -0
- package/dist/templates/minimal-jest/tsconfig.json +19 -0
- package/dist/templates/standard-jest/.github/workflows/test.yml +32 -0
- package/dist/templates/standard-jest/README.md +44 -0
- package/dist/templates/standard-jest/examples/mock-server.js +122 -0
- package/dist/templates/standard-jest/jest.config.js +39 -0
- package/dist/templates/standard-jest/package.json +27 -0
- package/dist/templates/standard-jest/tests/helpers.ts +43 -0
- package/dist/templates/standard-jest/tests/prompts.test.ts +37 -0
- package/dist/templates/standard-jest/tests/resources.test.ts +37 -0
- package/dist/templates/standard-jest/tests/server.test.ts +30 -0
- package/dist/templates/standard-jest/tests/tools.test.ts +87 -0
- package/dist/templates/standard-jest/tsconfig.json +19 -0
- package/dist/utils/masking.d.ts.map +1 -1
- package/dist/utils/masking.js +0 -3
- package/dist/utils/masking.js.map +1 -1
- package/dist/utils/validation.d.ts.map +1 -1
- package/dist/utils/validation.js +9 -0
- package/dist/utils/validation.js.map +1 -1
- package/dist/utils/version.d.ts +13 -0
- package/dist/utils/version.d.ts.map +1 -0
- package/dist/utils/version.js +81 -0
- package/dist/utils/version.js.map +1 -0
- package/docs/advanced.md +424 -0
- package/docs/ai-generation.md +211 -0
- package/docs/api-reference.md +819 -0
- package/docs/cicd.md +289 -0
- package/docs/cli.md +225 -0
- package/docs/examples.md +300 -0
- package/docs/nodejs-compatibility.md +81 -0
- package/docs/releases.md +413 -0
- package/docs/starter-templates.md +110 -0
- package/docs/testing.md +443 -0
- package/docs/troubleshooting.md +139 -0
- package/jest.d.ts +43 -0
- package/package.json +11 -20
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI test-case provider — pluggable, optional augmentation of the
|
|
3
|
+
* deterministic edge-case engine.
|
|
4
|
+
*
|
|
5
|
+
* The provider speaks the OpenAI-compatible chat-completions protocol
|
|
6
|
+
* (`POST {baseUrl}/chat/completions`), which covers OpenAI, Ollama, LM
|
|
7
|
+
* Studio, vLLM, Groq, Together, and most local gateways. Implemented with
|
|
8
|
+
* plain `fetch` — no SDK dependency.
|
|
9
|
+
*
|
|
10
|
+
* Security model: the LLM returns *data* (JSON test cases), never code.
|
|
11
|
+
* Every case is validated against the tool's real schema by
|
|
12
|
+
* `mergeAndValidateCases` before it can reach a generated file, so prompt
|
|
13
|
+
* injection via server-provided tool descriptions cannot inject code.
|
|
14
|
+
*
|
|
15
|
+
* @module ai/provider
|
|
16
|
+
*/
|
|
17
|
+
import { createHash } from 'node:crypto';
|
|
18
|
+
import { mkdirSync, readFileSync, writeFileSync, existsSync, statSync } from 'node:fs';
|
|
19
|
+
import { join } from 'node:path';
|
|
20
|
+
import { caseKey, mergeAndValidateCases, } from '../generate-cases.js';
|
|
21
|
+
export const DEFAULT_AI_BASE_URL = 'https://api.openai.com/v1';
|
|
22
|
+
export const DEFAULT_AI_MODEL = 'gpt-4o-mini';
|
|
23
|
+
/** Longest tool description (chars) fed to the model. */
|
|
24
|
+
const MAX_DESCRIPTION_CHARS = 500;
|
|
25
|
+
/** Maximum tools per request. */
|
|
26
|
+
const MAX_TOOLS_PER_REQUEST = 40;
|
|
27
|
+
/**
|
|
28
|
+
* Wraps `statSync` so a transient FS error doesn't crash cache reads. We
|
|
29
|
+
* return `null` for any failure (missing file, permission, EBUSY) so the
|
|
30
|
+
* caller falls back to a fresh fetch.
|
|
31
|
+
*/
|
|
32
|
+
function statSyncSafe(file) {
|
|
33
|
+
try {
|
|
34
|
+
return statSync(file);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const SYSTEM_PROMPT = `You are a test designer for MCP (Model Context Protocol) servers.
|
|
41
|
+
Given tool names, descriptions and JSON Schemas, propose edge-case test calls.
|
|
42
|
+
|
|
43
|
+
Return STRICT JSON: {"cases":[{"tool":"<tool name>","title":"<short test name>","args":{...},"expectation":"success"|"error"|"observe","rationale":"<why>"}]}
|
|
44
|
+
|
|
45
|
+
Rules:
|
|
46
|
+
- "args" MUST use only property names from the tool's inputSchema, with correct JSON types.
|
|
47
|
+
- expectation "error": input is clearly invalid per the schema (missing required field, wrong type, out of range/enum).
|
|
48
|
+
- expectation "success": valid but unusual input that should still work (empty string if allowed, unicode, boundary values, zero, negative numbers).
|
|
49
|
+
- expectation "observe": behavior is server-defined (e.g. truncation past maxLength); do not assert.
|
|
50
|
+
- 2-5 cases per tool. No duplicates of the same arguments. Respond with JSON only.`;
|
|
51
|
+
/**
|
|
52
|
+
* Extract a JSON object from model content, tolerating markdown fences and
|
|
53
|
+
* prose around the payload. Uses brace-aware scanning so a `}` inside a
|
|
54
|
+
* string value or in a nested object doesn't truncate the payload.
|
|
55
|
+
*
|
|
56
|
+
* @returns The parsed object, or `null` when no parseable JSON object is
|
|
57
|
+
* found in the input.
|
|
58
|
+
*/
|
|
59
|
+
export function parseJsonLoose(content) {
|
|
60
|
+
if (typeof content !== 'string' || content.length === 0)
|
|
61
|
+
return null;
|
|
62
|
+
// 1. Prefer a fenced code block (with or without a language hint) if any.
|
|
63
|
+
const fenceRe = /```(?:[a-zA-Z0-9_-]+)?\s*([\s\S]*?)```/g;
|
|
64
|
+
for (const m of content.matchAll(fenceRe)) {
|
|
65
|
+
if (!m[1] || !m[1].includes('{'))
|
|
66
|
+
continue;
|
|
67
|
+
const fenced = extractLargestJsonObject(m[1]);
|
|
68
|
+
if (fenced)
|
|
69
|
+
return fenced;
|
|
70
|
+
// Fall through: the fence may hold prose or a non-JSON snippet while the
|
|
71
|
+
// real payload sits outside it, so keep trying other fences/content.
|
|
72
|
+
}
|
|
73
|
+
// 2. Otherwise, scan for the largest parseable JSON object directly.
|
|
74
|
+
return extractLargestJsonObject(content);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Find the largest top-level JSON object in `text` that parses successfully.
|
|
78
|
+
* Walks every `{` candidate, attempts to parse from there using a brace/quote
|
|
79
|
+
* tracker, and returns the parse with the most characters consumed (longest
|
|
80
|
+
* valid object).
|
|
81
|
+
*/
|
|
82
|
+
function extractLargestJsonObject(text) {
|
|
83
|
+
let best = null;
|
|
84
|
+
let bestLen = -1;
|
|
85
|
+
for (let i = 0; i < text.length; i++) {
|
|
86
|
+
if (text[i] !== '{')
|
|
87
|
+
continue;
|
|
88
|
+
const end = scanJsonObjectEnd(text, i);
|
|
89
|
+
if (end === -1)
|
|
90
|
+
continue;
|
|
91
|
+
const slice = text.slice(i, end + 1);
|
|
92
|
+
try {
|
|
93
|
+
const parsed = JSON.parse(slice);
|
|
94
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
95
|
+
if (slice.length > bestLen) {
|
|
96
|
+
best = parsed;
|
|
97
|
+
bestLen = slice.length;
|
|
98
|
+
}
|
|
99
|
+
// Any `{` strictly inside this well-formed object can only parse to a
|
|
100
|
+
// shorter object, so skip past it (keeps the scan linear for valid JSON).
|
|
101
|
+
i = end;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
// not a valid object; keep scanning inside it for a nested one
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return best;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Given the index of an opening `{`, return the index of its matching `}`,
|
|
112
|
+
* honoring strings (including escape sequences) and nested objects/arrays.
|
|
113
|
+
* Returns `-1` if the object is unterminated.
|
|
114
|
+
*/
|
|
115
|
+
function scanJsonObjectEnd(text, start) {
|
|
116
|
+
if (text[start] !== '{')
|
|
117
|
+
return -1;
|
|
118
|
+
let depth = 0;
|
|
119
|
+
let inString = false;
|
|
120
|
+
let escape = false;
|
|
121
|
+
for (let i = start; i < text.length; i++) {
|
|
122
|
+
const ch = text[i];
|
|
123
|
+
if (inString) {
|
|
124
|
+
if (escape) {
|
|
125
|
+
escape = false;
|
|
126
|
+
}
|
|
127
|
+
else if (ch === '\\') {
|
|
128
|
+
escape = true;
|
|
129
|
+
}
|
|
130
|
+
else if (ch === '"') {
|
|
131
|
+
inString = false;
|
|
132
|
+
}
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (ch === '"') {
|
|
136
|
+
inString = true;
|
|
137
|
+
}
|
|
138
|
+
else if (ch === '{' || ch === '[') {
|
|
139
|
+
depth++;
|
|
140
|
+
}
|
|
141
|
+
else if (ch === '}' || ch === ']') {
|
|
142
|
+
depth--;
|
|
143
|
+
if (depth === 0)
|
|
144
|
+
return i;
|
|
145
|
+
if (depth < 0)
|
|
146
|
+
return -1;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return -1;
|
|
150
|
+
}
|
|
151
|
+
/** Coerce one raw model case into a {@link GeneratedCase}, or null. */
|
|
152
|
+
function coerceCase(raw, providerName) {
|
|
153
|
+
if (!raw || typeof raw !== 'object')
|
|
154
|
+
return null;
|
|
155
|
+
const r = raw;
|
|
156
|
+
if (typeof r.tool !== 'string' || r.tool.length === 0)
|
|
157
|
+
return null;
|
|
158
|
+
if (typeof r.title !== 'string' || r.title.length === 0)
|
|
159
|
+
return null;
|
|
160
|
+
if (!r.args || typeof r.args !== 'object' || Array.isArray(r.args))
|
|
161
|
+
return null;
|
|
162
|
+
const expectation = r.expectation;
|
|
163
|
+
if (expectation !== 'success' && expectation !== 'error' && expectation !== 'observe') {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
tool: r.tool,
|
|
168
|
+
title: r.title.slice(0, 120),
|
|
169
|
+
args: r.args,
|
|
170
|
+
expectation,
|
|
171
|
+
rationale: typeof r.rationale === 'string' ? r.rationale.slice(0, 300) : 'AI-suggested case',
|
|
172
|
+
source: `ai:${providerName}`,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* OpenAI-compatible chat-completions provider.
|
|
177
|
+
*
|
|
178
|
+
* Works with OpenAI, Ollama (`http://localhost:11434/v1`), LM Studio,
|
|
179
|
+
* vLLM, Groq, and any compatible gateway.
|
|
180
|
+
*/
|
|
181
|
+
export class OpenAICompatProvider {
|
|
182
|
+
name;
|
|
183
|
+
baseUrl;
|
|
184
|
+
model;
|
|
185
|
+
apiKey;
|
|
186
|
+
timeout;
|
|
187
|
+
maxTokens;
|
|
188
|
+
cacheDir;
|
|
189
|
+
cacheMaxAgeMs;
|
|
190
|
+
fetchImpl;
|
|
191
|
+
strictJson;
|
|
192
|
+
/** Tracks whether this provider has already observed a `response_format` rejection. */
|
|
193
|
+
responseFormatDisabled = false;
|
|
194
|
+
constructor(options = {}) {
|
|
195
|
+
this.baseUrl = (options.baseUrl || DEFAULT_AI_BASE_URL).replace(/\/+$/, '');
|
|
196
|
+
this.model = options.model || DEFAULT_AI_MODEL;
|
|
197
|
+
this.apiKey = options.apiKey;
|
|
198
|
+
this.timeout = options.timeout ?? 60_000;
|
|
199
|
+
this.maxTokens = options.maxTokens ?? 4096;
|
|
200
|
+
this.cacheDir = options.cacheDir === undefined ? '.mcp-tester-cache' : options.cacheDir;
|
|
201
|
+
this.cacheMaxAgeMs = options.cacheMaxAgeMs;
|
|
202
|
+
this.fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
203
|
+
this.strictJson = options.strictJson !== false;
|
|
204
|
+
this.name = this.model;
|
|
205
|
+
}
|
|
206
|
+
async suggestCases(analysis) {
|
|
207
|
+
if (analysis.tools.length === 0)
|
|
208
|
+
return [];
|
|
209
|
+
const cacheKey = this.cacheKeyFor(analysis);
|
|
210
|
+
const cached = this.readCache(cacheKey);
|
|
211
|
+
if (cached)
|
|
212
|
+
return cached;
|
|
213
|
+
const tools = analysis.tools.slice(0, MAX_TOOLS_PER_REQUEST).map((t) => ({
|
|
214
|
+
name: t.name,
|
|
215
|
+
description: t.description ? t.description.slice(0, MAX_DESCRIPTION_CHARS) : '',
|
|
216
|
+
inputSchema: t.inputSchema,
|
|
217
|
+
}));
|
|
218
|
+
const content = await this.chatCompletion({
|
|
219
|
+
serverLabel: analysis.serverLabel,
|
|
220
|
+
tools,
|
|
221
|
+
});
|
|
222
|
+
const parsed = parseJsonLoose(content);
|
|
223
|
+
const rawCases = parsed && Array.isArray(parsed.cases) ? parsed.cases : [];
|
|
224
|
+
// Coerce + dedupe. Schema validation happens later, in
|
|
225
|
+
// mergeAndValidateCases, where rules and AI cases are merged together.
|
|
226
|
+
const seen = new Set();
|
|
227
|
+
const cases = [];
|
|
228
|
+
for (const raw of rawCases) {
|
|
229
|
+
const c = coerceCase(raw, this.name);
|
|
230
|
+
if (!c)
|
|
231
|
+
continue;
|
|
232
|
+
const key = caseKey(c);
|
|
233
|
+
if (seen.has(key))
|
|
234
|
+
continue;
|
|
235
|
+
seen.add(key);
|
|
236
|
+
cases.push(c);
|
|
237
|
+
}
|
|
238
|
+
this.writeCache(cacheKey, cases, analysis.serverLabel);
|
|
239
|
+
return cases;
|
|
240
|
+
}
|
|
241
|
+
/** Single chat-completions round trip. Throws with response context. */
|
|
242
|
+
async chatCompletion(payload) {
|
|
243
|
+
const userMessage = [
|
|
244
|
+
payload.serverLabel ? `Server: ${payload.serverLabel}` : null,
|
|
245
|
+
'Tools:',
|
|
246
|
+
JSON.stringify(payload.tools, null, 2),
|
|
247
|
+
]
|
|
248
|
+
.filter(Boolean)
|
|
249
|
+
.join('\n\n');
|
|
250
|
+
// First attempt: include response_format if strictJson wasn't disabled
|
|
251
|
+
// (either by the user or by a previous rejection from this provider).
|
|
252
|
+
const wantsResponseFormat = this.responseFormatDisabled ? false : this.strictJson !== false;
|
|
253
|
+
const primary = await this.rawChatCompletion(userMessage, wantsResponseFormat);
|
|
254
|
+
if (primary.kind === 'ok')
|
|
255
|
+
return primary.content;
|
|
256
|
+
if (primary.kind === 'timeout') {
|
|
257
|
+
throw new Error(`AI request timed out after ${primary.ms}ms`);
|
|
258
|
+
}
|
|
259
|
+
if (primary.kind !== 'response_format_rejected') {
|
|
260
|
+
throw new Error(`AI request failed: HTTP ${primary.status} from ${this.baseUrl}/chat/completions${primary.body ? ` — ${primary.body.slice(0, 200)}` : ''}`);
|
|
261
|
+
}
|
|
262
|
+
// Auto-fallback: server complained about response_format. Disable it
|
|
263
|
+
// for this provider instance and retry once. We won't fall back a
|
|
264
|
+
// second time even if the user explicitly set strictJson=true, because
|
|
265
|
+
// the gateway clearly doesn't support it.
|
|
266
|
+
this.responseFormatDisabled = true;
|
|
267
|
+
const retry = await this.rawChatCompletion(userMessage, false);
|
|
268
|
+
if (retry.kind === 'ok')
|
|
269
|
+
return retry.content;
|
|
270
|
+
if (retry.kind === 'timeout') {
|
|
271
|
+
throw new Error(`AI request timed out after ${retry.ms}ms`);
|
|
272
|
+
}
|
|
273
|
+
throw new Error(`AI request failed after response_format fallback: HTTP ${retry.status} from ${this.baseUrl}/chat/completions${retry.body ? ` — ${retry.body.slice(0, 200)}` : ''}`);
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* One HTTP request to the chat-completions endpoint. Categorises the
|
|
277
|
+
* response so the caller can decide whether to retry without
|
|
278
|
+
* `response_format` or just throw.
|
|
279
|
+
*/
|
|
280
|
+
async rawChatCompletion(userMessage, includeResponseFormat) {
|
|
281
|
+
const body = {
|
|
282
|
+
model: this.model,
|
|
283
|
+
messages: [
|
|
284
|
+
{ role: 'system', content: SYSTEM_PROMPT },
|
|
285
|
+
{ role: 'user', content: userMessage },
|
|
286
|
+
],
|
|
287
|
+
temperature: 0.2,
|
|
288
|
+
max_tokens: this.maxTokens,
|
|
289
|
+
};
|
|
290
|
+
if (includeResponseFormat) {
|
|
291
|
+
body['response_format'] = { type: 'json_object' };
|
|
292
|
+
}
|
|
293
|
+
const controller = new AbortController();
|
|
294
|
+
const timer = setTimeout(() => controller.abort(), this.timeout);
|
|
295
|
+
try {
|
|
296
|
+
const response = await this.fetchImpl(`${this.baseUrl}/chat/completions`, {
|
|
297
|
+
method: 'POST',
|
|
298
|
+
headers: {
|
|
299
|
+
'Content-Type': 'application/json',
|
|
300
|
+
...(this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {}),
|
|
301
|
+
},
|
|
302
|
+
body: JSON.stringify(body),
|
|
303
|
+
signal: controller.signal,
|
|
304
|
+
});
|
|
305
|
+
if (response.ok) {
|
|
306
|
+
const data = (await response.json());
|
|
307
|
+
const content = data.choices?.[0]?.message?.content;
|
|
308
|
+
if (typeof content !== 'string') {
|
|
309
|
+
return { kind: 'http_error', status: 502, body: 'no message content in response' };
|
|
310
|
+
}
|
|
311
|
+
return { kind: 'ok', content };
|
|
312
|
+
}
|
|
313
|
+
const errBody = await response.text().catch(() => '');
|
|
314
|
+
if (includeResponseFormat && response.status === 400 && /response_format/i.test(errBody)) {
|
|
315
|
+
return { kind: 'response_format_rejected', status: 400, body: errBody };
|
|
316
|
+
}
|
|
317
|
+
return { kind: 'http_error', status: response.status, body: errBody };
|
|
318
|
+
}
|
|
319
|
+
catch (error) {
|
|
320
|
+
if (error instanceof Error && error.name === 'AbortError') {
|
|
321
|
+
return { kind: 'timeout', ms: this.timeout };
|
|
322
|
+
}
|
|
323
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
324
|
+
return { kind: 'http_error', status: 0, body: message };
|
|
325
|
+
}
|
|
326
|
+
finally {
|
|
327
|
+
clearTimeout(timer);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
cacheKeyFor(analysis) {
|
|
331
|
+
// Include serverLabel so two servers with identical tool sets don't
|
|
332
|
+
// share an entry. Include the tool set in a stable order.
|
|
333
|
+
const toolsKey = analysis.tools
|
|
334
|
+
.map((t) => ({
|
|
335
|
+
name: t.name,
|
|
336
|
+
description: t.description ? t.description.slice(0, MAX_DESCRIPTION_CHARS) : '',
|
|
337
|
+
inputSchema: t.inputSchema,
|
|
338
|
+
}))
|
|
339
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
340
|
+
const hash = createHash('sha256')
|
|
341
|
+
.update(this.model)
|
|
342
|
+
.update('\u0000')
|
|
343
|
+
.update(analysis.serverLabel ?? '')
|
|
344
|
+
.update('\u0000')
|
|
345
|
+
.update(JSON.stringify(toolsKey))
|
|
346
|
+
.digest('hex');
|
|
347
|
+
return `ai-cases-${hash}.json`;
|
|
348
|
+
}
|
|
349
|
+
readCache(key) {
|
|
350
|
+
if (!this.cacheDir)
|
|
351
|
+
return null;
|
|
352
|
+
const file = join(this.cacheDir, key);
|
|
353
|
+
try {
|
|
354
|
+
if (!existsSync(file))
|
|
355
|
+
return null;
|
|
356
|
+
// Honour cacheMaxAgeMs via mtime. statSync errors are treated as a
|
|
357
|
+
// miss so a corrupt FS doesn't lock the user out of fresh fetches.
|
|
358
|
+
if (typeof this.cacheMaxAgeMs === 'number' && this.cacheMaxAgeMs > 0) {
|
|
359
|
+
const stat = statSyncSafe(file);
|
|
360
|
+
if (stat && Date.now() - stat.mtimeMs > this.cacheMaxAgeMs)
|
|
361
|
+
return null;
|
|
362
|
+
}
|
|
363
|
+
const data = JSON.parse(readFileSync(file, 'utf-8'));
|
|
364
|
+
if (data.model !== this.model || !Array.isArray(data.cases))
|
|
365
|
+
return null;
|
|
366
|
+
return data.cases
|
|
367
|
+
.map((c) => coerceCase(c, this.name))
|
|
368
|
+
.filter((c) => c !== null);
|
|
369
|
+
}
|
|
370
|
+
catch {
|
|
371
|
+
return null;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
writeCache(key, cases, serverLabel) {
|
|
375
|
+
if (!this.cacheDir)
|
|
376
|
+
return;
|
|
377
|
+
try {
|
|
378
|
+
mkdirSync(this.cacheDir, { recursive: true });
|
|
379
|
+
writeFileSync(join(this.cacheDir, key), JSON.stringify({ model: this.model, serverLabel, cases }, null, 2), 'utf-8');
|
|
380
|
+
}
|
|
381
|
+
catch {
|
|
382
|
+
// Cache is best-effort; never fail generation over it.
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Build a provider from `MCP_TESTER_AI_*` environment variables.
|
|
388
|
+
*
|
|
389
|
+
* - `MCP_TESTER_AI_API_KEY` — required for hosted APIs; optional for local.
|
|
390
|
+
* - `MCP_TESTER_AI_BASE_URL` — @defaultValue 'https://api.openai.com/v1'
|
|
391
|
+
* - `MCP_TESTER_AI_MODEL` — @defaultValue 'gpt-4o-mini'
|
|
392
|
+
*
|
|
393
|
+
* @returns A provider, or `null` when no API key is configured. A base URL
|
|
394
|
+
* pointing at localhost counts as configured even without a key (Ollama
|
|
395
|
+
* and friends don't need auth).
|
|
396
|
+
*/
|
|
397
|
+
export function createProviderFromEnv(env = process.env) {
|
|
398
|
+
const apiKey = env['MCP_TESTER_AI_API_KEY'];
|
|
399
|
+
const baseUrl = env['MCP_TESTER_AI_BASE_URL'];
|
|
400
|
+
const model = env['MCP_TESTER_AI_MODEL'];
|
|
401
|
+
const isLocal = baseUrl ? /\/\/(localhost|127\.0\.0\.1|\[::1\])/.test(baseUrl) : false;
|
|
402
|
+
if (!apiKey && !isLocal)
|
|
403
|
+
return null;
|
|
404
|
+
return new OpenAICompatProvider({
|
|
405
|
+
apiKey,
|
|
406
|
+
baseUrl,
|
|
407
|
+
model,
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Convenience: ask the AI provider to suggest test cases, then validate them
|
|
412
|
+
* against the tools' real input schemas. Does NOT merge with deterministic
|
|
413
|
+
* rules — callers wanting the union should call {@link suggestEdgeCases} (or
|
|
414
|
+
* `suggestEdgeCasesForTools`) themselves and concatenate.
|
|
415
|
+
*
|
|
416
|
+
* @returns Validated cases, rejected entries with reasons, and the provider
|
|
417
|
+
* name (for diagnostic logging).
|
|
418
|
+
*/
|
|
419
|
+
export async function suggestCasesWithAI(analysis, provider, schemas) {
|
|
420
|
+
const aiCases = await provider.suggestCases(analysis);
|
|
421
|
+
const { cases, rejected } = mergeAndValidateCases([aiCases], schemas);
|
|
422
|
+
return {
|
|
423
|
+
cases,
|
|
424
|
+
rejected: rejected.map((r) => `${r.c.tool}/${r.c.title}: ${r.reason}`),
|
|
425
|
+
providerName: provider.name,
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
//# sourceMappingURL=provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.js","sourceRoot":"","sources":["../../src/ai/provider.ts"],"names":[],"mappings":"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"}
|