-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathmanaged-mcp-session.mjs
More file actions
104 lines (88 loc) · 2.63 KB
/
managed-mcp-session.mjs
File metadata and controls
104 lines (88 loc) · 2.63 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
import process from 'node:process';
async function loadSdkClient() {
const [{ Client }, { StdioClientTransport }] = await Promise.all([
import('@modelcontextprotocol/sdk/client/index.js'),
import('@modelcontextprotocol/sdk/client/stdio.js')
]);
return { Client, StdioClientTransport };
}
function createTimeoutError(timeoutMs) {
const seconds = Math.max(1, Math.round(timeoutMs / 1000));
return new Error(`MCP client connect timed out after ${seconds}s`);
}
function withTimeout(promise, timeoutMs) {
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
return promise;
}
let timer;
const timeoutPromise = new Promise((_, reject) => {
timer = setTimeout(() => reject(createTimeoutError(timeoutMs)), timeoutMs);
timer.unref?.();
});
return Promise.race([promise, timeoutPromise]).finally(() => {
if (timer) {
clearTimeout(timer);
}
});
}
function delay(timeoutMs) {
return new Promise((resolve) => {
const timer = setTimeout(resolve, timeoutMs);
timer.unref?.();
});
}
async function safeClose(client, transport, connected) {
const closeAttempts = [];
if (connected) {
closeAttempts.push(client.close().catch(() => undefined));
}
closeAttempts.push(transport.close?.().catch(() => undefined));
await Promise.all(closeAttempts);
}
export async function withManagedStdioClientSession(options, callback) {
const {
serverCommand,
serverArgs = [],
serverEnv = {},
cwd,
clientInfo = { name: 'benchmark-runner', version: '1.0.0' },
connectTimeoutMs = 10_000,
onSpawn
} = options;
const { Client, StdioClientTransport } = await loadSdkClient();
const transport = new StdioClientTransport({
command: serverCommand,
args: serverArgs,
env: { ...process.env, ...serverEnv },
cwd
});
const client = new Client(clientInfo);
let connected = false;
let settling = false;
const connectPromise = client.connect(transport);
const spawnNotification = (async () => {
if (typeof onSpawn !== 'function') {
return;
}
while (!settling) {
if (transport.pid !== null) {
onSpawn(transport.pid);
return;
}
await new Promise((resolve) => setTimeout(resolve, 10));
}
if (transport.pid !== null) {
onSpawn(transport.pid);
}
})();
try {
await withTimeout(connectPromise, connectTimeoutMs);
connected = true;
return await callback({ client, transport });
} finally {
settling = true;
await safeClose(client, transport, connected);
await spawnNotification.catch(() => undefined);
await Promise.race([connectPromise, delay(5_000)]).catch(() => undefined);
}
}