This commit is contained in:
Harshith Mullapudi 2025-07-17 08:29:10 +05:30
parent 78d401f618
commit af48e97166
4 changed files with 93 additions and 144 deletions

View File

@ -1,8 +1,6 @@
import { useEffect, useRef, useState } from "react";
import {
List,
InfiniteLoader,
WindowScroller,
AutoSizer,
CellMeasurer,
CellMeasurerCache,
@ -12,21 +10,15 @@ import {
import { type LogItem } from "~/hooks/use-logs";
import { Badge } from "~/components/ui/badge";
import { Card, CardContent } from "~/components/ui/card";
import { AlertCircle, ChevronDown, XCircle } from "lucide-react";
import { AlertCircle } from "lucide-react";
import { cn } from "~/lib/utils";
import { ScrollManagedList } from "../virtualized-list";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogClose,
} from "../ui/dialog";
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "../ui/dialog";
import { Button } from "../ui";
// --- LogTextCollapse component ---
function LogTextCollapse({ text }: { text?: string }) {
function LogTextCollapse({ text, error }: { text?: string; error?: string }) {
const [dialogOpen, setDialogOpen] = useState(false);
// Show collapse if text is long (by word count)
@ -52,38 +44,60 @@ function LogTextCollapse({ text }: { text?: string }) {
}
return (
<div className="mb-2">
<p
className={cn(
"whitespace-p-wrap pt-2 text-sm break-words",
isLong ? "max-h-16 overflow-hidden" : "",
<>
<div className="mb-2">
<p
className={cn(
"whitespace-p-wrap pt-2 text-sm break-words",
isLong ? "max-h-16 overflow-hidden" : "",
)}
style={{ lineHeight: "1.5" }}
>
{displayText}
</p>
{isLong && (
<>
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
<DialogContent className="max-w-2xl p-4">
<DialogHeader>
<DialogTitle className="flex w-full items-center justify-between">
<span>Log Details</span>
</DialogTitle>
</DialogHeader>
<div className="max-h-[70vh] overflow-auto p-0">
<p
className="px-3 py-2 text-sm break-words whitespace-pre-wrap"
style={{ lineHeight: "1.5" }}
>
{text}
</p>
</div>
</DialogContent>
</Dialog>
</>
)}
</div>
<div
className={cn(
"text-muted-foreground flex items-center justify-end text-xs",
isLong && "justify-between",
)}
style={{ lineHeight: "1.5" }}
>
{displayText}
</p>
{isLong && (
<>
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
<DialogContent className="max-w-2xl p-4">
<DialogHeader>
<DialogTitle className="flex w-full items-center justify-between">
<span>Log Details</span>
</DialogTitle>
</DialogHeader>
<div className="max-h-[70vh] overflow-auto p-0">
<p
className="px-3 py-2 text-sm break-words whitespace-pre-wrap"
style={{ lineHeight: "1.5" }}
>
{text}
</p>
</div>
</DialogContent>
</Dialog>
</>
)}
</div>
{isLong && (
<Button variant="ghost" size="sm" className="-ml-2 rounded">
See full
</Button>
)}
{error && (
<div className="flex items-center gap-1 text-red-600">
<AlertCircle className="h-3 w-3" />
<span className="max-w-[200px] truncate" title={error}>
{error}
</span>
</div>
)}
</div>
</>
);
}
@ -169,26 +183,7 @@ function LogItemRenderer(
</div>
</div>
<LogTextCollapse text={log.ingestText} />
<div className="text-muted-foreground flex items-center justify-between text-xs">
<div className="flex items-center gap-4">
{log.processedAt && (
<span>
Processed: {new Date(log.processedAt).toLocaleString()}
</span>
)}
</div>
{log.error && (
<div className="flex items-center gap-1 text-red-600">
<AlertCircle className="h-3 w-3" />
<span className="max-w-[200px] truncate" title={log.error}>
{log.error}
</span>
</div>
)}
</div>
<LogTextCollapse text={log.ingestText} error={log.error} />
</CardContent>
</Card>
</div>

View File

@ -116,33 +116,8 @@ export function createMCPProxy(
bridgeOptions
);
// Set up timeout
const timeoutId = config.timeout
? setTimeout(() => {
bridge?.close().catch(console.error);
if (!resolve) return;
resolve(
new Response(
JSON.stringify({
error: "Request timeout",
}),
{
status: 408,
headers: { "Content-Type": "application/json" },
}
)
);
}, config.timeout)
: null;
// Start only the client transport (server is already started)
await clientTransport.start();
// Clean up after a reasonable time (since HTTP is request/response)
setTimeout(() => {
if (timeoutId) clearTimeout(timeoutId);
bridge?.close().catch(console.error);
}, 1000);
} catch (error) {
console.error("MCP Transport Proxy Error:", error);
@ -186,32 +161,16 @@ export function createMCPProxy(
// Create transport based on strategy (don't start yet)
let transport: SSEClientTransport | StreamableHTTPClientTransport;
// For SSE, we need eventSourceInit for authentication
const eventSourceInit = {
fetch: (url: string | URL, init?: RequestInit) => {
return fetch(url, {
...init,
headers: {
...(init?.headers as Record<string, string> | undefined),
...headers,
Accept: "text/event-stream",
} as Record<string, string>,
});
},
};
switch (transportStrategy) {
case "sse-only":
transport = new SSEClientTransport(url, {
authProvider,
requestInit: { headers },
eventSourceInit,
});
break;
case "http-only":
transport = new StreamableHTTPClientTransport(url, {
authProvider,
requestInit: { headers },
});
break;
@ -222,7 +181,6 @@ export function createMCPProxy(
transport = new SSEClientTransport(url, {
authProvider,
requestInit: { headers },
eventSourceInit,
});
} catch (error) {
console.warn("SSE transport failed, falling back to HTTP:", error);
@ -237,7 +195,6 @@ export function createMCPProxy(
// Try HTTP first, fallback to SSE on error
try {
transport = new StreamableHTTPClientTransport(url, {
authProvider,
requestInit: { headers },
});
} catch (error) {
@ -245,7 +202,6 @@ export function createMCPProxy(
transport = new SSEClientTransport(url, {
authProvider,
requestInit: { headers },
eventSourceInit,
});
}
break;

View File

@ -9,8 +9,8 @@ export function createMCPTransportBridge(
serverTransport: Transport,
options: {
debug?: boolean;
onMessage?: (direction: 'client-to-server' | 'server-to-client', message: any) => void;
onError?: (error: Error, source: 'client' | 'server') => void;
onMessage?: (direction: "client-to-server" | "server-to-client", message: any) => void;
onError?: (error: Error, source: "client" | "server") => void;
} = {}
) {
let clientClosed = false;
@ -23,23 +23,25 @@ export function createMCPTransportBridge(
// Forward messages from client to server
clientTransport.onmessage = (message: any) => {
log('[Client→Server]', message.method || message.id);
onMessage?.('client-to-server', message);
serverTransport.send(message).catch(error => {
logError('Error sending to server:', error);
onError?.(error, 'server');
console.log(JSON.stringify(message));
log("[Client→Server]", message.method || message.id);
onMessage?.("client-to-server", message);
serverTransport.send(message).catch((error) => {
logError("Error sending to server:", error);
onError?.(error, "server");
});
};
// Forward messages from server to client
serverTransport.onmessage = (message: any) => {
log('[Server→Client]', message.method || message.id);
onMessage?.('server-to-client', message);
clientTransport.send(message).catch(error => {
logError('Error sending to client:', error);
onError?.(error, 'client');
console.log(JSON.stringify(message));
log("[Server→Client]", message.method || message.id);
onMessage?.("server-to-client", message);
clientTransport.send(message).catch((error) => {
logError("Error sending to client:", error);
onError?.(error, "client");
});
};
@ -47,30 +49,30 @@ export function createMCPTransportBridge(
clientTransport.onclose = () => {
if (serverClosed) return;
clientClosed = true;
log('Client transport closed, closing server transport');
serverTransport.close().catch(error => {
logError('Error closing server transport:', error);
log("Client transport closed, closing server transport");
serverTransport.close().catch((error) => {
logError("Error closing server transport:", error);
});
};
serverTransport.onclose = () => {
if (clientClosed) return;
serverClosed = true;
log('Server transport closed, closing client transport');
clientTransport.close().catch(error => {
logError('Error closing client transport:', error);
log("Server transport closed, closing client transport");
clientTransport.close().catch((error) => {
logError("Error closing client transport:", error);
});
};
// Error handling
clientTransport.onerror = (error: Error) => {
logError('Client transport error:', error);
onError?.(error, 'client');
logError("Client transport error:", error);
onError?.(error, "client");
};
serverTransport.onerror = (error: Error) => {
logError('Server transport error:', error);
onError?.(error, 'server');
logError("Server transport error:", error);
onError?.(error, "server");
};
return {
@ -79,13 +81,10 @@ export function createMCPTransportBridge(
*/
start: async () => {
try {
await Promise.all([
clientTransport.start(),
serverTransport.start()
]);
log('MCP transport bridge started successfully');
await Promise.all([clientTransport.start(), serverTransport.start()]);
log("MCP transport bridge started successfully");
} catch (error) {
logError('Error starting transport bridge:', error);
logError("Error starting transport bridge:", error);
throw error;
}
},
@ -95,13 +94,10 @@ export function createMCPTransportBridge(
*/
close: async () => {
try {
await Promise.all([
clientTransport.close(),
serverTransport.close()
]);
log('MCP transport bridge closed successfully');
await Promise.all([clientTransport.close(), serverTransport.close()]);
log("MCP transport bridge closed successfully");
} catch (error) {
logError('Error closing transport bridge:', error);
logError("Error closing transport bridge:", error);
throw error;
}
},
@ -111,6 +107,6 @@ export function createMCPTransportBridge(
*/
get isClosed() {
return clientClosed || serverClosed;
}
},
};
}
}

View File

@ -83,5 +83,7 @@ export class RemixMCPTransport implements Transport {
onmessage: (message: any) => void = () => {};
onclose: () => void = () => {};
onerror: (error: Error) => void = () => {};
async onerror(error: Error) {
console.log(error);
}
}