Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 46 additions & 1 deletion src/server/mcp-proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ export interface MCPToolResult {
error?: string;
}

class MCPSessionExpiredError extends Error {
constructor() {
super('MCP session expired or not found');
this.name = 'MCPSessionExpiredError';
}
}

export class MCPProxy {
private db: CapaDatabase;
private projectId: string;
Expand Down Expand Up @@ -77,7 +84,6 @@ export class MCPProxy {

try {
this.logger.debug('Calling tool on MCP server...');
// Call the tool
const result = await client.callTool({
name: definition.tool,
arguments: args,
Expand All @@ -89,6 +95,25 @@ export class MCPProxy {
result: result.content,
};
} catch (error: any) {
if (error instanceof MCPSessionExpiredError) {
this.logger.warn(`Session expired for ${serverId}, reconnecting and retrying tool call...`);
this.clients.delete(serverId);
const freshClient = await this.getOrCreateClient(serverId, resolvedServerDef);
if (!freshClient) {
return { success: false, error: `Failed to reconnect to MCP server: ${serverId}` };
}
try {
const retryResult = await freshClient.callTool({
name: definition.tool,
arguments: args,
});
this.logger.success('Tool call succeeded after reconnect');
return { success: true, result: retryResult.content };
} catch (retryError: any) {
this.logger.failure(`Tool call failed after reconnect: ${retryError.message}`);
return { success: false, error: retryError.message || 'Tool execution failed after reconnect' };
}
}
this.logger.failure(`Tool call failed: ${error.message}`);
return {
success: false,
Expand Down Expand Up @@ -119,6 +144,19 @@ export class MCPProxy {
const result = await client.listTools();
return result.tools;
} catch (error) {
if (error instanceof MCPSessionExpiredError) {
this.logger.warn(`Session expired for ${cleanServerId}, reconnecting...`);
this.clients.delete(cleanServerId);
const freshClient = await this.getOrCreateClient(cleanServerId, resolvedServerDef);
if (!freshClient) return [];
try {
const result = await freshClient.listTools();
return result.tools;
} catch (retryError) {
this.logger.error(`Failed to list tools from ${cleanServerId} after reconnect:`, retryError);
return [];
}
}
this.logger.error(`Failed to list tools from ${cleanServerId}:`, error);
return [];
}
Expand Down Expand Up @@ -427,6 +465,13 @@ class HttpMCPTransport implements Transport {
throw new Error('Authentication failed. Please reconnect OAuth2.');
}

// Handle 404 - remote server may have expired our session
if (response.status === 404 && this.sessionId) {
this.logger.warn(`404 Not Found with active session ID - session likely expired, clearing session`);
this.sessionId = undefined;
throw new MCPSessionExpiredError();
}

if (!response.ok) {
// Try to get error details from response body
let errorDetails = '';
Expand Down
Loading