Skip to main content
Glama
config.html26 kB
<!DOCTYPE html> <html> <head> <title>Metabase MCP Configuration</title> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/github-markdown-css@5.1.0/github-markdown.min.css"> <script src="https://cdn.jsdelivr.net/npm/marked@4.0.0/marked.min.js"></script> <style> body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; } .form-group { margin-bottom: 15px; } label { display: block; margin-bottom: 5px; font-weight: bold; } input[type="text"] { width: 100%; padding: 8px; box-sizing: border-box; } button { background-color: #4CAF50; color: white; padding: 10px 15px; border: none; cursor: pointer; margin-right: 10px; } button:hover { background-color: #45a049; } .test-button { background-color: #2196F3; } .test-button:hover { background-color: #0b7dda; } .message { margin: 15px 0; padding: 10px; border-radius: 5px; } .success { background-color: #d4edda; color: #155724; } .error { background-color: #f8d7da; color: #721c24; } .result-area { margin: 20px 0; padding:.875rem; background: #f5f5f5; border-radius: 4px; border: 1px solid #ddd; min-height: 100px; max-height: 400px; overflow-y: auto; white-space: pre-wrap; display: none; } .section { margin-top: 30px; border-top: 1px solid #ddd; padding-top: 20px; } textarea.form-control { width: 100%; padding: 8px; box-sizing: border-box; font-family: monospace; border: 1px solid #ccc; border-radius: 4px; } .markdown-body { box-sizing: border-box; min-width: 200px; max-width: 100%; padding: 15px; background-color: #fff; border-radius: 4px; border: 1px solid #ddd; color: #24292e; font-weight: normal; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; } .markdown-body h1, .markdown-body h2, .markdown-body h3, .markdown-body h4 { color: #24292e; font-weight: 600; margin-top: 24px; margin-bottom: 16px; } .markdown-body table { display: table; width: 100%; border-collapse: collapse; margin-bottom: 16px; border: 1px solid #ddd; } .markdown-body table th { font-weight: 600; padding: 8px 13px; border: 1px solid #ddd; background-color: #f6f8fa; color: #24292e; } .markdown-body table td { padding: 8px 13px; border: 1px solid #ddd; color: #24292e; } .markdown-body table tr { background-color: #fff; border-top: 1px solid #c6cbd1; } .markdown-body table tr:nth-child(2n) { background-color: #f6f8fa; } .markdown-body p, .markdown-body ul, .markdown-body ol, .markdown-body blockquote { color: #24292e; margin-bottom: 16px; } .markdown-body pre, .markdown-body code { background-color: #f6f8fa; border-radius: 3px; padding: 0.2em 0.4em; color: #24292e; font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace; } .result-container { margin-top: 15px; } </style> </head> <body> <h1>Metabase MCP Configuration</h1> {% with messages = get_flashed_messages() %} {% if messages %} <div class="message {% if 'error' in messages[0].lower() %}error{% else %}success{% endif %}"> {{ messages[0] }} </div> {% endif %} {% endwith %} <form method="post" action="/save_config" id="config-form"> <div class="form-group"> <label for="metabase_url">Metabase URL:</label> <input type="text" id="metabase_url" name="metabase_url" value="{{ metabase_url }}" placeholder="http://localhost:3000"> </div> <div class="form-group"> <label for="api_key">API Key:</label> <input type="password" id="api_key" name="api_key" class="form-control" value="{{ api_key }}" placeholder="{{ 'API key is set' if api_key_set else 'Enter your Metabase API key' }}" required> <small class="form-text text-muted"> {% if api_key_set %} Your API key is stored encrypted. Enter a new key to change it. {% else %} Your API key will be stored encrypted and never displayed in plain text. {% endif %} </small> </div> <div class="form-group"> <button type="submit">Save Configuration</button> <button type="button" class="test-button" id="test-connection">Test Connection</button> </div> </form> <div id="connection-result" class="result-area"></div> <div class="section"> <h2>Test Tools</h2> <h3>List Databases</h3> <button type="button" id="test-list-databases">Test List Databases</button> <div id="list-databases-result" class="result-area"></div> <h3>Get Database Metadata</h3> <div class="form-group"> <label for="database_id">Database ID:</label> <input type="text" id="database_id" placeholder="Enter database ID"> </div> <button type="button" id="test-get-metadata">Test Get Metadata</button> <div id="get-metadata-result" class="result-area"></div> <h3>DB Overview</h3> <div class="form-group"> <label for="db_overview_database_id">Database ID:</label> <input type="text" id="db_overview_database_id" placeholder="Enter database ID"> </div> <button type="button" id="test-db-overview">Get Database Overview</button> <div id="db-overview-result" class="result-area"></div> <h3>Table Detail</h3> <div class="form-group"> <label for="table_detail_database_id">Database ID:</label> <input type="text" id="table_detail_database_id" placeholder="Enter database ID"> </div> <div class="form-group"> <label for="table_detail_table_id">Table ID:</label> <input type="text" id="table_detail_table_id" placeholder="Enter table ID (from DB Overview)"> </div> <button type="button" id="test-table-detail">Get Table Detail</button> <div id="table-detail-result" class="result-area"></div> <h3>Visualize Database Relationships</h3> <div class="form-group"> <label for="relationship_database_id">Database ID:</label> <input type="text" id="relationship_database_id" placeholder="Enter database ID"> </div> <button type="button" id="test-visualize-relationships">Visualize Relationships</button> <div id="visualize-relationships-result" class="result-area"></div> <h3>Run Database Query</h3> <div class="form-group"> <label for="query_database_id">Database ID:</label> <input type="text" id="query_database_id" placeholder="Enter database ID"> </div> <div class="form-group"> <label for="sql_query">SQL Query:</label> <textarea id="sql_query" placeholder="Enter SQL query" rows="4" class="form-control"></textarea> <small class="form-text text-muted">Query will be limited to returning 5 rows for safety.</small> </div> <button type="button" id="test-run-query">Run Query</button> <div id="run-query-result" class="result-area"></div> </div> <div class="section"> <h3>List Actions</h3> <button type="button" id="test-list-actions">Test List Actions</button> <div id="list-actions-result" class="result-area"></div> <h3>Get Action Details</h3> <div class="form-group"> <label for="action_id">Action ID:</label> <input type="text" id="action_id" placeholder="Enter action ID"> </div> <button type="button" id="test-get-action">Test Get Action Details</button> <div id="get-action-result" class="result-area"></div> <h3>Execute Action</h3> <div class="form-group"> <label for="exec_action_id">Action ID:</label> <input type="text" id="exec_action_id" placeholder="Enter action ID"> </div> <button type="button" id="load-action-params">Load Parameters</button> <div id="action-parameters" class="form-group"></div> <button type="button" id="test-execute-action">Execute Action</button> <div id="execute-action-result" class="result-area"></div> </div> <script> // Configure marked.js options marked.setOptions({ gfm: true, breaks: true, tables: true, sanitize: false }); // Function to format response data with proper Markdown rendering function formatResponse(container, data) { if (data.success) { // Check if response has result or message property const content = data.result || data.message || ''; // Only try to parse as markdown if it's not empty if (content) { container.innerHTML = '<div class="markdown-body">' + marked.parse(content) + '</div>'; } else { container.innerHTML = '<div class="markdown-body">Operation completed successfully.</div>'; } // Add success class if not already present if (!container.className.includes('success')) { container.className += ' success'; } } else { const errorMsg = data.error || data.message || 'Unknown error'; container.innerHTML = '<div class="error">' + errorMsg + '</div>'; // Add error class if not already present if (!container.className.includes('error')) { container.className += ' error'; } } } // Test connection document.getElementById('test-connection').addEventListener('click', function() { const url = document.getElementById('metabase_url').value; const apiKey = document.getElementById('api_key').value; const resultArea = document.getElementById('connection-result'); resultArea.style.display = 'block'; resultArea.innerHTML = 'Testing connection...'; fetch('/test_connection', { method: 'POST', headers: {'Content-Type': 'application/x-www-form-urlencoded'}, body: `metabase_url=${encodeURIComponent(url)}&api_key=${encodeURIComponent(apiKey)}` }) .then(response => response.json()) .then(data => { formatResponse(resultArea, data); }) .catch(error => { resultArea.className = 'result-area error'; resultArea.innerHTML = `Error: ${error}`; }); }); // Test list databases document.getElementById('test-list-databases').addEventListener('click', function() { const resultArea = document.getElementById('list-databases-result'); resultArea.style.display = 'block'; resultArea.innerHTML = 'Fetching databases...'; fetch('/test_list_databases') .then(response => response.json()) .then(data => { formatResponse(resultArea, data); }) .catch(error => { resultArea.className = 'result-area error'; resultArea.innerHTML = `Error: ${error}`; }); }); // Test get metadata document.getElementById('test-get-metadata').addEventListener('click', function() { const databaseId = document.getElementById('database_id').value; const resultArea = document.getElementById('get-metadata-result'); if (!databaseId) { resultArea.style.display = 'block'; resultArea.className = 'result-area error'; resultArea.innerHTML = 'Please enter a database ID'; return; } resultArea.style.display = 'block'; resultArea.innerHTML = 'Fetching metadata...'; fetch('/test_get_metadata', { method: 'POST', headers: {'Content-Type': 'application/x-www-form-urlencoded'}, body: `database_id=${encodeURIComponent(databaseId)}` }) .then(response => response.json()) .then(data => { formatResponse(resultArea, data); }) .catch(error => { resultArea.className = 'result-area error'; resultArea.innerHTML = `Error: ${error}`; }); }); // Test list actions document.getElementById('test-list-actions').addEventListener('click', function() { const resultArea = document.getElementById('list-actions-result'); resultArea.style.display = 'block'; resultArea.innerHTML = 'Fetching actions...'; fetch('/test_list_actions') .then(response => response.json()) .then(data => { formatResponse(resultArea, data); }) .catch(error => { resultArea.className = 'result-area error'; resultArea.innerHTML = `Error: ${error}`; }); }); // Test get action details document.getElementById('test-get-action').addEventListener('click', function() { const actionId = document.getElementById('action_id').value; const resultArea = document.getElementById('get-action-result'); if (!actionId) { resultArea.style.display = 'block'; resultArea.className = 'result-area error'; resultArea.innerHTML = 'Please enter an action ID'; return; } resultArea.style.display = 'block'; resultArea.innerHTML = 'Fetching action details...'; fetch('/test_get_action_details', { method: 'POST', headers: {'Content-Type': 'application/x-www-form-urlencoded'}, body: `action_id=${encodeURIComponent(actionId)}` }) .then(response => response.json()) .then(data => { formatResponse(resultArea, data); }) .catch(error => { resultArea.className = 'result-area error'; resultArea.innerHTML = `Error: ${error}`; }); }); // Load action parameters document.getElementById('load-action-params').addEventListener('click', function() { const actionId = document.getElementById('exec_action_id').value; const paramsContainer = document.getElementById('action-parameters'); if (!actionId) { alert('Please enter an action ID'); return; } paramsContainer.innerHTML = 'Loading parameters...'; fetch('/test_get_action_details', { method: 'POST', headers: {'Content-Type': 'application/x-www-form-urlencoded'}, body: `action_id=${encodeURIComponent(actionId)}` }) .then(response => response.json()) .then(data => { if (data.success) { // Parse the markdown to extract parameters const paramSection = data.result.split('### Parameters')[1]; if (!paramSection) { paramsContainer.innerHTML = 'No parameters found for this action.'; return; } // Clear container paramsContainer.innerHTML = ''; // Extract parameter names from the markdown const paramLines = paramSection.split('\n'); let paramCount = 0; for (const line of paramLines) { if (line.startsWith('- **')) { const paramName = line.split('**:')[0].replace('- **', '').trim(); const formGroup = document.createElement('div'); formGroup.className = 'form-group'; const label = document.createElement('label'); label.setAttribute('for', `param_${paramName}`); label.textContent = `Parameter: ${paramName}`; const input = document.createElement('input'); input.type = 'text'; input.id = `param_${paramName}`; input.name = `param_${paramName}`; input.placeholder = `Enter value for ${paramName}`; formGroup.appendChild(label); formGroup.appendChild(input); paramsContainer.appendChild(formGroup); paramCount++; } } if (paramCount === 0) { paramsContainer.innerHTML = 'No parameters found for this action.'; } } else { paramsContainer.innerHTML = `Error: ${data.error}`; } }) .catch(error => { paramsContainer.innerHTML = `Error: ${error}`; }); }); // Execute action document.getElementById('test-execute-action').addEventListener('click', function() { const actionId = document.getElementById('exec_action_id').value; const resultArea = document.getElementById('execute-action-result'); const paramsContainer = document.getElementById('action-parameters'); if (!actionId) { resultArea.style.display = 'block'; resultArea.className = 'result-area error'; resultArea.innerHTML = 'Please enter an action ID'; return; } // Collect parameters const formData = new FormData(); formData.append('action_id', actionId); // Add all input fields from the parameters container const inputs = paramsContainer.querySelectorAll('input'); inputs.forEach(input => { if (input.name && input.value) { formData.append(input.name, input.value); } }); resultArea.style.display = 'block'; resultArea.innerHTML = 'Executing action...'; fetch('/test_execute_action', { method: 'POST', body: formData }) .then(response => response.json()) .then(data => { formatResponse(resultArea, data); }) .catch(error => { resultArea.className = 'result-area error'; resultArea.innerHTML = `Error: ${error}`; }); }); // Test visualize relationships document.getElementById('test-visualize-relationships').addEventListener('click', function() { const databaseId = document.getElementById('relationship_database_id').value; if (!databaseId) { alert('Please enter a database ID'); return; } const resultArea = document.getElementById('visualize-relationships-result'); resultArea.style.display = 'block'; resultArea.innerHTML = 'Visualizing relationships...'; fetch('/test_visualize_relationships', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: `database_id=${databaseId}` }) .then(response => response.json()) .then(data => { formatResponse(resultArea, data); }) .catch(error => { resultArea.innerHTML = `<div class="error">Error: ${error.message}</div>`; }); }); // Test run query document.getElementById('test-run-query').addEventListener('click', function() { const databaseId = document.getElementById('query_database_id').value; const sqlQuery = document.getElementById('sql_query').value; if (!databaseId || !sqlQuery) { alert('Please enter both database ID and SQL query'); return; } const resultArea = document.getElementById('run-query-result'); resultArea.style.display = 'block'; resultArea.innerHTML = 'Executing query...'; fetch('/test_run_query', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: `database_id=${encodeURIComponent(databaseId)}&query=${encodeURIComponent(sqlQuery)}` }) .then(response => response.json()) .then(data => { formatResponse(resultArea, data); }) .catch(error => { resultArea.innerHTML = `<div class="error">Error: ${error.message}</div>`; }); }); // Test DB Overview document.getElementById('test-db-overview').addEventListener('click', function() { const databaseId = document.getElementById('db_overview_database_id').value; if (!databaseId) { alert('Please enter a database ID'); return; } const resultArea = document.getElementById('db-overview-result'); resultArea.style.display = 'block'; resultArea.innerHTML = 'Loading database overview...'; fetch('/test_db_overview', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: `database_id=${databaseId}` }) .then(response => response.json()) .then(data => { formatResponse(resultArea, data); }) .catch(error => { resultArea.innerHTML = `<div class="error">Error: ${error.message}</div>`; }); }); // Test Table Detail document.getElementById('test-table-detail').addEventListener('click', function() { const databaseId = document.getElementById('table_detail_database_id').value; const tableId = document.getElementById('table_detail_table_id').value; if (!databaseId || !tableId) { alert('Please enter both database ID and table ID'); return; } const resultArea = document.getElementById('table-detail-result'); resultArea.style.display = 'block'; resultArea.innerHTML = 'Loading table details...'; fetch('/test_table_detail', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: `database_id=${encodeURIComponent(databaseId)}&table_id=${encodeURIComponent(tableId)}` }) .then(response => response.json()) .then(data => { formatResponse(resultArea, data); }) .catch(error => { resultArea.innerHTML = `<div class="error">Error: ${error.message}</div>`; }); }); </script> </body> </html>

Latest Blog Posts

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/sazboxai/MCP_MetaBase'

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