From f6469556f0b396c8ded76b2dc7fdba9f92e09209 Mon Sep 17 00:00:00 2001 From: Advait Paliwal Date: Fri, 20 Mar 2026 23:37:38 -0700 Subject: [PATCH] Harden alphaXiv transport retries and logging --- cli/src/lib/alphaxiv.js | 112 +++++++++++++++++++++++++++++----------- 1 file changed, 83 insertions(+), 29 deletions(-) diff --git a/cli/src/lib/alphaxiv.js b/cli/src/lib/alphaxiv.js index bc582eb..42d2bcb 100644 --- a/cli/src/lib/alphaxiv.js +++ b/cli/src/lib/alphaxiv.js @@ -6,6 +6,43 @@ const ALPHAXIV_MCP_URL = 'https://api.alphaxiv.org/mcp/v1'; let _client = null; let _connected = false; +let _lastTransportLog = { message: '', time: 0 }; + +function getErrorMessage(err) { + if (!err) return 'Unknown error'; + if (typeof err === 'string') return err; + if (err instanceof Error) return err.message || String(err); + return String(err); +} + +function isTransientTransportError(err) { + const message = getErrorMessage(err); + return ( + message.includes('SSE stream disconnected') || + message.includes('Failed to open SSE stream') || + message.includes('Failed to reconnect SSE stream') || + message.includes('Maximum reconnection attempts') || + message.includes('Bad Gateway') || + message.includes('TypeError: terminated') || + message.includes('terminated') + ); +} + +function logTransportError(err) { + const message = getErrorMessage(err); + + if (isTransientTransportError(message)) { + const now = Date.now(); + if (_lastTransportLog.message === message && now - _lastTransportLog.time < 10000) { + return; + } + _lastTransportLog = { message, time: now }; + process.stderr.write(`[alpha] alphaXiv MCP transient transport issue: ${message}\n`); + return; + } + + process.stderr.write(`[alpha] alphaXiv MCP error: ${message}\n`); +} async function getClient() { if (_client && _connected) return _client; @@ -18,7 +55,10 @@ async function getClient() { _client = new Client({ name: 'alpha', version: '0.1.0' }); _client.onerror = (err) => { - process.stderr.write(`[alpha] alphaXiv MCP error: ${err.message || err}\n`); + if (isTransientTransportError(err)) { + _connected = false; + } + logTransportError(err); }; const transport = new StreamableHTTPClientTransport(new URL(ALPHAXIV_MCP_URL), { @@ -36,39 +76,53 @@ async function getClient() { } async function callTool(name, args) { - let client; - try { - client = await getClient(); - } catch (err) { - if (err.message?.includes('401') || err.message?.includes('Unauthorized')) { - const newToken = await refreshAccessToken(); - if (newToken) { - _client = null; - _connected = false; - client = await getClient(); + let lastError = null; + + for (let attempt = 0; attempt < 3; attempt++) { + let client; + try { + client = await getClient(); + } catch (err) { + if (err.message?.includes('401') || err.message?.includes('Unauthorized')) { + const newToken = await refreshAccessToken(); + if (newToken) { + _client = null; + _connected = false; + client = await getClient(); + } else { + throw new Error('Session expired. Run `alpha login` to re-authenticate.'); + } } else { - throw new Error('Session expired. Run `alpha login` to re-authenticate.'); + throw err; } - } else { - throw err; + } + + try { + const result = await client.callTool({ name, arguments: args }); + + if (result.isError) { + const text = result.content?.[0]?.text || 'Unknown error'; + throw new Error(text); + } + + const text = result.content?.[0]?.text; + if (!text) return result.content; + + try { + return JSON.parse(text); + } catch { + return text; + } + } catch (err) { + lastError = err; + if (!isTransientTransportError(err) || attempt === 2) { + throw err; + } + await disconnect(); } } - const result = await client.callTool({ name, arguments: args }); - - if (result.isError) { - const text = result.content?.[0]?.text || 'Unknown error'; - throw new Error(text); - } - - const text = result.content?.[0]?.text; - if (!text) return result.content; - - try { - return JSON.parse(text); - } catch { - return text; - } + throw lastError ?? new Error('alphaXiv MCP call failed'); } export async function searchByEmbedding(query) {