mock-mcp 0.0.1 → 0.1.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/LICENSE +21 -0
- package/README.md +373 -0
- package/dist/connect.cjs +269 -0
- package/dist/connect.d.cts +90 -0
- package/dist/package.json +73 -0
- package/dist/src/client/batch-mock-collector.d.ts +82 -0
- package/dist/src/client/batch-mock-collector.js +201 -0
- package/dist/src/client/connect.d.ts +8 -0
- package/dist/src/client/connect.js +16 -0
- package/dist/src/client/index.d.ts +2 -0
- package/dist/src/client/index.js +2 -0
- package/dist/src/index.d.ts +10 -0
- package/dist/src/index.js +53 -0
- package/dist/src/server/index.d.ts +1 -0
- package/dist/src/server/index.js +1 -0
- package/dist/src/server/test-mock-mcp-server.d.ts +73 -0
- package/dist/src/server/test-mock-mcp-server.js +392 -0
- package/dist/src/types.d.ts +42 -0
- package/dist/src/types.js +2 -0
- package/package.json +67 -5
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
type Logger = Pick<Console, "log" | "warn" | "error"> & {
|
|
2
|
+
debug?: (...args: unknown[]) => void;
|
|
3
|
+
};
|
|
4
|
+
interface BatchMockCollectorOptions {
|
|
5
|
+
/**
|
|
6
|
+
* TCP port exposed by {@link TestMockMCPServer}.
|
|
7
|
+
*
|
|
8
|
+
* @default 8080
|
|
9
|
+
*/
|
|
10
|
+
port?: number;
|
|
11
|
+
/**
|
|
12
|
+
* Timeout for individual mock requests in milliseconds.
|
|
13
|
+
*
|
|
14
|
+
* @default 60000
|
|
15
|
+
*/
|
|
16
|
+
timeout?: number;
|
|
17
|
+
/**
|
|
18
|
+
* Delay (in milliseconds) that determines how long the collector waits before
|
|
19
|
+
* flushing the current batch. Setting this to 0 mirrors the "flush on the next
|
|
20
|
+
* macrotask" approach described in the technical design document.
|
|
21
|
+
*
|
|
22
|
+
* @default 0
|
|
23
|
+
*/
|
|
24
|
+
batchDebounceMs?: number;
|
|
25
|
+
/**
|
|
26
|
+
* Maximum number of requests that may be included in a single batch payload.
|
|
27
|
+
* Requests that exceed this limit will be split into multiple batches.
|
|
28
|
+
*
|
|
29
|
+
* @default 50
|
|
30
|
+
*/
|
|
31
|
+
maxBatchSize?: number;
|
|
32
|
+
/**
|
|
33
|
+
* Optional custom logger. Defaults to `console`.
|
|
34
|
+
*/
|
|
35
|
+
logger?: Logger;
|
|
36
|
+
}
|
|
37
|
+
interface RequestMockOptions {
|
|
38
|
+
body?: unknown;
|
|
39
|
+
headers?: Record<string, string>;
|
|
40
|
+
metadata?: Record<string, unknown>;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Collects HTTP requests issued during a single macrotask and forwards them to
|
|
44
|
+
* the MCP server as a batch for AI-assisted mock generation.
|
|
45
|
+
*/
|
|
46
|
+
declare class BatchMockCollector {
|
|
47
|
+
private readonly ws;
|
|
48
|
+
private readonly pendingRequests;
|
|
49
|
+
private readonly queuedRequestIds;
|
|
50
|
+
private readonly timeout;
|
|
51
|
+
private readonly batchDebounceMs;
|
|
52
|
+
private readonly maxBatchSize;
|
|
53
|
+
private readonly logger;
|
|
54
|
+
private batchTimer;
|
|
55
|
+
private requestIdCounter;
|
|
56
|
+
private closed;
|
|
57
|
+
private readyResolve?;
|
|
58
|
+
private readyReject?;
|
|
59
|
+
private readonly readyPromise;
|
|
60
|
+
constructor(options?: BatchMockCollectorOptions);
|
|
61
|
+
/**
|
|
62
|
+
* Ensures the underlying WebSocket connection is ready for use.
|
|
63
|
+
*/
|
|
64
|
+
waitUntilReady(): Promise<void>;
|
|
65
|
+
/**
|
|
66
|
+
* Request mock data for a specific endpoint/method pair.
|
|
67
|
+
*/
|
|
68
|
+
requestMock<T = unknown>(endpoint: string, method: string, options?: RequestMockOptions): Promise<T>;
|
|
69
|
+
/**
|
|
70
|
+
* Close the underlying connection and fail all pending requests.
|
|
71
|
+
*/
|
|
72
|
+
close(code?: number): Promise<void>;
|
|
73
|
+
private setupWebSocket;
|
|
74
|
+
private handleMessage;
|
|
75
|
+
private resolveRequest;
|
|
76
|
+
private enqueueRequest;
|
|
77
|
+
private flushQueue;
|
|
78
|
+
private sendBatch;
|
|
79
|
+
private rejectRequest;
|
|
80
|
+
private failAllPending;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
type ConnectOptions = number | BatchMockCollectorOptions | undefined;
|
|
84
|
+
/**
|
|
85
|
+
* Convenience helper that creates a {@link BatchMockCollector} and waits for the
|
|
86
|
+
* underlying WebSocket connection to become ready before resolving.
|
|
87
|
+
*/
|
|
88
|
+
declare const connect: (options?: ConnectOptions) => Promise<BatchMockCollector | void>;
|
|
89
|
+
|
|
90
|
+
export { type ConnectOptions, connect };
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mock-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "An MCP server enabling LLMs to write integration tests through live test environment interaction",
|
|
5
|
+
"main": "./dist/connect.cjs",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"mock-mcp": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public"
|
|
15
|
+
},
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=18.0.0"
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsc && tsup src/client/connect.ts --dts",
|
|
21
|
+
"start": "node dist/index.js",
|
|
22
|
+
"dev": "tsx src/index.ts",
|
|
23
|
+
"lint": "eslint . --ext .ts",
|
|
24
|
+
"test": "vitest run",
|
|
25
|
+
"test:watch": "vitest --watch",
|
|
26
|
+
"prepublishOnly": "npm run build",
|
|
27
|
+
"link": "npm link",
|
|
28
|
+
"unlink": "npm unlink",
|
|
29
|
+
"commit": "yarn git-cz"
|
|
30
|
+
},
|
|
31
|
+
"keywords": [
|
|
32
|
+
"mcp",
|
|
33
|
+
"model-context-protocol",
|
|
34
|
+
"mock",
|
|
35
|
+
"mock-mcp"
|
|
36
|
+
],
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "git+https://github.com/mcpland/mock-mcp.git"
|
|
40
|
+
},
|
|
41
|
+
"author": "unadlib",
|
|
42
|
+
"license": "MIT",
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@cfworker/json-schema": "^4.1.1",
|
|
45
|
+
"@modelcontextprotocol/sdk": "^1.20.2",
|
|
46
|
+
"ts-morph": "^27.0.2",
|
|
47
|
+
"ws": "^8.18.3",
|
|
48
|
+
"zod": "^4.1.12"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@types/node": "^24.0.15",
|
|
52
|
+
"@types/ws": "^8.18.1",
|
|
53
|
+
"@typescript-eslint/eslint-plugin": "^8.38.0",
|
|
54
|
+
"@typescript-eslint/parser": "^8.38.0",
|
|
55
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
56
|
+
"commitizen": "^4.3.1",
|
|
57
|
+
"eslint": "^9.31.0",
|
|
58
|
+
"node-fetch": "^3.3.2",
|
|
59
|
+
"tsup": "^8.5.0",
|
|
60
|
+
"tsx": "^4.20.3",
|
|
61
|
+
"typescript": "^5.8.3",
|
|
62
|
+
"vite": "^7.0.5",
|
|
63
|
+
"vitest": "^3.2.4"
|
|
64
|
+
},
|
|
65
|
+
"resolutions": {
|
|
66
|
+
"rollup": "4.52.5"
|
|
67
|
+
},
|
|
68
|
+
"config": {
|
|
69
|
+
"commitizen": {
|
|
70
|
+
"path": "cz-conventional-changelog"
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
type Logger = Pick<Console, "log" | "warn" | "error"> & {
|
|
2
|
+
debug?: (...args: unknown[]) => void;
|
|
3
|
+
};
|
|
4
|
+
export interface BatchMockCollectorOptions {
|
|
5
|
+
/**
|
|
6
|
+
* TCP port exposed by {@link TestMockMCPServer}.
|
|
7
|
+
*
|
|
8
|
+
* @default 8080
|
|
9
|
+
*/
|
|
10
|
+
port?: number;
|
|
11
|
+
/**
|
|
12
|
+
* Timeout for individual mock requests in milliseconds.
|
|
13
|
+
*
|
|
14
|
+
* @default 60000
|
|
15
|
+
*/
|
|
16
|
+
timeout?: number;
|
|
17
|
+
/**
|
|
18
|
+
* Delay (in milliseconds) that determines how long the collector waits before
|
|
19
|
+
* flushing the current batch. Setting this to 0 mirrors the "flush on the next
|
|
20
|
+
* macrotask" approach described in the technical design document.
|
|
21
|
+
*
|
|
22
|
+
* @default 0
|
|
23
|
+
*/
|
|
24
|
+
batchDebounceMs?: number;
|
|
25
|
+
/**
|
|
26
|
+
* Maximum number of requests that may be included in a single batch payload.
|
|
27
|
+
* Requests that exceed this limit will be split into multiple batches.
|
|
28
|
+
*
|
|
29
|
+
* @default 50
|
|
30
|
+
*/
|
|
31
|
+
maxBatchSize?: number;
|
|
32
|
+
/**
|
|
33
|
+
* Optional custom logger. Defaults to `console`.
|
|
34
|
+
*/
|
|
35
|
+
logger?: Logger;
|
|
36
|
+
}
|
|
37
|
+
export interface RequestMockOptions {
|
|
38
|
+
body?: unknown;
|
|
39
|
+
headers?: Record<string, string>;
|
|
40
|
+
metadata?: Record<string, unknown>;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Collects HTTP requests issued during a single macrotask and forwards them to
|
|
44
|
+
* the MCP server as a batch for AI-assisted mock generation.
|
|
45
|
+
*/
|
|
46
|
+
export declare class BatchMockCollector {
|
|
47
|
+
private readonly ws;
|
|
48
|
+
private readonly pendingRequests;
|
|
49
|
+
private readonly queuedRequestIds;
|
|
50
|
+
private readonly timeout;
|
|
51
|
+
private readonly batchDebounceMs;
|
|
52
|
+
private readonly maxBatchSize;
|
|
53
|
+
private readonly logger;
|
|
54
|
+
private batchTimer;
|
|
55
|
+
private requestIdCounter;
|
|
56
|
+
private closed;
|
|
57
|
+
private readyResolve?;
|
|
58
|
+
private readyReject?;
|
|
59
|
+
private readonly readyPromise;
|
|
60
|
+
constructor(options?: BatchMockCollectorOptions);
|
|
61
|
+
/**
|
|
62
|
+
* Ensures the underlying WebSocket connection is ready for use.
|
|
63
|
+
*/
|
|
64
|
+
waitUntilReady(): Promise<void>;
|
|
65
|
+
/**
|
|
66
|
+
* Request mock data for a specific endpoint/method pair.
|
|
67
|
+
*/
|
|
68
|
+
requestMock<T = unknown>(endpoint: string, method: string, options?: RequestMockOptions): Promise<T>;
|
|
69
|
+
/**
|
|
70
|
+
* Close the underlying connection and fail all pending requests.
|
|
71
|
+
*/
|
|
72
|
+
close(code?: number): Promise<void>;
|
|
73
|
+
private setupWebSocket;
|
|
74
|
+
private handleMessage;
|
|
75
|
+
private resolveRequest;
|
|
76
|
+
private enqueueRequest;
|
|
77
|
+
private flushQueue;
|
|
78
|
+
private sendBatch;
|
|
79
|
+
private rejectRequest;
|
|
80
|
+
private failAllPending;
|
|
81
|
+
}
|
|
82
|
+
export {};
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import WebSocket from "ws";
|
|
2
|
+
import { BATCH_MOCK_REQUEST, BATCH_MOCK_RESPONSE, } from "../types.js";
|
|
3
|
+
const DEFAULT_TIMEOUT = 60_000;
|
|
4
|
+
const DEFAULT_BATCH_DEBOUNCE_MS = 0;
|
|
5
|
+
const DEFAULT_MAX_BATCH_SIZE = 50;
|
|
6
|
+
const DEFAULT_PORT = 8080;
|
|
7
|
+
/**
|
|
8
|
+
* Collects HTTP requests issued during a single macrotask and forwards them to
|
|
9
|
+
* the MCP server as a batch for AI-assisted mock generation.
|
|
10
|
+
*/
|
|
11
|
+
export class BatchMockCollector {
|
|
12
|
+
ws;
|
|
13
|
+
pendingRequests = new Map();
|
|
14
|
+
queuedRequestIds = new Set();
|
|
15
|
+
timeout;
|
|
16
|
+
batchDebounceMs;
|
|
17
|
+
maxBatchSize;
|
|
18
|
+
logger;
|
|
19
|
+
batchTimer = null;
|
|
20
|
+
requestIdCounter = 0;
|
|
21
|
+
closed = false;
|
|
22
|
+
readyResolve;
|
|
23
|
+
readyReject;
|
|
24
|
+
readyPromise;
|
|
25
|
+
constructor(options = {}) {
|
|
26
|
+
this.timeout = options.timeout ?? DEFAULT_TIMEOUT;
|
|
27
|
+
this.batchDebounceMs = options.batchDebounceMs ?? DEFAULT_BATCH_DEBOUNCE_MS;
|
|
28
|
+
this.maxBatchSize = options.maxBatchSize ?? DEFAULT_MAX_BATCH_SIZE;
|
|
29
|
+
this.logger = options.logger ?? console;
|
|
30
|
+
const port = options.port ?? DEFAULT_PORT;
|
|
31
|
+
this.readyPromise = new Promise((resolve, reject) => {
|
|
32
|
+
this.readyResolve = resolve;
|
|
33
|
+
this.readyReject = reject;
|
|
34
|
+
});
|
|
35
|
+
const wsUrl = `ws://localhost:${port}`;
|
|
36
|
+
this.ws = new WebSocket(wsUrl);
|
|
37
|
+
this.setupWebSocket();
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Ensures the underlying WebSocket connection is ready for use.
|
|
41
|
+
*/
|
|
42
|
+
async waitUntilReady() {
|
|
43
|
+
return this.readyPromise;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Request mock data for a specific endpoint/method pair.
|
|
47
|
+
*/
|
|
48
|
+
async requestMock(endpoint, method, options = {}) {
|
|
49
|
+
if (this.closed) {
|
|
50
|
+
throw new Error("BatchMockCollector has been closed");
|
|
51
|
+
}
|
|
52
|
+
await this.waitUntilReady();
|
|
53
|
+
const requestId = `req-${++this.requestIdCounter}`;
|
|
54
|
+
const request = {
|
|
55
|
+
requestId,
|
|
56
|
+
endpoint,
|
|
57
|
+
method,
|
|
58
|
+
body: options.body,
|
|
59
|
+
headers: options.headers,
|
|
60
|
+
metadata: options.metadata,
|
|
61
|
+
};
|
|
62
|
+
return new Promise((resolve, reject) => {
|
|
63
|
+
const timeoutId = setTimeout(() => {
|
|
64
|
+
this.pendingRequests.delete(requestId);
|
|
65
|
+
reject(new Error(`Mock request timed out after ${this.timeout}ms: ${method} ${endpoint}`));
|
|
66
|
+
}, this.timeout);
|
|
67
|
+
this.pendingRequests.set(requestId, {
|
|
68
|
+
request,
|
|
69
|
+
resolve: (data) => {
|
|
70
|
+
resolve(data);
|
|
71
|
+
},
|
|
72
|
+
reject: (error) => {
|
|
73
|
+
reject(error);
|
|
74
|
+
},
|
|
75
|
+
timeoutId,
|
|
76
|
+
});
|
|
77
|
+
this.enqueueRequest(requestId);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Close the underlying connection and fail all pending requests.
|
|
82
|
+
*/
|
|
83
|
+
async close(code) {
|
|
84
|
+
if (this.closed) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
this.closed = true;
|
|
88
|
+
if (this.batchTimer) {
|
|
89
|
+
clearTimeout(this.batchTimer);
|
|
90
|
+
this.batchTimer = null;
|
|
91
|
+
}
|
|
92
|
+
this.queuedRequestIds.clear();
|
|
93
|
+
const closePromise = new Promise((resolve) => {
|
|
94
|
+
this.ws.once("close", () => resolve());
|
|
95
|
+
});
|
|
96
|
+
this.ws.close(code);
|
|
97
|
+
this.failAllPending(new Error("BatchMockCollector has been closed"));
|
|
98
|
+
await closePromise;
|
|
99
|
+
}
|
|
100
|
+
setupWebSocket() {
|
|
101
|
+
this.ws.on("open", () => {
|
|
102
|
+
this.logger.log("🔌 Connected to mock MCP WebSocket endpoint");
|
|
103
|
+
this.readyResolve?.();
|
|
104
|
+
});
|
|
105
|
+
this.ws.on("message", (data) => this.handleMessage(data));
|
|
106
|
+
this.ws.on("error", (error) => {
|
|
107
|
+
this.logger.error("❌ WebSocket error:", error);
|
|
108
|
+
this.readyReject?.(error instanceof Error ? error : new Error(String(error)));
|
|
109
|
+
this.failAllPending(error instanceof Error ? error : new Error(String(error)));
|
|
110
|
+
});
|
|
111
|
+
this.ws.on("close", () => {
|
|
112
|
+
this.logger.warn("🔌 WebSocket connection closed");
|
|
113
|
+
this.failAllPending(new Error("WebSocket connection closed"));
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
handleMessage(data) {
|
|
117
|
+
let parsed;
|
|
118
|
+
try {
|
|
119
|
+
parsed = JSON.parse(data.toString());
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
this.logger.error("Failed to parse server message:", error);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (parsed.type !== BATCH_MOCK_RESPONSE) {
|
|
126
|
+
this.logger.warn("Received unsupported message type", parsed.type);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
this.logger.debug?.(`📦 Received mock data for ${parsed.mocks.length} requests (batch ${parsed.batchId})`);
|
|
130
|
+
for (const mock of parsed.mocks) {
|
|
131
|
+
this.resolveRequest(mock);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
resolveRequest(mock) {
|
|
135
|
+
const pending = this.pendingRequests.get(mock.requestId);
|
|
136
|
+
if (!pending) {
|
|
137
|
+
this.logger.warn(`Received mock for unknown request: ${mock.requestId}`);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
clearTimeout(pending.timeoutId);
|
|
141
|
+
this.pendingRequests.delete(mock.requestId);
|
|
142
|
+
pending.resolve(mock.data);
|
|
143
|
+
}
|
|
144
|
+
enqueueRequest(requestId) {
|
|
145
|
+
this.queuedRequestIds.add(requestId);
|
|
146
|
+
if (this.batchTimer) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
this.batchTimer = setTimeout(() => {
|
|
150
|
+
this.batchTimer = null;
|
|
151
|
+
this.flushQueue();
|
|
152
|
+
}, this.batchDebounceMs);
|
|
153
|
+
}
|
|
154
|
+
flushQueue() {
|
|
155
|
+
const queuedIds = Array.from(this.queuedRequestIds);
|
|
156
|
+
this.queuedRequestIds.clear();
|
|
157
|
+
if (queuedIds.length === 0) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
for (let i = 0; i < queuedIds.length; i += this.maxBatchSize) {
|
|
161
|
+
const chunkIds = queuedIds.slice(i, i + this.maxBatchSize);
|
|
162
|
+
const requests = [];
|
|
163
|
+
for (const id of chunkIds) {
|
|
164
|
+
const pending = this.pendingRequests.get(id);
|
|
165
|
+
if (pending) {
|
|
166
|
+
requests.push(pending.request);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (requests.length > 0) {
|
|
170
|
+
this.sendBatch(requests);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
sendBatch(requests) {
|
|
175
|
+
if (this.ws.readyState !== WebSocket.OPEN) {
|
|
176
|
+
const error = new Error("WebSocket is not open");
|
|
177
|
+
requests.forEach((request) => this.rejectRequest(request.requestId, error));
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
const payload = {
|
|
181
|
+
type: BATCH_MOCK_REQUEST,
|
|
182
|
+
requests,
|
|
183
|
+
};
|
|
184
|
+
this.logger.debug?.(`📤 Sending batch with ${requests.length} request(s) to MCP server`);
|
|
185
|
+
this.ws.send(JSON.stringify(payload));
|
|
186
|
+
}
|
|
187
|
+
rejectRequest(requestId, error) {
|
|
188
|
+
const pending = this.pendingRequests.get(requestId);
|
|
189
|
+
if (!pending) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
clearTimeout(pending.timeoutId);
|
|
193
|
+
this.pendingRequests.delete(requestId);
|
|
194
|
+
pending.reject(error);
|
|
195
|
+
}
|
|
196
|
+
failAllPending(error) {
|
|
197
|
+
for (const requestId of Array.from(this.pendingRequests.keys())) {
|
|
198
|
+
this.rejectRequest(requestId, error);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { BatchMockCollector } from "./batch-mock-collector.js";
|
|
2
|
+
import type { BatchMockCollectorOptions } from "./batch-mock-collector.js";
|
|
3
|
+
export type ConnectOptions = number | BatchMockCollectorOptions | undefined;
|
|
4
|
+
/**
|
|
5
|
+
* Convenience helper that creates a {@link BatchMockCollector} and waits for the
|
|
6
|
+
* underlying WebSocket connection to become ready before resolving.
|
|
7
|
+
*/
|
|
8
|
+
export declare const connect: (options?: ConnectOptions) => Promise<BatchMockCollector | void>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { BatchMockCollector } from "./batch-mock-collector.js";
|
|
2
|
+
/**
|
|
3
|
+
* Convenience helper that creates a {@link BatchMockCollector} and waits for the
|
|
4
|
+
* underlying WebSocket connection to become ready before resolving.
|
|
5
|
+
*/
|
|
6
|
+
export const connect = async (options) => {
|
|
7
|
+
const isEnabled = process.env.MOCK_MCP !== undefined && process.env.MOCK_MCP !== "0";
|
|
8
|
+
if (!isEnabled) {
|
|
9
|
+
console.log("[mock-mcp] Skipping (set MOCK_MCP=1 to enable)");
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
const resolvedOptions = typeof options === "number" ? { port: options } : options ?? {};
|
|
13
|
+
const collector = new BatchMockCollector(resolvedOptions);
|
|
14
|
+
await collector.waitUntilReady();
|
|
15
|
+
return collector;
|
|
16
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { TestMockMCPServer, type TestMockMCPServerOptions } from "./server/test-mock-mcp-server.js";
|
|
3
|
+
import { BatchMockCollector, type BatchMockCollectorOptions, type RequestMockOptions } from "./client/batch-mock-collector.js";
|
|
4
|
+
import { connect, type ConnectOptions } from "./client/connect.js";
|
|
5
|
+
export { TestMockMCPServer };
|
|
6
|
+
export type { TestMockMCPServerOptions };
|
|
7
|
+
export { BatchMockCollector };
|
|
8
|
+
export type { BatchMockCollectorOptions, RequestMockOptions };
|
|
9
|
+
export { connect };
|
|
10
|
+
export type { ConnectOptions };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { pathToFileURL } from "node:url";
|
|
3
|
+
import process from "node:process";
|
|
4
|
+
import { TestMockMCPServer, } from "./server/test-mock-mcp-server.js";
|
|
5
|
+
import { BatchMockCollector, } from "./client/batch-mock-collector.js";
|
|
6
|
+
import { connect } from "./client/connect.js";
|
|
7
|
+
const DEFAULT_PORT = 3002;
|
|
8
|
+
async function runCli() {
|
|
9
|
+
const cliArgs = process.argv.slice(2);
|
|
10
|
+
let port = Number.parseInt(process.env.MCP_SERVER_PORT ?? "", 10);
|
|
11
|
+
let enableMcpTransport = true;
|
|
12
|
+
if (Number.isNaN(port)) {
|
|
13
|
+
port = DEFAULT_PORT;
|
|
14
|
+
}
|
|
15
|
+
for (let i = 0; i < cliArgs.length; i += 1) {
|
|
16
|
+
const arg = cliArgs[i];
|
|
17
|
+
if ((arg === "--port" || arg === "-p") && cliArgs[i + 1]) {
|
|
18
|
+
port = Number.parseInt(cliArgs[i + 1], 10);
|
|
19
|
+
i += 1;
|
|
20
|
+
}
|
|
21
|
+
else if (arg === "--no-stdio") {
|
|
22
|
+
enableMcpTransport = false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const server = new TestMockMCPServer({
|
|
26
|
+
port,
|
|
27
|
+
enableMcpTransport,
|
|
28
|
+
});
|
|
29
|
+
await server.start();
|
|
30
|
+
console.log(`🎯 Test Mock MCP server ready on ws://localhost:${server.port ?? port}`);
|
|
31
|
+
const shutdown = async () => {
|
|
32
|
+
console.log("👋 Shutting down Test Mock MCP server...");
|
|
33
|
+
await server.stop();
|
|
34
|
+
process.exit(0);
|
|
35
|
+
};
|
|
36
|
+
process.on("SIGINT", shutdown);
|
|
37
|
+
process.on("SIGTERM", shutdown);
|
|
38
|
+
}
|
|
39
|
+
const isCliExecution = (() => {
|
|
40
|
+
if (typeof process === "undefined" || !process.argv?.[1]) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
return import.meta.url === pathToFileURL(process.argv[1]).href;
|
|
44
|
+
})();
|
|
45
|
+
if (isCliExecution) {
|
|
46
|
+
runCli().catch((error) => {
|
|
47
|
+
console.error("Failed to start Test Mock MCP server:", error);
|
|
48
|
+
process.exitCode = 1;
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
export { TestMockMCPServer };
|
|
52
|
+
export { BatchMockCollector };
|
|
53
|
+
export { connect };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { TestMockMCPServer, type TestMockMCPServerOptions, } from "./test-mock-mcp-server.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { TestMockMCPServer, } from "./test-mock-mcp-server.js";
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
|
|
2
|
+
import { type CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
+
import { type PendingBatchSummary, type ProvideBatchMockDataArgs } from "../types.js";
|
|
4
|
+
type Logger = Pick<Console, "log" | "warn" | "error"> & {
|
|
5
|
+
debug?: (...args: unknown[]) => void;
|
|
6
|
+
};
|
|
7
|
+
export interface TestMockMCPServerOptions {
|
|
8
|
+
port?: number;
|
|
9
|
+
logger?: Logger;
|
|
10
|
+
batchTtlMs?: number;
|
|
11
|
+
sweepIntervalMs?: number;
|
|
12
|
+
enableMcpTransport?: boolean;
|
|
13
|
+
transportFactory?: () => Transport;
|
|
14
|
+
serverName?: string;
|
|
15
|
+
serverVersion?: string;
|
|
16
|
+
mockLogOptions?: MockLogOptions;
|
|
17
|
+
}
|
|
18
|
+
export interface MockLogOptions {
|
|
19
|
+
enabled?: boolean;
|
|
20
|
+
directory?: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Bridges the integration-test process and the MCP client, making it possible
|
|
24
|
+
* to generate realistic mock data on demand.
|
|
25
|
+
*/
|
|
26
|
+
export declare class TestMockMCPServer {
|
|
27
|
+
private readonly logger;
|
|
28
|
+
private readonly options;
|
|
29
|
+
private wss?;
|
|
30
|
+
private cleanupTimer?;
|
|
31
|
+
private mcpServer?;
|
|
32
|
+
private transport?;
|
|
33
|
+
private started;
|
|
34
|
+
private actualPort?;
|
|
35
|
+
private readonly pendingBatches;
|
|
36
|
+
private readonly clients;
|
|
37
|
+
private batchCounter;
|
|
38
|
+
constructor(options?: TestMockMCPServerOptions);
|
|
39
|
+
/**
|
|
40
|
+
* Start both the WebSocket server (for the test runner) and the MCP server
|
|
41
|
+
* (for the AI client).
|
|
42
|
+
*/
|
|
43
|
+
start(): Promise<void>;
|
|
44
|
+
/**
|
|
45
|
+
* Shut down all transports and clear pending batches.
|
|
46
|
+
*/
|
|
47
|
+
stop(): Promise<void>;
|
|
48
|
+
/**
|
|
49
|
+
* Expose the TCP port that the WebSocket server is listening on. Useful when
|
|
50
|
+
* `port=0` is supplied for ephemeral environments or tests.
|
|
51
|
+
*/
|
|
52
|
+
get port(): number | undefined;
|
|
53
|
+
/**
|
|
54
|
+
* Return summaries of all batches that are awaiting AI-provided mock data.
|
|
55
|
+
*/
|
|
56
|
+
getPendingBatches(): PendingBatchSummary[];
|
|
57
|
+
/**
|
|
58
|
+
* Send AI-generated mock data back to the corresponding test process.
|
|
59
|
+
*/
|
|
60
|
+
provideMockData(args: ProvideBatchMockDataArgs): Promise<CallToolResult>;
|
|
61
|
+
private startWebSocketServer;
|
|
62
|
+
private startMcpServer;
|
|
63
|
+
private handleConnection;
|
|
64
|
+
private handleClientMessage;
|
|
65
|
+
private handleBatchRequest;
|
|
66
|
+
private dropBatchesForClient;
|
|
67
|
+
private sweepExpiredBatches;
|
|
68
|
+
private persistMockBatch;
|
|
69
|
+
private buildLogEntry;
|
|
70
|
+
private extractBatchContext;
|
|
71
|
+
private buildToolResponse;
|
|
72
|
+
}
|
|
73
|
+
export {};
|