Skip to content

Commit 9740e09

Browse files
authored
Merge pull request #84 from dengmik-commits/main
feat: MCP 服务器手动重连功能
2 parents 3c15003 + c323f2f commit 9740e09

6 files changed

Lines changed: 347 additions & 151 deletions

File tree

src/mcp/mcp-client.ts

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,19 +107,24 @@ export class McpClient {
107107
>();
108108
private stderrBuffer = "";
109109
private notificationHandler: McpNotificationHandler | null = null;
110+
private disconnectHandler: ((reason: string) => void) | null = null;
111+
private intentionallyDisconnected = false;
110112

111113
constructor(
112114
private readonly serverName: string,
113115
private readonly command: string,
114116
private readonly args: string[] = [],
115117
private readonly env?: Record<string, string>,
116-
onNotification?: McpNotificationHandler
118+
onNotification?: McpNotificationHandler,
119+
onDisconnect?: (reason: string) => void
117120
) {
118121
this.notificationHandler = onNotification ?? null;
122+
this.disconnectHandler = onDisconnect ?? null;
119123
}
120124

121125
async connect(timeoutMs: number): Promise<void> {
122126
return new Promise((resolve, reject) => {
127+
this.intentionallyDisconnected = false;
123128
const childEnv = {
124129
...process.env,
125130
...this.env,
@@ -145,17 +150,35 @@ export class McpClient {
145150
});
146151
}
147152

153+
let resolved = false;
154+
const safeReject = (err: Error) => {
155+
if (!resolved) {
156+
resolved = true;
157+
reject(err);
158+
}
159+
};
160+
148161
this.process.on("error", (err) => {
149-
reject(this.withStderr(`Failed to start MCP server "${this.serverName}" (${this.command}): ${err.message}`));
162+
safeReject(
163+
this.withStderr(`Failed to start MCP server "${this.serverName}" (${this.command}): ${err.message}`)
164+
);
150165
});
151166

152167
this.process.on("close", (code) => {
153-
const error = this.withStderr(`MCP server "${this.serverName}" exited with code ${code}`);
168+
const reason = `MCP server "${this.serverName}" exited with code ${code}`;
169+
const error = this.withStderr(reason);
154170
for (const [, pending] of this.pendingRequests) {
155171
clearTimeout(pending.timer);
156172
pending.reject(error);
157173
}
158174
this.pendingRequests.clear();
175+
this.reader?.close();
176+
this.reader = null;
177+
this.process = null;
178+
if (!this.intentionallyDisconnected && this.disconnectHandler) {
179+
this.disconnectHandler(reason);
180+
}
181+
safeReject(error);
159182
});
160183

161184
if (this.process.stderr) {
@@ -264,6 +287,7 @@ export class McpClient {
264287
}
265288

266289
disconnect(): void {
290+
this.intentionallyDisconnected = true;
267291
if (this.reader) {
268292
this.reader.close();
269293
this.reader = null;
@@ -278,6 +302,10 @@ export class McpClient {
278302
}
279303
}
280304

305+
isConnected(): boolean {
306+
return this.process !== null && this.process.exitCode === null;
307+
}
308+
281309
private sendRequest(method: string, params: Record<string, unknown>, timeoutMs = 30_000): Promise<unknown> {
282310
return new Promise((resolve, reject) => {
283311
const id = this.nextId++;

0 commit comments

Comments
 (0)