Skip to main content
Glama

Database MCP Server

index.ts8.68 kB
import { Hono } from "hono"; import { cors } from "hono/cors"; import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import * as pg from "pg"; import { renderDatabaseCredentialsForm, parseDatabaseCredentials, validateDatabaseCredentials, type DatabaseCredentials, } from "./database-form.js"; import { DatabaseTools } from "./tools/index.js"; interface Env { OAUTH_KV: KVNamespace; } const app = new Hono<{ Bindings: Env }>(); // Enable CORS for all routes app.use("/*", cors({ origin: "*", allowMethods: ["GET", "POST", "OPTIONS"], allowHeaders: ["Content-Type", "Authorization"], })); // Landing page with database connection form app.get("/", async (c) => { const html = ` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Database MCP Server</title> <style> body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; background: #f5f5f5; } .container { background: white; padding: 40px; border-radius: 12px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); } h1 { color: #333; } .info { background: #e3f2fd; padding: 20px; border-radius: 8px; margin: 20px 0; border-left: 4px solid #2196f3; } .btn { display: inline-block; background: #2196f3; color: white; padding: 12px 24px; text-decoration: none; border-radius: 6px; margin: 10px 0; } .btn:hover { background: #1976d2; } code { background: #f5f5f5; padding: 2px 6px; border-radius: 4px; font-family: 'Monaco', 'Courier New', monospace; } </style> </head> <body> <div class="container"> <h1>🗄️ Database MCP Server</h1> <p>A remote Model Context Protocol server for PostgreSQL and Supabase databases.</p> <div class="info"> <h3>🚀 Quick Start</h3> <ol> <li>Click "Connect Database" below</li> <li>Enter your database credentials</li> <li>Get your MCP endpoint URL</li> <li>Use the endpoint in your AI assistant (Claude, Cursor, etc.)</li> </ol> </div> <a href="/connect" class="btn">Connect Database</a> <h3>📋 Supported Databases</h3> <ul> <li><strong>Supabase</strong> - Use your project's database URL</li> <li><strong>PostgreSQL</strong> - Any PostgreSQL 12+ database</li> <li><strong>AWS RDS</strong> - PostgreSQL instances</li> <li><strong>Google Cloud SQL</strong> - PostgreSQL instances</li> <li><strong>Azure Database</strong> - PostgreSQL instances</li> </ul> <h3>🔧 Available Tools</h3> <ul> <li><code>list_tables</code> - List all tables in specified schemas</li> <li><code>list_extensions</code> - List PostgreSQL extensions</li> <li><code>list_migrations</code> - List applied migrations</li> <li><code>apply_migration</code> - Apply DDL migrations</li> <li><code>execute_sql</code> - Execute SQL queries and updates</li> </ul> <div class="info"> <strong>🔒 Security:</strong> Your database credentials are encrypted and only used for your session. No data is stored permanently on our servers. </div> </div> </body> </html>`; return c.html(html); }); // Database connection form app.get("/connect", async (c) => { return renderDatabaseCredentialsForm(c.req.raw, {} as any); }); // Handle database connection setup app.post("/connect", async (c) => { try { const formData = await c.req.formData(); // Parse and validate database credentials const dbCredentials = parseDatabaseCredentials(formData); const validationErrors = validateDatabaseCredentials(dbCredentials); if (validationErrors.length > 0) { return c.html(` <h2>Validation Errors</h2> <ul>${validationErrors.map(err => `<li>${err}</li>`).join('')}</ul> <a href="/connect">← Go Back</a> `); } // Test database connection const { host, port, database, user, password } = dbCredentials; const connectionString = `postgresql://${encodeURIComponent(user)}:${encodeURIComponent(password)}@${host}:${port}/${database}?sslmode=prefer`; const testPool = new pg.Pool({ connectionString, max: 1, connectionTimeoutMillis: 5000, }); try { const client = await testPool.connect(); await client.query('SELECT 1'); client.release(); await testPool.end(); // Generate a simple session token const sessionToken = generateSessionToken(dbCredentials); // Store credentials in KV (encrypted) await c.env.OAUTH_KV.put( `session:${sessionToken}`, JSON.stringify(dbCredentials), { expirationTtl: 24 * 60 * 60 } // 24 hours ); const mcpEndpoint = `${new URL(c.req.url).origin}/mcp/${sessionToken}`; return c.html(` <div style="max-width: 600px; margin: 0 auto; padding: 20px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;"> <h2>✅ Connection Successful!</h2> <p>Your database connection has been established successfully.</p> <div style="background: #e8f5e8; padding: 20px; border-radius: 8px; margin: 20px 0;"> <h3>🔗 Your MCP Endpoint:</h3> <code style="background: white; padding: 10px; display: block; border-radius: 4px; word-break: break-all;">${mcpEndpoint}</code> </div> <div style="background: #f0f8ff; padding: 20px; border-radius: 8px; margin: 20px 0;"> <h3>📱 How to Use:</h3> <ol> <li>Copy the MCP endpoint URL above</li> <li>Add it to your AI assistant (Claude, Cursor, etc.)</li> <li>Start querying your database with natural language!</li> </ol> </div> <p><strong>Session expires in 24 hours.</strong> You can reconnect anytime using the same process.</p> <a href="/" style="display: inline-block; background: #2196f3; color: white; padding: 12px 24px; text-decoration: none; border-radius: 6px;">← Back to Home</a> </div> `); } catch (dbError) { await testPool.end(); return c.html(` <h2>❌ Database Connection Failed</h2> <p>Error: ${dbError instanceof Error ? dbError.message : 'Unknown error'}</p> <a href="/connect">← Try Again</a> `); } } catch (error) { return c.html(` <h2>❌ Server Error</h2> <p>Error: ${error instanceof Error ? error.message : 'Unknown error'}</p> <a href="/connect">← Try Again</a> `); } }); // MCP Server endpoint with session token app.all("/mcp/:sessionToken", async (c) => { const sessionToken = c.req.param("sessionToken"); // Retrieve credentials from KV const credentialsJson = await c.env.OAUTH_KV.get(`session:${sessionToken}`); if (!credentialsJson) { return c.text("Invalid or expired session", 401); } const dbCredentials = JSON.parse(credentialsJson) as DatabaseCredentials; // Create MCP server with user's database credentials const server = new McpServer({ name: "Database MCP Server", version: "1.0.0", }); // Set up database connection const { host, port, database, user, password } = dbCredentials; const connectionString = `postgresql://${encodeURIComponent(user)}:${encodeURIComponent(password)}@${host}:${port}/${database}?sslmode=prefer`; const pool = new pg.Pool({ connectionString, max: 10, idleTimeoutMillis: 30000, connectionTimeoutMillis: 2000, }); // Register database tools DatabaseTools(server, { pool }); // For now, return a simple response indicating the MCP server is ready // In a full implementation, you would integrate with the MCP transport layer return c.json({ success: true, message: "MCP Server initialized successfully", endpoint: `/mcp/${sessionToken}`, tools: [ "list_tables", "list_extensions", "list_migrations", "apply_migration", "execute_sql" ] }); }); function generateSessionToken(credentials: DatabaseCredentials): string { // Generate a secure random token const timestamp = Date.now().toString(); const random = Math.random().toString(36).substring(2); const hash = btoa(`${credentials.connectionName}-${timestamp}-${random}`).replace(/[^a-zA-Z0-9]/g, ''); return hash.substring(0, 32); } export default app;

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/EmilyThaHuman/database-mcp'

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