Skip to main content
Glama

Stata-MCP

config.js16.5 kB
/** * Configuration management JavaScript for Stata-MCP web UI * Provides client-side validation, AJAX interactions, and user feedback */ class ConfigManager { constructor() { this.form = document.getElementById('configForm'); this.stataCliInput = document.getElementById('stata_cli'); this.outputPathInput = document.getElementById('output_base_path'); this.llmTypeSelect = document.getElementById('llm_type'); this.ollamaModelInput = document.getElementById('ollama_model'); this.ollamaUrlInput = document.getElementById('ollama_url'); this.openaiModelInput = document.getElementById('openai_model'); this.openaiUrlInput = document.getElementById('openai_url'); this.openaiKeyInput = document.getElementById('openai_key'); this.importFileInput = document.getElementById('importFile'); this.init(); } init() { this.bindEvents(); this.setupValidation(); } bindEvents() { // Form submission this.form.addEventListener('submit', this.handleSubmit.bind(this)); // Real-time validation this.stataCliInput.addEventListener('blur', () => this.validateField('stata.stata_cli')); this.stataCliInput.addEventListener('input', () => this.clearValidation('stata.stata_cli')); this.outputPathInput.addEventListener('blur', () => this.validateField('stata-mcp.output_base_path')); this.outputPathInput.addEventListener('input', () => this.clearValidation('stata-mcp.output_base_path')); // LLM validation this.llmTypeSelect.addEventListener('change', () => this.validateField('llm.LLM_TYPE')); this.ollamaModelInput.addEventListener('blur', () => this.validateField('llm.ollama.MODEL')); this.ollamaModelInput.addEventListener('input', () => this.clearValidation('llm.ollama.MODEL')); this.ollamaUrlInput.addEventListener('blur', () => this.validateField('llm.ollama.BASE_URL')); this.ollamaUrlInput.addEventListener('input', () => this.clearValidation('llm.ollama.BASE_URL')); this.openaiModelInput.addEventListener('blur', () => this.validateField('llm.openai.MODEL')); this.openaiModelInput.addEventListener('input', () => this.clearValidation('llm.openai.MODEL')); this.openaiUrlInput.addEventListener('blur', () => this.validateField('llm.openai.BASE_URL')); this.openaiUrlInput.addEventListener('input', () => this.clearValidation('llm.openai.BASE_URL')); this.openaiKeyInput.addEventListener('blur', () => this.validateField('llm.openai.API_KEY')); this.openaiKeyInput.addEventListener('input', () => this.clearValidation('llm.openai.API_KEY')); // Help buttons document.querySelectorAll('.help-btn').forEach(btn => { btn.addEventListener('click', this.toggleHelp.bind(this)); }); // Import file this.importFileInput.addEventListener('change', this.handleImport.bind(this)); } setupValidation() { // Add loading states this.addLoadingStyles(); } async validateField(fieldName) { const input = document.getElementById(fieldName.replace(/\./g, '_')); let value = ''; if (input) { value = input.value.trim(); } // Skip validation for hidden LLM sections if (fieldName.startsWith('llm.ollama') && this.llmTypeSelect.value !== 'ollama') { return; } if (fieldName.startsWith('llm.openai') && this.llmTypeSelect.value !== 'openai') { return; } this.showLoading(fieldName); try { const payload = this.buildValidationPayload(); const response = await fetch('/api/validate', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(payload) }); const data = await response.json(); const sections = fieldName.split('.'); let result = data; // Navigate nested structure for (let section of sections) { if (result && result[section]) { result = result[section]; } else { result = null; break; } } if (result && result.valid !== undefined) { if (result.valid) { this.showValidation(fieldName, true, 'Valid'); } else { this.showValidation(fieldName, false, result.error, result.suggestion); } } else { this.showValidation(fieldName, false, 'Validation error'); } } catch (error) { this.showValidation(fieldName, false, 'Validation failed: ' + error.message); } finally { this.hideLoading(fieldName); } } buildValidationPayload() { return { stata: { stata_cli: this.stataCliInput.value.trim() }, 'stata-mcp': { output_base_path: this.outputPathInput.value.trim() }, llm: { LLM_TYPE: this.llmTypeSelect.value, ollama: { MODEL: this.ollamaModelInput.value.trim(), BASE_URL: this.ollamaUrlInput.value.trim() }, openai: { MODEL: this.openaiModelInput.value.trim(), BASE_URL: this.openaiUrlInput.value.trim(), API_KEY: this.openaiKeyInput.value.trim() } } }; } showValidation(fieldName, isValid, message, suggestion = null) { const container = document.getElementById(`${fieldName}_validation`); const input = document.getElementById(fieldName.replace(/\./g, '_')); container.className = `validation-message ${isValid ? 'success' : 'error'}`; let html = message; if (suggestion) { html += ` <br><strong>Suggestion:</strong> ${suggestion}`; } container.innerHTML = html; // Add visual feedback to input if (isValid) { input.style.borderColor = '#4caf50'; input.classList.add('valid'); input.classList.remove('invalid'); } else { input.style.borderColor = '#f44336'; input.classList.add('invalid'); input.classList.remove('valid'); } } clearValidation(fieldName) { const container = document.getElementById(`${fieldName}_validation`); const input = document.getElementById(fieldName.replace(/\./g, '_')); container.innerHTML = ''; container.className = 'validation-message'; if (input) { input.style.borderColor = '#e0e0e0'; input.classList.remove('valid', 'invalid'); } } showLoading(fieldName) { const container = document.getElementById(`${fieldName}_validation`); container.innerHTML = 'Validating...'; container.className = 'validation-message'; } hideLoading(fieldName) { // Loading is replaced by validation result } async handleSubmit(event) { event.preventDefault(); // Validate all fields before submission const validations = [ this.validateField('stata.stata_cli'), this.validateField('stata-mcp.output_base_path'), this.validateField('llm.LLM_TYPE'), this.validateField('llm.ollama.MODEL'), this.validateField('llm.ollama.BASE_URL'), this.validateField('llm.openai.MODEL'), this.validateField('llm.openai.BASE_URL'), this.validateField('llm.openai.API_KEY') ]; await Promise.all(validations); // Check if all validations passed const validationMessages = document.querySelectorAll('.validation-message'); const hasErrors = Array.from(validationMessages).some(v => v.classList.contains('error')); if (hasErrors) { this.showToast('Please fix all validation errors', 'error'); return; } // If no errors, submit the form this.form.submit(); } toggleHelp(event) { const button = event.target; const formGroup = button.closest('.form-group'); const helpDiv = formGroup.querySelector('.field-help'); if (helpDiv) { helpDiv.classList.toggle('active'); } } showStataHelp() { const helpDiv = document.querySelector('#stata_cli').closest('.form-group').querySelector('.field-help'); helpDiv.classList.toggle('active'); } async browseDirectory() { // This would ideally open a directory picker, but for now we'll just focus the input this.outputPathInput.focus(); this.showToast('Please enter the full path to your desired output directory', 'info'); } async exportConfig() { try { const response = await fetch('/api/export'); if (response.ok) { const blob = await response.blob(); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'stata-mcp-config.json'; document.body.appendChild(a); a.click(); document.body.removeChild(a); window.URL.revokeObjectURL(url); this.showToast('Configuration exported successfully', 'success'); } } catch (error) { this.showToast('Failed to export configuration', 'error'); } } async importConfig() { this.importFileInput.click(); } async handleImport(event) { const file = event.target.files[0]; if (!file) return; if (!file.name.endsWith('.json')) { this.showToast('Please select a JSON file', 'error'); return; } try { const formData = new FormData(); formData.append('file', file); const response = await fetch('/api/import', { method: 'POST', body: formData }); const data = await response.json(); if (data.success) { this.showToast('Configuration imported successfully', 'success'); // Reload page to show new configuration setTimeout(() => window.location.reload(), 1000); } else { if (data.errors) { const errorMessages = Object.values(data.errors).join('\n'); this.showToast(`Import failed:\n${errorMessages}`, 'error'); } else { this.showToast(data.error || 'Import failed', 'error'); } } } catch (error) { this.showToast('Failed to import configuration', 'error'); } } async resetConfig() { if (!confirm('Are you sure you want to reset to default configuration? This will create a backup of your current settings.')) { return; } try { const response = await fetch('/api/reset', { method: 'POST', headers: { 'Content-Type': 'application/json', } }); const data = await response.json(); if (data.success) { this.showToast('Configuration reset to defaults', 'success'); setTimeout(() => window.location.reload(), 1000); } else { this.showToast(data.error || 'Failed to reset configuration', 'error'); } } catch (error) { this.showToast('Failed to reset configuration', 'error'); } } showToast(message, type = 'info') { // Remove existing toasts const existingToast = document.querySelector('.toast'); if (existingToast) { existingToast.remove(); } const toast = document.createElement('div'); toast.className = `toast toast-${type}`; toast.innerHTML = ` <span class="toast-icon">${this.getIconForType(type)}</span> <span class="toast-message">${message}</span> <button class="toast-close">×</button> `; document.body.appendChild(toast); // Add styles Object.assign(toast.style, { position: 'fixed', top: '20px', right: '20px', padding: '15px 20px', borderRadius: '8px', color: 'white', fontWeight: '500', zIndex: '10000', display: 'flex', alignItems: 'center', gap: '10px', boxShadow: '0 4px 12px rgba(0,0,0,0.15)', ...this.getStylesForType(type) }); // Auto-close after 5 seconds setTimeout(() => { if (toast.parentNode) { toast.remove(); } }, 5000); // Close button toast.querySelector('.toast-close').addEventListener('click', () => { toast.remove(); }); } getIconForType(type) { const icons = { success: '✅', error: '❌', warning: '⚠️', info: 'ℹ️' }; return icons[type] || icons.info; } getStylesForType(type) { const styles = { success: { backgroundColor: '#28a745' }, error: { backgroundColor: '#dc3545' }, warning: { backgroundColor: '#ffc107', color: '#212529' }, info: { backgroundColor: '#17a2b8' } }; return styles[type] || styles.info; } addLoadingStyles() { const style = document.createElement('style'); style.textContent = ` .input-loading { position: relative; } .input-loading::after { content: ''; position: absolute; right: 10px; top: 50%; transform: translateY(-50%); width: 16px; height: 16px; border: 2px solid #e0e0e0; border-top: 2px solid #007bff; border-radius: 50%; animation: spin 1s linear infinite; } @keyframes spin { 0% { transform: translateY(-50%) rotate(0deg); } 100% { transform: translateY(-50%) rotate(360deg); } } .toast-close { background: none; border: none; color: inherit; font-size: 18px; cursor: pointer; margin-left: 10px; } `; document.head.appendChild(style); } } function toggleLLMSections() { const llmType = document.getElementById('llm_type').value; const ollamaSection = document.getElementById('ollama-section'); const openaiSection = document.getElementById('openai-section'); if (llmType === 'ollama') { ollamaSection.style.display = 'block'; openaiSection.style.display = 'none'; } else { ollamaSection.style.display = 'none'; openaiSection.style.display = 'block'; } } // Initialize when DOM is loaded document.addEventListener('DOMContentLoaded', () => { new ConfigManager(); toggleLLMSections(); // Initialize LLM sections }); // Utility functions function showStataHelp() { const configManager = new ConfigManager(); configManager.showStataHelp(); } function browseDirectory() { const configManager = new ConfigManager(); configManager.browseDirectory(); } function exportConfig() { const configManager = new ConfigManager(); configManager.exportConfig(); } function importConfig() { const configManager = new ConfigManager(); configManager.importConfig(); } function resetConfig() { const configManager = new ConfigManager(); configManager.resetConfig(); }

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/SepineTam/stata-mcp'

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