Skip to content

Commit 91608a0

Browse files
Fix MCP message monitor layout and add collapsible sections
- Made MCP tab sections (Summary, Add Servers, Server List) collapsible to save space - Expanded sidebar width limit from 600px to 80% of window width for better debugging - Fixed message monitor overflow by establishing proper height constraint chain - Removed auto-scroll and show/hide toggle for message monitor - always visible now - Server list now has fixed height (max-h-60) with internal scroll - Message monitor takes remaining space with proper internal scrolling - Used h-full chain instead of flex-1 to establish explicit height constraints - Message monitor now properly scrolls within its container instead of at app level 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 1ab5496 commit 91608a0

File tree

8 files changed

+409
-101
lines changed

8 files changed

+409
-101
lines changed

src/components/ConversationApp.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export function ConversationApp() {
2121
if (!isResizing.current) return;
2222

2323
const newWidth = e.clientX;
24-
if (newWidth >= 240 && newWidth <= 600) {
24+
if (newWidth >= 240 && newWidth <= window.innerWidth * 0.8) {
2525
setSidebarWidth(newWidth);
2626
}
2727
}, []);
@@ -39,7 +39,7 @@ export function ConversationApp() {
3939
<div className="h-screen flex bg-gray-100 dark:bg-gray-900">
4040
{/* Left Sidebar - Tabbed Interface */}
4141
<div
42-
className="bg-white dark:bg-gray-800 border-r border-gray-200 dark:border-gray-700 flex flex-col relative"
42+
className="bg-white dark:bg-gray-800 border-r border-gray-200 dark:border-gray-700 flex flex-col relative h-full"
4343
style={{ width: `${sidebarWidth}px` }}
4444
>
4545
<TabbedSidebar />

src/components/MCPMessageMonitor.tsx

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
// Component to monitor and display MCP messages
2+
3+
import { useState, useEffect, useRef } from 'react';
4+
import { useMCP } from '@/contexts/MCPContext';
5+
import type { JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js';
6+
7+
interface MCPMessageEntry {
8+
id: string;
9+
timestamp: Date;
10+
connectionId: string;
11+
connectionName: string;
12+
direction: 'sent' | 'received';
13+
message: JSONRPCMessage;
14+
extra?: any;
15+
}
16+
17+
export function MCPMessageMonitor() {
18+
const { addMessageCallback, removeMessageCallback, connections } = useMCP();
19+
const [messages, setMessages] = useState<MCPMessageEntry[]>([]);
20+
const [maxMessages, setMaxMessages] = useState(50);
21+
const messagesEndRef = useRef<HTMLDivElement>(null);
22+
23+
// Remove auto-scroll - let user control scroll position
24+
25+
// Register message callback
26+
useEffect(() => {
27+
const callbackId = addMessageCallback((connectionId, _client, message, direction, extra) => {
28+
const connection = connections.find(c => c.id === connectionId);
29+
30+
setMessages(prev => {
31+
const newMessage: MCPMessageEntry = {
32+
id: `${Date.now()}-${Math.random()}`,
33+
timestamp: new Date(),
34+
connectionId,
35+
connectionName: connection?.name || 'Unknown',
36+
direction,
37+
message,
38+
extra,
39+
};
40+
41+
// Keep only the latest maxMessages
42+
const newMessages = [...prev, newMessage];
43+
return newMessages.slice(-maxMessages);
44+
});
45+
});
46+
47+
return () => {
48+
removeMessageCallback(callbackId);
49+
};
50+
}, [addMessageCallback, removeMessageCallback, connections, maxMessages]);
51+
52+
const formatMessage = (msg: JSONRPCMessage) => {
53+
if ('method' in msg) {
54+
return `${msg.method}${'id' in msg ? ` (${msg.id})` : ''}`;
55+
}
56+
if ('id' in msg && 'result' in msg) {
57+
return `Response (${msg.id})`;
58+
}
59+
if ('id' in msg && 'error' in msg) {
60+
return `Error (${msg.id})`;
61+
}
62+
return 'Unknown';
63+
};
64+
65+
const getMessageTypeColor = (msg: JSONRPCMessage, direction: string) => {
66+
const baseClass = direction === 'sent'
67+
? 'bg-blue-50 dark:bg-blue-900/30 border-blue-200 dark:border-blue-800'
68+
: 'bg-green-50 dark:bg-green-900/30 border-green-200 dark:border-green-800';
69+
70+
if ('error' in msg) {
71+
return 'bg-red-50 dark:bg-red-900/30 border-red-200 dark:border-red-800';
72+
}
73+
74+
return baseClass;
75+
};
76+
77+
const clearMessages = () => {
78+
setMessages([]);
79+
};
80+
81+
return (
82+
<div className="flex-1 flex flex-col border-t border-gray-200 dark:border-gray-700 min-h-0">
83+
<div className="flex items-center justify-between p-4 pb-2 flex-shrink-0">
84+
<h3 className="font-medium text-gray-900 dark:text-white">
85+
Message Monitor ({messages.length})
86+
</h3>
87+
<button
88+
onClick={clearMessages}
89+
className="text-xs px-2 py-1 bg-red-100 dark:bg-red-900/50 text-red-800 dark:text-red-200 rounded hover:bg-red-200 dark:hover:bg-red-800"
90+
>
91+
Clear
92+
</button>
93+
</div>
94+
95+
<div className="flex-1 mx-4 mb-2 overflow-y-auto border border-gray-200 dark:border-gray-600 rounded-lg">
96+
{messages.length === 0 ? (
97+
<div className="p-4 text-center text-gray-500 dark:text-gray-400 text-sm">
98+
No messages yet. Messages will appear here as they're sent/received.
99+
</div>
100+
) : (
101+
<div className="p-2 space-y-2">
102+
{messages.map((entry) => (
103+
<div
104+
key={entry.id}
105+
className={`p-2 rounded border text-xs ${getMessageTypeColor(entry.message, entry.direction)}`}
106+
>
107+
<div className="flex items-center justify-between mb-1">
108+
<div className="flex items-center space-x-2">
109+
<span className={entry.direction === 'sent' ? 'text-blue-600 dark:text-blue-400' : 'text-green-600 dark:text-green-400'}>
110+
{entry.direction === 'sent' ? '→' : '←'}
111+
</span>
112+
<span className="font-medium text-gray-900 dark:text-white">
113+
{formatMessage(entry.message)}
114+
</span>
115+
<span className="text-gray-500 dark:text-gray-400">
116+
{entry.connectionName}
117+
</span>
118+
</div>
119+
<span className="text-gray-500 dark:text-gray-400">
120+
{entry.timestamp.toLocaleTimeString()}
121+
</span>
122+
</div>
123+
<details className="text-gray-600 dark:text-gray-300">
124+
<summary className="cursor-pointer hover:text-gray-800 dark:hover:text-gray-100">
125+
Details
126+
</summary>
127+
<pre className="mt-1 text-xs bg-gray-100 dark:bg-gray-800 p-2 rounded overflow-x-auto">
128+
{JSON.stringify(entry.message, null, 2)}
129+
</pre>
130+
</details>
131+
</div>
132+
))}
133+
<div ref={messagesEndRef} />
134+
</div>
135+
)}
136+
</div>
137+
138+
{/* Controls */}
139+
<div className="p-4 pt-2 flex items-center justify-between text-xs border-t border-gray-200 dark:border-gray-700 flex-shrink-0">
140+
<div className="flex items-center space-x-2">
141+
<label className="text-gray-600 dark:text-gray-400">
142+
Max messages:
143+
</label>
144+
<select
145+
value={maxMessages}
146+
onChange={(e) => setMaxMessages(Number(e.target.value))}
147+
className="px-2 py-1 border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
148+
>
149+
<option value={25}>25</option>
150+
<option value={50}>50</option>
151+
<option value={100}>100</option>
152+
<option value={200}>200</option>
153+
</select>
154+
</div>
155+
<div className="text-gray-500 dark:text-gray-400">
156+
Live monitoring active
157+
</div>
158+
</div>
159+
</div>
160+
);
161+
}

src/components/MCPTab.tsx

Lines changed: 88 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,19 @@
22

33
import { useState } from 'react';
44
import { useMCP } from '@/contexts/MCPContext';
5+
import { MCPMessageMonitor } from './MCPMessageMonitor';
56

67
export function MCPTab() {
78
const { connections, addMcpServer, removeMcpServer, reconnectServer, isLoading } = useMCP();
89
const [showAddForm, setShowAddForm] = useState(false);
910
const [newServerName, setNewServerName] = useState('');
1011
const [newServerUrl, setNewServerUrl] = useState('');
1112
const [authType, setAuthType] = useState<'none' | 'oauth'>('none');
13+
14+
// Collapsible section states
15+
const [showSummary, setShowSummary] = useState(true);
16+
const [showAddServers, setShowAddServers] = useState(true);
17+
const [showServerList, setShowServerList] = useState(true);
1218

1319
const handleAddExampleServer = async () => {
1420
try {
@@ -73,50 +79,75 @@ export function MCPTab() {
7379
const allTools = connections.flatMap(conn => conn.tools);
7480

7581
return (
76-
<div className="flex-1 flex flex-col overflow-hidden">
82+
<div className="h-full flex flex-col overflow-hidden">
7783
{/* Summary */}
78-
<div className="p-4 border-b border-gray-200 dark:border-gray-700">
79-
<div className="bg-gray-50 dark:bg-gray-700 rounded-lg p-3">
80-
<h3 className="font-medium text-gray-900 dark:text-white mb-2">
81-
MCP Summary
82-
</h3>
83-
<div className="text-sm space-y-1">
84-
<p className="text-gray-700 dark:text-gray-300">
85-
<span className="font-medium">{connectedServers.length}</span> servers connected
86-
</p>
87-
<p className="text-gray-700 dark:text-gray-300">
88-
<span className="font-medium">{allTools.length}</span> tools available
89-
</p>
84+
<div className="border-b border-gray-200 dark:border-gray-700">
85+
<button
86+
onClick={() => setShowSummary(!showSummary)}
87+
className="w-full p-4 text-left hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors"
88+
>
89+
<div className="flex items-center justify-between">
90+
<h3 className="font-medium text-gray-900 dark:text-white">
91+
MCP Summary
92+
</h3>
93+
<span className="text-gray-500 dark:text-gray-400">
94+
{showSummary ? '−' : '+'}
95+
</span>
9096
</div>
91-
</div>
97+
</button>
98+
{showSummary && (
99+
<div className="px-4 pb-4">
100+
<div className="bg-gray-50 dark:bg-gray-700 rounded-lg p-3">
101+
<div className="text-sm space-y-1">
102+
<p className="text-gray-700 dark:text-gray-300">
103+
<span className="font-medium">{connectedServers.length}</span> servers connected
104+
</p>
105+
<p className="text-gray-700 dark:text-gray-300">
106+
<span className="font-medium">{allTools.length}</span> tools available
107+
</p>
108+
</div>
109+
</div>
110+
</div>
111+
)}
92112
</div>
93113

94114
{/* Add Servers Section */}
95-
<div className="p-4 border-b border-gray-200 dark:border-gray-700">
96-
<h3 className="font-medium text-gray-900 dark:text-white mb-3">
97-
Add Servers
98-
</h3>
99-
100-
{/* Example Server Button */}
115+
<div className="border-b border-gray-200 dark:border-gray-700">
101116
<button
102-
onClick={handleAddExampleServer}
103-
disabled={isLoading || connections.some(conn => conn.name === 'Example Server')}
104-
className="w-full mb-3 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed transition-colors"
117+
onClick={() => setShowAddServers(!showAddServers)}
118+
className="w-full p-4 text-left hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors"
105119
>
106-
{connections.some(conn => conn.name === 'Example Server') ? 'Example Server Added' : 'Add Example Server'}
120+
<div className="flex items-center justify-between">
121+
<h3 className="font-medium text-gray-900 dark:text-white">
122+
Add Servers
123+
</h3>
124+
<span className="text-gray-500 dark:text-gray-400">
125+
{showAddServers ? '−' : '+'}
126+
</span>
127+
</div>
107128
</button>
129+
{showAddServers && (
130+
<div className="px-4 pb-4 space-y-3">
131+
{/* Example Server Button */}
132+
<button
133+
onClick={handleAddExampleServer}
134+
disabled={isLoading || connections.some(conn => conn.name === 'Example Server')}
135+
className="w-full px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed transition-colors"
136+
>
137+
{connections.some(conn => conn.name === 'Example Server') ? 'Example Server Added' : 'Add Example Server'}
138+
</button>
108139

109-
{/* Custom Server Form Toggle */}
110-
<button
111-
onClick={() => setShowAddForm(!showAddForm)}
112-
className="w-full px-4 py-2 text-sm bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors"
113-
>
114-
{showAddForm ? 'Cancel' : 'Add Custom Server'}
115-
</button>
140+
{/* Custom Server Form Toggle */}
141+
<button
142+
onClick={() => setShowAddForm(!showAddForm)}
143+
className="w-full px-4 py-2 text-sm bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors"
144+
>
145+
{showAddForm ? 'Cancel' : 'Add Custom Server'}
146+
</button>
116147

117-
{/* Custom Server Form */}
118-
{showAddForm && (
119-
<form onSubmit={handleAddCustomServer} className="mt-3 space-y-3">
148+
{/* Custom Server Form */}
149+
{showAddForm && (
150+
<form onSubmit={handleAddCustomServer} className="space-y-3">
120151
<div>
121152
<label className="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-1">
122153
Server Name
@@ -168,15 +199,27 @@ export function MCPTab() {
168199
</button>
169200
</form>
170201
)}
202+
</div>
203+
)}
171204
</div>
172205

173206
{/* Server List */}
174-
<div className="flex-1 overflow-y-auto p-4">
175-
<div className="flex items-center justify-between mb-3">
176-
<h3 className="font-medium text-gray-900 dark:text-white">
177-
Servers ({connections.length})
178-
</h3>
179-
</div>
207+
<div className="flex-shrink-0">
208+
<button
209+
onClick={() => setShowServerList(!showServerList)}
210+
className="w-full p-4 text-left hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors border-b border-gray-200 dark:border-gray-700"
211+
>
212+
<div className="flex items-center justify-between">
213+
<h3 className="font-medium text-gray-900 dark:text-white">
214+
Servers ({connections.length})
215+
</h3>
216+
<span className="text-gray-500 dark:text-gray-400">
217+
{showServerList ? '−' : '+'}
218+
</span>
219+
</div>
220+
</button>
221+
{showServerList && (
222+
<div className="max-h-60 overflow-y-auto p-4">
180223

181224
{connections.length === 0 ? (
182225
<p className="text-sm text-gray-500 dark:text-gray-400">
@@ -258,7 +301,12 @@ export function MCPTab() {
258301
))}
259302
</div>
260303
)}
304+
</div>
305+
)}
261306
</div>
307+
308+
{/* Message Monitor */}
309+
<MCPMessageMonitor />
262310
</div>
263311
);
264312
}

src/components/TabbedSidebar.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ export function TabbedSidebar() {
1717
];
1818

1919
return (
20-
<div className="flex-1 flex flex-col">
20+
<div className="h-full flex flex-col">
2121
{/* Tab Headers */}
22-
<div className="border-b border-gray-200 dark:border-gray-700">
22+
<div className="flex-shrink-0 border-b border-gray-200 dark:border-gray-700">
2323
<div className="flex">
2424
{tabs.map((tab) => (
2525
<button
@@ -39,7 +39,7 @@ export function TabbedSidebar() {
3939
</div>
4040

4141
{/* Tab Content */}
42-
<div className="flex-1 overflow-hidden">
42+
<div className="flex-1 overflow-hidden min-h-0">
4343
{activeTab === 'mcp' && <MCPTab />}
4444
{activeTab === 'conversations' && <ConversationSidebar />}
4545
{activeTab === 'inference' && <InferenceTab />}

0 commit comments

Comments
 (0)