@brutalist/mcp 0.6.0 → 0.6.1
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 +3 -1
- package/dist/brutalist-server.d.ts +5 -0
- package/dist/brutalist-server.d.ts.map +1 -1
- package/dist/brutalist-server.js +244 -80
- package/dist/brutalist-server.js.map +1 -1
- package/dist/cli-agents.d.ts +7 -3
- package/dist/cli-agents.d.ts.map +1 -1
- package/dist/cli-agents.js +307 -48
- package/dist/cli-agents.js.map +1 -1
- package/dist/streaming/circuit-breaker.d.ts +186 -0
- package/dist/streaming/circuit-breaker.d.ts.map +1 -0
- package/dist/streaming/circuit-breaker.js +463 -0
- package/dist/streaming/circuit-breaker.js.map +1 -0
- package/dist/streaming/intelligent-buffer.d.ts +141 -0
- package/dist/streaming/intelligent-buffer.d.ts.map +1 -0
- package/dist/streaming/intelligent-buffer.js +555 -0
- package/dist/streaming/intelligent-buffer.js.map +1 -0
- package/dist/streaming/output-parser.d.ts +89 -0
- package/dist/streaming/output-parser.d.ts.map +1 -0
- package/dist/streaming/output-parser.js +349 -0
- package/dist/streaming/output-parser.js.map +1 -0
- package/dist/streaming/progress-tracker.d.ts +149 -0
- package/dist/streaming/progress-tracker.d.ts.map +1 -0
- package/dist/streaming/progress-tracker.js +519 -0
- package/dist/streaming/progress-tracker.js.map +1 -0
- package/dist/streaming/session-manager.d.ts +238 -0
- package/dist/streaming/session-manager.d.ts.map +1 -0
- package/dist/streaming/session-manager.js +546 -0
- package/dist/streaming/session-manager.js.map +1 -0
- package/dist/streaming/sse-transport.d.ts +95 -0
- package/dist/streaming/sse-transport.d.ts.map +1 -0
- package/dist/streaming/sse-transport.js +319 -0
- package/dist/streaming/sse-transport.js.map +1 -0
- package/dist/streaming/streaming-orchestrator.d.ts +153 -0
- package/dist/streaming/streaming-orchestrator.d.ts.map +1 -0
- package/dist/streaming/streaming-orchestrator.js +436 -0
- package/dist/streaming/streaming-orchestrator.js.map +1 -0
- package/dist/test-utils/process-manager.d.ts +61 -0
- package/dist/test-utils/process-manager.d.ts.map +1 -0
- package/dist/test-utils/process-manager.js +262 -0
- package/dist/test-utils/process-manager.js.map +1 -0
- package/dist/test-utils/server-harness.d.ts +73 -0
- package/dist/test-utils/server-harness.d.ts.map +1 -0
- package/dist/test-utils/server-harness.js +296 -0
- package/dist/test-utils/server-harness.js.map +1 -0
- package/dist/test-utils/streaming-fuzz.d.ts +57 -0
- package/dist/test-utils/streaming-fuzz.d.ts.map +1 -0
- package/dist/test-utils/streaming-fuzz.js +287 -0
- package/dist/test-utils/streaming-fuzz.js.map +1 -0
- package/dist/test-utils/test-isolation.d.ts +70 -0
- package/dist/test-utils/test-isolation.d.ts.map +1 -0
- package/dist/test-utils/test-isolation.js +193 -0
- package/dist/test-utils/test-isolation.js.map +1 -0
- package/dist/tool-definitions.d.ts.map +1 -1
- package/dist/tool-definitions.js +12 -6
- package/dist/tool-definitions.js.map +1 -1
- package/dist/types/brutalist.d.ts +3 -3
- package/dist/types/brutalist.d.ts.map +1 -1
- package/dist/types/tool-config.d.ts +0 -1
- package/dist/types/tool-config.d.ts.map +1 -1
- package/dist/types/tool-config.js +0 -1
- package/dist/types/tool-config.js.map +1 -1
- package/dist/utils/response-cache.d.ts +14 -7
- package/dist/utils/response-cache.d.ts.map +1 -1
- package/dist/utils/response-cache.js +173 -62
- package/dist/utils/response-cache.js.map +1 -1
- package/package.json +13 -3
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import { spawn, execSync } from 'child_process';
|
|
2
|
+
import { logger } from '../logger.js';
|
|
3
|
+
import * as os from 'os';
|
|
4
|
+
/**
|
|
5
|
+
* Cross-platform process manager that tracks all spawned processes
|
|
6
|
+
* and ensures proper cleanup, preventing orphaned processes in tests
|
|
7
|
+
*/
|
|
8
|
+
export class ProcessManager {
|
|
9
|
+
static instance;
|
|
10
|
+
processes = new Map();
|
|
11
|
+
isWindows = os.platform() === 'win32';
|
|
12
|
+
cleanupRegistered = false;
|
|
13
|
+
constructor() {
|
|
14
|
+
// Register global cleanup handlers
|
|
15
|
+
this.registerCleanupHandlers();
|
|
16
|
+
}
|
|
17
|
+
static getInstance() {
|
|
18
|
+
if (!ProcessManager.instance) {
|
|
19
|
+
ProcessManager.instance = new ProcessManager();
|
|
20
|
+
}
|
|
21
|
+
return ProcessManager.instance;
|
|
22
|
+
}
|
|
23
|
+
registerCleanupHandlers() {
|
|
24
|
+
if (this.cleanupRegistered)
|
|
25
|
+
return;
|
|
26
|
+
const cleanup = async () => {
|
|
27
|
+
await this.cleanup();
|
|
28
|
+
};
|
|
29
|
+
process.on('exit', cleanup);
|
|
30
|
+
process.on('SIGINT', cleanup);
|
|
31
|
+
process.on('SIGTERM', cleanup);
|
|
32
|
+
process.on('uncaughtException', async (err) => {
|
|
33
|
+
logger.error('Uncaught exception, cleaning up processes:', err);
|
|
34
|
+
await cleanup();
|
|
35
|
+
});
|
|
36
|
+
this.cleanupRegistered = true;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Spawn a managed process with automatic tracking and cleanup
|
|
40
|
+
*/
|
|
41
|
+
async spawn(command, args, options = {}) {
|
|
42
|
+
return new Promise((resolve, reject) => {
|
|
43
|
+
const startTime = Date.now();
|
|
44
|
+
const cwd = options.cwd || process.cwd();
|
|
45
|
+
// Handle shell builtins that don't exist as standalone executables
|
|
46
|
+
const shellBuiltins = ['echo', 'cd', 'pwd', 'test', 'true', 'false'];
|
|
47
|
+
const needsShell = shellBuiltins.includes(command);
|
|
48
|
+
// Create new process group on POSIX for proper tree killing
|
|
49
|
+
const spawnOptions = {
|
|
50
|
+
cwd,
|
|
51
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
52
|
+
shell: needsShell, // Enable shell for builtins
|
|
53
|
+
env: options.env || process.env
|
|
54
|
+
};
|
|
55
|
+
// On POSIX, create new process group for tree killing
|
|
56
|
+
if (!this.isWindows) {
|
|
57
|
+
spawnOptions.detached = true;
|
|
58
|
+
}
|
|
59
|
+
const child = spawn(command, args, spawnOptions);
|
|
60
|
+
if (!child.pid) {
|
|
61
|
+
reject(new Error(`Failed to spawn process: ${command}`));
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const managed = {
|
|
65
|
+
pid: child.pid,
|
|
66
|
+
command,
|
|
67
|
+
args,
|
|
68
|
+
process: child,
|
|
69
|
+
stdout: '',
|
|
70
|
+
stderr: '',
|
|
71
|
+
killed: false,
|
|
72
|
+
startTime
|
|
73
|
+
};
|
|
74
|
+
this.processes.set(child.pid, managed);
|
|
75
|
+
logger.debug(`ProcessManager: Spawned ${command} with PID ${child.pid}`);
|
|
76
|
+
let timedOut = false;
|
|
77
|
+
let timer;
|
|
78
|
+
let killTimer;
|
|
79
|
+
// Set up timeout with escalation
|
|
80
|
+
if (options.timeout) {
|
|
81
|
+
timer = setTimeout(() => {
|
|
82
|
+
timedOut = true;
|
|
83
|
+
logger.warn(`Process ${child.pid} timed out after ${options.timeout}ms`);
|
|
84
|
+
this.killProcessTree(child.pid).catch(err => {
|
|
85
|
+
logger.error(`Failed to kill timed out process ${child.pid}:`, err);
|
|
86
|
+
});
|
|
87
|
+
}, options.timeout);
|
|
88
|
+
}
|
|
89
|
+
// Handle stdout with buffer limit
|
|
90
|
+
child.stdout?.on('data', (data) => {
|
|
91
|
+
const chunk = data.toString();
|
|
92
|
+
if (options.maxBuffer && managed.stdout.length + chunk.length > options.maxBuffer) {
|
|
93
|
+
logger.warn(`Process ${child.pid} exceeded stdout buffer limit`);
|
|
94
|
+
this.killProcessTree(child.pid);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
managed.stdout += chunk;
|
|
98
|
+
options.onProgress?.(chunk, 'stdout');
|
|
99
|
+
});
|
|
100
|
+
// Handle stderr with buffer limit
|
|
101
|
+
child.stderr?.on('data', (data) => {
|
|
102
|
+
const chunk = data.toString();
|
|
103
|
+
if (options.maxBuffer && managed.stderr.length + chunk.length > options.maxBuffer) {
|
|
104
|
+
logger.warn(`Process ${child.pid} exceeded stderr buffer limit`);
|
|
105
|
+
this.killProcessTree(child.pid);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
managed.stderr += chunk;
|
|
109
|
+
options.onProgress?.(chunk, 'stderr');
|
|
110
|
+
});
|
|
111
|
+
// Handle process exit
|
|
112
|
+
child.on('exit', (code, signal) => {
|
|
113
|
+
managed.killed = true;
|
|
114
|
+
if (timer)
|
|
115
|
+
clearTimeout(timer);
|
|
116
|
+
if (killTimer)
|
|
117
|
+
clearTimeout(killTimer);
|
|
118
|
+
this.processes.delete(child.pid);
|
|
119
|
+
logger.debug(`Process ${child.pid} exited with code ${code}, signal ${signal}`);
|
|
120
|
+
if (timedOut) {
|
|
121
|
+
reject(new Error(`Process timed out after ${options.timeout}ms`));
|
|
122
|
+
}
|
|
123
|
+
else if (signal) {
|
|
124
|
+
reject(new Error(`Process killed with signal ${signal}`));
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
resolve({
|
|
128
|
+
stdout: managed.stdout,
|
|
129
|
+
stderr: managed.stderr,
|
|
130
|
+
exitCode: code
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
child.on('error', (error) => {
|
|
135
|
+
managed.killed = true;
|
|
136
|
+
if (timer)
|
|
137
|
+
clearTimeout(timer);
|
|
138
|
+
if (killTimer)
|
|
139
|
+
clearTimeout(killTimer);
|
|
140
|
+
this.processes.delete(child.pid);
|
|
141
|
+
logger.error(`Process ${child.pid} error:`, error);
|
|
142
|
+
reject(error);
|
|
143
|
+
});
|
|
144
|
+
// Write stdin if provided
|
|
145
|
+
if (options.input) {
|
|
146
|
+
child.stdin?.write(options.input);
|
|
147
|
+
child.stdin?.end();
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Kill a process and all its children (cross-platform)
|
|
153
|
+
*/
|
|
154
|
+
async killProcessTree(pid, signal = 'SIGTERM') {
|
|
155
|
+
const managed = this.processes.get(pid);
|
|
156
|
+
if (!managed || managed.killed) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
logger.info(`Killing process tree for PID ${pid}`);
|
|
160
|
+
managed.killed = true;
|
|
161
|
+
try {
|
|
162
|
+
if (this.isWindows) {
|
|
163
|
+
// Windows: Use taskkill to kill process tree
|
|
164
|
+
try {
|
|
165
|
+
execSync(`taskkill /pid ${pid} /T /F`, { stdio: 'ignore' });
|
|
166
|
+
}
|
|
167
|
+
catch (err) {
|
|
168
|
+
logger.warn(`Windows taskkill failed for ${pid}, trying direct kill`);
|
|
169
|
+
managed.process.kill('SIGKILL');
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
// POSIX: Kill process group
|
|
174
|
+
try {
|
|
175
|
+
// First try SIGTERM to the process group
|
|
176
|
+
process.kill(-pid, signal);
|
|
177
|
+
// Give processes 5 seconds to exit gracefully
|
|
178
|
+
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
179
|
+
// Check if still running and escalate to SIGKILL
|
|
180
|
+
try {
|
|
181
|
+
process.kill(-pid, 0); // Check if process group still exists
|
|
182
|
+
logger.warn(`Process group ${pid} still alive after SIGTERM, using SIGKILL`);
|
|
183
|
+
process.kill(-pid, 'SIGKILL');
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
// Process group is gone, good
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
catch (err) {
|
|
190
|
+
// Fallback to direct process kill if group kill fails
|
|
191
|
+
logger.warn(`Failed to kill process group -${pid}, trying direct kill`);
|
|
192
|
+
managed.process.kill('SIGKILL');
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
catch (error) {
|
|
197
|
+
logger.error(`Failed to kill process tree ${pid}:`, error);
|
|
198
|
+
throw error;
|
|
199
|
+
}
|
|
200
|
+
finally {
|
|
201
|
+
this.processes.delete(pid);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Get all currently running managed processes
|
|
206
|
+
*/
|
|
207
|
+
getRunningProcesses() {
|
|
208
|
+
return Array.from(this.processes.values()).filter(p => !p.killed);
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Clean up all tracked processes
|
|
212
|
+
*/
|
|
213
|
+
async cleanup() {
|
|
214
|
+
const running = this.getRunningProcesses();
|
|
215
|
+
if (running.length === 0) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
logger.info(`ProcessManager: Cleaning up ${running.length} processes`);
|
|
219
|
+
const killPromises = running.map(async (managed) => {
|
|
220
|
+
try {
|
|
221
|
+
await this.killProcessTree(managed.pid);
|
|
222
|
+
}
|
|
223
|
+
catch (err) {
|
|
224
|
+
logger.error(`Failed to clean up process ${managed.pid}:`, err);
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
await Promise.all(killPromises);
|
|
228
|
+
this.processes.clear();
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Get diagnostic information about running processes
|
|
232
|
+
*/
|
|
233
|
+
getDiagnostics() {
|
|
234
|
+
const running = this.getRunningProcesses();
|
|
235
|
+
if (running.length === 0) {
|
|
236
|
+
return 'No running processes';
|
|
237
|
+
}
|
|
238
|
+
const lines = ['Running processes:'];
|
|
239
|
+
for (const proc of running) {
|
|
240
|
+
const runtime = Date.now() - proc.startTime;
|
|
241
|
+
lines.push(` PID ${proc.pid}: ${proc.command} ${proc.args.join(' ')} (running ${runtime}ms)`);
|
|
242
|
+
if (proc.stdout) {
|
|
243
|
+
lines.push(` Last stdout: ${proc.stdout.slice(-100)}`);
|
|
244
|
+
}
|
|
245
|
+
if (proc.stderr) {
|
|
246
|
+
lines.push(` Last stderr: ${proc.stderr.slice(-100)}`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return lines.join('\n');
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Assert no processes are leaked (for test cleanup validation)
|
|
253
|
+
*/
|
|
254
|
+
assertNoLeakedProcesses() {
|
|
255
|
+
const running = this.getRunningProcesses();
|
|
256
|
+
if (running.length > 0) {
|
|
257
|
+
const diagnostics = this.getDiagnostics();
|
|
258
|
+
throw new Error(`Test leaked ${running.length} processes:\n${diagnostics}`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
//# sourceMappingURL=process-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"process-manager.js","sourceRoot":"","sources":["../../src/test-utils/process-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { BrutalistServer } from '../brutalist-server.js';
|
|
2
|
+
export interface ServerHarnessOptions {
|
|
3
|
+
maxStartupTime?: number;
|
|
4
|
+
healthCheckInterval?: number;
|
|
5
|
+
shutdownTimeout?: number;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Harness for deterministic server lifecycle management in tests.
|
|
9
|
+
* Provides event-based readiness detection and proper cleanup.
|
|
10
|
+
*/
|
|
11
|
+
export declare class ServerHarness {
|
|
12
|
+
private options;
|
|
13
|
+
private server;
|
|
14
|
+
private actualPort;
|
|
15
|
+
private baseUrl;
|
|
16
|
+
private startTime;
|
|
17
|
+
private httpServer;
|
|
18
|
+
private sessionId;
|
|
19
|
+
constructor(options?: ServerHarnessOptions);
|
|
20
|
+
/**
|
|
21
|
+
* Start server and wait for it to be ready (not just started)
|
|
22
|
+
*/
|
|
23
|
+
start(config?: any): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Wait for HTTP server to respond to health checks
|
|
26
|
+
*/
|
|
27
|
+
private waitForHttpReady;
|
|
28
|
+
/**
|
|
29
|
+
* Parse SSE response format to extract JSON data
|
|
30
|
+
*/
|
|
31
|
+
private parseSSEResponse;
|
|
32
|
+
/**
|
|
33
|
+
* Initialize MCP connection with handshake
|
|
34
|
+
*/
|
|
35
|
+
private initializeMCP;
|
|
36
|
+
/**
|
|
37
|
+
* Stop server with graceful shutdown and forced kill if needed
|
|
38
|
+
*/
|
|
39
|
+
stop(): Promise<void>;
|
|
40
|
+
/**
|
|
41
|
+
* Reset server state (for cleanup after failed startup)
|
|
42
|
+
*/
|
|
43
|
+
private cleanup;
|
|
44
|
+
/**
|
|
45
|
+
* Get the actual port the server is listening on
|
|
46
|
+
*/
|
|
47
|
+
getPort(): number;
|
|
48
|
+
/**
|
|
49
|
+
* Get the base URL for HTTP requests
|
|
50
|
+
*/
|
|
51
|
+
getBaseUrl(): string;
|
|
52
|
+
/**
|
|
53
|
+
* Get the server instance for direct access if needed
|
|
54
|
+
*/
|
|
55
|
+
getServer(): BrutalistServer;
|
|
56
|
+
/**
|
|
57
|
+
* Check if server is currently running
|
|
58
|
+
*/
|
|
59
|
+
isRunning(): boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Make a test request to the server
|
|
62
|
+
*/
|
|
63
|
+
testRequest(path: string, options?: any): Promise<any>;
|
|
64
|
+
/**
|
|
65
|
+
* Execute an MCP tool via HTTP
|
|
66
|
+
*/
|
|
67
|
+
executeTool(toolName: string, args: any, progressToken?: string): Promise<any>;
|
|
68
|
+
/**
|
|
69
|
+
* Get diagnostic information about the server
|
|
70
|
+
*/
|
|
71
|
+
getDiagnostics(): string;
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=server-harness.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server-harness.d.ts","sourceRoot":"","sources":["../../src/test-utils/server-harness.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAGzD,MAAM,WAAW,oBAAoB;IACnC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;GAGG;AACH,qBAAa,aAAa;IAQZ,OAAO,CAAC,OAAO;IAP3B,OAAO,CAAC,MAAM,CAAgC;IAC9C,OAAO,CAAC,UAAU,CAAqB;IACvC,OAAO,CAAC,OAAO,CAAqB;IACpC,OAAO,CAAC,SAAS,CAAqB;IACtC,OAAO,CAAC,UAAU,CAAkB;IACpC,OAAO,CAAC,SAAS,CAAqB;gBAElB,OAAO,GAAE,oBAAyB;IAStD;;OAEG;IACG,KAAK,CAAC,MAAM,GAAE,GAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IA2C5C;;OAEG;YACW,gBAAgB;IA0B9B;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAgBxB;;OAEG;YACW,aAAa;IAwC3B;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IA0C3B;;OAEG;IACH,OAAO,CAAC,OAAO;IASf;;OAEG;IACH,OAAO,IAAI,MAAM;IAOjB;;OAEG;IACH,UAAU,IAAI,MAAM;IAOpB;;OAEG;IACH,SAAS,IAAI,eAAe;IAO5B;;OAEG;IACH,SAAS,IAAI,OAAO;IAIpB;;OAEG;IACG,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,GAAQ,GAAG,OAAO,CAAC,GAAG,CAAC;IA8BhE;;OAEG;IACG,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC;IA4BpF;;OAEG;IACH,cAAc,IAAI,MAAM;CAUzB"}
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import { BrutalistServer } from '../brutalist-server.js';
|
|
2
|
+
import { logger } from '../logger.js';
|
|
3
|
+
/**
|
|
4
|
+
* Harness for deterministic server lifecycle management in tests.
|
|
5
|
+
* Provides event-based readiness detection and proper cleanup.
|
|
6
|
+
*/
|
|
7
|
+
export class ServerHarness {
|
|
8
|
+
options;
|
|
9
|
+
server = null;
|
|
10
|
+
actualPort;
|
|
11
|
+
baseUrl;
|
|
12
|
+
startTime;
|
|
13
|
+
httpServer;
|
|
14
|
+
sessionId;
|
|
15
|
+
constructor(options = {}) {
|
|
16
|
+
this.options = options;
|
|
17
|
+
this.options = {
|
|
18
|
+
maxStartupTime: 30000,
|
|
19
|
+
healthCheckInterval: 100,
|
|
20
|
+
shutdownTimeout: 5000,
|
|
21
|
+
...options
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Start server and wait for it to be ready (not just started)
|
|
26
|
+
*/
|
|
27
|
+
async start(config = {}) {
|
|
28
|
+
if (this.server) {
|
|
29
|
+
throw new Error('Server already started');
|
|
30
|
+
}
|
|
31
|
+
this.startTime = Date.now();
|
|
32
|
+
logger.info('ServerHarness: Starting server...');
|
|
33
|
+
try {
|
|
34
|
+
// Create server instance
|
|
35
|
+
this.server = new BrutalistServer({
|
|
36
|
+
...config,
|
|
37
|
+
httpPort: config.httpPort ?? 0 // Use 0 for random port if not specified
|
|
38
|
+
});
|
|
39
|
+
// Start the server
|
|
40
|
+
await this.server.start();
|
|
41
|
+
// Get actual port for HTTP transport
|
|
42
|
+
if (config.transport === 'http' || !config.transport) {
|
|
43
|
+
this.actualPort = this.server.getActualPort();
|
|
44
|
+
if (!this.actualPort) {
|
|
45
|
+
throw new Error('Failed to get actual server port');
|
|
46
|
+
}
|
|
47
|
+
this.baseUrl = `http://localhost:${this.actualPort}`;
|
|
48
|
+
// Wait for HTTP server to be ready
|
|
49
|
+
await this.waitForHttpReady();
|
|
50
|
+
// Initialize MCP connection
|
|
51
|
+
await this.initializeMCP();
|
|
52
|
+
}
|
|
53
|
+
const startupTime = Date.now() - this.startTime;
|
|
54
|
+
logger.info(`ServerHarness: Server ready in ${startupTime}ms on port ${this.actualPort}`);
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
// Cleanup on startup failure
|
|
58
|
+
logger.error('ServerHarness: Startup failed:', error);
|
|
59
|
+
this.cleanup();
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Wait for HTTP server to respond to health checks
|
|
65
|
+
*/
|
|
66
|
+
async waitForHttpReady() {
|
|
67
|
+
const deadline = Date.now() + this.options.maxStartupTime;
|
|
68
|
+
let lastError;
|
|
69
|
+
while (Date.now() < deadline) {
|
|
70
|
+
try {
|
|
71
|
+
const response = await fetch(`${this.baseUrl}/health`);
|
|
72
|
+
if (response.ok) {
|
|
73
|
+
const data = await response.json();
|
|
74
|
+
if (data.status === 'ok') {
|
|
75
|
+
logger.debug('ServerHarness: Health check passed');
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
lastError = error;
|
|
82
|
+
// Server not ready yet, keep trying
|
|
83
|
+
}
|
|
84
|
+
await new Promise(resolve => setTimeout(resolve, this.options.healthCheckInterval));
|
|
85
|
+
}
|
|
86
|
+
throw new Error(`Server failed to become ready within ${this.options.maxStartupTime}ms: ${lastError?.message}`);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Parse SSE response format to extract JSON data
|
|
90
|
+
*/
|
|
91
|
+
parseSSEResponse(responseText) {
|
|
92
|
+
const lines = responseText.split('\n');
|
|
93
|
+
for (const line of lines) {
|
|
94
|
+
if (line.startsWith('data: ')) {
|
|
95
|
+
try {
|
|
96
|
+
return JSON.parse(line.substring(6)); // Remove "data: " prefix
|
|
97
|
+
}
|
|
98
|
+
catch (e) {
|
|
99
|
+
// Continue looking for valid JSON
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
throw new Error(`Failed to parse SSE response: ${responseText}`);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Initialize MCP connection with handshake
|
|
107
|
+
*/
|
|
108
|
+
async initializeMCP() {
|
|
109
|
+
const initRequest = {
|
|
110
|
+
jsonrpc: '2.0',
|
|
111
|
+
id: 1,
|
|
112
|
+
method: 'initialize',
|
|
113
|
+
params: {
|
|
114
|
+
protocolVersion: '2024-11-05',
|
|
115
|
+
capabilities: {
|
|
116
|
+
tools: {}
|
|
117
|
+
},
|
|
118
|
+
clientInfo: {
|
|
119
|
+
name: 'test-client',
|
|
120
|
+
version: '1.0.0'
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
const response = await this.testRequest('/mcp', {
|
|
125
|
+
method: 'POST',
|
|
126
|
+
body: JSON.stringify(initRequest)
|
|
127
|
+
});
|
|
128
|
+
// Handle SSE response format
|
|
129
|
+
const responseText = await response.text();
|
|
130
|
+
const jsonData = this.parseSSEResponse(responseText);
|
|
131
|
+
if (jsonData.error) {
|
|
132
|
+
throw new Error(`MCP initialization failed: ${JSON.stringify(jsonData.error)}`);
|
|
133
|
+
}
|
|
134
|
+
// Extract session ID from response headers
|
|
135
|
+
const sessionIdHeader = response.headers.get('mcp-session-id');
|
|
136
|
+
if (sessionIdHeader) {
|
|
137
|
+
this.sessionId = sessionIdHeader;
|
|
138
|
+
logger.debug(`MCP session ID: ${this.sessionId}`);
|
|
139
|
+
}
|
|
140
|
+
logger.debug('MCP server initialized successfully');
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Stop server with graceful shutdown and forced kill if needed
|
|
144
|
+
*/
|
|
145
|
+
async stop() {
|
|
146
|
+
if (!this.server) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
logger.info('ServerHarness: Stopping server...');
|
|
150
|
+
const stopStart = Date.now();
|
|
151
|
+
try {
|
|
152
|
+
// Try graceful shutdown first
|
|
153
|
+
if (this.httpServer) {
|
|
154
|
+
await new Promise((resolve, reject) => {
|
|
155
|
+
const timeout = setTimeout(() => {
|
|
156
|
+
reject(new Error('Server shutdown timed out'));
|
|
157
|
+
}, this.options.shutdownTimeout);
|
|
158
|
+
this.httpServer.close((err) => {
|
|
159
|
+
clearTimeout(timeout);
|
|
160
|
+
if (err)
|
|
161
|
+
reject(err);
|
|
162
|
+
else
|
|
163
|
+
resolve();
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
// Additional server cleanup if needed
|
|
168
|
+
if (this.server) {
|
|
169
|
+
this.server.cleanup();
|
|
170
|
+
}
|
|
171
|
+
const stopTime = Date.now() - stopStart;
|
|
172
|
+
logger.info(`ServerHarness: Server stopped in ${stopTime}ms`);
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
logger.error('ServerHarness: Graceful shutdown failed, forcing stop:', error);
|
|
176
|
+
// Force cleanup
|
|
177
|
+
this.httpServer = undefined;
|
|
178
|
+
}
|
|
179
|
+
finally {
|
|
180
|
+
this.server = null;
|
|
181
|
+
this.actualPort = undefined;
|
|
182
|
+
this.baseUrl = undefined;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Reset server state (for cleanup after failed startup)
|
|
187
|
+
*/
|
|
188
|
+
cleanup() {
|
|
189
|
+
this.server = null;
|
|
190
|
+
this.actualPort = undefined;
|
|
191
|
+
this.baseUrl = undefined;
|
|
192
|
+
this.httpServer = undefined;
|
|
193
|
+
this.sessionId = undefined;
|
|
194
|
+
this.startTime = undefined;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Get the actual port the server is listening on
|
|
198
|
+
*/
|
|
199
|
+
getPort() {
|
|
200
|
+
if (!this.actualPort) {
|
|
201
|
+
throw new Error('Server not started or port not available');
|
|
202
|
+
}
|
|
203
|
+
return this.actualPort;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Get the base URL for HTTP requests
|
|
207
|
+
*/
|
|
208
|
+
getBaseUrl() {
|
|
209
|
+
if (!this.baseUrl) {
|
|
210
|
+
throw new Error('Server not started or not using HTTP transport');
|
|
211
|
+
}
|
|
212
|
+
return this.baseUrl;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Get the server instance for direct access if needed
|
|
216
|
+
*/
|
|
217
|
+
getServer() {
|
|
218
|
+
if (!this.server) {
|
|
219
|
+
throw new Error('Server not started');
|
|
220
|
+
}
|
|
221
|
+
return this.server;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Check if server is currently running
|
|
225
|
+
*/
|
|
226
|
+
isRunning() {
|
|
227
|
+
return this.server !== null;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Make a test request to the server
|
|
231
|
+
*/
|
|
232
|
+
async testRequest(path, options = {}) {
|
|
233
|
+
if (!this.baseUrl) {
|
|
234
|
+
throw new Error('Server not started or not using HTTP transport');
|
|
235
|
+
}
|
|
236
|
+
const url = `${this.baseUrl}${path}`;
|
|
237
|
+
const headers = {
|
|
238
|
+
'Content-Type': 'application/json',
|
|
239
|
+
'Accept': 'application/json, text/event-stream',
|
|
240
|
+
...options.headers
|
|
241
|
+
};
|
|
242
|
+
// Add session ID if available and this is an MCP request
|
|
243
|
+
if (this.sessionId && path === '/mcp') {
|
|
244
|
+
headers['Mcp-Session-Id'] = this.sessionId;
|
|
245
|
+
}
|
|
246
|
+
const response = await fetch(url, {
|
|
247
|
+
...options,
|
|
248
|
+
headers
|
|
249
|
+
});
|
|
250
|
+
if (!response.ok && !options.allowFailure) {
|
|
251
|
+
const text = await response.text();
|
|
252
|
+
throw new Error(`Request failed: ${response.status} ${response.statusText}\n${text}`);
|
|
253
|
+
}
|
|
254
|
+
return response;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Execute an MCP tool via HTTP
|
|
258
|
+
*/
|
|
259
|
+
async executeTool(toolName, args, progressToken) {
|
|
260
|
+
const request = {
|
|
261
|
+
jsonrpc: '2.0',
|
|
262
|
+
id: Date.now(),
|
|
263
|
+
method: 'tools/call',
|
|
264
|
+
params: {
|
|
265
|
+
name: toolName,
|
|
266
|
+
arguments: args,
|
|
267
|
+
_meta: progressToken ? { progressToken } : undefined
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
const response = await this.testRequest('/mcp', {
|
|
271
|
+
method: 'POST',
|
|
272
|
+
body: JSON.stringify(request)
|
|
273
|
+
});
|
|
274
|
+
// Handle SSE response format
|
|
275
|
+
const responseText = await response.text();
|
|
276
|
+
const jsonData = this.parseSSEResponse(responseText);
|
|
277
|
+
if (jsonData.error) {
|
|
278
|
+
throw new Error(`Tool execution failed: ${JSON.stringify(jsonData.error)}`);
|
|
279
|
+
}
|
|
280
|
+
return jsonData.result;
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Get diagnostic information about the server
|
|
284
|
+
*/
|
|
285
|
+
getDiagnostics() {
|
|
286
|
+
const lines = ['ServerHarness diagnostics:'];
|
|
287
|
+
lines.push(` Running: ${this.isRunning()}`);
|
|
288
|
+
if (this.server) {
|
|
289
|
+
lines.push(` Port: ${this.actualPort}`);
|
|
290
|
+
lines.push(` Base URL: ${this.baseUrl}`);
|
|
291
|
+
lines.push(` Uptime: ${Date.now() - this.startTime}ms`);
|
|
292
|
+
}
|
|
293
|
+
return lines.join('\n');
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
//# sourceMappingURL=server-harness.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server-harness.js","sourceRoot":"","sources":["../../src/test-utils/server-harness.ts"],"names":[],"mappings":"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"}
|