Skip to main content
Glama

Read-only Database MCP

by Crei03
index.html13.4 kB
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>DB MCP Control</title> <style> :root { --bg: #f7f7f7; --panel: #ffffff; --text: #0f172a; --muted: #475569; --accent: #0ea5e9; --border: #d9e1ec; } :root[data-theme="dark"] { --bg: #0b1220; --panel: #111827; --text: #e5e7eb; --muted: #94a3b8; --accent: #38bdf8; --border: #1f2937; } * { box-sizing: border-box; } body { margin: 0; font-family: "IBM Plex Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background: var(--bg); color: var(--text); } header { display: flex; justify-content: space-between; align-items: center; padding: 16px 20px; border-bottom: 1px solid var(--border); } h1 { margin: 0; font-size: 18px; letter-spacing: 0.02em; } main { padding: 20px; max-width: 1080px; margin: 0 auto; display: grid; gap: 16px; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); } .card { background: var(--panel); border: 1px solid var(--border); border-radius: 10px; padding: 16px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.05); } .card h2 { margin: 0 0 10px; font-size: 16px; } label { display: block; font-size: 13px; color: var(--muted); margin-bottom: 4px; } input, select, textarea { width: 100%; padding: 8px 10px; border-radius: 8px; border: 1px solid var(--border); background: transparent; color: var(--text); font-size: 14px; } textarea { min-height: 80px; resize: vertical; } .row { display: grid; gap: 10px; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); } button { background: var(--accent); color: #0b1220; border: none; border-radius: 8px; padding: 10px 12px; font-weight: 600; cursor: pointer; } button.secondary { background: transparent; color: var(--text); border: 1px solid var(--border); } ul { list-style: none; padding: 0; margin: 0; } li + li { margin-top: 8px; } .pill { display: inline-flex; align-items: center; gap: 6px; padding: 4px 8px; border-radius: 999px; background: rgba(14, 165, 233, 0.12); color: var(--text); border: 1px solid var(--border); font-size: 12px; } .history-item { display: flex; justify-content: space-between; border: 1px dashed var(--border); padding: 10px; border-radius: 8px; } .muted { color: var(--muted); font-size: 12px; } .theme-toggle { display: inline-flex; align-items: center; gap: 6px; background: transparent; border: 1px solid var(--border); border-radius: 999px; padding: 6px 10px; color: var(--text); font-weight: 600; } .stack { display: grid; gap: 10px; } pre { background: #0b1220; color: #e5e7eb; border-radius: 8px; padding: 10px; overflow: auto; } .output { min-height: 80px; background: var(--panel); border: 1px solid var(--border); border-radius: 8px; padding: 10px; font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace; white-space: pre-wrap; } </style> </head> <body> <header> <div> <h1>Read-only Database MCP</h1> <div class="muted">Save DB configs, hand them to your agent by name, and audit history.</div> </div> <button id="themeToggle" class="theme-toggle">Toggle theme</button> </header> <main> <section class="card"> <h2>Database configuration</h2> <div class="stack"> <div class="row"> <div> <label for="name">Friendly name</label> <input id="name" placeholder="analytics-prod" /> </div> <div> <label for="kind">Type</label> <select id="kind"> <option value="postgres">PostgreSQL</option> <option value="mysql">MySQL / MariaDB</option> </select> </div> </div> <div class="row"> <div> <label for="host">Host</label> <input id="host" placeholder="db.mycompany.com" /> </div> <div> <label for="port">Port</label> <input id="port" type="number" value="5432" /> </div> </div> <div class="row"> <div> <label for="user">User</label> <input id="user" placeholder="readonly" /> </div> <div> <label for="password">Password</label> <input id="password" type="password" /> </div> </div> <div> <label for="database">Database</label> <input id="database" placeholder="app_db" /> </div> <div> <label for="sslMode">SSL mode</label> <select id="sslMode"> <option value="disable">Disable</option> <option value="require">Require (verify cert)</option> <option value="no-verify">Require (allow self-signed)</option> </select> </div> <div class="row"> <button id="save">Save config</button> <button id="loadSelected" class="secondary">Load saved config</button> </div> <select id="savedConfigs"></select> </div> </section> <section class="card"> <h2>Read-only actions</h2> <div class="stack"> <div> <label for="activeDb">Use config</label> <select id="activeDb"></select> </div> <div class="row"> <div> <label for="tableName">Preview table</label> <input id="tableName" placeholder="users" /> </div> <div> <label for="limit">Rows</label> <input id="limit" type="number" value="20" /> </div> </div> <div class="row"> <button id="listTables">List tables</button> <button id="preview">Preview table</button> </div> <div> <label for="sql">Run SELECT</label> <textarea id="sql" placeholder="SELECT * FROM users LIMIT 10;"></textarea> </div> <button id="runSelect" class="secondary">Run</button> <div id="output" class="output"></div> </div> </section> <section class="card"> <h2>Agent history</h2> <div id="history" class="stack"></div> </section> </main> <script> const nameEl = document.getElementById("name"); const kindEl = document.getElementById("kind"); const hostEl = document.getElementById("host"); const portEl = document.getElementById("port"); const userEl = document.getElementById("user"); const passwordEl = document.getElementById("password"); const databaseEl = document.getElementById("database"); const sslModeEl = document.getElementById("sslMode"); const savedConfigsEl = document.getElementById("savedConfigs"); const activeDbEl = document.getElementById("activeDb"); const tableNameEl = document.getElementById("tableName"); const limitEl = document.getElementById("limit"); const outputEl = document.getElementById("output"); const historyEl = document.getElementById("history"); async function fetchJson(path, options) { const res = await fetch(path, options); const data = await res.json(); if (!res.ok) { throw new Error(data.error || "Request failed"); } return data; } async function refreshConfigs() { const configs = await fetchJson("/api/configs"); savedConfigsEl.innerHTML = ""; activeDbEl.innerHTML = ""; configs.forEach((c) => { const opt = document.createElement("option"); opt.value = c.name; opt.textContent = c.name; savedConfigsEl.appendChild(opt); activeDbEl.appendChild(opt.cloneNode(true)); }); } async function refreshHistory() { const history = await fetchJson("/api/history"); historyEl.innerHTML = ""; if (!history.length) { historyEl.innerHTML = '<div class="muted">No history yet.</div>'; return; } history.forEach((item) => { const node = document.createElement("div"); node.className = "history-item"; node.innerHTML = ` <div> <div><strong>${item.dbName}</strong></div> <div class="muted">${item.tables.join(", ") || "No tables recorded"}</div> </div> <div class="muted">${new Date(item.lastUsed).toLocaleString()}</div> `; historyEl.appendChild(node); }); } document.getElementById("save").onclick = async () => { const payload = { name: nameEl.value.trim(), kind: kindEl.value, host: hostEl.value.trim(), port: Number(portEl.value), user: userEl.value.trim(), password: passwordEl.value, database: databaseEl.value.trim(), sslMode: sslModeEl.value, }; try { await fetchJson("/api/configs", { method: "POST", body: JSON.stringify(payload) }); await refreshConfigs(); outputEl.textContent = `Saved config "${payload.name}".`; } catch (err) { outputEl.textContent = `Save failed: ${err.message}`; } }; document.getElementById("loadSelected").onclick = () => { const selected = savedConfigsEl.value; if (!selected) return; fetchJson("/api/configs").then((configs) => { const cfg = configs.find((c) => c.name === selected); if (!cfg) return; nameEl.value = cfg.name; kindEl.value = cfg.kind; hostEl.value = cfg.host; portEl.value = cfg.port; userEl.value = cfg.user; passwordEl.value = cfg.password; databaseEl.value = cfg.database; if (cfg.sslMode) { sslModeEl.value = cfg.sslMode; } else if (typeof cfg.ssl === "boolean") { sslModeEl.value = cfg.ssl ? "no-verify" : "disable"; } }); }; document.getElementById("listTables").onclick = async () => { const db = activeDbEl.value; if (!db) return; try { const tables = await fetchJson(`/api/tables?db=${encodeURIComponent(db)}`); outputEl.textContent = JSON.stringify(tables, null, 2); await refreshHistory(); } catch (err) { outputEl.textContent = `Error: ${err.message}`; } }; document.getElementById("preview").onclick = async () => { const db = activeDbEl.value; if (!db || !tableNameEl.value) return; try { const data = await fetchJson( `/api/preview?db=${encodeURIComponent(db)}&table=${encodeURIComponent(tableNameEl.value)}&limit=${encodeURIComponent(limitEl.value)}`, ); outputEl.textContent = JSON.stringify(data, null, 2); await refreshHistory(); } catch (err) { outputEl.textContent = `Error: ${err.message}`; } }; document.getElementById("runSelect").onclick = async () => { const db = activeDbEl.value; if (!db || !document.getElementById("sql").value.trim()) return; try { const rows = await fetchJson("/api/select", { method: "POST", body: JSON.stringify({ db, sql: document.getElementById("sql").value }), }); outputEl.textContent = JSON.stringify(rows, null, 2); await refreshHistory(); } catch (err) { outputEl.textContent = `Error: ${err.message}`; } }; const themeToggle = document.getElementById("themeToggle"); function setTheme(value) { document.documentElement.dataset.theme = value; localStorage.setItem("theme", value); themeToggle.textContent = value === "dark" ? "Switch to light" : "Switch to dark"; } themeToggle.onclick = () => { const next = document.documentElement.dataset.theme === "dark" ? "light" : "dark"; setTheme(next); }; setTheme(localStorage.getItem("theme") || "light"); refreshConfigs(); refreshHistory(); </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/Crei03/mcp-db'

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