Skip to main content
Glama

MySQL MCP Server

by QiPanTanYi
app.js14.9 kB
const API_URL = 'http://localhost:3000'; const messagesContainer = document.getElementById('messages'); const messageInput = document.getElementById('messageInput'); const sendButton = document.getElementById('sendButton'); const historyButton = document.getElementById('historyButton'); const newChatButton = document.getElementById('newChatButton'); const historySidebar = document.getElementById('historySidebar'); const closeHistoryButton = document.getElementById('closeHistoryButton'); const historyList = document.getElementById('historyList'); const overlay = document.getElementById('overlay'); let isProcessing = false; let currentToolExecutionContainer = null; let currentConversationId = null; // 聊天历史管理 class ChatHistoryManager { constructor() { this.storageKey = 'mysql_mcp_chat_history'; this.currentChatKey = 'mysql_mcp_current_chat'; } // 获取所有聊天历史 getAllChats() { const data = localStorage.getItem(this.storageKey); return data ? JSON.parse(data) : []; } // 保存聊天历史 saveAllChats(chats) { localStorage.setItem(this.storageKey, JSON.stringify(chats)); } // 获取当前聊天 ID getCurrentChatId() { return localStorage.getItem(this.currentChatKey); } // 设置当前聊天 ID setCurrentChatId(id) { localStorage.setItem(this.currentChatKey, id); } // 创建新聊天 createNewChat() { const chats = this.getAllChats(); const now = new Date(); const title = now.toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', }); const newChat = { id: Date.now().toString(), title: title, messages: [], createdAt: now.toISOString(), updatedAt: now.toISOString(), }; chats.unshift(newChat); this.saveAllChats(chats); this.setCurrentChatId(newChat.id); return newChat; } // 获取聊天 getChat(id) { const chats = this.getAllChats(); return chats.find(chat => chat.id === id); } // 更新聊天 updateChat(id, updates) { const chats = this.getAllChats(); const index = chats.findIndex(chat => chat.id === id); if (index !== -1) { chats[index] = { ...chats[index], ...updates, updatedAt: new Date().toISOString(), }; this.saveAllChats(chats); } } // 删除聊天 deleteChat(id) { const chats = this.getAllChats(); const filtered = chats.filter(chat => chat.id !== id); this.saveAllChats(filtered); // 如果删除的是当前聊天,切换到第一个 if (this.getCurrentChatId() === id) { if (filtered.length > 0) { this.setCurrentChatId(filtered[0].id); } else { localStorage.removeItem(this.currentChatKey); } } } // 添加消息到当前聊天 addMessage(chatId, message) { const chat = this.getChat(chatId); if (chat) { chat.messages.push(message); // 自动生成标题(使用第一条用户消息) if (message.type === 'user' && chat.messages.filter(m => m.type === 'user').length === 1) { // 第一条用户消息,更新标题 chat.title = message.content.substring(0, 30) + (message.content.length > 30 ? '...' : ''); } this.updateChat(chatId, { messages: chat.messages, title: chat.title }); } } } const historyManager = new ChatHistoryManager(); // 添加消息到界面 function addMessage(role, content) { const messageDiv = document.createElement('div'); messageDiv.className = `message ${role}`; const contentDiv = document.createElement('div'); contentDiv.className = 'message-content'; contentDiv.textContent = content; messageDiv.appendChild(contentDiv); messagesContainer.appendChild(messageDiv); scrollToBottom(); return messageDiv; } // 创建工具执行容器 function createToolExecutionContainer() { const container = document.createElement('div'); container.className = 'tool-execution-container'; const header = document.createElement('div'); header.className = 'tool-execution-header'; header.innerHTML = ` <span class="title">🔧 工具执行过程</span> <span class="toggle">点击展开 ▼</span> `; const content = document.createElement('div'); content.className = 'tool-execution-content'; header.addEventListener('click', () => { const isExpanded = content.classList.contains('expanded'); if (isExpanded) { content.classList.remove('expanded'); header.querySelector('.toggle').textContent = '点击展开 ▼'; } else { content.classList.add('expanded'); header.querySelector('.toggle').textContent = '点击收起 ▲'; } }); container.appendChild(header); container.appendChild(content); messagesContainer.appendChild(container); scrollToBottom(); return content; } // 添加工具调用信息 function addToolCall(toolName, args) { if (!currentToolExecutionContainer) { currentToolExecutionContainer = createToolExecutionContainer(); } const toolDiv = document.createElement('div'); toolDiv.className = 'tool-call'; toolDiv.innerHTML = ` <div class="tool-name">🔧 调用工具: ${toolName}</div> <pre>${JSON.stringify(args, null, 2)}</pre> `; currentToolExecutionContainer.appendChild(toolDiv); scrollToBottom(); } // 添加工具结果 function addToolResult(toolName, result) { if (!currentToolExecutionContainer) { currentToolExecutionContainer = createToolExecutionContainer(); } const resultDiv = document.createElement('div'); resultDiv.className = 'tool-result'; let resultText = result; try { const parsed = JSON.parse(result); resultText = JSON.stringify(parsed, null, 2); } catch (e) { // 保持原样 } resultDiv.innerHTML = ` <div class="tool-name">✅ ${toolName} 执行结果:</div> <pre>${resultText}</pre> `; currentToolExecutionContainer.appendChild(resultDiv); scrollToBottom(); } // 添加工具错误 function addToolError(toolName, error) { if (!currentToolExecutionContainer) { currentToolExecutionContainer = createToolExecutionContainer(); } const errorDiv = document.createElement('div'); errorDiv.className = 'tool-error'; errorDiv.innerHTML = ` <div class="tool-name">❌ ${toolName} 执行失败:</div> <div>${error}</div> `; currentToolExecutionContainer.appendChild(errorDiv); scrollToBottom(); } // 添加加载状态 function addLoadingMessage() { const loadingDiv = document.createElement('div'); loadingDiv.className = 'message assistant'; loadingDiv.id = 'loading-message'; loadingDiv.innerHTML = ` <div class="message-content"> <span class="loading"></span> <span class="loading" style="animation-delay: 0.2s;"></span> <span class="loading" style="animation-delay: 0.4s;"></span> </div> `; messagesContainer.appendChild(loadingDiv); scrollToBottom(); return loadingDiv; } // 移除加载状态 function removeLoadingMessage() { const loadingMsg = document.getElementById('loading-message'); if (loadingMsg) { loadingMsg.remove(); } } // 滚动到底部 function scrollToBottom() { messagesContainer.scrollTop = messagesContainer.scrollHeight; } // 初始化或加载聊天 function initializeChat() { let chatId = historyManager.getCurrentChatId(); let chat = chatId ? historyManager.getChat(chatId) : null; if (!chat) { chat = historyManager.createNewChat(); chatId = chat.id; } currentConversationId = chatId; // 加载聊天消息 messagesContainer.innerHTML = ''; if (chat.messages.length === 0) { // 显示欢迎消息 addMessage('system', `欢迎使用 MySQL MCP 聊天助手!你可以用自然语言查询、插入、更新或删除数据库中的数据。 示例: • "查询所有用户" • "插入一个新用户,用户名是 test,邮箱是 test@example.com,年龄是 25" • "更新 id 为 1 的用户年龄为 30"`); } else { // 恢复历史消息 chat.messages.forEach(msg => { if (msg.type === 'user' || msg.type === 'assistant' || msg.type === 'system') { const messageDiv = document.createElement('div'); messageDiv.className = `message ${msg.type}`; const contentDiv = document.createElement('div'); contentDiv.className = 'message-content'; contentDiv.textContent = msg.content; messageDiv.appendChild(contentDiv); messagesContainer.appendChild(messageDiv); } }); scrollToBottom(); } updateHistoryUI(); } // 发送消息 async function sendMessage() { const message = messageInput.value.trim(); if (!message || isProcessing) return; // 添加用户消息 addMessage('user', message); // 保存到历史 historyManager.addMessage(currentConversationId, { type: 'user', content: message, timestamp: new Date().toISOString(), }); messageInput.value = ''; isProcessing = true; sendButton.disabled = true; sendButton.textContent = '处理中...'; currentToolExecutionContainer = null; // 重置工具执行容器 const loadingMsg = addLoadingMessage(); try { const response = await fetch(`${API_URL}/chat/message`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ message }), }); const reader = response.body.getReader(); const decoder = new TextDecoder(); while (true) { const { done, value } = await reader.read(); if (done) break; const chunk = decoder.decode(value); const lines = chunk.split('\n'); for (const line of lines) { if (line.startsWith('data: ')) { const data = line.slice(6); if (data === '[DONE]') { removeLoadingMessage(); continue; } try { const event = JSON.parse(data); switch (event.type) { case 'thinking': // 保持加载状态 break; case 'tool_calls': removeLoadingMessage(); addMessage('system', '需要调用以下工具:'); break; case 'tool_executing': addToolCall(event.tool, event.arguments); break; case 'tool_result': addToolResult(event.tool, event.result); break; case 'tool_error': addToolError(event.tool, event.error); break; case 'generating': removeLoadingMessage(); addLoadingMessage(); break; case 'message': removeLoadingMessage(); addMessage('assistant', event.content); // 保存到历史 historyManager.addMessage(currentConversationId, { type: 'assistant', content: event.content, timestamp: new Date().toISOString(), }); currentToolExecutionContainer = null; // 完成后重置 updateHistoryUI(); // 更新历史列表 break; case 'error': removeLoadingMessage(); addMessage('system', `错误: ${event.message || '未知错误'}`); break; } } catch (e) { console.error('解析事件失败:', e, data); } } } } } catch (error) { removeLoadingMessage(); addMessage('system', `连接失败: ${error.message}`); } finally { isProcessing = false; sendButton.disabled = false; sendButton.textContent = '发送'; messageInput.focus(); } } // 事件监听 sendButton.addEventListener('click', sendMessage); messageInput.addEventListener('keypress', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); } }); // 更新历史记录 UI function updateHistoryUI() { const chats = historyManager.getAllChats(); const currentId = historyManager.getCurrentChatId(); if (chats.length === 0) { historyList.innerHTML = '<div class="history-empty">暂无聊天记录</div>'; return; } historyList.innerHTML = ''; chats.forEach(chat => { const item = document.createElement('div'); item.className = 'history-item' + (chat.id === currentId ? ' active' : ''); const preview = chat.messages.length > 0 ? chat.messages[chat.messages.length - 1].content : '空对话'; const time = new Date(chat.updatedAt).toLocaleString('zh-CN', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', }); item.innerHTML = ` <div class="history-item-title">${chat.title}</div> <div class="history-item-preview">${preview}</div> <div class="history-item-time">${time}</div> <button class="history-item-delete" onclick="deleteChat('${chat.id}', event)">删除</button> `; item.addEventListener('click', () => switchChat(chat.id)); historyList.appendChild(item); }); } // 切换聊天 function switchChat(chatId) { if (chatId === currentConversationId) { closeHistory(); return; } historyManager.setCurrentChatId(chatId); currentConversationId = chatId; // 重新加载聊天 initializeChat(); closeHistory(); } // 删除聊天 function deleteChat(chatId, event) { event.stopPropagation(); if (confirm('确定要删除这个对话吗?')) { historyManager.deleteChat(chatId); // 如果删除的是当前聊天,重新初始化 if (chatId === currentConversationId) { initializeChat(); } else { updateHistoryUI(); } } } // 新建聊天 function createNewChat() { const newChat = historyManager.createNewChat(); currentConversationId = newChat.id; initializeChat(); closeHistory(); } // 打开历史记录 function openHistory() { updateHistoryUI(); historySidebar.classList.add('open'); overlay.classList.add('show'); } // 关闭历史记录 function closeHistory() { historySidebar.classList.remove('open'); overlay.classList.remove('show'); } // 事件监听 historyButton.addEventListener('click', openHistory); newChatButton.addEventListener('click', createNewChat); closeHistoryButton.addEventListener('click', closeHistory); overlay.addEventListener('click', closeHistory); // 页面加载完成后初始化 window.addEventListener('load', () => { initializeChat(); messageInput.focus(); });

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/QiPanTanYi/mysql-mcp'

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