Skip to main content
Glama

MCPDB - Database Access MCP Server

by jantuitman
McpController.php11.9 kB
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use Illuminate\Http\Response; use Illuminate\Support\Str; use Illuminate\Support\Facades\Cache; use App\Models\SubjectConnection; class McpController extends Controller { public function handle(Request $request) { if ($request->isMethod('GET')) { return $this->handleSseStream($request); } if ($request->isMethod('POST')) { return $this->handleJsonRpcMessage($request); } return response()->json(['error' => 'Method not allowed'], 405); } private function handleJsonRpcMessage(Request $request) { $jsonRpc = $request->json()->all(); // Valideer JSON-RPC format if (!isset($jsonRpc['jsonrpc']) || $jsonRpc['jsonrpc'] !== '2.0') { return $this->errorResponse(-32600, 'Invalid Request'); } $method = $jsonRpc['method'] ?? null; $id = $jsonRpc['id'] ?? null; switch ($method) { case 'initialize': return $this->handleInitialize($jsonRpc); case 'tools/list': return $this->handleToolsList($jsonRpc); case 'tools/call': return $this->handleToolsCall($jsonRpc); case 'resources/list': return $this->handleResourcesList($jsonRpc); case 'prompts/list': return $this->handlePromptsList($jsonRpc); case 'notifications/cancelled': return $this->handleCancelled($jsonRpc); default: return $this->errorResponse(-32601, 'Method not found', $id); } } private function handleInitialize($jsonRpc) { $sessionId = Str::uuid(); // Store session $this->storeSession($sessionId); // Use the same protocol version as the client $clientProtocolVersion = $jsonRpc['params']['protocolVersion'] ?? '2024-11-05'; $response = [ 'jsonrpc' => '2.0', 'id' => $jsonRpc['id'], 'result' => [ 'protocolVersion' => $clientProtocolVersion, 'capabilities' => [ 'tools' => [ 'listChanged' => true ], 'resources' => [ 'subscribe' => false, 'listChanged' => false ], 'prompts' => [ 'listChanged' => false ] ], 'serverInfo' => [ 'name' => 'MCPDB Laravel Server', 'version' => '1.0.0' ] ] ]; return response()->json($response) ->header('Mcp-Session-Id', $sessionId); } private function handleToolsList($jsonRpc) { $response = [ 'jsonrpc' => '2.0', 'id' => $jsonRpc['id'], 'result' => [ 'tools' => [ [ 'name' => 'list_connections', 'description' => 'Lists all the database connections that can be queried using the MCPDB tool.', 'inputSchema' => [ 'type' => 'object', 'properties' => (object)[], 'required' => [] ] ], [ 'name' => 'execute_sql', 'description' => 'Executes SQL query on a specified database connection. the name should be coming from list_connections.', 'inputSchema' => [ 'type' => 'object', 'properties' => [ 'connection_name' => [ 'type' => 'string', 'description' => 'Name of the database connection to use' ], 'sql' => [ 'type' => 'string', 'description' => 'SQL query to execute' ] ], 'required' => ['connection_name', 'sql'] ] ] ] ] ]; return response()->json($response); } private function handleToolsCall($jsonRpc) { $params = $jsonRpc['params'] ?? []; $toolName = $params['name'] ?? null; $arguments = $params['arguments'] ?? []; switch ($toolName) { case 'list_connections': return $this->handleListConnections($jsonRpc); case 'execute_sql': return $this->handleExecuteSql($jsonRpc, $arguments); default: return $this->errorResponse(-32601, 'Tool not found', $jsonRpc['id']); } } private function handleListConnections($jsonRpc) { try { $connections = SubjectConnection::where('is_active', true) ->select('connection_name', 'driver', 'host', 'port', 'database', 'description') ->get(); $connectionList = $connections->map(function ($conn) { return [ 'name' => $conn->connection_name, 'driver' => $conn->driver, 'host' => $conn->host, 'port' => $conn->port, 'database' => $conn->database, 'description' => $conn->description ]; })->toArray(); $response = [ 'jsonrpc' => '2.0', 'id' => $jsonRpc['id'], 'result' => [ 'content' => [ [ 'type' => 'text', 'text' => json_encode($connectionList, JSON_PRETTY_PRINT) ] ], 'isError' => false ] ]; return response()->json($response); } catch (\Exception $e) { return $this->errorResponse(-32603, 'Internal error: ' . $e->getMessage(), $jsonRpc['id']); } } private function handleExecuteSql($jsonRpc, $arguments) { try { // Validate required parameters if (!isset($arguments['connection_name']) || !isset($arguments['sql'])) { return $this->errorResponse(-32602, 'Missing required parameters', $jsonRpc['id']); } $connectionName = $arguments['connection_name']; $sql = $arguments['sql']; // Find the connection $connection = SubjectConnection::where('connection_name', $connectionName) ->where('is_active', true) ->first(); if (!$connection) { return $this->errorResponse(-32602, "Connection '{$connectionName}' not found or inactive", $jsonRpc['id']); } // Execute the query $result = $connection->executeQuery($sql); // Format the result if (is_array($result) && count($result) > 0) { // SELECT-type query with results $output = "Query executed successfully. Results:\n\n"; $output .= json_encode($result, JSON_PRETTY_PRINT); } elseif (is_array($result) && count($result) === 0) { // SELECT-type query with no results $output = "Query executed successfully. No results returned."; } else { // Non-SELECT query (INSERT, UPDATE, DELETE, etc.) $output = "Query executed successfully."; } $response = [ 'jsonrpc' => '2.0', 'id' => $jsonRpc['id'], 'result' => [ 'content' => [ [ 'type' => 'text', 'text' => $output ] ], 'isError' => false ] ]; return response()->json($response); } catch (\Exception $e) { return $this->errorResponse(-32603, 'SQL execution error: ' . $e->getMessage(), $jsonRpc['id']); } } private function handleResourcesList($jsonRpc) { // Return empty resources list - we don't provide any resources currently $response = [ 'jsonrpc' => '2.0', 'id' => $jsonRpc['id'], 'result' => [ 'resources' => [] ] ]; return response()->json($response); } private function handlePromptsList($jsonRpc) { // Return empty prompts list - we don't provide any prompts currently $response = [ 'jsonrpc' => '2.0', 'id' => $jsonRpc['id'], 'result' => [ 'prompts' => [] ] ]; return response()->json($response); } private function handleCancelled($jsonRpc) { // Handle client disconnect/cancellation return response()->json(['status' => 'acknowledged'], 202); } private function handleSseStream(Request $request) { // Set SSE headers $headers = [ 'Content-Type' => 'text/event-stream', 'Cache-Control' => 'no-cache', 'Connection' => 'keep-alive', 'Access-Control-Allow-Origin' => '*', 'Access-Control-Allow-Headers' => 'Cache-Control', ]; return response()->stream(function () { // Send initial connection event echo "event: connect\n"; echo "data: {\"type\":\"connect\",\"message\":\"MCP Server connected\"}\n\n"; ob_flush(); flush(); // Keep connection alive $counter = 0; while ($counter < 3600) { // Run for 1 hour max echo "event: ping\n"; echo "data: {\"type\":\"ping\",\"timestamp\":\"" . now()->toISOString() . "\"}\n\n"; ob_flush(); flush(); sleep(30); // Ping every 30 seconds $counter += 30; // Check if client disconnected if (connection_aborted()) { break; } } }, 200, $headers); } private function validateSession(Request $request) { $sessionId = $request->header('Mcp-Session-Id'); if (!$sessionId) { return false; } // Check of session bestaat in cache return Cache::has("mcp_session_{$sessionId}"); } private function storeSession($sessionId) { // Sla session op voor 24 uur Cache::put("mcp_session_{$sessionId}", [ 'created_at' => now(), 'last_activity' => now() ], 86400); } private function errorResponse($code, $message, $id = null, $data = null) { $error = [ 'code' => $code, 'message' => $message ]; if ($data !== null) { $error['data'] = $data; } return response()->json([ 'jsonrpc' => '2.0', 'id' => $id, 'error' => $error ]); } public function health() { return response()->json([ 'status' => 'healthy', 'timestamp' => now()->toISOString(), 'server' => 'Laravel MCP Server', 'version' => '1.0.0' ]); } }

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/jantuitman/mcpdb'

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