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
package/dist/connect.d.cts
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shape of the mock data that needs to be returned for a request.
|
|
3
|
-
*/
|
|
4
|
-
interface MockResponseDescriptor {
|
|
5
|
-
requestId: string;
|
|
6
|
-
data: unknown;
|
|
7
|
-
status?: number;
|
|
8
|
-
headers?: Record<string, string>;
|
|
9
|
-
delayMs?: number;
|
|
10
|
-
}
|
|
11
|
-
interface ResolvedMock<T = unknown> extends Omit<MockResponseDescriptor, "data"> {
|
|
12
|
-
data: T;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
type Logger = Pick<Console, "log" | "warn" | "error"> & {
|
|
16
|
-
debug?: (...args: unknown[]) => void;
|
|
17
|
-
};
|
|
18
|
-
interface BatchMockCollectorOptions {
|
|
19
|
-
/**
|
|
20
|
-
* TCP port exposed by {@link TestMockMCPServer}.
|
|
21
|
-
*
|
|
22
|
-
* @default 3002
|
|
23
|
-
*/
|
|
24
|
-
port?: number;
|
|
25
|
-
/**
|
|
26
|
-
* Timeout for individual mock requests in milliseconds.
|
|
27
|
-
*
|
|
28
|
-
* @default 60000
|
|
29
|
-
*/
|
|
30
|
-
timeout?: number;
|
|
31
|
-
/**
|
|
32
|
-
* Delay (in milliseconds) that determines how long the collector waits before
|
|
33
|
-
* flushing the current batch. Setting this to 0 mirrors the "flush on the next
|
|
34
|
-
* macrotask" approach described in the technical design document.
|
|
35
|
-
*
|
|
36
|
-
* @default 0
|
|
37
|
-
*/
|
|
38
|
-
batchDebounceMs?: number;
|
|
39
|
-
/**
|
|
40
|
-
* Maximum number of requests that may be included in a single batch payload.
|
|
41
|
-
* Requests that exceed this limit will be split into multiple batches.
|
|
42
|
-
*
|
|
43
|
-
* @default 50
|
|
44
|
-
*/
|
|
45
|
-
maxBatchSize?: number;
|
|
46
|
-
/**
|
|
47
|
-
* Optional custom logger. Defaults to `console`.
|
|
48
|
-
*/
|
|
49
|
-
logger?: Logger;
|
|
50
|
-
/**
|
|
51
|
-
* Interval for WebSocket heartbeats in milliseconds. Set to 0 to disable.
|
|
52
|
-
*
|
|
53
|
-
* @default 15000
|
|
54
|
-
*/
|
|
55
|
-
heartbeatIntervalMs?: number;
|
|
56
|
-
/**
|
|
57
|
-
* Automatically attempt to reconnect when the WebSocket closes unexpectedly.
|
|
58
|
-
*
|
|
59
|
-
* @default true
|
|
60
|
-
*/
|
|
61
|
-
enableReconnect?: boolean;
|
|
62
|
-
}
|
|
63
|
-
interface RequestMockOptions {
|
|
64
|
-
body?: unknown;
|
|
65
|
-
headers?: Record<string, string>;
|
|
66
|
-
metadata?: Record<string, unknown>;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
type ConnectOptions = number | BatchMockCollectorOptions | undefined;
|
|
70
|
-
interface MockClient {
|
|
71
|
-
waitUntilReady(): Promise<void>;
|
|
72
|
-
requestMock<T = unknown>(endpoint: string, method: string, options?: RequestMockOptions): Promise<ResolvedMock<T>>;
|
|
73
|
-
waitForPendingRequests(): Promise<void>;
|
|
74
|
-
close(code?: number): Promise<void>;
|
|
75
|
-
}
|
|
76
|
-
/**
|
|
77
|
-
* Convenience helper that creates a {@link BatchMockCollector} and waits for the
|
|
78
|
-
* underlying WebSocket connection to become ready before resolving.
|
|
79
|
-
*/
|
|
80
|
-
declare const connect: (options?: ConnectOptions) => Promise<MockClient>;
|
|
81
|
-
|
|
82
|
-
export { type ConnectOptions, type MockClient, connect };
|
package/dist/server/index.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { TestMockMCPServer, type TestMockMCPServerOptions, } from "./test-mock-mcp-server.js";
|
package/dist/server/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { TestMockMCPServer, } from "./test-mock-mcp-server.js";
|
|
@@ -1,73 +0,0 @@
|
|
|
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 {};
|
|
@@ -1,419 +0,0 @@
|
|
|
1
|
-
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
|
-
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
-
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
4
|
-
import { WebSocketServer, WebSocket } from "ws";
|
|
5
|
-
import { mkdir, writeFile } from "node:fs/promises";
|
|
6
|
-
import path from "node:path";
|
|
7
|
-
import packageJson from "../../package.json" with { type: "json" };
|
|
8
|
-
import { BATCH_MOCK_REQUEST, BATCH_MOCK_RESPONSE, } from "../types.js";
|
|
9
|
-
const DEFAULT_PORT = 3002;
|
|
10
|
-
const DEFAULT_BATCH_TTL_MS = 5 * 60 * 1000;
|
|
11
|
-
/**
|
|
12
|
-
* Bridges the integration-test process and the MCP client, making it possible
|
|
13
|
-
* to generate realistic mock data on demand.
|
|
14
|
-
*/
|
|
15
|
-
export class TestMockMCPServer {
|
|
16
|
-
logger;
|
|
17
|
-
options;
|
|
18
|
-
wss;
|
|
19
|
-
cleanupTimer;
|
|
20
|
-
mcpServer;
|
|
21
|
-
transport;
|
|
22
|
-
started = false;
|
|
23
|
-
actualPort;
|
|
24
|
-
pendingBatches = new Map();
|
|
25
|
-
clients = new Set();
|
|
26
|
-
batchCounter = 0;
|
|
27
|
-
constructor(options = {}) {
|
|
28
|
-
this.logger = options.logger ?? console;
|
|
29
|
-
this.options = {
|
|
30
|
-
port: options.port,
|
|
31
|
-
logger: this.logger,
|
|
32
|
-
batchTtlMs: options.batchTtlMs ?? DEFAULT_BATCH_TTL_MS,
|
|
33
|
-
sweepIntervalMs: options.sweepIntervalMs,
|
|
34
|
-
enableMcpTransport: options.enableMcpTransport ?? true,
|
|
35
|
-
transportFactory: options.transportFactory,
|
|
36
|
-
serverName: options.serverName ?? "test-mock-server",
|
|
37
|
-
serverVersion: options.serverVersion ?? packageJson.version ?? "0.0.0",
|
|
38
|
-
mockLogOptions: options.mockLogOptions,
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* Start both the WebSocket server (for the test runner) and the MCP server
|
|
43
|
-
* (for the AI client).
|
|
44
|
-
*/
|
|
45
|
-
async start() {
|
|
46
|
-
if (this.started) {
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
await this.startWebSocketServer();
|
|
50
|
-
if (this.options.enableMcpTransport) {
|
|
51
|
-
await this.startMcpServer();
|
|
52
|
-
}
|
|
53
|
-
this.started = true;
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* Shut down all transports and clear pending batches.
|
|
57
|
-
*/
|
|
58
|
-
async stop() {
|
|
59
|
-
if (this.cleanupTimer) {
|
|
60
|
-
clearInterval(this.cleanupTimer);
|
|
61
|
-
this.cleanupTimer = undefined;
|
|
62
|
-
}
|
|
63
|
-
for (const ws of this.clients) {
|
|
64
|
-
ws.close();
|
|
65
|
-
}
|
|
66
|
-
this.clients.clear();
|
|
67
|
-
await new Promise((resolve) => {
|
|
68
|
-
if (!this.wss) {
|
|
69
|
-
resolve();
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
this.wss.close(() => resolve());
|
|
73
|
-
this.wss = undefined;
|
|
74
|
-
});
|
|
75
|
-
if (this.transport) {
|
|
76
|
-
await this.transport.close().catch((error) => {
|
|
77
|
-
this.logger.warn("Failed to close MCP transport:", error);
|
|
78
|
-
});
|
|
79
|
-
this.transport = undefined;
|
|
80
|
-
}
|
|
81
|
-
this.pendingBatches.clear();
|
|
82
|
-
this.mcpServer = undefined;
|
|
83
|
-
this.started = false;
|
|
84
|
-
}
|
|
85
|
-
/**
|
|
86
|
-
* Expose the TCP port that the WebSocket server is listening on. Useful when
|
|
87
|
-
* `port=0` is supplied for ephemeral environments or tests.
|
|
88
|
-
*/
|
|
89
|
-
get port() {
|
|
90
|
-
return this.actualPort ?? this.options.port;
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
|
-
* Return summaries of all batches that are awaiting AI-provided mock data.
|
|
94
|
-
*/
|
|
95
|
-
getPendingBatches() {
|
|
96
|
-
return Array.from(this.pendingBatches.values()).map(({ batchId, timestamp, requests }) => ({
|
|
97
|
-
batchId,
|
|
98
|
-
timestamp,
|
|
99
|
-
requestCount: requests.length,
|
|
100
|
-
requests,
|
|
101
|
-
}));
|
|
102
|
-
}
|
|
103
|
-
/**
|
|
104
|
-
* Send AI-generated mock data back to the corresponding test process.
|
|
105
|
-
*/
|
|
106
|
-
async provideMockData(args) {
|
|
107
|
-
const { batchId, mocks } = args;
|
|
108
|
-
const batch = this.pendingBatches.get(batchId);
|
|
109
|
-
if (!batch) {
|
|
110
|
-
throw new Error(`Batch not found: ${batchId}`);
|
|
111
|
-
}
|
|
112
|
-
const expectedIds = new Set(batch.requests.map((request) => request.requestId));
|
|
113
|
-
const providedIds = new Set();
|
|
114
|
-
const unknownMock = mocks.find((mock) => !expectedIds.has(mock.requestId));
|
|
115
|
-
if (unknownMock) {
|
|
116
|
-
throw new Error(`Mock data references unknown requestId: ${unknownMock.requestId}`);
|
|
117
|
-
}
|
|
118
|
-
for (const mock of mocks) {
|
|
119
|
-
if (providedIds.has(mock.requestId)) {
|
|
120
|
-
throw new Error(`Duplicate mock data provided for requestId: ${mock.requestId}`);
|
|
121
|
-
}
|
|
122
|
-
providedIds.add(mock.requestId);
|
|
123
|
-
}
|
|
124
|
-
const missingIds = Array.from(expectedIds).filter((requestId) => !providedIds.has(requestId));
|
|
125
|
-
if (missingIds.length > 0) {
|
|
126
|
-
throw new Error(`Missing mock data for requestId(s): ${missingIds.join(", ")}`);
|
|
127
|
-
}
|
|
128
|
-
if (batch.ws.readyState !== WebSocket.OPEN) {
|
|
129
|
-
this.pendingBatches.delete(batchId);
|
|
130
|
-
throw new Error(`Test process disconnected before mocks were provided for ${batchId}`);
|
|
131
|
-
}
|
|
132
|
-
try {
|
|
133
|
-
await this.persistMockBatch(batch, mocks);
|
|
134
|
-
}
|
|
135
|
-
catch (error) {
|
|
136
|
-
this.logger.warn(`Failed to persist mock batch ${batchId}:`, error);
|
|
137
|
-
}
|
|
138
|
-
const payload = {
|
|
139
|
-
type: BATCH_MOCK_RESPONSE,
|
|
140
|
-
batchId,
|
|
141
|
-
mocks,
|
|
142
|
-
};
|
|
143
|
-
batch.ws.send(JSON.stringify(payload));
|
|
144
|
-
this.pendingBatches.delete(batchId);
|
|
145
|
-
this.logger.error(`โ
Delivered ${mocks.length} mock(s) to test process for ${batchId}`);
|
|
146
|
-
return this.buildToolResponse(JSON.stringify({
|
|
147
|
-
success: true,
|
|
148
|
-
message: `Provided mock data for ${batchId}`,
|
|
149
|
-
}));
|
|
150
|
-
}
|
|
151
|
-
async startWebSocketServer() {
|
|
152
|
-
if (this.wss) {
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
const desiredPort = this.options.port ?? DEFAULT_PORT;
|
|
156
|
-
await new Promise((resolve, reject) => {
|
|
157
|
-
const wss = new WebSocketServer({ port: desiredPort });
|
|
158
|
-
this.wss = wss;
|
|
159
|
-
wss.once("listening", () => {
|
|
160
|
-
const address = wss.address();
|
|
161
|
-
this.actualPort = address?.port ?? desiredPort;
|
|
162
|
-
this.logger.error(`๐ WebSocket server listening on ws://localhost:${this.actualPort}`);
|
|
163
|
-
resolve();
|
|
164
|
-
});
|
|
165
|
-
wss.once("error", (error) => {
|
|
166
|
-
this.logger.error("Failed to start WebSocket server:", error);
|
|
167
|
-
reject(error);
|
|
168
|
-
});
|
|
169
|
-
wss.on("connection", (ws) => this.handleConnection(ws));
|
|
170
|
-
});
|
|
171
|
-
if (this.options.batchTtlMs > 0) {
|
|
172
|
-
const interval = this.options.sweepIntervalMs ??
|
|
173
|
-
Math.min(this.options.batchTtlMs, 30_000);
|
|
174
|
-
this.cleanupTimer = setInterval(() => this.sweepExpiredBatches(), interval);
|
|
175
|
-
this.cleanupTimer.unref?.();
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
async startMcpServer() {
|
|
179
|
-
if (this.mcpServer) {
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
const serverName = this.options.serverName ?? "test-mock-server";
|
|
183
|
-
const serverVersion = this.options.serverVersion ??
|
|
184
|
-
packageJson.version ??
|
|
185
|
-
"0.0.0";
|
|
186
|
-
this.mcpServer = new Server({
|
|
187
|
-
name: serverName,
|
|
188
|
-
version: serverVersion,
|
|
189
|
-
}, {
|
|
190
|
-
capabilities: { tools: {} },
|
|
191
|
-
});
|
|
192
|
-
this.mcpServer.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
193
|
-
tools: [
|
|
194
|
-
{
|
|
195
|
-
name: "get_pending_batches",
|
|
196
|
-
description: "Inspect pending mock batches produced by the test run",
|
|
197
|
-
inputSchema: {
|
|
198
|
-
type: "object",
|
|
199
|
-
properties: {},
|
|
200
|
-
required: [],
|
|
201
|
-
},
|
|
202
|
-
},
|
|
203
|
-
{
|
|
204
|
-
name: "provide_batch_mock_data",
|
|
205
|
-
description: "Provide mock data for a specific batch",
|
|
206
|
-
inputSchema: {
|
|
207
|
-
type: "object",
|
|
208
|
-
properties: {
|
|
209
|
-
batchId: {
|
|
210
|
-
type: "string",
|
|
211
|
-
},
|
|
212
|
-
mocks: {
|
|
213
|
-
type: "array",
|
|
214
|
-
items: {
|
|
215
|
-
type: "object",
|
|
216
|
-
properties: {
|
|
217
|
-
requestId: { type: "string" },
|
|
218
|
-
data: {
|
|
219
|
-
anyOf: [
|
|
220
|
-
{ type: "object" },
|
|
221
|
-
{ type: "array" },
|
|
222
|
-
{ type: "string" },
|
|
223
|
-
{ type: "number" },
|
|
224
|
-
{ type: "boolean" },
|
|
225
|
-
{ type: "null" },
|
|
226
|
-
],
|
|
227
|
-
},
|
|
228
|
-
status: {
|
|
229
|
-
type: "number",
|
|
230
|
-
},
|
|
231
|
-
headers: {
|
|
232
|
-
type: "object",
|
|
233
|
-
},
|
|
234
|
-
delayMs: {
|
|
235
|
-
type: "number",
|
|
236
|
-
},
|
|
237
|
-
},
|
|
238
|
-
required: ["requestId", "data"],
|
|
239
|
-
},
|
|
240
|
-
},
|
|
241
|
-
},
|
|
242
|
-
required: ["batchId", "mocks"],
|
|
243
|
-
},
|
|
244
|
-
},
|
|
245
|
-
],
|
|
246
|
-
}));
|
|
247
|
-
this.mcpServer.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
248
|
-
const toolName = request.params.name;
|
|
249
|
-
if (toolName === "get_pending_batches") {
|
|
250
|
-
this.logger.error("๐ MCP client inspected pending batches");
|
|
251
|
-
return this.buildToolResponse(JSON.stringify(this.getPendingBatches(), null, 2));
|
|
252
|
-
}
|
|
253
|
-
if (toolName === "provide_batch_mock_data") {
|
|
254
|
-
const args = request.params.arguments;
|
|
255
|
-
if (!isProvideBatchMockDataArgs(args)) {
|
|
256
|
-
throw new Error("Invalid arguments for provide_batch_mock_data");
|
|
257
|
-
}
|
|
258
|
-
return this.provideMockData(args);
|
|
259
|
-
}
|
|
260
|
-
throw new Error(`Unknown tool: ${toolName}`);
|
|
261
|
-
});
|
|
262
|
-
this.transport =
|
|
263
|
-
this.options.transportFactory?.() ?? new StdioServerTransport();
|
|
264
|
-
await this.mcpServer.connect(this.transport);
|
|
265
|
-
this.logger.error("โ
MCP server is ready (stdio transport)");
|
|
266
|
-
}
|
|
267
|
-
handleConnection(ws) {
|
|
268
|
-
this.logger.error("๐ Test process connected");
|
|
269
|
-
this.clients.add(ws);
|
|
270
|
-
ws.on("message", (data) => this.handleClientMessage(ws, data));
|
|
271
|
-
ws.on("ping", () => {
|
|
272
|
-
try {
|
|
273
|
-
ws.pong();
|
|
274
|
-
}
|
|
275
|
-
catch (error) {
|
|
276
|
-
this.logger.warn("Failed to respond to ping:", error);
|
|
277
|
-
}
|
|
278
|
-
});
|
|
279
|
-
ws.on("close", () => {
|
|
280
|
-
this.logger.error("๐ Test process disconnected");
|
|
281
|
-
this.clients.delete(ws);
|
|
282
|
-
this.dropBatchesForClient(ws);
|
|
283
|
-
});
|
|
284
|
-
ws.on("error", (error) => {
|
|
285
|
-
this.logger.error("Test process WebSocket error:", error);
|
|
286
|
-
this.dropBatchesForClient(ws);
|
|
287
|
-
});
|
|
288
|
-
}
|
|
289
|
-
handleClientMessage(ws, data) {
|
|
290
|
-
let payload;
|
|
291
|
-
try {
|
|
292
|
-
payload = JSON.parse(data.toString());
|
|
293
|
-
}
|
|
294
|
-
catch (error) {
|
|
295
|
-
this.logger.error("Failed to parse WebSocket message:", error);
|
|
296
|
-
return;
|
|
297
|
-
}
|
|
298
|
-
if (payload.type !== BATCH_MOCK_REQUEST) {
|
|
299
|
-
this.logger.warn("Unsupported message type received:", payload.type);
|
|
300
|
-
return;
|
|
301
|
-
}
|
|
302
|
-
if (!Array.isArray(payload.requests) || payload.requests.length === 0) {
|
|
303
|
-
this.logger.warn("Received a batch without requests");
|
|
304
|
-
return;
|
|
305
|
-
}
|
|
306
|
-
this.handleBatchRequest(ws, payload.requests);
|
|
307
|
-
}
|
|
308
|
-
handleBatchRequest(ws, requests) {
|
|
309
|
-
const batchId = `batch-${++this.batchCounter}`;
|
|
310
|
-
const timestamp = new Date().toISOString();
|
|
311
|
-
const expiresAt = this.options.batchTtlMs
|
|
312
|
-
? Date.now() + this.options.batchTtlMs
|
|
313
|
-
: undefined;
|
|
314
|
-
this.pendingBatches.set(batchId, {
|
|
315
|
-
batchId,
|
|
316
|
-
timestamp,
|
|
317
|
-
requestCount: requests.length,
|
|
318
|
-
requests,
|
|
319
|
-
ws,
|
|
320
|
-
expiresAt,
|
|
321
|
-
});
|
|
322
|
-
this.logger.error([
|
|
323
|
-
`๐ฅ Received ${requests.length} request(s) (${batchId})`,
|
|
324
|
-
...requests.map((req, index) => ` ${index + 1}. ${req.method} ${req.endpoint} (${req.requestId})`),
|
|
325
|
-
].join("\n"));
|
|
326
|
-
this.logger.error("โณ Awaiting mock data from MCP client...");
|
|
327
|
-
}
|
|
328
|
-
dropBatchesForClient(ws) {
|
|
329
|
-
for (const [batchId, batch] of this.pendingBatches) {
|
|
330
|
-
if (batch.ws === ws) {
|
|
331
|
-
this.pendingBatches.delete(batchId);
|
|
332
|
-
this.logger.warn(`๐งน Dropped pending batch ${batchId} because the test client disconnected`);
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
sweepExpiredBatches() {
|
|
337
|
-
const now = Date.now();
|
|
338
|
-
for (const [batchId, batch] of this.pendingBatches) {
|
|
339
|
-
if (batch.expiresAt && batch.expiresAt <= now) {
|
|
340
|
-
this.pendingBatches.delete(batchId);
|
|
341
|
-
this.logger.warn(`๐งน Removed expired batch ${batchId} (waited more than ${this.options.batchTtlMs / 1000}s)`);
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
async persistMockBatch(batch, mocks) {
|
|
346
|
-
const mockLogOptions = this.options.mockLogOptions;
|
|
347
|
-
if (!mockLogOptions?.enabled) {
|
|
348
|
-
return;
|
|
349
|
-
}
|
|
350
|
-
const directory = path.resolve(process.cwd(), mockLogOptions.directory ?? "logs");
|
|
351
|
-
await mkdir(directory, { recursive: true });
|
|
352
|
-
const entry = this.buildLogEntry(batch, mocks);
|
|
353
|
-
const filePath = path.join(directory, `mock-${batch.batchId}.json`);
|
|
354
|
-
await writeFile(filePath, JSON.stringify(entry, null, 2), "utf8");
|
|
355
|
-
this.logger.error(`๐ Saved mock batch ${batch.batchId} to ${filePath}`);
|
|
356
|
-
}
|
|
357
|
-
buildLogEntry(batch, mocks) {
|
|
358
|
-
const mockMap = new Map(mocks.map((mock) => [mock.requestId, mock]));
|
|
359
|
-
return {
|
|
360
|
-
batchId: batch.batchId,
|
|
361
|
-
timestamp: batch.timestamp,
|
|
362
|
-
requestCount: batch.requestCount,
|
|
363
|
-
context: this.extractBatchContext(batch.requests),
|
|
364
|
-
requests: batch.requests.map((request) => ({
|
|
365
|
-
...request,
|
|
366
|
-
mock: mockMap.get(request.requestId),
|
|
367
|
-
})),
|
|
368
|
-
mocks,
|
|
369
|
-
};
|
|
370
|
-
}
|
|
371
|
-
extractBatchContext(requests) {
|
|
372
|
-
const contextKeys = ["testCaseId", "testFile", "testTitle", "testName"];
|
|
373
|
-
const context = {};
|
|
374
|
-
for (const key of contextKeys) {
|
|
375
|
-
const requestWithValue = requests.find((request) => request.metadata && request.metadata[key] !== undefined);
|
|
376
|
-
if (requestWithValue?.metadata) {
|
|
377
|
-
context[key] = requestWithValue.metadata[key];
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
const metadataSnapshots = requests
|
|
381
|
-
.map((request) => request.metadata)
|
|
382
|
-
.filter((metadata) => {
|
|
383
|
-
if (!metadata) {
|
|
384
|
-
return false;
|
|
385
|
-
}
|
|
386
|
-
return Object.keys(metadata).length > 0;
|
|
387
|
-
});
|
|
388
|
-
if (metadataSnapshots.length > 0) {
|
|
389
|
-
context.metadata = metadataSnapshots;
|
|
390
|
-
}
|
|
391
|
-
return Object.keys(context).length > 0 ? context : undefined;
|
|
392
|
-
}
|
|
393
|
-
buildToolResponse(text) {
|
|
394
|
-
return {
|
|
395
|
-
content: [
|
|
396
|
-
{
|
|
397
|
-
type: "text",
|
|
398
|
-
text,
|
|
399
|
-
},
|
|
400
|
-
],
|
|
401
|
-
};
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
function isProvideBatchMockDataArgs(value) {
|
|
405
|
-
if (!value || typeof value !== "object") {
|
|
406
|
-
return false;
|
|
407
|
-
}
|
|
408
|
-
const candidate = value;
|
|
409
|
-
if (typeof candidate.batchId !== "string" || !Array.isArray(candidate.mocks)) {
|
|
410
|
-
return false;
|
|
411
|
-
}
|
|
412
|
-
return candidate.mocks.every((mock) => {
|
|
413
|
-
if (!mock || typeof mock !== "object") {
|
|
414
|
-
return false;
|
|
415
|
-
}
|
|
416
|
-
const descriptor = mock;
|
|
417
|
-
return typeof descriptor.requestId === "string" && "data" in descriptor;
|
|
418
|
-
});
|
|
419
|
-
}
|
package/dist/types.d.ts
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
export declare const BATCH_MOCK_REQUEST: "BATCH_MOCK_REQUEST";
|
|
2
|
-
export declare const BATCH_MOCK_RESPONSE: "BATCH_MOCK_RESPONSE";
|
|
3
|
-
/**
|
|
4
|
-
* Shape of a mock request emitted by the test process.
|
|
5
|
-
*/
|
|
6
|
-
export interface MockRequestDescriptor {
|
|
7
|
-
requestId: string;
|
|
8
|
-
endpoint: string;
|
|
9
|
-
method: string;
|
|
10
|
-
body?: unknown;
|
|
11
|
-
headers?: Record<string, string>;
|
|
12
|
-
metadata?: Record<string, unknown>;
|
|
13
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* Shape of the mock data that needs to be returned for a request.
|
|
16
|
-
*/
|
|
17
|
-
export interface MockResponseDescriptor {
|
|
18
|
-
requestId: string;
|
|
19
|
-
data: unknown;
|
|
20
|
-
status?: number;
|
|
21
|
-
headers?: Record<string, string>;
|
|
22
|
-
delayMs?: number;
|
|
23
|
-
}
|
|
24
|
-
export interface ResolvedMock<T = unknown> extends Omit<MockResponseDescriptor, "data"> {
|
|
25
|
-
data: T;
|
|
26
|
-
}
|
|
27
|
-
export interface BatchMockRequestMessage {
|
|
28
|
-
type: typeof BATCH_MOCK_REQUEST;
|
|
29
|
-
requests: MockRequestDescriptor[];
|
|
30
|
-
}
|
|
31
|
-
export interface BatchMockResponseMessage {
|
|
32
|
-
type: typeof BATCH_MOCK_RESPONSE;
|
|
33
|
-
batchId: string;
|
|
34
|
-
mocks: MockResponseDescriptor[];
|
|
35
|
-
}
|
|
36
|
-
export interface PendingBatchSummary {
|
|
37
|
-
batchId: string;
|
|
38
|
-
timestamp: string;
|
|
39
|
-
requestCount: number;
|
|
40
|
-
requests: MockRequestDescriptor[];
|
|
41
|
-
}
|
|
42
|
-
export interface ProvideBatchMockDataArgs {
|
|
43
|
-
batchId: string;
|
|
44
|
-
mocks: MockResponseDescriptor[];
|
|
45
|
-
}
|
package/dist/types.js
DELETED