Skip to main content
Glama

Bedrock MCP Agent

frontend.html34.1 kB
<!DOCTYPE html> <html lang="es"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Bedrock MCP Agent Conversacional + Glue</title> <script src="https://unpkg.com/react@18/umd/react.development.js"></script> <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script> <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> <script src="https://cdn.tailwindcss.com"></script> <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet"> <style> .chat-container { height: calc(100vh - 200px); } </style> </head> <body class="bg-gray-100"> <div id="root"></div> <script type="text/babel"> const { useState, useEffect, useRef } = React; // Configuración const API_BASE_URL = 'http://localhost:5000'; const FIXED_MODEL = 'Claude 3 Sonnet Conversacional'; function BedrockMCPApp() { const [messages, setMessages] = useState([]); const [currentMessage, setCurrentMessage] = useState(''); const [isLoading, setIsLoading] = useState(false); const [serverStatus, setServerStatus] = useState('checking'); const [settings, setSettings] = useState({ temperature: 0.7, maxTokens: 1000 }); const [showSettings, setShowSettings] = useState(false); const [showGluePanel, setShowGluePanel] = useState(false); const [showConversationPanel, setShowConversationPanel] = useState(false); const [glueStats, setGlueStats] = useState(null); const [hasGlueIntegration, setHasGlueIntegration] = useState(false); const [conversationSummary, setConversationSummary] = useState(null); const [sessionId, setSessionId] = useState(''); const messagesEndRef = useRef(null); // Auto scroll al final const scrollToBottom = () => { messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); }; useEffect(() => { scrollToBottom(); }, [messages]); // Verificar estado del servidor al cargar useEffect(() => { checkServerHealth(); loadGlueStats(); loadConversationSummary(); }, []); // Verificar salud del servidor const checkServerHealth = async () => { try { const response = await fetch(`${API_BASE_URL}/health`, { credentials: 'include' }); if (response.ok) { const data = await response.json(); setServerStatus(data.status === 'healthy' ? 'connected' : 'error'); setHasGlueIntegration(data.glue_mcp_status === 'initialized'); setSessionId(data.session_id || ''); } else { setServerStatus('error'); } } catch (error) { setServerStatus('offline'); console.error('Error verificando servidor:', error); } }; // Cargar estadísticas de Glue const loadGlueStats = async () => { try { const response = await fetch(`${API_BASE_URL}/api/glue/stats`, { credentials: 'include' }); if (response.ok) { const data = await response.json(); if (data.success) { setGlueStats(data.data); } } } catch (error) { console.error('Error cargando estadísticas de Glue:', error); } }; // Cargar resumen de conversación const loadConversationSummary = async () => { try { const response = await fetch(`${API_BASE_URL}/api/conversation/summary`, { credentials: 'include' }); if (response.ok) { const data = await response.json(); if (data.success) { setConversationSummary(data.summary); } } } catch (error) { console.error('Error cargando resumen de conversación:', error); } }; // Enviar mensaje const sendMessage = async () => { if (!currentMessage.trim() || isLoading || serverStatus !== 'connected') { return; } const userMessage = { id: Date.now(), type: 'user', content: currentMessage.trim(), timestamp: new Date().toLocaleTimeString() }; setMessages(prev => [...prev, userMessage]); const messageToSend = currentMessage.trim(); setCurrentMessage(''); setIsLoading(true); try { const response = await fetch(`${API_BASE_URL}/api/chat`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, credentials: 'include', body: JSON.stringify({ prompt: messageToSend, temperature: settings.temperature, max_tokens: settings.maxTokens }) }); const data = await response.json(); if (data.success) { const botMessage = { id: Date.now() + 1, type: 'assistant', content: data.response, timestamp: new Date().toLocaleTimeString(), metadata: data.metadata }; setMessages(prev => [...prev, botMessage]); // Actualizar resumen de conversación loadConversationSummary(); } else { throw new Error(data.error || 'Error desconocido del servidor'); } } catch (error) { const errorMessage = { id: Date.now() + 1, type: 'error', content: `❌ Error: ${error.message}`, timestamp: new Date().toLocaleTimeString() }; setMessages(prev => [...prev, errorMessage]); } finally { setIsLoading(false); } }; // Limpiar conversación const clearConversation = async () => { try { const response = await fetch(`${API_BASE_URL}/api/conversation/clear`, { method: 'POST', credentials: 'include' }); if (response.ok) { setMessages([]); setConversationSummary(null); loadConversationSummary(); } } catch (error) { console.error('Error limpiando conversación:', error); } }; // Enviar consulta predefinida const sendQuery = (query) => { setCurrentMessage(query); setTimeout(() => { sendMessage(); }, 100); }; // Manejar Enter const handleKeyPress = (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); } }; // Componente de estado del servidor const ServerStatus = () => { const statusConfig = { connected: { color: 'text-green-600', icon: 'fas fa-circle', text: 'Conectado', bg: 'bg-green-100' }, error: { color: 'text-yellow-600', icon: 'fas fa-exclamation-triangle', text: 'Error AWS', bg: 'bg-yellow-100' }, offline: { color: 'text-red-600', icon: 'fas fa-times-circle', text: 'Servidor Off', bg: 'bg-red-100' }, checking: { color: 'text-gray-600', icon: 'fas fa-spinner fa-spin', text: 'Verificando...', bg: 'bg-gray-100' } }; const config = statusConfig[serverStatus] || statusConfig.checking; return ( <div className={`flex items-center space-x-2 px-3 py-1 rounded-full ${config.bg}`}> <i className={`${config.icon} text-sm ${config.color}`}></i> <span className={`text-sm font-medium ${config.color}`}>{config.text}</span> {hasGlueIntegration && ( <span className="text-xs bg-purple-500 text-white px-2 py-1 rounded-full ml-2"> +Glue </span> )} <span className="text-xs bg-blue-500 text-white px-2 py-1 rounded-full ml-1"> 💬 </span> </div> ); }; // Panel de conversación const ConversationPanel = () => { if (!showConversationPanel) return null; return ( <div className="lg:col-span-1"> <div className="bg-white rounded-xl shadow-lg p-6"> <h3 className="text-lg font-semibold text-gray-900 mb-4 flex items-center"> <i className="fas fa-comments mr-2 text-blue-600"></i> Conversación </h3> {conversationSummary && ( <div className="mb-4 p-3 bg-blue-50 rounded-lg"> <div className="text-sm text-blue-800 space-y-1"> <div>💬 {conversationSummary.total_messages} mensajes</div> <div>🆔 {sessionId}</div> </div> </div> )} <div className="space-y-2"> <button onClick={clearConversation} className="w-full text-left px-3 py-2 text-sm bg-red-50 hover:bg-red-100 rounded-lg transition-colors text-red-700" > 🧹 Limpiar conversación </button> </div> <div className="mt-4 p-3 bg-green-50 rounded-lg"> <div className="text-xs text-green-800"> 💡 <strong>Memoria conversacional:</strong> El asistente recuerda toda nuestra conversación. </div> </div> </div> </div> ); }; // Panel de Glue Catalog const GluePanel = () => { if (!hasGlueIntegration || !showGluePanel) return null; return ( <div className="lg:col-span-1"> <div className="bg-white rounded-xl shadow-lg p-6"> <h3 className="text-lg font-semibold text-gray-900 mb-4 flex items-center"> <i className="fas fa-database mr-2 text-purple-600"></i> Glue Catalog </h3> {glueStats && ( <div className="mb-4 p-3 bg-purple-50 rounded-lg"> <div className="text-sm text-purple-800"> <div>📊 {glueStats.catalog_stats?.total_databases || 0} bases de datos</div> <div>📋 {glueStats.catalog_stats?.total_tables || 0} tablas</div> <div>🌍 {glueStats.region}</div> </div> </div> )} <div className="space-y-2"> <h4 className="text-sm font-medium text-gray-700">Consultas rápidas:</h4> <button onClick={() => sendQuery('Lista todas las bases de datos y dame un resumen de lo que contienen')} className="w-full text-left px-3 py-2 text-sm bg-purple-50 hover:bg-purple-100 rounded-lg transition-colors" > 📋 Explorar bases de datos </button> <button onClick={() => sendQuery('Analiza las estadísticas del catálogo y explícame qué tipo de datos tengo disponibles')} className="w-full text-left px-3 py-2 text-sm bg-purple-50 hover:bg-purple-100 rounded-lg transition-colors" > 📊 Análisis del catálogo </button> </div> </div> </div> ); }; return ( <div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100"> {/* Header */} <div className="bg-white shadow-lg border-b border-gray-200"> <div className="max-w-7xl mx-auto px-4 py-4"> <div className="flex items-center justify-between"> <div className="flex items-center space-x-4"> <div className="bg-gradient-to-r from-blue-600 to-indigo-600 p-3 rounded-xl"> <i className="fas fa-brain text-white text-xl"></i> </div> <div> <h1 className="text-2xl font-bold text-gray-900">Bedrock MCP Agent</h1> <p className="text-sm text-gray-600"> {FIXED_MODEL} {hasGlueIntegration && ( <span className="ml-2 text-purple-600">+ AWS Glue</span> )} </p> </div> </div> <div className="flex items-center space-x-2"> <ServerStatus /> <button onClick={() => setShowConversationPanel(!showConversationPanel)} className={`p-2 rounded-lg transition-all ${ showConversationPanel ? 'text-blue-700 bg-blue-100' : 'text-gray-500 hover:text-blue-700 hover:bg-blue-50' }`} > <i className="fas fa-comments"></i> </button> {hasGlueIntegration && ( <button onClick={() => setShowGluePanel(!showGluePanel)} className={`p-2 rounded-lg transition-all ${ showGluePanel ? 'text-purple-700 bg-purple-100' : 'text-gray-500 hover:text-purple-700 hover:bg-purple-50' }`} > <i className="fas fa-database"></i> </button> )} <button onClick={() => setShowSettings(!showSettings)} className="p-2 text-gray-500 hover:text-gray-700 hover:bg-gray-100 rounded-lg transition-all" > <i className="fas fa-cog"></i> </button> <button onClick={clearConversation} className="p-2 text-gray-500 hover:text-red-700 hover:bg-red-50 rounded-lg transition-all" > <i className="fas fa-broom"></i> </button> </div> </div> </div> </div> <div className="max-w-7xl mx-auto px-4 py-6"> <div className="grid grid-cols-1 lg:grid-cols-6 gap-6"> {/* Paneles laterales */} {showSettings && ( <div className="lg:col-span-1"> <div className="bg-white rounded-xl shadow-lg p-6"> <h3 className="text-lg font-semibold text-gray-900 mb-4"> <i className="fas fa-sliders-h mr-2 text-blue-600"></i> Configuración </h3> <div className="space-y-4"> <div> <label className="block text-sm font-medium text-gray-700 mb-2"> Temperatura: {settings.temperature} </label> <input type="range" min="0" max="1" step="0.1" value={settings.temperature} onChange={(e) => setSettings(prev => ({ ...prev, temperature: parseFloat(e.target.value) }))} className="w-full accent-blue-600" /> </div> <div> <label className="block text-sm font-medium text-gray-700 mb-2"> Tokens Máximos </label> <input type="number" min="50" max="4000" step="50" value={settings.maxTokens} onChange={(e) => setSettings(prev => ({ ...prev, maxTokens: parseInt(e.target.value) || 1000 }))} className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500" /> </div> </div> </div> </div> )} <ConversationPanel /> <GluePanel /> {/* Chat principal */} <div className={`${ [showSettings, showConversationPanel, showGluePanel].filter(Boolean).length === 0 ? "lg:col-span-6" : [showSettings, showConversationPanel, showGluePanel].filter(Boolean).length === 1 ? "lg:col-span-5" : [showSettings, showConversationPanel, showGluePanel].filter(Boolean).length === 2 ? "lg:col-span-4" : "lg:col-span-3" }`}> <div className="bg-white rounded-xl shadow-lg chat-container flex flex-col"> {/* Área de mensajes */} <div className="flex-1 overflow-y-auto p-6"> {messages.length === 0 ? ( <div className="flex items-center justify-center h-full"> <div className="text-center max-w-lg"> <div className="bg-gradient-to-r from-blue-600 to-indigo-600 w-20 h-20 rounded-full flex items-center justify-center mx-auto mb-6"> <i className="fas fa-comments text-white text-3xl"></i> </div> <h3 className="text-2xl font-bold text-gray-900 mb-3"> ¡Conversación inteligente! </h3> <p className="text-gray-600 mb-4"> {FIXED_MODEL} con memoria conversacional {hasGlueIntegration && ( <span className="block text-purple-600 font-medium mt-1"> + AWS Glue Catalog </span> )} </p> <div className="bg-gradient-to-r from-blue-50 to-purple-50 border border-blue-200 rounded-lg p-4 mb-4"> <ul className="text-sm text-blue-700 text-left space-y-1"> <li>• 🧠 Memoria completa de conversación</li> <li>• 🔗 Referencias a mensajes anteriores</li> <li>• 📊 Análisis con datos reales</li> <li>• 💡 Recomendaciones personalizadas</li> </ul> </div> {serverStatus === 'offline' && ( <div className="bg-red-50 border border-red-200 rounded-lg p-4 text-left"> <p className="text-red-700 text-sm mb-2"> Ejecuta: <code className="bg-gray-800 text-green-400 px-2 py-1 rounded">python app.py</code> </p> </div> )} </div> </div> ) : ( <div className="space-y-4"> {messages.map((message) => ( <div key={message.id} className={`flex ${message.type === 'user' ? 'justify-end' : 'justify-start'}`} > <div className={`max-w-4xl px-4 py-3 rounded-2xl ${ message.type === 'user' ? 'bg-blue-600 text-white' : message.type === 'error' ? 'bg-red-50 text-red-800 border border-red-200' : 'bg-gray-50 text-gray-900 border border-gray-200' }`} > <div className="whitespace-pre-wrap"> {message.content} </div> {message.metadata && ( <div className="flex items-center space-x-3 mt-2 pt-2 border-t border-gray-200 text-xs opacity-75"> <span>⏱️ {message.metadata.processing_time_ms}ms</span> {message.metadata.has_context && ( <span className="text-blue-600">🧠 Contexto</span> )} {message.metadata.glue_enhanced && ( <span className="text-purple-600">🗄️ Glue</span> )} {message.metadata.conversation_length > 0 && ( <span className="text-green-600">💬 {message.metadata.conversation_length}</span> )} </div> )} <div className="text-xs opacity-75 mt-2"> {message.timestamp} </div> </div> </div> ))} {isLoading && ( <div className="flex justify-start"> <div className="bg-gray-50 text-gray-700 px-4 py-3 rounded-2xl border border-gray-200"> <div className="flex items-center space-x-2"> <div className="animate-spin rounded-full h-4 w-4 border-2 border-blue-600 border-t-transparent"></div> <span>Generando respuesta...</span> </div> </div> </div> )} </div> )} <div ref={messagesEndRef} /> </div> {/* Input de mensaje */} <div className="border-t border-gray-200 p-4"> <div className="flex space-x-3"> <div className="flex-1"> <textarea value={currentMessage} onChange={(e) => setCurrentMessage(e.target.value)} onKeyPress={handleKeyPress} placeholder={ serverStatus === 'connected' ? "Chatea con memoria conversacional... (Enter para enviar)" : "Servidor desconectado..." } className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent resize-none" rows="2" disabled={isLoading || serverStatus !== 'connected'} /> </div> <button onClick={sendMessage} disabled={isLoading || !currentMessage.trim() || serverStatus !== 'connected'} className="px-6 py-3 bg-blue-600 text-white rounded-xl hover:bg-blue-700 focus:ring-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed transition-all flex items-center" > {isLoading ? ( <i className="fas fa-spinner animate-spin"></i> ) : ( <i className="fas fa-paper-plane"></i> )} </button> </div> </div> </div> </div> </div> </div> </div> ); } // Renderizar la aplicación ReactDOM.render(<BedrockMCPApp />, document.getElementById('root')); </script> </body> </html>

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/harold-moncaleano/bedrock-mcp-agent'

If you have feedback or need assistance with the MCP directory API, please join our Discord server