[fix] recover initial MCP signaling connections and preserve failure reasons

This commit is contained in:
dijunkun
2026-11-06 20:02:52 +08:00
parent 09ef60dc6f
commit 2535a04d2b
10 changed files with 83 additions and 17 deletions
+2
View File
@@ -13,6 +13,7 @@ export class Failure extends Error {
public code: string,
public sideEffect = false,
public retryable = false,
public reason?: string,
) {
super(code);
}
@@ -23,6 +24,7 @@ export function failure(error: unknown): Data {
code: e.code,
retryable: e.retryable,
side_effect_possible: e.sideEffect,
...(e.reason ? { reason: e.reason } : {}),
};
}
export function parse<T extends z.ZodType>(
+2 -1
View File
@@ -201,6 +201,7 @@ async function main() {
});
}
main().catch((error) => {
process.stderr.write(`crossdesk-mcp: ${failure(error).code}\n`);
const result = failure(error);
process.stderr.write(`crossdesk-mcp: ${result.code}${result.reason ? ` (${result.reason})` : ""}\n`);
process.exitCode = 1;
});
+25 -6
View File
@@ -15,6 +15,26 @@ import { DeviceStore, type Device } from "./store.js";
import { nativeBinary, NativeClient } from "./native.js";
import { displayEnvironment } from "./environment.js";
// Keep the public error categories while exposing only known native reasons.
const connectionErrors = new Map<string, string>([
["incorrect_password", "AUTH_FAILED"],
["signaling_timeout", "CONNECTION_TIMEOUT"],
["connection_timeout", "CONNECTION_TIMEOUT"],
["signaling_login_failed", "CONNECTION_FAILED"],
["signaling_disconnected", "CONNECTION_FAILED"],
["tls_certificate_error", "CONNECTION_FAILED"],
["cli_identity_not_supported", "CONNECTION_FAILED"],
["join_request_failed", "CONNECTION_FAILED"],
["device_not_found", "CONNECTION_FAILED"],
["device_offline_or_unavailable", "CONNECTION_FAILED"],
["connection_lost", "CONNECTION_FAILED"],
["connection_failed", "CONNECTION_FAILED"],
["remote_disconnected", "CONNECTION_FAILED"],
["display_not_available", "CONNECTION_FAILED"],
["media_initialization_failed", "CONNECTION_FAILED"],
["control_event_overflow", "CONNECTION_FAILED"],
]);
export interface View {
id: string;
generation: number;
@@ -346,12 +366,11 @@ export class Sessions {
s.connecting = false;
return { ...s.status, session_id: s.id, device_alias: s.device.alias };
}
if (["failed", "closed", "disconnected"].includes(s.status.state))
throw new Failure(
s.status.error === "incorrect_password"
? "AUTH_FAILED"
: "CONNECTION_FAILED",
);
if (["failed", "closed", "disconnected"].includes(s.status.state)) {
const code = connectionErrors.get(s.status.error);
throw new Failure(code ?? "CONNECTION_FAILED", false, false,
code ? s.status.error : undefined);
}
await delay(100, undefined, { signal });
}
throw new Failure("CONNECTION_TIMEOUT");
+2 -1
View File
@@ -161,7 +161,8 @@ export class TerminalClient {
if (!buffer.includes(10)) return;
try {
const reply = JSON.parse(buffer.toString("utf8"));
if (reply.error) finish(new Failure(reply.error.code));
if (reply.error) finish(new Failure(reply.error.code,
!!reply.error.side_effect_possible, !!reply.error.retryable, reply.error.reason));
else if (reply.result && typeof reply.result === "object") finish(undefined, reply.result);
else finish(new Failure("INVALID_RESPONSE"));
} catch { finish(new Failure("INVALID_RESPONSE")); }