mock-mcp 0.3.1 → 0.5.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 +212 -124
- package/dist/adapter/index.cjs +712 -0
- package/dist/adapter/index.d.cts +55 -0
- package/dist/adapter/index.d.ts +55 -0
- package/dist/adapter/index.js +672 -0
- package/dist/client/connect.cjs +913 -0
- package/dist/client/connect.d.cts +211 -0
- package/dist/client/connect.d.ts +204 -7
- package/dist/client/connect.js +863 -20
- package/dist/client/index.cjs +914 -0
- package/dist/client/index.d.cts +4 -0
- package/dist/client/index.d.ts +4 -2
- package/dist/client/index.js +873 -2
- package/dist/daemon/index.cjs +667 -0
- package/dist/daemon/index.d.cts +62 -0
- package/dist/daemon/index.d.ts +62 -0
- package/dist/daemon/index.js +628 -0
- package/dist/discovery-Dc2LdF8q.d.cts +105 -0
- package/dist/discovery-Dc2LdF8q.d.ts +105 -0
- package/dist/index.cjs +2238 -0
- package/dist/index.d.cts +472 -0
- package/dist/index.d.ts +472 -11
- package/dist/index.js +2185 -53
- package/dist/protocol-CiwaQFOt.d.ts +239 -0
- package/dist/protocol-xZu-wb0n.d.cts +239 -0
- package/dist/shared/index.cjs +386 -0
- package/dist/shared/index.d.cts +4 -0
- package/dist/shared/index.d.ts +4 -0
- package/dist/shared/index.js +310 -0
- package/dist/types-BKREdsyr.d.cts +32 -0
- package/dist/types-BKREdsyr.d.ts +32 -0
- package/package.json +44 -4
- package/dist/client/batch-mock-collector.d.ts +0 -111
- package/dist/client/batch-mock-collector.js +0 -308
- package/dist/client/util.d.ts +0 -1
- package/dist/client/util.js +0 -3
- package/dist/connect.cjs +0 -400
- package/dist/connect.d.cts +0 -82
- package/dist/server/index.d.ts +0 -1
- package/dist/server/index.js +0 -1
- package/dist/server/test-mock-mcp-server.d.ts +0 -73
- package/dist/server/test-mock-mcp-server.js +0 -419
- package/dist/types.d.ts +0 -45
- package/dist/types.js +0 -2
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import fssync from 'fs';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import crypto from 'crypto';
|
|
6
|
+
import { spawn } from 'child_process';
|
|
7
|
+
import http from 'http';
|
|
8
|
+
import { fileURLToPath, pathToFileURL } from 'url';
|
|
9
|
+
import { createRequire } from 'module';
|
|
10
|
+
|
|
11
|
+
// src/shared/discovery.ts
|
|
12
|
+
function debugLog(_msg) {
|
|
13
|
+
}
|
|
14
|
+
var __curDirname = (() => {
|
|
15
|
+
try {
|
|
16
|
+
const metaUrl = import.meta.url;
|
|
17
|
+
if (metaUrl && typeof metaUrl === "string" && metaUrl.startsWith("file://")) {
|
|
18
|
+
return path.dirname(fileURLToPath(metaUrl));
|
|
19
|
+
}
|
|
20
|
+
} catch {
|
|
21
|
+
}
|
|
22
|
+
return process.cwd();
|
|
23
|
+
})();
|
|
24
|
+
function resolveProjectRoot(startDir = process.cwd()) {
|
|
25
|
+
let current = path.resolve(startDir);
|
|
26
|
+
const root = path.parse(current).root;
|
|
27
|
+
while (current !== root) {
|
|
28
|
+
const gitPath = path.join(current, ".git");
|
|
29
|
+
try {
|
|
30
|
+
const stat = fssync.statSync(gitPath);
|
|
31
|
+
if (stat.isDirectory() || stat.isFile()) {
|
|
32
|
+
return current;
|
|
33
|
+
}
|
|
34
|
+
} catch {
|
|
35
|
+
}
|
|
36
|
+
const pkgPath = path.join(current, "package.json");
|
|
37
|
+
try {
|
|
38
|
+
fssync.accessSync(pkgPath, fssync.constants.F_OK);
|
|
39
|
+
return current;
|
|
40
|
+
} catch {
|
|
41
|
+
}
|
|
42
|
+
current = path.dirname(current);
|
|
43
|
+
}
|
|
44
|
+
return path.resolve(startDir);
|
|
45
|
+
}
|
|
46
|
+
function computeProjectId(projectRoot) {
|
|
47
|
+
const real = fssync.realpathSync(projectRoot);
|
|
48
|
+
return crypto.createHash("sha256").update(real).digest("hex").slice(0, 16);
|
|
49
|
+
}
|
|
50
|
+
function getCacheDir(override) {
|
|
51
|
+
if (override) {
|
|
52
|
+
return override;
|
|
53
|
+
}
|
|
54
|
+
const envCacheDir = process.env.MOCK_MCP_CACHE_DIR;
|
|
55
|
+
if (envCacheDir) {
|
|
56
|
+
return envCacheDir;
|
|
57
|
+
}
|
|
58
|
+
const xdg = process.env.XDG_CACHE_HOME;
|
|
59
|
+
if (xdg) {
|
|
60
|
+
return xdg;
|
|
61
|
+
}
|
|
62
|
+
if (process.platform === "win32" && process.env.LOCALAPPDATA) {
|
|
63
|
+
return process.env.LOCALAPPDATA;
|
|
64
|
+
}
|
|
65
|
+
const home = os.homedir();
|
|
66
|
+
if (home) {
|
|
67
|
+
return path.join(home, ".cache");
|
|
68
|
+
}
|
|
69
|
+
return os.tmpdir();
|
|
70
|
+
}
|
|
71
|
+
function getPaths(projectId, cacheDir) {
|
|
72
|
+
const base = path.join(getCacheDir(cacheDir), "mock-mcp");
|
|
73
|
+
const registryPath = path.join(base, `${projectId}.json`);
|
|
74
|
+
const lockPath = path.join(base, `${projectId}.lock`);
|
|
75
|
+
const ipcPath = process.platform === "win32" ? `\\\\.\\pipe\\mock-mcp-${projectId}` : path.join(base, `${projectId}.sock`);
|
|
76
|
+
return { base, registryPath, lockPath, ipcPath };
|
|
77
|
+
}
|
|
78
|
+
async function readRegistry(registryPath) {
|
|
79
|
+
try {
|
|
80
|
+
const txt = await fs.readFile(registryPath, "utf-8");
|
|
81
|
+
return JSON.parse(txt);
|
|
82
|
+
} catch (error) {
|
|
83
|
+
debugLog(`readRegistry error for ${registryPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
async function writeRegistry(registryPath, registry) {
|
|
88
|
+
await fs.writeFile(registryPath, JSON.stringify(registry, null, 2), {
|
|
89
|
+
encoding: "utf-8",
|
|
90
|
+
mode: 384
|
|
91
|
+
// Read/write for owner only
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
async function healthCheck(ipcPath, timeoutMs = 2e3) {
|
|
95
|
+
return new Promise((resolve) => {
|
|
96
|
+
const req = http.request(
|
|
97
|
+
{
|
|
98
|
+
method: "GET",
|
|
99
|
+
socketPath: ipcPath,
|
|
100
|
+
path: "/health",
|
|
101
|
+
timeout: timeoutMs
|
|
102
|
+
},
|
|
103
|
+
(res) => {
|
|
104
|
+
resolve(res.statusCode === 200);
|
|
105
|
+
}
|
|
106
|
+
);
|
|
107
|
+
req.on("error", () => resolve(false));
|
|
108
|
+
req.on("timeout", () => {
|
|
109
|
+
req.destroy();
|
|
110
|
+
resolve(false);
|
|
111
|
+
});
|
|
112
|
+
req.end();
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
async function tryAcquireLock(lockPath) {
|
|
116
|
+
try {
|
|
117
|
+
const fh = await fs.open(lockPath, "wx");
|
|
118
|
+
await fh.write(`${process.pid}
|
|
119
|
+
`);
|
|
120
|
+
return fh;
|
|
121
|
+
} catch {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
async function releaseLock(lockPath, fh) {
|
|
126
|
+
await fh.close();
|
|
127
|
+
await fs.rm(lockPath).catch(() => {
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
function randomToken() {
|
|
131
|
+
return crypto.randomBytes(24).toString("base64url");
|
|
132
|
+
}
|
|
133
|
+
function getDaemonEntryPath() {
|
|
134
|
+
try {
|
|
135
|
+
const cwdRequire = createRequire(pathToFileURL(path.join(process.cwd(), "index.js")).href);
|
|
136
|
+
const resolved = cwdRequire.resolve("mock-mcp");
|
|
137
|
+
const distDir = path.dirname(resolved);
|
|
138
|
+
const daemonEntry = path.join(distDir, "index.js");
|
|
139
|
+
if (fssync.existsSync(daemonEntry)) {
|
|
140
|
+
return daemonEntry;
|
|
141
|
+
}
|
|
142
|
+
} catch {
|
|
143
|
+
}
|
|
144
|
+
try {
|
|
145
|
+
const packageRoot = resolveProjectRoot(__curDirname);
|
|
146
|
+
const distPath = path.join(packageRoot, "dist", "index.js");
|
|
147
|
+
if (fssync.existsSync(distPath)) {
|
|
148
|
+
return distPath;
|
|
149
|
+
}
|
|
150
|
+
} catch {
|
|
151
|
+
}
|
|
152
|
+
if (process.argv[1]) {
|
|
153
|
+
return process.argv[1];
|
|
154
|
+
}
|
|
155
|
+
return path.join(process.cwd(), "dist", "index.js");
|
|
156
|
+
}
|
|
157
|
+
async function ensureDaemonRunning(opts = {}) {
|
|
158
|
+
const projectRoot = opts.projectRoot ?? resolveProjectRoot();
|
|
159
|
+
const projectId = computeProjectId(projectRoot);
|
|
160
|
+
const { base, registryPath, lockPath, ipcPath } = getPaths(
|
|
161
|
+
projectId,
|
|
162
|
+
opts.cacheDir
|
|
163
|
+
);
|
|
164
|
+
const timeoutMs = opts.timeoutMs ?? 1e4;
|
|
165
|
+
await fs.mkdir(base, { recursive: true });
|
|
166
|
+
const existing = await readRegistry(registryPath);
|
|
167
|
+
debugLog(`Registry read result: ${existing ? "Found (PID " + existing.pid + ")" : "Null"}`);
|
|
168
|
+
if (existing) {
|
|
169
|
+
let healthy = false;
|
|
170
|
+
for (let i = 0; i < 3; i++) {
|
|
171
|
+
debugLog(`Checking health attempt ${i + 1}/3 on ${existing.ipcPath}`);
|
|
172
|
+
healthy = await healthCheck(existing.ipcPath);
|
|
173
|
+
if (healthy) break;
|
|
174
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
175
|
+
}
|
|
176
|
+
if (healthy) {
|
|
177
|
+
return existing;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (process.platform !== "win32") {
|
|
181
|
+
try {
|
|
182
|
+
await fs.rm(ipcPath);
|
|
183
|
+
} catch {
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
const lock = await tryAcquireLock(lockPath);
|
|
187
|
+
if (lock) {
|
|
188
|
+
try {
|
|
189
|
+
const recheckReg = await readRegistry(registryPath);
|
|
190
|
+
if (recheckReg && await healthCheck(recheckReg.ipcPath)) {
|
|
191
|
+
return recheckReg;
|
|
192
|
+
}
|
|
193
|
+
const token = randomToken();
|
|
194
|
+
const daemonEntry = getDaemonEntryPath();
|
|
195
|
+
const child = spawn(
|
|
196
|
+
process.execPath,
|
|
197
|
+
[daemonEntry, "daemon", "--project-root", projectRoot, "--token", token],
|
|
198
|
+
{
|
|
199
|
+
detached: true,
|
|
200
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
201
|
+
env: {
|
|
202
|
+
...process.env,
|
|
203
|
+
MOCK_MCP_CACHE_DIR: opts.cacheDir ?? ""
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
);
|
|
207
|
+
let daemonStderr = "";
|
|
208
|
+
let daemonStdout = "";
|
|
209
|
+
child.stdout?.on("data", (data) => {
|
|
210
|
+
const str = data.toString();
|
|
211
|
+
debugLog(`Daemon stdout: ${str}`);
|
|
212
|
+
});
|
|
213
|
+
child.stderr?.on("data", (data) => {
|
|
214
|
+
daemonStderr += data.toString();
|
|
215
|
+
debugLog(`Daemon stderr: ${data.toString()}`);
|
|
216
|
+
});
|
|
217
|
+
child.on("error", (err) => {
|
|
218
|
+
console.error(`[mock-mcp] Daemon spawn error: ${err.message}`);
|
|
219
|
+
});
|
|
220
|
+
child.on("exit", (code, signal) => {
|
|
221
|
+
if (code !== null && code !== 0) {
|
|
222
|
+
console.error(`[mock-mcp] Daemon exited with code: ${code}`);
|
|
223
|
+
if (daemonStderr) {
|
|
224
|
+
console.error(`[mock-mcp] Daemon stderr: ${daemonStderr.slice(0, 500)}`);
|
|
225
|
+
}
|
|
226
|
+
} else if (signal) {
|
|
227
|
+
console.error(`[mock-mcp] Daemon killed by signal: ${signal}`);
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
child.unref();
|
|
231
|
+
const deadline2 = Date.now() + timeoutMs;
|
|
232
|
+
while (Date.now() < deadline2) {
|
|
233
|
+
const reg = await readRegistry(registryPath);
|
|
234
|
+
if (reg && await healthCheck(reg.ipcPath)) {
|
|
235
|
+
return reg;
|
|
236
|
+
}
|
|
237
|
+
await sleep(50);
|
|
238
|
+
}
|
|
239
|
+
console.error("[mock-mcp] Daemon failed to start within timeout");
|
|
240
|
+
if (daemonStderr) {
|
|
241
|
+
console.error(`[mock-mcp] Daemon stderr:
|
|
242
|
+
${daemonStderr}`);
|
|
243
|
+
}
|
|
244
|
+
throw new Error(
|
|
245
|
+
`Daemon start timeout after ${timeoutMs}ms. Check logs for details.`
|
|
246
|
+
);
|
|
247
|
+
} finally {
|
|
248
|
+
await releaseLock(lockPath, lock);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
const deadline = Date.now() + timeoutMs;
|
|
252
|
+
while (Date.now() < deadline) {
|
|
253
|
+
const reg = await readRegistry(registryPath);
|
|
254
|
+
if (reg && await healthCheck(reg.ipcPath)) {
|
|
255
|
+
return reg;
|
|
256
|
+
}
|
|
257
|
+
await sleep(50);
|
|
258
|
+
}
|
|
259
|
+
throw new Error(
|
|
260
|
+
`Waiting for daemon timed out after ${timeoutMs}ms. Another process may have failed to start it.`
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
function sleep(ms) {
|
|
264
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// src/shared/protocol.ts
|
|
268
|
+
var HELLO_TEST = "HELLO_TEST";
|
|
269
|
+
var HELLO_ACK = "HELLO_ACK";
|
|
270
|
+
var BATCH_MOCK_REQUEST = "BATCH_MOCK_REQUEST";
|
|
271
|
+
var BATCH_MOCK_RESULT = "BATCH_MOCK_RESULT";
|
|
272
|
+
var HEARTBEAT = "HEARTBEAT";
|
|
273
|
+
var HEARTBEAT_ACK = "HEARTBEAT_ACK";
|
|
274
|
+
var RPC_GET_STATUS = "getStatus";
|
|
275
|
+
var RPC_LIST_RUNS = "listRuns";
|
|
276
|
+
var RPC_CLAIM_NEXT_BATCH = "claimNextBatch";
|
|
277
|
+
var RPC_PROVIDE_BATCH = "provideBatch";
|
|
278
|
+
var RPC_RELEASE_BATCH = "releaseBatch";
|
|
279
|
+
var RPC_GET_BATCH = "getBatch";
|
|
280
|
+
var RPC_ERROR_PARSE = -32700;
|
|
281
|
+
var RPC_ERROR_INVALID_REQUEST = -32600;
|
|
282
|
+
var RPC_ERROR_METHOD_NOT_FOUND = -32601;
|
|
283
|
+
var RPC_ERROR_INVALID_PARAMS = -32602;
|
|
284
|
+
var RPC_ERROR_INTERNAL = -32603;
|
|
285
|
+
var RPC_ERROR_NOT_FOUND = -32e3;
|
|
286
|
+
var RPC_ERROR_UNAUTHORIZED = -32001;
|
|
287
|
+
var RPC_ERROR_CONFLICT = -32002;
|
|
288
|
+
var RPC_ERROR_EXPIRED = -32003;
|
|
289
|
+
function isHelloTestMessage(msg) {
|
|
290
|
+
if (!msg || typeof msg !== "object") return false;
|
|
291
|
+
const m = msg;
|
|
292
|
+
return m.type === HELLO_TEST && typeof m.token === "string" && typeof m.runId === "string" && typeof m.pid === "number" && typeof m.cwd === "string";
|
|
293
|
+
}
|
|
294
|
+
function isBatchMockRequestMessage(msg) {
|
|
295
|
+
if (!msg || typeof msg !== "object") return false;
|
|
296
|
+
const m = msg;
|
|
297
|
+
return m.type === BATCH_MOCK_REQUEST && typeof m.runId === "string" && Array.isArray(m.requests);
|
|
298
|
+
}
|
|
299
|
+
function isHeartbeatMessage(msg) {
|
|
300
|
+
if (!msg || typeof msg !== "object") return false;
|
|
301
|
+
const m = msg;
|
|
302
|
+
return m.type === HEARTBEAT && typeof m.runId === "string";
|
|
303
|
+
}
|
|
304
|
+
function isJsonRpcRequest(msg) {
|
|
305
|
+
if (!msg || typeof msg !== "object") return false;
|
|
306
|
+
const m = msg;
|
|
307
|
+
return m.jsonrpc === "2.0" && (typeof m.id === "string" || typeof m.id === "number") && typeof m.method === "string";
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
export { BATCH_MOCK_REQUEST, BATCH_MOCK_RESULT, HEARTBEAT, HEARTBEAT_ACK, HELLO_ACK, HELLO_TEST, RPC_CLAIM_NEXT_BATCH, RPC_ERROR_CONFLICT, RPC_ERROR_EXPIRED, RPC_ERROR_INTERNAL, RPC_ERROR_INVALID_PARAMS, RPC_ERROR_INVALID_REQUEST, RPC_ERROR_METHOD_NOT_FOUND, RPC_ERROR_NOT_FOUND, RPC_ERROR_PARSE, RPC_ERROR_UNAUTHORIZED, RPC_GET_BATCH, RPC_GET_STATUS, RPC_LIST_RUNS, RPC_PROVIDE_BATCH, RPC_RELEASE_BATCH, computeProjectId, ensureDaemonRunning, getCacheDir, getDaemonEntryPath, getPaths, healthCheck, isBatchMockRequestMessage, isHeartbeatMessage, isHelloTestMessage, isJsonRpcRequest, randomToken, readRegistry, releaseLock, resolveProjectRoot, sleep, tryAcquireLock, writeRegistry };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core types for mock-mcp.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Shape of a mock request emitted by the test process.
|
|
6
|
+
*/
|
|
7
|
+
interface MockRequestDescriptor {
|
|
8
|
+
requestId: string;
|
|
9
|
+
endpoint: string;
|
|
10
|
+
method: string;
|
|
11
|
+
body?: unknown;
|
|
12
|
+
headers?: Record<string, string>;
|
|
13
|
+
metadata?: Record<string, unknown>;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Shape of the mock data that needs to be returned for a request.
|
|
17
|
+
*/
|
|
18
|
+
interface MockResponseDescriptor {
|
|
19
|
+
requestId: string;
|
|
20
|
+
data: unknown;
|
|
21
|
+
status?: number;
|
|
22
|
+
headers?: Record<string, string>;
|
|
23
|
+
delayMs?: number;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Resolved mock with typed data.
|
|
27
|
+
*/
|
|
28
|
+
interface ResolvedMock<T = unknown> extends Omit<MockResponseDescriptor, "data"> {
|
|
29
|
+
data: T;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type { MockRequestDescriptor as M, ResolvedMock as R, MockResponseDescriptor as a };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core types for mock-mcp.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Shape of a mock request emitted by the test process.
|
|
6
|
+
*/
|
|
7
|
+
interface MockRequestDescriptor {
|
|
8
|
+
requestId: string;
|
|
9
|
+
endpoint: string;
|
|
10
|
+
method: string;
|
|
11
|
+
body?: unknown;
|
|
12
|
+
headers?: Record<string, string>;
|
|
13
|
+
metadata?: Record<string, unknown>;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Shape of the mock data that needs to be returned for a request.
|
|
17
|
+
*/
|
|
18
|
+
interface MockResponseDescriptor {
|
|
19
|
+
requestId: string;
|
|
20
|
+
data: unknown;
|
|
21
|
+
status?: number;
|
|
22
|
+
headers?: Record<string, string>;
|
|
23
|
+
delayMs?: number;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Resolved mock with typed data.
|
|
27
|
+
*/
|
|
28
|
+
interface ResolvedMock<T = unknown> extends Omit<MockResponseDescriptor, "data"> {
|
|
29
|
+
data: T;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type { MockRequestDescriptor as M, ResolvedMock as R, MockResponseDescriptor as a };
|
package/package.json
CHANGED
|
@@ -1,12 +1,46 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mock-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "An MCP server enabling LLMs to write integration tests through live test environment interaction",
|
|
5
|
-
"main": "./dist/
|
|
5
|
+
"main": "./dist/index.cjs",
|
|
6
|
+
"module": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
6
8
|
"type": "module",
|
|
7
9
|
"bin": {
|
|
8
10
|
"mock-mcp": "./dist/index.js"
|
|
9
11
|
},
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"import": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"default": "./dist/index.js"
|
|
17
|
+
},
|
|
18
|
+
"require": {
|
|
19
|
+
"types": "./dist/index.d.cts",
|
|
20
|
+
"default": "./dist/index.cjs"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"./client": {
|
|
24
|
+
"import": {
|
|
25
|
+
"types": "./dist/client/index.d.ts",
|
|
26
|
+
"default": "./dist/client/index.js"
|
|
27
|
+
},
|
|
28
|
+
"require": {
|
|
29
|
+
"types": "./dist/client/index.d.cts",
|
|
30
|
+
"default": "./dist/client/index.cjs"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"./connect": {
|
|
34
|
+
"import": {
|
|
35
|
+
"types": "./dist/client/connect.d.ts",
|
|
36
|
+
"default": "./dist/client/connect.js"
|
|
37
|
+
},
|
|
38
|
+
"require": {
|
|
39
|
+
"types": "./dist/client/connect.d.cts",
|
|
40
|
+
"default": "./dist/client/connect.cjs"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
},
|
|
10
44
|
"files": [
|
|
11
45
|
"dist"
|
|
12
46
|
],
|
|
@@ -17,12 +51,17 @@
|
|
|
17
51
|
"node": ">=18.0.0"
|
|
18
52
|
},
|
|
19
53
|
"scripts": {
|
|
20
|
-
"build": "
|
|
54
|
+
"build": "tsup",
|
|
55
|
+
"clean": "rimraf dist",
|
|
21
56
|
"start": "node dist/index.js",
|
|
57
|
+
"start:adapter": "node dist/index.js adapter",
|
|
22
58
|
"dev": "tsx src/index.ts",
|
|
59
|
+
"dev:adapter": "tsx src/index.ts adapter",
|
|
23
60
|
"lint": "eslint . --ext .ts",
|
|
24
61
|
"test": "vitest run",
|
|
25
62
|
"test:watch": "vitest --watch",
|
|
63
|
+
"test:concurrency": "vitest run test/concurrency.test.ts",
|
|
64
|
+
"test:inspector": "vitest run test/inspector-integration.test.ts",
|
|
26
65
|
"prepublishOnly": "npm run build",
|
|
27
66
|
"link": "npm link",
|
|
28
67
|
"unlink": "npm unlink",
|
|
@@ -56,6 +95,7 @@
|
|
|
56
95
|
"commitizen": "^4.3.1",
|
|
57
96
|
"eslint": "^9.31.0",
|
|
58
97
|
"node-fetch": "^3.3.2",
|
|
98
|
+
"rimraf": "^6.1.2",
|
|
59
99
|
"tsup": "^8.5.0",
|
|
60
100
|
"tsx": "^4.20.3",
|
|
61
101
|
"typescript": "^5.8.3",
|
|
@@ -70,4 +110,4 @@
|
|
|
70
110
|
"path": "cz-conventional-changelog"
|
|
71
111
|
}
|
|
72
112
|
}
|
|
73
|
-
}
|
|
113
|
+
}
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
import { type ResolvedMock } from "../types.js";
|
|
2
|
-
type Logger = Pick<Console, "log" | "warn" | "error"> & {
|
|
3
|
-
debug?: (...args: unknown[]) => void;
|
|
4
|
-
};
|
|
5
|
-
export interface BatchMockCollectorOptions {
|
|
6
|
-
/**
|
|
7
|
-
* TCP port exposed by {@link TestMockMCPServer}.
|
|
8
|
-
*
|
|
9
|
-
* @default 3002
|
|
10
|
-
*/
|
|
11
|
-
port?: number;
|
|
12
|
-
/**
|
|
13
|
-
* Timeout for individual mock requests in milliseconds.
|
|
14
|
-
*
|
|
15
|
-
* @default 60000
|
|
16
|
-
*/
|
|
17
|
-
timeout?: number;
|
|
18
|
-
/**
|
|
19
|
-
* Delay (in milliseconds) that determines how long the collector waits before
|
|
20
|
-
* flushing the current batch. Setting this to 0 mirrors the "flush on the next
|
|
21
|
-
* macrotask" approach described in the technical design document.
|
|
22
|
-
*
|
|
23
|
-
* @default 0
|
|
24
|
-
*/
|
|
25
|
-
batchDebounceMs?: number;
|
|
26
|
-
/**
|
|
27
|
-
* Maximum number of requests that may be included in a single batch payload.
|
|
28
|
-
* Requests that exceed this limit will be split into multiple batches.
|
|
29
|
-
*
|
|
30
|
-
* @default 50
|
|
31
|
-
*/
|
|
32
|
-
maxBatchSize?: number;
|
|
33
|
-
/**
|
|
34
|
-
* Optional custom logger. Defaults to `console`.
|
|
35
|
-
*/
|
|
36
|
-
logger?: Logger;
|
|
37
|
-
/**
|
|
38
|
-
* Interval for WebSocket heartbeats in milliseconds. Set to 0 to disable.
|
|
39
|
-
*
|
|
40
|
-
* @default 15000
|
|
41
|
-
*/
|
|
42
|
-
heartbeatIntervalMs?: number;
|
|
43
|
-
/**
|
|
44
|
-
* Automatically attempt to reconnect when the WebSocket closes unexpectedly.
|
|
45
|
-
*
|
|
46
|
-
* @default true
|
|
47
|
-
*/
|
|
48
|
-
enableReconnect?: boolean;
|
|
49
|
-
}
|
|
50
|
-
export interface RequestMockOptions {
|
|
51
|
-
body?: unknown;
|
|
52
|
-
headers?: Record<string, string>;
|
|
53
|
-
metadata?: Record<string, unknown>;
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* Collects HTTP requests issued during a single macrotask and forwards them to
|
|
57
|
-
* the MCP server as a batch for AI-assisted mock generation.
|
|
58
|
-
*/
|
|
59
|
-
export declare class BatchMockCollector {
|
|
60
|
-
private ws;
|
|
61
|
-
private readonly pendingRequests;
|
|
62
|
-
private readonly queuedRequestIds;
|
|
63
|
-
private readonly timeout;
|
|
64
|
-
private readonly batchDebounceMs;
|
|
65
|
-
private readonly maxBatchSize;
|
|
66
|
-
private readonly logger;
|
|
67
|
-
private readonly heartbeatIntervalMs;
|
|
68
|
-
private readonly enableReconnect;
|
|
69
|
-
private readonly port;
|
|
70
|
-
private batchTimer;
|
|
71
|
-
private heartbeatTimer;
|
|
72
|
-
private reconnectTimer;
|
|
73
|
-
private requestIdCounter;
|
|
74
|
-
private closed;
|
|
75
|
-
private readyResolve?;
|
|
76
|
-
private readyReject?;
|
|
77
|
-
private readyPromise;
|
|
78
|
-
constructor(options?: BatchMockCollectorOptions);
|
|
79
|
-
/**
|
|
80
|
-
* Ensures the underlying WebSocket connection is ready for use.
|
|
81
|
-
*/
|
|
82
|
-
waitUntilReady(): Promise<void>;
|
|
83
|
-
/**
|
|
84
|
-
* Request mock data for a specific endpoint/method pair.
|
|
85
|
-
*/
|
|
86
|
-
requestMock<T = unknown>(endpoint: string, method: string, options?: RequestMockOptions): Promise<ResolvedMock<T>>;
|
|
87
|
-
/**
|
|
88
|
-
* Wait for all requests that are currently pending to settle. Requests
|
|
89
|
-
* created after this method is called are not included.
|
|
90
|
-
*/
|
|
91
|
-
waitForPendingRequests(): Promise<void>;
|
|
92
|
-
/**
|
|
93
|
-
* Close the underlying connection and fail all pending requests.
|
|
94
|
-
*/
|
|
95
|
-
close(code?: number): Promise<void>;
|
|
96
|
-
private setupWebSocket;
|
|
97
|
-
private createWebSocket;
|
|
98
|
-
private resetReadyPromise;
|
|
99
|
-
private startHeartbeat;
|
|
100
|
-
private stopHeartbeat;
|
|
101
|
-
private scheduleReconnect;
|
|
102
|
-
private handleMessage;
|
|
103
|
-
private resolveRequest;
|
|
104
|
-
private enqueueRequest;
|
|
105
|
-
private flushQueue;
|
|
106
|
-
private sendBatch;
|
|
107
|
-
private buildResolvedMock;
|
|
108
|
-
private rejectRequest;
|
|
109
|
-
private failAllPending;
|
|
110
|
-
}
|
|
111
|
-
export {};
|