diff --git a/src/components/ConversationApp.tsx b/src/components/ConversationApp.tsx index bcdd268..c1faafc 100644 --- a/src/components/ConversationApp.tsx +++ b/src/components/ConversationApp.tsx @@ -21,7 +21,7 @@ export function ConversationApp() { if (!isResizing.current) return; const newWidth = e.clientX; - if (newWidth >= 240 && newWidth <= 600) { + if (newWidth >= 240 && newWidth <= window.innerWidth * 0.8) { setSidebarWidth(newWidth); } }, []); @@ -39,7 +39,7 @@ export function ConversationApp() {
{/* Left Sidebar - Tabbed Interface */}
diff --git a/src/components/MCPMessageMonitor.tsx b/src/components/MCPMessageMonitor.tsx new file mode 100644 index 0000000..8aefdc3 --- /dev/null +++ b/src/components/MCPMessageMonitor.tsx @@ -0,0 +1,161 @@ +// Component to monitor and display MCP messages + +import { useState, useEffect, useRef } from 'react'; +import { useMCP } from '@/contexts/MCPContext'; +import type { JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js'; + +interface MCPMessageEntry { + id: string; + timestamp: Date; + connectionId: string; + connectionName: string; + direction: 'sent' | 'received'; + message: JSONRPCMessage; + extra?: any; +} + +export function MCPMessageMonitor() { + const { addMessageCallback, removeMessageCallback, connections } = useMCP(); + const [messages, setMessages] = useState([]); + const [maxMessages, setMaxMessages] = useState(50); + const messagesEndRef = useRef(null); + + // Remove auto-scroll - let user control scroll position + + // Register message callback + useEffect(() => { + const callbackId = addMessageCallback((connectionId, _client, message, direction, extra) => { + const connection = connections.find(c => c.id === connectionId); + + setMessages(prev => { + const newMessage: MCPMessageEntry = { + id: `${Date.now()}-${Math.random()}`, + timestamp: new Date(), + connectionId, + connectionName: connection?.name || 'Unknown', + direction, + message, + extra, + }; + + // Keep only the latest maxMessages + const newMessages = [...prev, newMessage]; + return newMessages.slice(-maxMessages); + }); + }); + + return () => { + removeMessageCallback(callbackId); + }; + }, [addMessageCallback, removeMessageCallback, connections, maxMessages]); + + const formatMessage = (msg: JSONRPCMessage) => { + if ('method' in msg) { + return `${msg.method}${'id' in msg ? ` (${msg.id})` : ''}`; + } + if ('id' in msg && 'result' in msg) { + return `Response (${msg.id})`; + } + if ('id' in msg && 'error' in msg) { + return `Error (${msg.id})`; + } + return 'Unknown'; + }; + + const getMessageTypeColor = (msg: JSONRPCMessage, direction: string) => { + const baseClass = direction === 'sent' + ? 'bg-blue-50 dark:bg-blue-900/30 border-blue-200 dark:border-blue-800' + : 'bg-green-50 dark:bg-green-900/30 border-green-200 dark:border-green-800'; + + if ('error' in msg) { + return 'bg-red-50 dark:bg-red-900/30 border-red-200 dark:border-red-800'; + } + + return baseClass; + }; + + const clearMessages = () => { + setMessages([]); + }; + + return ( +
+
+

+ Message Monitor ({messages.length}) +

+ +
+ +
+ {messages.length === 0 ? ( +
+ No messages yet. Messages will appear here as they're sent/received. +
+ ) : ( +
+ {messages.map((entry) => ( +
+
+
+ + {entry.direction === 'sent' ? '→' : '←'} + + + {formatMessage(entry.message)} + + + {entry.connectionName} + +
+ + {entry.timestamp.toLocaleTimeString()} + +
+
+ + Details + +
+                    {JSON.stringify(entry.message, null, 2)}
+                  
+
+
+ ))} +
+
+ )} +
+ + {/* Controls */} +
+
+ + +
+
+ Live monitoring active +
+
+
+ ); +} \ No newline at end of file diff --git a/src/components/MCPTab.tsx b/src/components/MCPTab.tsx index febfc2a..c77f497 100644 --- a/src/components/MCPTab.tsx +++ b/src/components/MCPTab.tsx @@ -2,6 +2,7 @@ import { useState } from 'react'; import { useMCP } from '@/contexts/MCPContext'; +import { MCPMessageMonitor } from './MCPMessageMonitor'; export function MCPTab() { const { connections, addMcpServer, removeMcpServer, reconnectServer, isLoading } = useMCP(); @@ -9,6 +10,11 @@ export function MCPTab() { const [newServerName, setNewServerName] = useState(''); const [newServerUrl, setNewServerUrl] = useState(''); const [authType, setAuthType] = useState<'none' | 'oauth'>('none'); + + // Collapsible section states + const [showSummary, setShowSummary] = useState(true); + const [showAddServers, setShowAddServers] = useState(true); + const [showServerList, setShowServerList] = useState(true); const handleAddExampleServer = async () => { try { @@ -73,50 +79,75 @@ export function MCPTab() { const allTools = connections.flatMap(conn => conn.tools); return ( -
+
{/* Summary */} -
-
-

- MCP Summary -

-
-

- {connectedServers.length} servers connected -

-

- {allTools.length} tools available -

+
+
+ + {showSummary && ( +
+
+
+

+ {connectedServers.length} servers connected +

+

+ {allTools.length} tools available +

+
+
+
+ )}
{/* Add Servers Section */} -
-

- Add Servers -

- - {/* Example Server Button */} +
+ {showAddServers && ( +
+ {/* Example Server Button */} + - {/* Custom Server Form Toggle */} - + {/* Custom Server Form Toggle */} + - {/* Custom Server Form */} - {showAddForm && ( -
+ {/* Custom Server Form */} + {showAddForm && ( +
+ )}
{/* Server List */} -
-
-

- Servers ({connections.length}) -

-
+
+ + {showServerList && ( +
{connections.length === 0 ? (

@@ -258,7 +301,12 @@ export function MCPTab() { ))}

)} +
+ )}
+ + {/* Message Monitor */} +
); } \ No newline at end of file diff --git a/src/components/TabbedSidebar.tsx b/src/components/TabbedSidebar.tsx index a827a5f..89565ae 100644 --- a/src/components/TabbedSidebar.tsx +++ b/src/components/TabbedSidebar.tsx @@ -17,9 +17,9 @@ export function TabbedSidebar() { ]; return ( -
+
{/* Tab Headers */} -
+
{tabs.map((tab) => (