diff --git a/app/Mcp/Resources/DatabaseQueries.php b/app/Mcp/Resources/DatabaseQueries.php new file mode 100644 index 0000000..9173637 --- /dev/null +++ b/app/Mcp/Resources/DatabaseQueries.php @@ -0,0 +1,22 @@ +render()); + } +} diff --git a/app/Mcp/Servers/FiveMServer.php b/app/Mcp/Servers/FiveMServer.php index d9dab1f..94540bf 100644 --- a/app/Mcp/Servers/FiveMServer.php +++ b/app/Mcp/Servers/FiveMServer.php @@ -9,11 +9,16 @@ use App\Mcp\Prompts\DebugIssues; use App\Mcp\Prompts\OptimizePerformance; use App\Mcp\Resources\BestPractices; use App\Mcp\Resources\CodeSnippets; +use App\Mcp\Resources\DatabaseQueries; use App\Mcp\Resources\FrameworkComparison; use App\Mcp\Tools\GenerateManifest; use App\Mcp\Tools\GenerateResourceBoilerplate; +use App\Mcp\Tools\GetCOXEventReference; +use App\Mcp\Tools\GetCOXFunction; use App\Mcp\Tools\GetEventReference; use App\Mcp\Tools\GetNativeFunction; +use App\Mcp\Tools\GetQBCoreEventReference; +use App\Mcp\Tools\GetQBCoreFunction; use App\Mcp\Tools\SearchFiveMDocs; use Laravel\Mcp\Server; use Laravel\Mcp\Server\Attributes\Instructions; @@ -43,12 +48,17 @@ class FiveMServer extends Server GenerateManifest::class, GetEventReference::class, GenerateResourceBoilerplate::class, + GetQBCoreEventReference::class, + GetQBCoreFunction::class, + GetCOXEventReference::class, + GetCOXFunction::class, ]; protected array $resources = [ CodeSnippets::class, BestPractices::class, FrameworkComparison::class, + DatabaseQueries::class, ]; protected array $prompts = [ diff --git a/app/Mcp/Tools/GetCOXEventReference.php b/app/Mcp/Tools/GetCOXEventReference.php new file mode 100644 index 0000000..f0a2a8f --- /dev/null +++ b/app/Mcp/Tools/GetCOXEventReference.php @@ -0,0 +1,213 @@ +get('event_name'); + $eventType = $request->get('event_type', 'all'); + $language = $request->get('language', 'lua'); + + if ($eventName) { + return $this->getEventInfo($eventName, $language); + } + + return $this->listEvents($eventType, $language); + } + + /** + * Get specific event information. + */ + protected function getEventInfo(string $eventName, string $language): Response + { + $events = $this->getCOXEventsDatabase(); + + foreach ($events as $event) { + if (strtolower($event['name']) === strtolower($eventName)) { + return Response::text($this->formatEventInfo($event, $language)); + } + } + + return Response::text(sprintf("COX event '%s' not found in the database.", $eventName)); + } + + /** + * List events by type. + */ + protected function listEvents(string $type, string $language): Response + { + $events = $this->getCOXEventsDatabase(); + + if ($type !== 'all') { + $events = array_filter($events, fn ($e) => $e['type'] === $type); + } + + $eventsByType = []; + foreach ($events as $event) { + $eventsByType[$event['type']][] = $event; + } + + return Response::text( + view('mcp.cox-event-list', [ + 'eventsByType' => $eventsByType, + ])->render() + ); + } + + /** + * Format event information. + */ + protected function formatEventInfo(array $event, string $language): string + { + return view('mcp.cox-event-reference', [ + 'event' => $event, + 'language' => $language, + ])->render(); + } + + /** + * Get COX events database. + */ + protected function getCOXEventsDatabase(): array + { + return [ + // Query Events + [ + 'name' => 'coxMySQL:queryCompleted', + 'type' => 'query', + 'description' => 'Triggered when a query completes (success or failure)', + 'parameters' => [ + ['name' => 'sql', 'type' => 'string', 'description' => 'The SQL query that was executed'], + ['name' => 'result', 'type' => 'table|boolean', 'description' => 'Query result or false if failed'], + ], + 'lua_example' => "RegisterNetEvent('coxMySQL:queryCompleted', function(sql, result)\n if result then\n print('Query succeeded')\n else\n print('Query failed: ' .. sql)\n end\nend)", + 'js_example' => "RegisterNetEvent('coxMySQL:queryCompleted', (sql, result) => {\n if (result) {\n console.log('Query succeeded');\n } else {\n console.log('Query failed: ' + sql);\n }\n});", + ], + [ + 'name' => 'coxMySQL:insertCompleted', + 'type' => 'query', + 'description' => 'Triggered when an insert query completes', + 'parameters' => [ + ['name' => 'insertId', 'type' => 'int', 'description' => 'ID of the inserted row'], + ['name' => 'affectedRows', 'type' => 'int', 'description' => 'Number of rows affected'], + ], + 'lua_example' => "RegisterNetEvent('coxMySQL:insertCompleted', function(insertId, affectedRows)\n print('Inserted ID: ' .. insertId .. ', Rows affected: ' .. affectedRows)\nend)", + 'js_example' => "RegisterNetEvent('coxMySQL:insertCompleted', (insertId, affectedRows) => {\n console.log(`Inserted ID: \${insertId}, Rows affected: \${affectedRows}`);\n});", + ], + + // Connection Events + [ + 'name' => 'coxMySQL:connected', + 'type' => 'connection', + 'description' => 'Triggered when the database connection is established', + 'parameters' => [], + 'lua_example' => "RegisterNetEvent('coxMySQL:connected', function()\n print('Database connected successfully!')\nend)", + 'js_example' => "RegisterNetEvent('coxMySQL:connected', () => {\n console.log('Database connected successfully!');\n});", + ], + [ + 'name' => 'coxMySQL:disconnected', + 'type' => 'connection', + 'description' => 'Triggered when the database connection closes', + 'parameters' => [], + 'lua_example' => "RegisterNetEvent('coxMySQL:disconnected', function()\n print('Database disconnected')\nend)", + 'js_example' => "RegisterNetEvent('coxMySQL:disconnected', () => {\n console.log('Database disconnected');\n});", + ], + [ + 'name' => 'coxMySQL:connectionError', + 'type' => 'connection', + 'description' => 'Triggered when a database connection error occurs', + 'parameters' => [ + ['name' => 'error', 'type' => 'string', 'description' => 'Error message'], + ], + 'lua_example' => "RegisterNetEvent('coxMySQL:connectionError', function(error)\n print('Connection error: ' .. error)\nend)", + 'js_example' => "RegisterNetEvent('coxMySQL:connectionError', (error) => {\n console.log('Connection error: ' + error);\n});", + ], + + // Transaction Events + [ + 'name' => 'coxMySQL:transactionBegun', + 'type' => 'transaction', + 'description' => 'Triggered when a database transaction begins', + 'parameters' => [], + 'lua_example' => "RegisterNetEvent('coxMySQL:transactionBegun', function()\n print('Transaction started')\nend)", + 'js_example' => "RegisterNetEvent('coxMySQL:transactionBegun', () => {\n console.log('Transaction started');\n});", + ], + [ + 'name' => 'coxMySQL:transactionCommitted', + 'type' => 'transaction', + 'description' => 'Triggered when a transaction is successfully committed', + 'parameters' => [], + 'lua_example' => "RegisterNetEvent('coxMySQL:transactionCommitted', function()\n print('Transaction committed successfully')\nend)", + 'js_example' => "RegisterNetEvent('coxMySQL:transactionCommitted', () => {\n console.log('Transaction committed successfully');\n});", + ], + [ + 'name' => 'coxMySQL:transactionRolledBack', + 'type' => 'transaction', + 'description' => 'Triggered when a transaction is rolled back', + 'parameters' => [], + 'lua_example' => "RegisterNetEvent('coxMySQL:transactionRolledBack', function()\n print('Transaction rolled back')\nend)", + 'js_example' => "RegisterNetEvent('coxMySQL:transactionRolledBack', () => {\n console.log('Transaction rolled back');\n});", + ], + + // Error Events + [ + 'name' => 'coxMySQL:queryError', + 'type' => 'error', + 'description' => 'Triggered when a query execution fails', + 'parameters' => [ + ['name' => 'sql', 'type' => 'string', 'description' => 'The SQL that failed'], + ['name' => 'errorMessage', 'type' => 'string', 'description' => 'MySQL error message'], + ], + 'lua_example' => "RegisterNetEvent('coxMySQL:queryError', function(sql, errorMessage)\n print('Query error: ' .. errorMessage)\n print('Failed query: ' .. sql)\nend)", + 'js_example' => "RegisterNetEvent('coxMySQL:queryError', (sql, errorMessage) => {\n console.log('Query error: ' + errorMessage);\n console.log('Failed query: ' + sql);\n});", + ], + [ + 'name' => 'coxMySQL:slowQuery', + 'type' => 'error', + 'description' => 'Triggered when a query exceeds the configured timeout', + 'parameters' => [ + ['name' => 'sql', 'type' => 'string', 'description' => 'The slow SQL query'], + ['name' => 'duration', 'type' => 'float', 'description' => 'Query duration in milliseconds'], + ], + 'lua_example' => "RegisterNetEvent('coxMySQL:slowQuery', function(sql, duration)\n print('Slow query (' .. duration .. 'ms): ' .. sql)\nend)", + 'js_example' => "RegisterNetEvent('coxMySQL:slowQuery', (sql, duration) => {\n console.log(`Slow query (\${duration}ms): \${sql}`);\n});", + ], + ]; + } + + /** + * Get the tool's input schema. + * + * @return array + */ + public function schema(JsonSchema $schema): array + { + return [ + 'event_name' => $schema + ->string() + ->description('Specific COX event name to look up (optional, leave empty to list all events)'), + 'event_type' => $schema + ->string() + ->enum(['all', 'query', 'connection', 'transaction', 'error']) + ->description('Filter events by type') + ->default('all'), + 'language' => $schema + ->string() + ->enum(['lua', 'js']) + ->description('The scripting language for code examples') + ->default('lua'), + ]; + } +} diff --git a/app/Mcp/Tools/GetCOXFunction.php b/app/Mcp/Tools/GetCOXFunction.php new file mode 100644 index 0000000..89eb13d --- /dev/null +++ b/app/Mcp/Tools/GetCOXFunction.php @@ -0,0 +1,230 @@ +get('function_name'); + $language = $request->get('language', 'lua'); + + $function = $this->findFunction($functionName); + + if (! $function) { + return Response::text(sprintf("COX function '%s' not found. Check COX documentation at https://coxdocs.dev/", $functionName)); + } + + $output = $this->formatFunctionInfo($function, $language); + + return Response::text($output); + } + + /** + * Find a COX function. + */ + protected function findFunction(string $name): ?array + { + $functions = [ + // Query Methods + 'MySQL.query' => [ + 'namespace' => 'Query', + 'description' => 'Execute a MySQL query with optional parameters (prepared statement)', + 'side' => 'server', + 'parameters' => [ + ['name' => 'sql', 'type' => 'string', 'description' => 'SQL query with ? placeholders for prepared statements'], + ['name' => 'params', 'type' => 'table|nil', 'description' => 'Array of parameters to bind to placeholders'], + ], + 'returns' => ['type' => 'boolean', 'description' => 'True if query succeeded, false otherwise'], + 'lua_example' => "MySQL.query('SELECT * FROM users WHERE id = ?', { 1 }, function(result)\n if result then\n for i = 1, #result do\n print(result[i].name)\n end\n end\nend)", + 'js_example' => "MySQL.query('SELECT * FROM users WHERE id = ?', [1], (result) => {\n if (result) {\n result.forEach(row => console.log(row.name));\n }\n});", + ], + 'MySQL.insert' => [ + 'namespace' => 'Query', + 'description' => 'Insert a row into the database and get the insert ID', + 'side' => 'server', + 'parameters' => [ + ['name' => 'tableName', 'type' => 'string', 'description' => 'Name of the table'], + ['name' => 'data', 'type' => 'table', 'description' => 'Table with column names as keys and values'], + ['name' => 'callback', 'type' => 'function|nil', 'description' => 'Optional callback function'], + ], + 'returns' => ['type' => 'void', 'description' => 'No return value'], + 'lua_example' => "MySQL.insert('users', {\n username = 'player',\n money = 500\n}, function(id)\n print('Inserted with ID: ' .. id)\nend)", + 'js_example' => "MySQL.insert('users', {\n username: 'player',\n money: 500\n}, (id) => {\n console.log('Inserted with ID: ' + id);\n});", + ], + 'MySQL.update' => [ + 'namespace' => 'Query', + 'description' => 'Update rows in the database where a condition matches', + 'side' => 'server', + 'parameters' => [ + ['name' => 'tableName', 'type' => 'string', 'description' => 'Name of the table'], + ['name' => 'data', 'type' => 'table', 'description' => 'Column names and new values'], + ['name' => 'conditions', 'type' => 'string|table', 'description' => 'WHERE clause or conditions table'], + ['name' => 'callback', 'type' => 'function|nil', 'description' => 'Optional callback function'], + ], + 'returns' => ['type' => 'void', 'description' => 'No return value'], + 'lua_example' => "MySQL.update('users', {\n money = 1000\n}, { id = 5 }, function(affectedRows)\n print('Updated ' .. affectedRows .. ' rows')\nend)", + 'js_example' => "MySQL.update('users', {\n money: 1000\n}, { id: 5 }, (affectedRows) => {\n console.log(`Updated \${affectedRows} rows`);\n});", + ], + 'MySQL.scalar' => [ + 'namespace' => 'Query', + 'description' => 'Get a single scalar value from the database (single result)', + 'side' => 'server', + 'parameters' => [ + ['name' => 'sql', 'type' => 'string', 'description' => 'SQL query'], + ['name' => 'params', 'type' => 'table|nil', 'description' => 'Query parameters'], + ['name' => 'callback', 'type' => 'function', 'description' => 'Callback with the scalar value'], + ], + 'returns' => ['type' => 'void', 'description' => 'Callback receives the value'], + 'lua_example' => "MySQL.scalar('SELECT COUNT(*) as count FROM users', {}, function(count)\n print('Total users: ' .. (count or 0))\nend)", + 'js_example' => "MySQL.scalar('SELECT COUNT(*) as count FROM users', [], (count) => {\n console.log('Total users: ' + (count || 0));\n});", + ], + 'MySQL.single' => [ + 'namespace' => 'Query', + 'description' => 'Get a single row from the database', + 'side' => 'server', + 'parameters' => [ + ['name' => 'sql', 'type' => 'string', 'description' => 'SQL query'], + ['name' => 'params', 'type' => 'table|nil', 'description' => 'Query parameters'], + ['name' => 'callback', 'type' => 'function', 'description' => 'Callback with the row data'], + ], + 'returns' => ['type' => 'void', 'description' => 'Callback receives a single row or nil'], + 'lua_example' => "MySQL.single('SELECT * FROM users WHERE id = ?', { 1 }, function(user)\n if user then\n print('User: ' .. user.username)\n end\nend)", + 'js_example' => "MySQL.single('SELECT * FROM users WHERE id = ?', [1], (user) => {\n if (user) {\n console.log('User: ' + user.username);\n }\n});", + ], + + // Transaction Methods + 'MySQL.transaction.begin' => [ + 'namespace' => 'Transaction', + 'description' => 'Begin a new database transaction', + 'side' => 'server', + 'parameters' => [], + 'returns' => ['type' => 'void', 'description' => 'No return value'], + 'lua_example' => "MySQL.transaction.begin()\nMySQL.update('users', { money = money - 500 }, { id = 1 })\nMySQL.update('users', { money = money + 500 }, { id = 2 })\nMySQL.transaction.commit()", + 'js_example' => "MySQL.transaction.begin();\nMySQL.update('users', { money: 'money - 500' }, { id: 1 });\nMySQL.update('users', { money: 'money + 500' }, { id: 2 });\nMySQL.transaction.commit();", + ], + 'MySQL.transaction.commit' => [ + 'namespace' => 'Transaction', + 'description' => 'Commit the current transaction', + 'side' => 'server', + 'parameters' => [ + ['name' => 'callback', 'type' => 'function|nil', 'description' => 'Optional callback after commit'], + ], + 'returns' => ['type' => 'void', 'description' => 'No return value'], + 'lua_example' => "MySQL.transaction.commit(function(success)\n if success then\n print('Transaction committed successfully')\n else\n print('Transaction commit failed')\n end\nend)", + 'js_example' => "MySQL.transaction.commit((success) => {\n if (success) {\n console.log('Transaction committed successfully');\n } else {\n console.log('Transaction commit failed');\n }\n});", + ], + 'MySQL.transaction.rollback' => [ + 'namespace' => 'Transaction', + 'description' => 'Rollback the current transaction', + 'side' => 'server', + 'parameters' => [ + ['name' => 'callback', 'type' => 'function|nil', 'description' => 'Optional callback after rollback'], + ], + 'returns' => ['type' => 'void', 'description' => 'No return value'], + 'lua_example' => "if error then\n MySQL.transaction.rollback(function(success)\n print('Transaction rolled back')\n end)\nend", + 'js_example' => "if (error) {\n MySQL.transaction.rollback((success) => {\n console.log('Transaction rolled back');\n });\n}", + ], + + // Connection Methods + 'MySQL.ready' => [ + 'namespace' => 'Connection', + 'description' => 'Check if the database connection is ready', + 'side' => 'server', + 'parameters' => [], + 'returns' => ['type' => 'boolean', 'description' => 'True if connection is ready'], + 'lua_example' => "if MySQL.ready then\n MySQL.query('SELECT 1', function(result)\n print('Database is connected!')\n end)\nend", + 'js_example' => "if (MySQL.ready) {\n MySQL.query('SELECT 1', (result) => {\n console.log('Database is connected!');\n });\n}", + ], + 'MySQL.getStatus' => [ + 'namespace' => 'Connection', + 'description' => 'Get the current MySQL connection status', + 'side' => 'server', + 'parameters' => [], + 'returns' => ['type' => 'string', 'description' => 'Status string (connected, connecting, disconnected, etc.)'], + 'lua_example' => "local status = MySQL.getStatus()\nprint('Connection status: ' .. status)", + 'js_example' => "const status = MySQL.getStatus();\nconsole.log('Connection status: ' + status);", + ], + + // Prepared Statements + 'MySQL.prepare' => [ + 'namespace' => 'Prepared', + 'description' => 'Prepare a query for execution with bound parameters (prevents SQL injection)', + 'side' => 'server', + 'parameters' => [ + ['name' => 'sql', 'type' => 'string', 'description' => 'SQL with ? placeholders'], + ['name' => 'params', 'type' => 'table', 'description' => 'Parameters to bind'], + ['name' => 'callback', 'type' => 'function', 'description' => 'Callback with results'], + ], + 'returns' => ['type' => 'void', 'description' => 'Callback receives prepared statement result'], + 'lua_example' => "MySQL.prepare('INSERT INTO logs (user_id, action) VALUES (?, ?)', { 5, 'joined' }, function(success)\n if success then\n print('Log inserted')\n end\nend)", + 'js_example' => "MySQL.prepare('INSERT INTO logs (user_id, action) VALUES (?, ?)', [5, 'joined'], (success) => {\n if (success) {\n console.log('Log inserted');\n }\n});", + ], + + // Async Methods + 'MySQL.async.query' => [ + 'namespace' => 'Async', + 'description' => 'Execute an async query (returns promise-like structure)', + 'side' => 'server', + 'parameters' => [ + ['name' => 'sql', 'type' => 'string', 'description' => 'SQL query'], + ['name' => 'params', 'type' => 'table|nil', 'description' => 'Query parameters'], + ], + 'returns' => ['type' => 'table', 'description' => 'Awaitable result table'], + 'lua_example' => "local result = MySQL.async.query('SELECT * FROM users WHERE banned = ?', { 0 })\nprint('Query completed')", + 'js_example' => "const result = MySQL.async.query('SELECT * FROM users WHERE banned = ?', [0]);\nconsole.log('Query completed');", + ], + ]; + + $nameLower = strtolower($name); + + foreach ($functions as $functionName => $functionData) { + if (strtolower($functionName) === $nameLower) { + return array_merge(['name' => $functionName], $functionData); + } + } + + return null; + } + + /** + * Format function info. + */ + protected function formatFunctionInfo(array $function, string $language): string + { + return view('mcp.cox-function', [ + 'function' => $function, + 'language' => $language, + ])->render(); + } + + /** + * Get the tool's input schema. + * + * @return array + */ + public function schema(JsonSchema $schema): array + { + return [ + 'function_name' => $schema + ->string() + ->description('The name of the COX function to look up (e.g., "MySQL.query", "MySQL.insert", "MySQL.transaction.begin")') + ->required(), + 'language' => $schema + ->string() + ->enum(['lua', 'js']) + ->description('The scripting language for code examples') + ->default('lua'), + ]; + } +} diff --git a/app/Mcp/Tools/SearchFiveMDocs.php b/app/Mcp/Tools/SearchFiveMDocs.php index a8ad12f..257c16e 100644 --- a/app/Mcp/Tools/SearchFiveMDocs.php +++ b/app/Mcp/Tools/SearchFiveMDocs.php @@ -191,6 +191,36 @@ class SearchFiveMDocs extends Tool 'description' => 'Database transaction handling with COX MySQL library', 'keywords' => ['cox', 'mysql', 'transaction', 'commit', 'rollback'], ], + [ + 'title' => 'COX Docs - Prepared Statements', + 'url' => 'https://coxdocs.dev/prepared-statements', + 'description' => 'Prepared statements for secure SQL queries and SQL injection prevention', + 'keywords' => ['cox', 'mysql', 'prepared', 'statement', 'security', 'injection'], + ], + [ + 'title' => 'COX Docs - Connection Pooling', + 'url' => 'https://coxdocs.dev/connection-pooling', + 'description' => 'Connection pooling and management for optimal database performance', + 'keywords' => ['cox', 'mysql', 'connection', 'pool', 'pooling', 'performance'], + ], + [ + 'title' => 'COX Docs - Common Patterns', + 'url' => 'https://coxdocs.dev/patterns', + 'description' => 'Common database patterns (CRUD operations, async queries, error handling)', + 'keywords' => ['cox', 'mysql', 'pattern', 'crud', 'create', 'read', 'update', 'delete'], + ], + [ + 'title' => 'COX Docs - Debugging & Performance', + 'url' => 'https://coxdocs.dev/debugging', + 'description' => 'Debugging MySQL queries, performance optimization, and troubleshooting', + 'keywords' => ['cox', 'mysql', 'debug', 'performance', 'optimize', 'slow', 'error'], + ], + [ + 'title' => 'COX Docs - Best Practices', + 'url' => 'https://coxdocs.dev/best-practices', + 'description' => 'Database best practices and conventions for FiveM development', + 'keywords' => ['cox', 'mysql', 'best', 'practice', 'convention', 'standard'], + ], ], ]; diff --git a/resources/views/docs/documentation.blade.php b/resources/views/docs/documentation.blade.php index a9dcc31..a62d7f3 100644 --- a/resources/views/docs/documentation.blade.php +++ b/resources/views/docs/documentation.blade.php @@ -3,534 +3,589 @@ @section('title', 'Full Documentation - FiveM MCP Server') @section('content') -
-
-

FiveM Development MCP Server

+
+
+

FiveM Development MCP Server

-

- A comprehensive Model Context Protocol (MCP) server for FiveM (GTA5) development, built with Laravel MCP. - This server provides AI assistants with powerful tools to help you develop FiveM resources more efficiently. -

+

+ A comprehensive Model Context Protocol (MCP) server for FiveM (GTA5) development, built with Laravel MCP. + This server provides AI assistants with powerful tools to help you develop FiveM resources more efficiently. +

- -
-

Installation & Connection

-

Choose how you want to connect to the FiveM MCP server:

+ +
+

Installation & Connection

+

Choose how you want to connect to the FiveM MCP server:

- -
- -
+ + + +
- -
- -
-
-

- Easy setup! Connect to a hosted MCP server. This is the easiest option and works from anywhere without any local setup. -

+ +
+ +
+
+

+ Easy setup! Connect to a hosted MCP server. This is the easiest option and works from anywhere without any local setup. +

-

Claude Desktop Configuration:

-

Edit your configuration file:

-
    -
  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
  • -
  • Windows: %APPDATA%\Claude\claude_desktop_config.json
  • -
+

Claude Desktop Configuration:

+

Edit your configuration file:

+
    +
  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
  • +
  • Windows: %APPDATA%\Claude\claude_desktop_config.json
  • +
-

Add this configuration (replace with your actual server URL):

-
{
-      "mcpServers": {
-        "fivem": {
-          "url": "https://your-domain.com/fivem"
-        }
-      }
-    }
+

Add this configuration (replace with your actual server URL):

+
{
+          "mcpServers": {
+            "fivem": {
+              "url": "https://your-domain.com/fivem"
+            }
+          }
+        }
+
+ +
+

VSCode Configuration (Cline Extension):

+

If using VSCode with the Cline extension, edit your settings:

+
    +
  • Open VSCode Settings (JSON)
  • +
  • Add to your global or workspace settings.json
  • +
+ +
{
+          "mcp": {
+            "servers": {
+              "fivem": {
+                "url": "https://your-domain.com/fivem"
+              }
+            }
+          }
+        }
+
-
-

VSCode Configuration (Cline Extension):

-

If using VSCode with the Cline extension, edit your settings:

-
    -
  • Open VSCode Settings (JSON)
  • -
  • Add to your global or workspace settings.json
  • -
+ +
- - + -
-

- 💡 Tip: After configuring Claude Desktop, completely quit and restart the application for changes to take effect. -

-
- + +
+

Available Tools

- -
-

Available Tools

- - -
-

1. SearchFiveMDocs

-

Search FiveM, QBCore, and COX MySQL documentation for specific topics.

- -

Parameters:

-
    -
  • query (required): Search query (e.g., "events", - "natives", "mysql", "database")
  • -
  • category (optional): Filter by category (all, - scripting, natives, networking, resources, qbcore, coxdocs)
  • -
- -

Examples:

-
Search for QBCore callbacks and exports
-
How do I query the database with COX MySQL?
-
- - +
-

2. GetNativeFunction

-

Look up GTA5/FiveM native functions.

+

1. SearchFiveMDocs

+

Search FiveM, QBCore, and COX MySQL documentation for specific topics.

Parameters:

    -
  • function_name (required): Native function name - (e.g., "GetPlayerPed", "SetEntityCoords")
  • -
  • language (optional): Code example language (lua - or js)
  • -
- -

Common Native Examples:

-
    -
  • Common natives: GetPlayerPed, SetEntityCoords, GetEntityCoords
  • -
  • CFX natives: RegisterNetEvent, TriggerServerEvent, TriggerClientEvent
  • -
  • ESX: ESX.GetPlayerFromId
  • -
  • QBCore: QBCore.Functions.GetPlayer
  • -
-
- - -
-

3. GenerateManifest

-

Generate a fxmanifest.lua file.

- -

Parameters:

-
    -
  • resource_name (required): Resource name
  • -
  • author (optional): Author name
  • -
  • description (optional): Resource description
  • -
  • version (optional): Version number (default: - "1.0.0")
  • -
  • script_type (optional): lua or js (default: lua) -
  • -
  • include_client (optional): Include client scripts - (default: true)
  • -
  • include_server (optional): Include server scripts - (default: true)
  • -
  • framework (optional): standalone, esx, or qbcore - (default: standalone)
  • -
- -

Example:

-
Generate a manifest for an ESX resource called "my-shop"
-
- - -
-

4. GetEventReference

-

Get information about FiveM, ESX, and QBCore events.

- -

Parameters:

-
    -
  • event_name (optional): Specific event name to - look up
  • -
  • event_type (optional): Filter by type (all, core, - esx, qbcore)
  • -
  • language (optional): Code example language (lua - or js)
  • +
  • query (required): Search query (e.g., "events", + "natives", "mysql", "database")
  • +
  • category (optional): Filter by category (all, + scripting, natives, networking, resources, qbcore, coxdocs)

Examples:

-
Show me all core FiveM events
-
Look up the QBCore:Client:OnPlayerLoaded event
-
List all ESX events
-
+
Search for QBCore callbacks and exports
+
How do I query the database with COX MySQL?
+
- -
-

5. GenerateResourceBoilerplate

-

Generate complete resource boilerplate code.

+ +
+

2. GetNativeFunction

+

Look up GTA5/FiveM native functions.

-

Parameters:

-
    -
  • resource_name (required): Resource name
  • -
  • framework (optional): standalone, esx, or qbcore - (default: standalone)
  • -
  • script_type (optional): lua or js (default: lua) -
  • -
  • include_nui (optional): Include NUI files - (default: false)
  • -
+

Parameters:

+
    +
  • function_name (required): Native function name + (e.g., "GetPlayerPed", "SetEntityCoords")
  • +
  • language (optional): Code example language (lua + or js)
  • +
-

Example:

-
Generate boilerplate for a QBCore resource with NUI called "admin-panel"
-
+

Common Native Examples:

+
    +
  • Common natives: GetPlayerPed, SetEntityCoords, GetEntityCoords
  • +
  • CFX natives: RegisterNetEvent, TriggerServerEvent, TriggerClientEvent
  • +
  • ESX: ESX.GetPlayerFromId
  • +
  • QBCore: QBCore.Functions.GetPlayer
  • +
+
- -
-

6. GetQBCoreEventReference

-

Get information about QBCore framework events.

+ +
+

3. GenerateManifest

+

Generate a fxmanifest.lua file.

-

Parameters:

-
    -
  • event_name (optional): Specific event name to - look up
  • -
  • event_category (optional): Filter by category - (all, player, job, inventory, vehicle, money)
  • -
  • language (optional): Code example language (lua - or js)
  • -
+

Parameters:

+
    +
  • resource_name (required): Resource name
  • +
  • author (optional): Author name
  • +
  • description (optional): Resource description
  • +
  • version (optional): Version number (default: + "1.0.0")
  • +
  • script_type (optional): lua or js (default: lua) +
  • +
  • include_client (optional): Include client scripts + (default: true)
  • +
  • include_server (optional): Include server scripts + (default: true)
  • +
  • framework (optional): standalone, esx, or qbcore + (default: standalone)
  • +
-

Examples:

-
What's the QBCore:Client:OnPlayerLoaded event?
-
Show me all QBCore player events
-
List all QBCore inventory events
-
+

Example:

+
Generate a manifest for an ESX resource called "my-shop"
+
- -
-

7. GetQBCoreFunction

-

Look up QBCore functions and exports.

+ +
+

4. GetEventReference

+

Get information about FiveM, ESX, and QBCore events.

-

Parameters:

-
    -
  • function_name (required): QBCore function name - (e.g., "QBCore.Functions.GetPlayer", "AddMoney")
  • -
  • language (optional): Code example language (lua - or js)
  • -
+

Parameters:

+
    +
  • event_name (optional): Specific event name to + look up
  • +
  • event_type (optional): Filter by type (all, core, + esx, qbcore)
  • +
  • language (optional): Code example language (lua + or js)
  • +
-

Common QBCore Functions:

-
    -
  • Player: QBCore.Functions.GetPlayer, QBCore.Functions.GetPlayerData
  • -
  • Items: AddItem, RemoveItem, GetItemByName
  • -
  • Money: AddMoney, RemoveMoney, GetMoney
  • -
  • Jobs: SetJob, GetJob
  • -
  • Vehicles: AddVehicle, DeleteVehicle
  • -
+

Examples:

+
Show me all core FiveM events
+
Look up the QBCore:Client:OnPlayerLoaded event
+
List all ESX events
+
-

Example:

-
How do I use the AddMoney function in QBCore?
-
- + +
+

5. GenerateResourceBoilerplate

+

Generate complete resource boilerplate code.

- -
-

Available Resources

-

MCP Resources provide read-only reference documentation that AI assistants can access:

+

Parameters:

+
    +
  • resource_name (required): Resource name
  • +
  • framework (optional): standalone, esx, or qbcore + (default: standalone)
  • +
  • script_type (optional): lua or js (default: lua) +
  • +
  • include_nui (optional): Include NUI files + (default: false)
  • +
- -
-

1. Code Snippets

-

Common FiveM code patterns and snippets for quick reference.

-

URI: fivem://snippets/common

-

Includes patterns for distance checks, event handlers, threads with cleanup, database queries, and NUI callbacks in both Lua and JavaScript.

-
+

Example:

+
Generate boilerplate for a QBCore resource with NUI called "admin-panel"
+
- -
-

2. Best Practices

-

FiveM development best practices and optimization tips.

-

URI: fivem://guides/best-practices

-

Covers performance optimization, security best practices, code organization, resource management, testing, and common pitfalls.

-
+ +
+

6. GetQBCoreEventReference

+

Get information about QBCore framework events.

- -
-

3. Framework Comparison

-

Comparison of FiveM frameworks: Standalone, ESX, and QBCore.

-

URI: fivem://guides/framework-comparison

-

Complete comparison with pros/cons, code examples, common functions, and migration guides.

-
- +

Parameters:

+
    +
  • event_name (optional): Specific event name to + look up
  • +
  • event_category (optional): Filter by category + (all, player, job, inventory, vehicle, money)
  • +
  • language (optional): Code example language (lua + or js)
  • +
- -
-

Available Prompts

-

MCP Prompts are pre-built prompt templates for common tasks:

+

Examples:

+
What's the QBCore:Client:OnPlayerLoaded event?
+
Show me all QBCore player events
+
List all QBCore inventory events
+
- -
-

1. Create New Resource

-

Create a new FiveM resource with framework and language selection.

-

Parameters: resource_name, framework (standalone/esx/qbcore), language (lua/js)

-
+ +
+

7. GetQBCoreFunction

+

Look up QBCore functions and exports.

- -
-

2. Debug Issues

-

Debug common FiveM issues and errors with detailed analysis.

-

Parameters: issue (description), error_message (from console)

-
+

Parameters:

+
    +
  • function_name (required): QBCore function name + (e.g., "QBCore.Functions.GetPlayer", "AddMoney")
  • +
  • language (optional): Code example language (lua + or js)
  • +
- -
-

3. Optimize Performance

-

Optimize FiveM script performance with detailed suggestions.

-

Parameters: code (script to optimize), script_type (client/server/shared)

-
+

Common QBCore Functions:

+
    +
  • Player: QBCore.Functions.GetPlayer, QBCore.Functions.GetPlayerData
  • +
  • Items: AddItem, RemoveItem, GetItemByName
  • +
  • Money: AddMoney, RemoveMoney, GetMoney
  • +
  • Jobs: SetJob, GetJob
  • +
  • Vehicles: AddVehicle, DeleteVehicle
  • +
- -
-

4. Convert Language

-

Convert code between Lua and JavaScript with proper syntax.

-

Parameters: code, from (lua/js), to (lua/js)

-
+

Example:

+
How do I use the AddMoney function in QBCore?
+
+ +
+

8. GetCOXEventReference

+

Get information about COX MySQL events and callbacks.

- -
-

5. Add Framework Integration

-

Add ESX or QBCore framework integration to existing resource.

-

Parameters: code (current resource), current_framework, target_framework (esx/qbcore)

-
- +

Parameters:

+
    +
  • event_name (optional): Specific event name to + look up
  • +
  • event_type (optional): Filter by type (all, + query, connection, transaction, error)
  • +
  • language (optional): Code example language (lua + or js)
  • +
- -
-

Testing the Server

-

You can test the MCP server locally using the inspector:

-
php artisan mcp:inspector fivem
-

This will open the MCP Inspector in your browser where you can test all the tools interactively.

-
- - -
-

Development

- -

Adding New Tools

-

1. Create a new tool class in app/Mcp/Tools/:

- -
<?php
-
-    namespace App\Mcp\Tools;
-
-    use Illuminate\Contracts\JsonSchema\JsonSchema;
-    use Laravel\Mcp\Request;
-    use Laravel\Mcp\Response;
-    use Laravel\Mcp\Server\Attributes\Description;
-    use Laravel\Mcp\Server\Tool;
-
-    #[Description('Your tool description')]
-    class YourTool extends Tool
-    {
-        public function handle(Request $request): Response
-        {
-            // Tool logic here
-            return Response::text('Result');
-        }
-
-        public function schema(JsonSchema $schema): array
-        {
-            return [
-                'param' => $schema->string()->required(),
-            ];
-        }
-    }
- -

2. Register the tool in app/Mcp/Servers/FiveMServer.php:

- -
protected array $tools = [
-        // ... existing tools
-        \App\Mcp\Tools\YourTool::class,
-    ];
- -

Code Formatting

-

Format code with Laravel Pint:

-
vendor/bin/pint
-
- - -
-

Supported Frameworks

-
    -
  • Standalone: Basic FiveM resources without framework dependencies
  • -
  • ESX: ESX Framework (es_extended)
  • -
  • QBCore: QBCore Framework (qb-core)
  • -
- -

Scripting Languages

-

Both Lua and JavaScript are fully supported across all tools and generators.

-
- - -
-

Resources

- -
- - -
-

Contributing

-

Contributions are welcome! Areas for expansion:

-
    -
  • More native functions in the database
  • -
  • Additional framework support (vRP, etc.)
  • -
  • More event types
  • -
  • Advanced code generators
  • -
  • Database query builders
  • -
-
- - -
-

License

-

MIT License

-
-
+

Examples:

+
What are the COX MySQL connection events?
+
Show me all COX transaction events
+
How do I handle query errors in COX?
- + @endsection diff --git a/resources/views/mcp/cox-event-list.blade.php b/resources/views/mcp/cox-event-list.blade.php new file mode 100644 index 0000000..7ce0152 --- /dev/null +++ b/resources/views/mcp/cox-event-list.blade.php @@ -0,0 +1,10 @@ +# COX MySQL Events + +@foreach($eventsByType as $type => $events) +## {{ ucfirst($type) }} Events + +@foreach($events as $event) +- **{{ $event['name'] }}** - {{ $event['description'] }} +@endforeach + +@endforeach diff --git a/resources/views/mcp/cox-event-reference.blade.php b/resources/views/mcp/cox-event-reference.blade.php new file mode 100644 index 0000000..cd69f75 --- /dev/null +++ b/resources/views/mcp/cox-event-reference.blade.php @@ -0,0 +1,29 @@ +# COX Event Reference + +## {{ $event['name'] }} + +**Type:** {{ $event['type'] }} + +### Description +{{ $event['description'] }} + +### Parameters +@if(count($event['parameters']) > 0) +@foreach($event['parameters'] as $param) +- **{{ $param['name'] }}** ({{ $param['type'] }}): {{ $param['description'] }} +@endforeach +@else +This event takes no parameters. +@endif + +### Code Examples + +**Lua:** +```lua +{{ $event['lua_example'] }} +``` + +**JavaScript:** +```javascript +{{ $event['js_example'] }} +``` diff --git a/resources/views/mcp/cox-function.blade.php b/resources/views/mcp/cox-function.blade.php new file mode 100644 index 0000000..748d903 --- /dev/null +++ b/resources/views/mcp/cox-function.blade.php @@ -0,0 +1,48 @@ +# COX Function: {{ $function['name'] }} + +**Namespace:** {{ $function['namespace'] }} +**Side:** {{ ucfirst($function['side']) }} + +### Description +{{ $function['description'] }} + +### Parameters +@if(count($function['parameters']) > 0) +@foreach($function['parameters'] as $param) +- **{{ $param['name'] }}** ({{ $param['type'] }}): {{ $param['description'] }} +@endforeach +@else +This function takes no parameters. +@endif + +### Returns +**Type:** {{ $function['returns']['type'] }} +{{ $function['returns']['description'] }} + +### Code Examples + +@if($language === 'js') +**JavaScript:** +```javascript +{{ $function['js_example'] }} +``` +@else +**Lua:** +```lua +{{ $function['lua_example'] }} +``` +@endif + +### Alternative Example + +@if($language === 'js') +**Lua (alternative):** +```lua +{{ $function['lua_example'] }} +``` +@else +**JavaScript (alternative):** +```javascript +{{ $function['js_example'] }} +``` +@endif diff --git a/resources/views/mcp/resources/database-queries.blade.php b/resources/views/mcp/resources/database-queries.blade.php new file mode 100644 index 0000000..19627d2 --- /dev/null +++ b/resources/views/mcp/resources/database-queries.blade.php @@ -0,0 +1,252 @@ +# FiveM Database Query Examples + +Common database patterns and examples for COX MySQL operations in FiveM development. + +## CRUD Operations + +### Create (INSERT) + +**Lua Example:** +```lua +MySQL.insert('users', { + username = 'player_name', + money = 1000, + bank = 5000, + created_at = os.date('%Y-%m-%d %H:%M:%S') +}, function(insertId) + print('User created with ID: ' .. insertId) +end) +``` + +**JavaScript Example:** +```javascript +MySQL.insert('users', { + username: 'player_name', + money: 1000, + bank: 5000, + created_at: new Date().toISOString() +}, (insertId) => { + console.log('User created with ID: ' + insertId); +}); +``` + +### Read (SELECT) + +**Get Multiple Rows:** +```lua +MySQL.query('SELECT * FROM users WHERE banned = ?', { 0 }, function(results) + if results then + for i = 1, #results do + print(results[i].username .. ' - $' .. results[i].money) + end + end +end) +``` + +**Get Single Row:** +```lua +MySQL.single('SELECT * FROM users WHERE id = ?', { playerId }, function(user) + if user then + print('Player: ' .. user.username) + end +end) +``` + +**Get Single Value:** +```lua +MySQL.scalar('SELECT COUNT(*) as count FROM users', {}, function(count) + print('Total users: ' .. (count or 0)) +end) +``` + +### Update (UPDATE) + +**Update Single Record:** +```lua +MySQL.update('users', { + money = 500, + updated_at = os.date('%Y-%m-%d %H:%M:%S') +}, { id = playerId }, function(affectedRows) + print('Updated ' .. affectedRows .. ' user(s)') +end) +``` + +**Update Multiple Records:** +```lua +MySQL.update('users', { banned = 1 }, { last_login = '<', '2024-01-01' }, function(updated) + print('Banned ' .. updated .. ' inactive users') +end) +``` + +### Delete (DELETE) + +**Lua Example:** +```lua +MySQL.query('DELETE FROM users WHERE banned = ? AND last_login < DATE_SUB(NOW(), INTERVAL 90 DAY)', { 1 }, function(result) + if result then + print('Cleanup completed') + end +end) +``` + +## Prepared Statements (Security) + +Always use prepared statements to prevent SQL injection: + +**Secure Query:** +```lua +-- Using ? placeholders for parameters +MySQL.query('SELECT * FROM users WHERE username = ? AND password_hash = ?', + { inputUsername, passwordHash }, + function(result) + -- Safe from SQL injection + end) +``` + +**Avoid This (Vulnerable):** +```lua +-- DO NOT DO THIS - Vulnerable to SQL injection +local query = 'SELECT * FROM users WHERE username = \'' .. inputUsername .. '\'' +MySQL.query(query, {}, callback) -- BAD! +``` + +## Transactions + +Transactions ensure atomicity - either all changes succeed or none: + +**Money Transfer Example:** +```lua +MySQL.transaction.begin() + +-- Deduct from source account +MySQL.query('UPDATE users SET money = money - ? WHERE id = ?', { amount, sourceId }) + +-- Add to destination account +MySQL.query('UPDATE users SET money = money + ? WHERE id = ?', { amount, destId }) + +-- Commit or rollback based on results +MySQL.transaction.commit(function(success) + if success then + print('Transfer success') + else + MySQL.transaction.rollback() + print('Transfer failed and rolled back') + end +end) +``` + +## Async Operations + +Using async queries for better code organization: + +**Lua Example:** +```lua +CreateThread(function() + -- Query executes asynchronously + local users = MySQL.async.query('SELECT id FROM users WHERE active = ?', { 1 }) + + if users then + print('Found ' .. #users .. ' active users') + end +end) +``` + +## Error Handling + +Always handle errors gracefully: + +**Try-Catch Pattern:** +```lua +RegisterNetEvent('myResource:registerUser', function() + local username = 'newuser' + local email = 'user@example.com' + + MySQL.insert('users', { username = username, email = email }, function(insertId) + if insertId then + TriggerClientEvent('notify', source, 'Account created!', 'success') + else + TriggerClientEvent('notify', source, 'Account creation failed', 'error') + end + end) +end) +``` + +## Connection Checks + +Always verify database is ready before executing queries: + +```lua +if MySQL.ready then + MySQL.query('SELECT 1', {}, function(result) + print('Database is available') + end) +else + print('Waiting for database connection...') +end +``` + +## Common Queries + +### Get Player Data +```lua +MySQL.single('SELECT * FROM users WHERE id = ?', { playerId }, function(user) + if user then + print('User: ' .. user.username .. ', Money: ' .. user.money) + end +end) +``` + +### Update Player Money +```lua +MySQL.update('users', { money = newAmount }, { id = playerId }, callback) +``` + +### Check User Exists +```lua +MySQL.scalar('SELECT COUNT(*) FROM users WHERE username = ?', { username }, function(exists) + if exists > 0 then + print('User already exists') + end +end) +``` + +### Get All Active Players +```lua +MySQL.query('SELECT id, username FROM users WHERE active = ? ORDER BY username ASC', { 1 }, callback) +``` + +### Batch Insert +```lua +for i = 1, 10 do + MySQL.insert('logs', { action = 'test', timestamp = os.time() }) +end +``` + +## Performance Tips + +1. **Use WHERE clauses** to limit result sets +2. **Use LIMIT** for pagination: `SELECT * FROM users LIMIT 10 OFFSET 20` +3. **Use indexes** on frequently queried columns (id, username, etc.) +4. **Use transactions** for related operations +5. **Use prepared statements** always +6. **Cache results** when data doesn't change frequently +7. **Avoid N+1 queries** - fetch related data in one query +8. **Use EXPLAIN** to analyze query performance + +## Debugging + +Monitor slow queries and errors: + +```lua +RegisterNetEvent('coxMySQL:queryError', function(sql, error) + print('Query error: ' .. error) + print('Failed query: ' .. sql) + -- Log to file or monitoring service +end) + +RegisterNetEvent('coxMySQL:slowQuery', function(sql, duration) + if duration > 500 then + print('SLOW QUERY (' .. duration .. 'ms): ' .. sql) + end +end) +``` diff --git a/tests/Feature/GetCOXEventReferenceToolTest.php b/tests/Feature/GetCOXEventReferenceToolTest.php new file mode 100644 index 0000000..f1a0ada --- /dev/null +++ b/tests/Feature/GetCOXEventReferenceToolTest.php @@ -0,0 +1,54 @@ + 'coxMySQL:connected']); + + $response = $tool->handle($request); + + expect($response)->not()->toBeNull(); + expect($response->content())->not()->toBeNull(); +}); + +it('get cox event reference handles unknown event', function () { + $tool = new GetCOXEventReference; + $request = new Request(['event_name' => 'NonExistentCOXEvent123']); + + $response = $tool->handle($request); + $content = (string) $response->content(); + + expect($content)->toContain('not found') || expect($content)->toContain('COX event'); +}); + +it('get cox event reference filters by type', function () { + $tool = new GetCOXEventReference; + $request = new Request(['event_type' => 'connection']); + + $response = $tool->handle($request); + + expect($response)->not()->toBeNull(); + expect($response->content())->not()->toBeNull(); +}); + +it('get cox event reference lists all events', function () { + $tool = new GetCOXEventReference; + $request = new Request(['event_type' => 'all']); + + $response = $tool->handle($request); + + expect($response)->not()->toBeNull(); + expect($response->content())->not()->toBeNull(); +}); + +it('get cox event reference supports query events', function () { + $tool = new GetCOXEventReference; + $request = new Request(['event_name' => 'coxMySQL:queryCompleted']); + + $response = $tool->handle($request); + + expect($response)->not()->toBeNull(); + expect($response->content())->not()->toBeNull(); +}); diff --git a/tests/Feature/GetCOXFunctionToolTest.php b/tests/Feature/GetCOXFunctionToolTest.php new file mode 100644 index 0000000..01932da --- /dev/null +++ b/tests/Feature/GetCOXFunctionToolTest.php @@ -0,0 +1,54 @@ + 'MySQL.query']); + + $response = $tool->handle($request); + + expect($response)->not()->toBeNull(); + expect($response->content())->not()->toBeNull(); +}); + +it('get cox function handles unknown function', function () { + $tool = new GetCOXFunction; + $request = new Request(['function_name' => 'NonExistentFunction123']); + + $response = $tool->handle($request); + $content = (string) $response->content(); + + expect($content)->toContain('not found') || expect($content)->toContain('COX'); +}); + +it('get cox function works with insert method', function () { + $tool = new GetCOXFunction; + $request = new Request(['function_name' => 'MySQL.insert']); + + $response = $tool->handle($request); + + expect($response)->not()->toBeNull(); + expect($response->content())->not()->toBeNull(); +}); + +it('get cox function supports javascript examples', function () { + $tool = new GetCOXFunction; + $request = new Request(['function_name' => 'MySQL.query', 'language' => 'js']); + + $response = $tool->handle($request); + + expect($response)->not()->toBeNull(); + expect($response->content())->not()->toBeNull(); +}); + +it('get cox function works with transaction methods', function () { + $tool = new GetCOXFunction; + $request = new Request(['function_name' => 'MySQL.transaction.begin']); + + $response = $tool->handle($request); + + expect($response)->not()->toBeNull(); + expect($response->content())->not()->toBeNull(); +});