Skip to main content
Glama

FM8 MCP Server

web-ui.html10.7 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>FM8 MCP Controller</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 20px; } .container { background: white; border-radius: 20px; padding: 40px; max-width: 800px; width: 100%; box-shadow: 0 20px 60px rgba(0,0,0,0.3); } h1 { color: #667eea; margin-bottom: 10px; font-size: 32px; } .subtitle { color: #666; margin-bottom: 30px; font-size: 14px; } .chat-container { height: 400px; overflow-y: auto; border: 2px solid #f0f0f0; border-radius: 10px; padding: 20px; margin-bottom: 20px; background: #fafafa; } .message { margin-bottom: 15px; padding: 12px 16px; border-radius: 12px; max-width: 80%; animation: slideIn 0.3s ease; } @keyframes slideIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } .user { background: #667eea; color: white; margin-left: auto; } .assistant { background: #e9ecef; color: #333; } .tool-call { background: #fff3cd; color: #856404; font-size: 13px; margin: 5px 0; padding: 8px 12px; } .input-container { display: flex; gap: 10px; } input { flex: 1; padding: 14px 18px; border: 2px solid #e0e0e0; border-radius: 10px; font-size: 16px; transition: border-color 0.3s; } input:focus { outline: none; border-color: #667eea; } button { padding: 14px 30px; background: #667eea; color: white; border: none; border-radius: 10px; font-size: 16px; cursor: pointer; transition: background 0.3s; font-weight: 600; } button:hover { background: #5568d3; } button:disabled { background: #ccc; cursor: not-allowed; } .status { display: flex; gap: 10px; margin-bottom: 20px; font-size: 13px; } .status-dot { display: inline-block; width: 10px; height: 10px; border-radius: 50%; margin-right: 5px; } .status-online { background: #28a745; } .status-offline { background: #dc3545; } .examples { margin-top: 20px; padding: 15px; background: #f8f9fa; border-radius: 10px; } .examples h3 { font-size: 14px; color: #666; margin-bottom: 10px; } .example-btn { display: inline-block; padding: 6px 12px; margin: 4px; background: white; border: 1px solid #ddd; border-radius: 6px; font-size: 12px; cursor: pointer; transition: all 0.2s; } .example-btn:hover { background: #667eea; color: white; border-color: #667eea; } </style> </head> <body> <div class="container"> <h1>🎹 FM8 Controller</h1> <p class="subtitle">Chat with AI to control your FM8 synthesizer via MIDI</p> <div class="status"> <div> <span class="status-dot" id="mcp-status"></span> <span id="mcp-text">MCP Server: Checking...</span> </div> <div> <span class="status-dot" id="ollama-status"></span> <span id="ollama-text">Ollama: Checking...</span> </div> </div> <div class="chat-container" id="chat"></div> <div class="input-container"> <input type="text" id="input" placeholder="Ask me to control FM8..." onkeypress="if(event.key==='Enter') sendMessage()" /> <button onclick="sendMessage()" id="send-btn">Send</button> </div> <div class="examples"> <h3>Try these examples:</h3> <span class="example-btn" onclick="setInput('Show me all available mappings')">List mappings</span> <span class="example-btn" onclick="setInput('Send CC 10 with value 64')">Send CC</span> <span class="example-btn" onclick="setInput('Set LFO1 to modulate filter cutoff at 80')">LFO modulation</span> <span class="example-btn" onclick="setInput('What is the server status?')">Check status</span> <span class="example-btn" onclick="setInput('Emergency panic!')">Panic</span> </div> </div> <script> const MCP_URL = 'http://localhost:3333'; const OLLAMA_URL = 'http://localhost:11434'; const MODEL = 'llama3.2'; let messages = []; let isProcessing = false; const tools = [ { type: "function", function: { name: "send_by_cc", description: "Send a raw MIDI CC (0-127) to FM8.", parameters: { type: "object", properties: { cc: { type: "number", description: "MIDI CC number (0-127)" }, value: { type: "number", description: "MIDI CC value (0-127)" } }, required: ["cc", "value"] } } }, { type: "function", function: { name: "send_by_route", description: "Send FM8 matrix route.", parameters: { type: "object", properties: { source: { type: "string", description: "Source (e.g., 'LFO1')" }, dest: { type: "string", description: "Destination (e.g., 'CUTOFF')" }, value: { type: "number", description: "Amount (0-127)" } }, required: ["source", "dest", "value"] } } }, { type: "function", function: { name: "list_mappings", description: "List all FM8 mappings.", parameters: { type: "object", properties: {} } } }, { type: "function", function: { name: "status", description: "Get server status.", parameters: { type: "object", properties: {} } } }, { type: "function", function: { name: "panic", description: "Emergency stop.", parameters: { type: "object", properties: {} } } } ]; // Check server status async function checkStatus() { try { const mcpRes = await fetch(`${MCP_URL}/health`); document.getElementById('mcp-status').className = 'status-dot status-online'; document.getElementById('mcp-text').textContent = 'MCP Server: Online'; } catch { document.getElementById('mcp-status').className = 'status-dot status-offline'; document.getElementById('mcp-text').textContent = 'MCP Server: Offline'; } try { const ollamaRes = await fetch(`${OLLAMA_URL}/api/tags`); document.getElementById('ollama-status').className = 'status-dot status-online'; document.getElementById('ollama-text').textContent = 'Ollama: Online'; } catch { document.getElementById('ollama-status').className = 'status-dot status-offline'; document.getElementById('ollama-text').textContent = 'Ollama: Offline'; } } async function callMCPTool(name, args) { const response = await fetch(`${MCP_URL}/mcp`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ jsonrpc: '2.0', id: Date.now(), method: 'tools/call', params: { name, arguments: args } }) }); const result = await response.json(); return result.result; } function addMessage(role, content, isToolCall = false) { const chat = document.getElementById('chat'); const msg = document.createElement('div'); msg.className = `message ${isToolCall ? 'tool-call' : role}`; msg.textContent = content; chat.appendChild(msg); chat.scrollTop = chat.scrollHeight; } async function sendMessage() { if (isProcessing) return; const input = document.getElementById('input'); const text = input.value.trim(); if (!text) return; input.value = ''; isProcessing = true; document.getElementById('send-btn').disabled = true; addMessage('user', text); messages.push({ role: 'user', content: text }); try { const response = await fetch(`${OLLAMA_URL}/api/chat`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model: MODEL, messages: messages, tools: tools, stream: false }) }); const data = await response.json(); if (data.message.tool_calls) { messages.push(data.message); for (const toolCall of data.message.tool_calls) { addMessage('assistant', `🔧 ${toolCall.function.name}(${JSON.stringify(toolCall.function.arguments)})`, true); const result = await callMCPTool(toolCall.function.name, toolCall.function.arguments); addMessage('assistant', `✅ ${JSON.stringify(result)}`, true); messages.push({ role: 'tool', content: JSON.stringify(result) }); } const finalResponse = await fetch(`${OLLAMA_URL}/api/chat`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model: MODEL, messages: messages, stream: false }) }); const finalData = await finalResponse.json(); messages.push(finalData.message); addMessage('assistant', finalData.message.content); } else { messages.push(data.message); addMessage('assistant', data.message.content); } } catch (error) { addMessage('assistant', `❌ Error: ${error.message}`); } isProcessing = false; document.getElementById('send-btn').disabled = false; input.focus(); } function setInput(text) { document.getElementById('input').value = text; document.getElementById('input').focus(); } // Initial status check checkStatus(); setInterval(checkStatus, 5000); </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/thesigma1receptor/fm8MCP'

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