Skip to main content
Glama

ACE MCP Server

app.jsโ€ข33.9 kB
// ACE MCP Server Implementation // Agentic Context Engineering Framework class ACEServer { constructor() { // In-memory storage (session only - no localStorage/sessionStorage) this.contexts = new Map(); this.contexts.set('default', { id: 'default', name: 'Default Context', bullets: [], created_at: Date.now() }); this.currentContextId = 'default'; this.operationLog = []; this.sessionStart = Date.now(); // Settings this.settings = { dedupThreshold: 0.85, maxBullets: 1000, maxIterations: 5, debugMode: false, autoDedup: true }; this.init(); } init() { this.setupEventListeners(); this.updateUI(); this.log('ACE MCP Server initialized successfully', 'info'); this.updateSessionInfo(); } setupEventListeners() { // Tab navigation document.querySelectorAll('.tab-btn').forEach(btn => { btn.addEventListener('click', (e) => this.switchTab(e.target.dataset.tab)); }); // Context management document.getElementById('context-select').addEventListener('change', (e) => { this.switchContext(e.target.value); }); document.getElementById('new-context-btn').addEventListener('click', () => this.showNewContextModal()); document.getElementById('create-context-btn').addEventListener('click', () => this.createNewContext()); document.getElementById('cancel-context-btn').addEventListener('click', () => this.hideNewContextModal()); document.getElementById('close-modal-btn').addEventListener('click', () => this.hideNewContextModal()); // Quick actions document.getElementById('demo-workflow-btn').addEventListener('click', () => this.runDemoWorkflow()); document.getElementById('export-playbook-btn').addEventListener('click', () => this.exportPlaybook()); document.getElementById('import-playbook-btn').addEventListener('click', () => this.importPlaybook()); document.getElementById('reset-context-btn').addEventListener('click', () => this.resetContext()); // MCP Tools document.getElementById('run-generate-btn').addEventListener('click', () => this.runGenerate()); document.getElementById('run-reflect-btn').addEventListener('click', () => this.runReflect()); document.getElementById('run-curate-btn').addEventListener('click', () => this.runCurate()); document.getElementById('run-update-btn').addEventListener('click', () => this.runUpdate()); document.getElementById('run-get-btn').addEventListener('click', () => this.runGet()); document.getElementById('run-reset-btn').addEventListener('click', () => this.runReset()); // Playbook browser document.getElementById('bullet-search').addEventListener('input', () => this.filterBullets()); document.getElementById('section-filter').addEventListener('change', () => this.filterBullets()); document.getElementById('sort-by').addEventListener('change', () => this.filterBullets()); // Settings document.getElementById('save-settings-btn').addEventListener('click', () => this.saveSettings()); document.getElementById('reset-settings-btn').addEventListener('click', () => this.resetSettings()); // Logs document.getElementById('clear-logs-btn').addEventListener('click', () => this.clearLogs()); document.getElementById('export-logs-btn').addEventListener('click', () => this.exportLogs()); } // Tab Management switchTab(tabName) { document.querySelectorAll('.tab-btn').forEach(btn => btn.classList.remove('active')); document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active')); document.querySelector(`[data-tab="${tabName}"]`).classList.add('active'); document.getElementById(`${tabName}-tab`).classList.add('active'); } // Context Management switchContext(contextId) { this.currentContextId = contextId; this.updateUI(); this.log(`Switched to context: ${contextId}`, 'info'); } showNewContextModal() { document.getElementById('new-context-modal').classList.add('show'); } hideNewContextModal() { document.getElementById('new-context-modal').classList.remove('show'); document.getElementById('new-context-name').value = ''; } createNewContext() { const name = document.getElementById('new-context-name').value.trim(); if (!name) { alert('Please enter a context name'); return; } const id = name.toLowerCase().replace(/\s+/g, '-'); if (this.contexts.has(id)) { alert('Context with this name already exists'); return; } this.contexts.set(id, { id, name, bullets: [], created_at: Date.now() }); this.updateContextSelector(); this.currentContextId = id; document.getElementById('context-select').value = id; this.hideNewContextModal(); this.updateUI(); this.log(`Created new context: ${name}`, 'success'); } updateContextSelector() { const select = document.getElementById('context-select'); select.innerHTML = ''; this.contexts.forEach(context => { const option = document.createElement('option'); option.value = context.id; option.textContent = context.name; select.appendChild(option); }); select.value = this.currentContextId; } getCurrentContext() { return this.contexts.get(this.currentContextId); } // MCP Tools Implementation // 1. ace_generate aceGenerate(query, contextId = this.currentContextId) { const context = this.contexts.get(contextId); if (!context) { throw new Error(`Context not found: ${contextId}`); } // TODO: Replace with actual LLM API call // This is a mock implementation showing the expected structure const relevantBullets = this.getRelevantBullets(query, context.bullets); const trajectory = { query, context_id: contextId, timestamp: Date.now(), steps: [ { action: 'Analyze query requirements', helpful_bullets: relevantBullets.slice(0, 2).map(b => b.id), harmful_bullets: [], reasoning: 'Used playbook bullets to understand best practices' }, { action: 'Generate solution', helpful_bullets: relevantBullets.slice(2, 4).map(b => b.id), harmful_bullets: [], reasoning: 'Applied strategies from successful patterns' } ], result: `Generated response for: ${query}`, success: true, bullets_used: relevantBullets.map(b => ({ id: b.id, section: b.section, content: b.content, helpful: true })) }; // Update helpful counts relevantBullets.forEach(bullet => { bullet.helpful_count++; }); return trajectory; } // 2. ace_reflect aceReflect(trajectory, maxIterations = this.settings.maxIterations) { if (!trajectory || !trajectory.query) { throw new Error('Invalid trajectory object'); } // TODO: Replace with actual LLM API call for insight extraction // This is a mock implementation const insights = { trajectory_id: trajectory.timestamp, iterations: maxIterations, successes: [], failures: [], suggestions: [], timestamp: Date.now() }; // Analyze helpful bullets if (trajectory.bullets_used && trajectory.bullets_used.length > 0) { insights.successes.push( `Successfully applied ${trajectory.bullets_used.length} relevant strategies`, 'Playbook guidance improved response accuracy' ); } else { insights.failures.push('No relevant bullets found in playbook'); insights.suggestions.push('Add more domain-specific strategies to playbook'); } // Extract patterns from steps if (trajectory.success) { insights.successes.push('Task completed successfully'); insights.suggestions.push( 'Codify successful patterns as new bullets', 'Enhance existing bullets with additional context' ); } else { insights.failures.push('Task failed to complete'); insights.suggestions.push( 'Analyze failure points and add preventive strategies', 'Update harmful bullets to avoid similar issues' ); } // Iterative refinement simulation for (let i = 1; i < maxIterations; i++) { insights.suggestions.push(`Refined insight from iteration ${i + 1}`); } return insights; } // 3. ace_curate aceCurate(insights, currentPlaybook) { if (!insights || !insights.suggestions) { throw new Error('Invalid insights object'); } const context = currentPlaybook || this.getCurrentContext(); // TODO: Replace with actual LLM API call for delta generation // This is a mock implementation const deltaOperations = []; // Generate ADD operations from suggestions insights.suggestions.forEach((suggestion, index) => { if (suggestion.includes('Add') || suggestion.includes('Codify')) { deltaOperations.push({ operation: 'ADD', bullet: { section: this.inferSection(suggestion), content: suggestion, helpful_count: 0, harmful_count: 0 } }); } }); // Generate UPDATE operations for successful bullets if (insights.successes && insights.successes.length > 0) { const bulletIds = this.extractBulletIds(insights); bulletIds.forEach(bulletId => { deltaOperations.push({ operation: 'UPDATE', bullet_id: bulletId, updates: { helpful_count: '+1' } }); }); } // Generate DELETE operations for harmful patterns if (insights.failures && insights.failures.length > 0) { const harmfulIds = this.extractHarmfulBulletIds(insights); harmfulIds.forEach(bulletId => { deltaOperations.push({ operation: 'DELETE', bullet_id: bulletId, reason: 'Harmful pattern identified' }); }); } return { delta_operations: deltaOperations, timestamp: Date.now(), total_operations: deltaOperations.length }; } // 4. ace_update_playbook aceUpdatePlaybook(deltaOperations, contextId = this.currentContextId) { const context = this.contexts.get(contextId); if (!context) { throw new Error(`Context not found: ${contextId}`); } const stats = { added: 0, updated: 0, deleted: 0, deduplicated: 0 }; deltaOperations.forEach(op => { switch (op.operation) { case 'ADD': const newBullet = { id: this.generateBulletId(), section: op.bullet.section, content: op.bullet.content, helpful_count: op.bullet.helpful_count || 0, harmful_count: op.bullet.harmful_count || 0, created_at: Date.now(), updated_at: Date.now() }; context.bullets.push(newBullet); stats.added++; break; case 'UPDATE': const bulletToUpdate = context.bullets.find(b => b.id === op.bullet_id); if (bulletToUpdate) { if (op.updates.helpful_count === '+1') { bulletToUpdate.helpful_count++; } else if (op.updates.helpful_count) { bulletToUpdate.helpful_count = op.updates.helpful_count; } if (op.updates.harmful_count === '+1') { bulletToUpdate.harmful_count++; } else if (op.updates.harmful_count) { bulletToUpdate.harmful_count = op.updates.harmful_count; } bulletToUpdate.updated_at = Date.now(); stats.updated++; } break; case 'DELETE': const indexToDelete = context.bullets.findIndex(b => b.id === op.bullet_id); if (indexToDelete !== -1) { context.bullets.splice(indexToDelete, 1); stats.deleted++; } break; } }); // Perform deduplication if enabled if (this.settings.autoDedup) { stats.deduplicated = this.deduplicateBullets(context); } // Enforce max bullets limit if (context.bullets.length > this.settings.maxBullets) { const excess = context.bullets.length - this.settings.maxBullets; // Remove oldest bullets context.bullets.sort((a, b) => a.created_at - b.created_at); context.bullets.splice(0, excess); stats.deleted += excess; } return { success: true, context_id: contextId, stats, total_bullets: context.bullets.length, timestamp: Date.now() }; } // 5. ace_get_playbook aceGetPlaybook(contextId = this.currentContextId, filter = null) { const context = this.contexts.get(contextId); if (!context) { throw new Error(`Context not found: ${contextId}`); } let bullets = context.bullets; if (filter) { if (filter.section) { bullets = bullets.filter(b => b.section === filter.section); } if (filter.min_helpful_count) { bullets = bullets.filter(b => b.helpful_count >= filter.min_helpful_count); } if (filter.search) { const searchLower = filter.search.toLowerCase(); bullets = bullets.filter(b => b.content.toLowerCase().includes(searchLower) || b.section.toLowerCase().includes(searchLower) ); } } return { context_id: contextId, context_name: context.name, bullets: bullets, total: bullets.length, sections: [...new Set(context.bullets.map(b => b.section))], timestamp: Date.now() }; } // 6. ace_reset_playbook aceResetPlaybook(contextId = this.currentContextId) { const context = this.contexts.get(contextId); if (!context) { throw new Error(`Context not found: ${contextId}`); } const previousCount = context.bullets.length; context.bullets = []; return { success: true, context_id: contextId, bullets_removed: previousCount, timestamp: Date.now() }; } // Helper Methods generateBulletId() { return 'b' + Date.now().toString(36) + Math.random().toString(36).substr(2, 5); } getRelevantBullets(query, bullets) { // Simple relevance scoring based on keyword matching const queryWords = query.toLowerCase().split(/\s+/); return bullets .map(bullet => { const contentWords = bullet.content.toLowerCase().split(/\s+/); const matches = queryWords.filter(word => contentWords.some(cw => cw.includes(word))).length; return { bullet, score: matches }; }) .filter(item => item.score > 0) .sort((a, b) => b.score - a.score) .slice(0, 5) .map(item => item.bullet); } inferSection(text) { const sectionKeywords = { 'Code Generation': ['code', 'function', 'class', 'method', 'generate'], 'API Design': ['api', 'endpoint', 'rest', 'http', 'request'], 'Validation': ['validate', 'validation', 'check', 'verify'], 'Security': ['security', 'auth', 'authentication', 'authorization'], 'Testing': ['test', 'testing', 'unit', 'integration'], 'Performance': ['performance', 'optimize', 'speed', 'cache'], 'Documentation': ['document', 'documentation', 'comment', 'readme'] }; const textLower = text.toLowerCase(); for (const [section, keywords] of Object.entries(sectionKeywords)) { if (keywords.some(keyword => textLower.includes(keyword))) { return section; } } return 'General'; } extractBulletIds(insights) { // Mock implementation - would extract from trajectory in real version return []; } extractHarmfulBulletIds(insights) { // Mock implementation return []; } // Deduplication using simple string similarity deduplicateBullets(context) { const bullets = context.bullets; const toRemove = new Set(); let deduped = 0; for (let i = 0; i < bullets.length; i++) { if (toRemove.has(i)) continue; for (let j = i + 1; j < bullets.length; j++) { if (toRemove.has(j)) continue; const similarity = this.calculateSimilarity(bullets[i].content, bullets[j].content); if (similarity >= this.settings.dedupThreshold) { // Merge: keep the one with higher helpful_count if (bullets[i].helpful_count >= bullets[j].helpful_count) { bullets[i].helpful_count += bullets[j].helpful_count; bullets[i].harmful_count += bullets[j].harmful_count; toRemove.add(j); } else { bullets[j].helpful_count += bullets[i].helpful_count; bullets[j].harmful_count += bullets[i].harmful_count; toRemove.add(i); break; } deduped++; } } } // Remove duplicates context.bullets = bullets.filter((_, index) => !toRemove.has(index)); return deduped; } calculateSimilarity(str1, str2) { // Simple Jaccard similarity const words1 = new Set(str1.toLowerCase().split(/\s+/)); const words2 = new Set(str2.toLowerCase().split(/\s+/)); const intersection = new Set([...words1].filter(x => words2.has(x))); const union = new Set([...words1, ...words2]); return intersection.size / union.size; } // UI Methods runGenerate() { const query = document.getElementById('generate-query').value.trim(); if (!query) { alert('Please enter a query'); return; } try { const trajectory = this.aceGenerate(query); this.showToolOutput('generate-output', trajectory); this.log(`Generated trajectory for query: "${query}"`, 'success'); this.updateUI(); } catch (error) { this.showToolOutput('generate-output', { error: error.message }); this.log(`Error in ace_generate: ${error.message}`, 'error'); } } runReflect() { const trajectoryJson = document.getElementById('reflect-trajectory').value.trim(); const iterations = parseInt(document.getElementById('reflect-iterations').value); if (!trajectoryJson) { alert('Please enter trajectory JSON'); return; } try { const trajectory = JSON.parse(trajectoryJson); const insights = this.aceReflect(trajectory, iterations); this.showToolOutput('reflect-output', insights); this.log(`Reflected on trajectory with ${iterations} iterations`, 'success'); } catch (error) { this.showToolOutput('reflect-output', { error: error.message }); this.log(`Error in ace_reflect: ${error.message}`, 'error'); } } runCurate() { const insightsJson = document.getElementById('curate-insights').value.trim(); if (!insightsJson) { alert('Please enter insights JSON'); return; } try { const insights = JSON.parse(insightsJson); const deltas = this.aceCurate(insights); this.showToolOutput('curate-output', deltas); this.log(`Curated ${deltas.total_operations} delta operations`, 'success'); } catch (error) { this.showToolOutput('curate-output', { error: error.message }); this.log(`Error in ace_curate: ${error.message}`, 'error'); } } runUpdate() { const deltasJson = document.getElementById('update-deltas').value.trim(); if (!deltasJson) { alert('Please enter delta operations JSON'); return; } try { const data = JSON.parse(deltasJson); const deltas = data.delta_operations || data; const result = this.aceUpdatePlaybook(deltas); this.showToolOutput('update-output', result); this.log(`Updated playbook: +${result.stats.added} -${result.stats.deleted} ~${result.stats.updated}`, 'success'); this.updateUI(); } catch (error) { this.showToolOutput('update-output', { error: error.message }); this.log(`Error in ace_update_playbook: ${error.message}`, 'error'); } } runGet() { const section = document.getElementById('get-section').value.trim(); try { const filter = section ? { section } : null; const result = this.aceGetPlaybook(this.currentContextId, filter); this.showToolOutput('get-output', result); this.log(`Retrieved playbook: ${result.total} bullets`, 'success'); } catch (error) { this.showToolOutput('get-output', { error: error.message }); this.log(`Error in ace_get_playbook: ${error.message}`, 'error'); } } runReset() { if (!confirm('Are you sure you want to reset the playbook? This will delete all bullets.')) { return; } try { const result = this.aceResetPlaybook(); this.showToolOutput('reset-output', result); this.log(`Reset playbook: removed ${result.bullets_removed} bullets`, 'warning'); this.updateUI(); } catch (error) { this.showToolOutput('reset-output', { error: error.message }); this.log(`Error in ace_reset_playbook: ${error.message}`, 'error'); } } showToolOutput(elementId, data) { const output = document.getElementById(elementId); output.innerHTML = `<pre>${JSON.stringify(data, null, 2)}</pre>`; output.classList.add('show'); } runDemoWorkflow() { this.log('Starting demo ACE workflow...', 'info'); // Step 1: Add some initial bullets const initialBullets = [ { section: 'Code Generation', content: 'Always add type annotations to function parameters' }, { section: 'API Design', content: 'Use RESTful conventions for endpoint naming' }, { section: 'Validation', content: 'Validate all user inputs before processing' } ]; const addOps = initialBullets.map(b => ({ operation: 'ADD', bullet: { ...b, helpful_count: 0, harmful_count: 0 } })); this.aceUpdatePlaybook(addOps); this.log('Added initial bullets to playbook', 'success'); // Step 2: Generate trajectory setTimeout(() => { const trajectory = this.aceGenerate('Create a user authentication endpoint'); document.getElementById('generate-query').value = 'Create a user authentication endpoint'; this.showToolOutput('generate-output', trajectory); this.log('Generated trajectory for authentication endpoint', 'success'); // Step 3: Reflect setTimeout(() => { const insights = this.aceReflect(trajectory); document.getElementById('reflect-trajectory').value = JSON.stringify(trajectory, null, 2); this.showToolOutput('reflect-output', insights); this.log('Reflected on trajectory and extracted insights', 'success'); // Step 4: Curate setTimeout(() => { const deltas = this.aceCurate(insights); document.getElementById('curate-insights').value = JSON.stringify(insights, null, 2); this.showToolOutput('curate-output', deltas); this.log('Curated delta operations from insights', 'success'); // Step 5: Update setTimeout(() => { const result = this.aceUpdatePlaybook(deltas.delta_operations); document.getElementById('update-deltas').value = JSON.stringify(deltas, null, 2); this.showToolOutput('update-output', result); this.log('Applied delta updates to playbook', 'success'); this.updateUI(); this.log('โœ… Demo workflow completed successfully!', 'success'); this.switchTab('tools'); }, 500); }, 500); }, 500); }, 500); } exportPlaybook() { const playbook = this.aceGetPlaybook(); const json = JSON.stringify(playbook, null, 2); const blob = new Blob([json], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `playbook-${this.currentContextId}-${Date.now()}.json`; a.click(); URL.revokeObjectURL(url); this.log('Exported playbook to JSON', 'success'); } importPlaybook() { const input = document.createElement('input'); input.type = 'file'; input.accept = '.json'; input.onchange = (e) => { const file = e.target.files[0]; const reader = new FileReader(); reader.onload = (event) => { try { const data = JSON.parse(event.target.result); if (data.bullets) { const context = this.getCurrentContext(); context.bullets = data.bullets; this.updateUI(); this.log(`Imported ${data.bullets.length} bullets`, 'success'); } } catch (error) { alert('Invalid JSON file'); this.log(`Import failed: ${error.message}`, 'error'); } }; reader.readAsText(file); }; input.click(); } resetContext() { if (!confirm('Reset current context? This will delete all bullets.')) { return; } this.aceResetPlaybook(); this.updateUI(); } filterBullets() { const search = document.getElementById('bullet-search').value.toLowerCase(); const sectionFilter = document.getElementById('section-filter').value; const sortBy = document.getElementById('sort-by').value; const context = this.getCurrentContext(); let bullets = [...context.bullets]; // Filter by search if (search) { bullets = bullets.filter(b => b.content.toLowerCase().includes(search) || b.section.toLowerCase().includes(search) ); } // Filter by section if (sectionFilter !== 'all') { bullets = bullets.filter(b => b.section === sectionFilter); } // Sort switch (sortBy) { case 'created_desc': bullets.sort((a, b) => b.created_at - a.created_at); break; case 'created_asc': bullets.sort((a, b) => a.created_at - b.created_at); break; case 'helpful_desc': bullets.sort((a, b) => b.helpful_count - a.helpful_count); break; case 'helpful_asc': bullets.sort((a, b) => a.helpful_count - b.helpful_count); break; } this.renderBullets(bullets); } renderBullets(bullets = null) { const container = document.getElementById('bullets-container'); if (!bullets) { bullets = this.getCurrentContext().bullets; } if (bullets.length === 0) { container.innerHTML = ` <div class="empty-state"> <div class="empty-icon">๐Ÿ“‹</div> <h3>No Bullets Yet</h3> <p>Your playbook is empty. Use the MCP Tools tab to generate and curate bullets.</p> </div> `; return; } container.innerHTML = bullets.map(bullet => ` <div class="bullet-card"> <div class="bullet-header"> <span class="bullet-section">${bullet.section}</span> <span class="bullet-id">${bullet.id}</span> </div> <div class="bullet-content">${bullet.content}</div> <div class="bullet-meta"> <div class="bullet-stats"> <div class="stat-item stat-helpful"> <span>โœ“</span> <span>${bullet.helpful_count}</span> </div> <div class="stat-item stat-harmful"> <span>โœ—</span> <span>${bullet.harmful_count}</span> </div> </div> <div class="bullet-timestamp">${this.formatDate(bullet.created_at)}</div> </div> </div> `).join(''); } updateUI() { const context = this.getCurrentContext(); const bullets = context.bullets; // Update stats document.getElementById('stat-total-bullets').textContent = bullets.length; document.getElementById('stat-contexts').textContent = this.contexts.size; document.getElementById('stat-helpful').textContent = bullets.reduce((sum, b) => sum + b.helpful_count, 0); document.getElementById('stat-operations').textContent = this.operationLog.length; // Update section filter const sections = [...new Set(bullets.map(b => b.section))]; const sectionFilter = document.getElementById('section-filter'); const currentValue = sectionFilter.value; sectionFilter.innerHTML = '<option value="all">All Sections</option>'; sections.forEach(section => { const option = document.createElement('option'); option.value = section; option.textContent = section; sectionFilter.appendChild(option); }); sectionFilter.value = currentValue; // Render bullets this.renderBullets(); // Update playbook overview chart this.updatePlaybookChart(sections, bullets); // Update context selector this.updateContextSelector(); // Update settings display document.getElementById('setting-dedup-threshold').value = this.settings.dedupThreshold; document.getElementById('setting-max-bullets').value = this.settings.maxBullets; document.getElementById('setting-max-iterations').value = this.settings.maxIterations; document.getElementById('setting-debug-mode').checked = this.settings.debugMode; document.getElementById('setting-auto-dedup').checked = this.settings.autoDedup; document.getElementById('info-contexts').textContent = this.contexts.size; } updatePlaybookChart(sections, bullets) { const chartDiv = document.getElementById('playbook-sections-chart'); if (sections.length === 0) { chartDiv.innerHTML = '<p style="text-align: center; color: var(--color-text-secondary);">No data to display</p>'; return; } const sectionCounts = {}; sections.forEach(section => { sectionCounts[section] = bullets.filter(b => b.section === section).length; }); const maxCount = Math.max(...Object.values(sectionCounts)); chartDiv.innerHTML = ` <div style="display: flex; flex-direction: column; gap: 12px;"> ${sections.map(section => { const count = sectionCounts[section]; const percentage = (count / maxCount) * 100; return ` <div> <div style="display: flex; justify-content: space-between; margin-bottom: 4px; font-size: 12px;"> <span>${section}</span> <span style="color: var(--color-text-secondary);">${count}</span> </div> <div style="background: var(--color-secondary); height: 8px; border-radius: 4px; overflow: hidden;"> <div style="background: var(--color-primary); width: ${percentage}%; height: 100%; transition: width 0.3s;"></div> </div> </div> `; }).join('')} </div> `; } saveSettings() { this.settings.dedupThreshold = parseFloat(document.getElementById('setting-dedup-threshold').value); this.settings.maxBullets = parseInt(document.getElementById('setting-max-bullets').value); this.settings.maxIterations = parseInt(document.getElementById('setting-max-iterations').value); this.settings.debugMode = document.getElementById('setting-debug-mode').checked; this.settings.autoDedup = document.getElementById('setting-auto-dedup').checked; this.log('Settings saved successfully', 'success'); alert('Settings saved!'); } resetSettings() { this.settings = { dedupThreshold: 0.85, maxBullets: 1000, maxIterations: 5, debugMode: false, autoDedup: true }; this.updateUI(); this.log('Settings reset to defaults', 'info'); alert('Settings reset to defaults!'); } updateSessionInfo() { const startTime = new Date(this.sessionStart).toLocaleString(); document.getElementById('info-session-start').textContent = startTime; } log(message, level = 'info') { const timestamp = new Date().toLocaleTimeString(); const entry = { timestamp, message, level }; this.operationLog.push(entry); const logsContainer = document.getElementById('logs-container'); const logEntry = document.createElement('div'); logEntry.className = `log-entry log-${level}`; logEntry.innerHTML = ` <span class="log-timestamp">[${timestamp}]</span> <span class="log-message">${message}</span> `; logsContainer.appendChild(logEntry); logsContainer.scrollTop = logsContainer.scrollHeight; if (this.settings.debugMode) { console.log(`[ACE ${level.toUpperCase()}]`, message); } } clearLogs() { this.operationLog = []; document.getElementById('logs-container').innerHTML = ''; this.log('Logs cleared', 'info'); } exportLogs() { const text = this.operationLog.map(log => `[${log.timestamp}] [${log.level.toUpperCase()}] ${log.message}` ).join('\n'); const blob = new Blob([text], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `ace-logs-${Date.now()}.txt`; a.click(); URL.revokeObjectURL(url); this.log('Exported activity logs', 'success'); } formatDate(timestamp) { const date = new Date(timestamp); return date.toLocaleDateString() + ' ' + date.toLocaleTimeString(); } } // Initialize ACE Server when DOM is ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { window.aceServer = new ACEServer(); }); } else { window.aceServer = new ACEServer(); }

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/Angry-Robot-Deals/ace-mcp'

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