Skip to content

Commit 93b3940

Browse files
Julien-serclaude
andcommitted
fix(client): display server stderr logs in Console tab
ConsoleTab was a static placeholder that showed no data. The proxy already forwarded MCP server stderr as notifications/message events, but the UI had no dedicated view for them. ConsoleTab now receives the notifications array and renders all notifications/message entries in a terminal-style panel: log levels are color-coded, the view auto-scrolls to the latest entry, and a Clear button lets users dismiss accumulated output without affecting the History/Notifications panel. Fixes #1283 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent f18775a commit 93b3940

2 files changed

Lines changed: 105 additions & 9 deletions

File tree

client/src/App.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1643,7 +1643,7 @@ const App = () => {
16431643
setNotifications((prev) => [...prev, notification]);
16441644
}}
16451645
/>
1646-
<ConsoleTab />
1646+
<ConsoleTab serverLogs={notifications} />
16471647
<PingTab
16481648
onPingClick={() => {
16491649
void sendMCPRequest(
Lines changed: 104 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,108 @@
1+
import { ServerNotification } from "@modelcontextprotocol/sdk/types.js";
2+
import { useEffect, useRef, useState } from "react";
13
import { TabsContent } from "@/components/ui/tabs";
4+
import { Button } from "@/components/ui/button";
25

3-
const ConsoleTab = () => (
4-
<TabsContent value="console" className="h-96">
5-
<div className="bg-gray-900 text-gray-100 p-4 rounded-lg h-full font-mono text-sm overflow-auto">
6-
<div className="opacity-50">Welcome to MCP Client Console</div>
7-
{/* Console output would go here */}
8-
</div>
9-
</TabsContent>
10-
);
6+
const LEVEL_COLORS: Record<string, string> = {
7+
debug: "text-gray-400",
8+
info: "text-green-400",
9+
notice: "text-blue-300",
10+
warning: "text-yellow-400",
11+
warn: "text-yellow-400",
12+
error: "text-red-400",
13+
critical: "text-red-500",
14+
alert: "text-orange-400",
15+
emergency: "text-red-600",
16+
};
17+
18+
const ConsoleTab = ({
19+
serverLogs = [],
20+
}: {
21+
serverLogs?: ServerNotification[];
22+
}) => {
23+
const bottomRef = useRef<HTMLDivElement>(null);
24+
const [clearedCount, setClearedCount] = useState(0);
25+
26+
const allLogEntries = serverLogs.filter(
27+
(n) => n.method === "notifications/message",
28+
);
29+
const logEntries = allLogEntries.slice(clearedCount);
30+
31+
useEffect(() => {
32+
bottomRef.current?.scrollIntoView({ behavior: "smooth" });
33+
}, [logEntries.length]);
34+
35+
return (
36+
<TabsContent value="console" className="h-96">
37+
<div className="flex flex-col h-full bg-gray-900 rounded-lg overflow-hidden">
38+
<div className="flex items-center justify-between px-4 py-2 border-b border-gray-700">
39+
<span className="text-xs text-gray-400 font-mono">
40+
Server Logs (stderr / MCP logging)
41+
</span>
42+
<Button
43+
variant="ghost"
44+
size="sm"
45+
onClick={() => setClearedCount(allLogEntries.length)}
46+
disabled={logEntries.length === 0}
47+
className="text-gray-400 hover:text-gray-200 h-6 text-xs"
48+
>
49+
Clear
50+
</Button>
51+
</div>
52+
<div className="flex-1 overflow-auto p-3 font-mono text-sm">
53+
{logEntries.length === 0 ? (
54+
<div className="text-gray-500 opacity-50 text-xs">
55+
No server logs yet. Stderr output and MCP logging notifications
56+
will appear here.
57+
</div>
58+
) : (
59+
<div className="space-y-0.5">
60+
{logEntries.map((notification, index) => {
61+
const params = notification.params as Record<string, unknown>;
62+
const level = String(params?.level ?? "info").toLowerCase();
63+
const logger = String(params?.logger ?? "");
64+
const data = params?.data;
65+
66+
let message: string;
67+
if (typeof data === "string") {
68+
message = data;
69+
} else if (typeof data === "object" && data !== null) {
70+
const dataObj = data as Record<string, unknown>;
71+
message =
72+
typeof dataObj.message === "string"
73+
? dataObj.message
74+
: JSON.stringify(data, null, 2);
75+
} else {
76+
message = String(data ?? "");
77+
}
78+
79+
const colorClass = LEVEL_COLORS[level] ?? "text-gray-100";
80+
81+
return (
82+
<div key={index} className="flex gap-2 leading-5">
83+
<span className="text-gray-500 select-none shrink-0 text-xs">
84+
[{level.toUpperCase().slice(0, 7).padEnd(7)}]
85+
</span>
86+
{logger && logger !== "stdio" && (
87+
<span className="text-gray-600 select-none shrink-0 text-xs">
88+
[{logger}]
89+
</span>
90+
)}
91+
<span
92+
className={`${colorClass} break-all whitespace-pre-wrap`}
93+
>
94+
{message}
95+
</span>
96+
</div>
97+
);
98+
})}
99+
<div ref={bottomRef} />
100+
</div>
101+
)}
102+
</div>
103+
</div>
104+
</TabsContent>
105+
);
106+
};
11107

12108
export default ConsoleTab;

0 commit comments

Comments
 (0)