feat: Add COX MySQL tools and resources for event references and function lookups
- Implemented DatabaseQueries resource for FiveM database query examples. - Created GetCOXEventReference tool to retrieve information about COX MySQL events. - Developed GetCOXFunction tool to look up COX MySQL functions and methods. - Added views for displaying event lists, event references, and function details. - Included comprehensive CRUD operation examples for database interactions in FiveM. - Established tests for GetCOXEventReference and GetCOXFunction tools to ensure functionality.
This commit is contained in:
12 files changed
+1484
-477
No files matched your search
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Mcp\Resources;
|
||||
|
||||
use Laravel\Mcp\Request;
|
||||
use Laravel\Mcp\Response;
|
||||
use Laravel\Mcp\Server\Attributes\Description;
|
||||
use Laravel\Mcp\Server\Resource;
|
||||
|
||||
#[Description('FiveM database query examples and common patterns for COX MySQL')]
|
||||
class DatabaseQueries extends Resource
|
||||
{
|
||||
public function uri(): string
|
||||
{
|
||||
return 'fivem://guides/database-queries';
|
||||
}
|
||||
|
||||
public function handle(Request $request): Response
|
||||
{
|
||||
return Response::text(view('mcp.resources.database-queries')->render());
|
||||
}
|
||||
}
|
||||
@@ -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 = [
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
<?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('Get information about COX MySQL events and callbacks.')]
|
||||
class GetCOXEventReference extends Tool
|
||||
{
|
||||
/**
|
||||
* Handle the tool request.
|
||||
*/
|
||||
public function handle(Request $request): Response
|
||||
{
|
||||
$eventName = $request->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<string, \Illuminate\Contracts\JsonSchema\JsonSchema>
|
||||
*/
|
||||
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'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
<?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('Look up COX MySQL functions and methods by name. Returns function signature, parameters, return type, and usage examples.')]
|
||||
class GetCOXFunction extends Tool
|
||||
{
|
||||
/**
|
||||
* Handle the tool request.
|
||||
*/
|
||||
public function handle(Request $request): Response
|
||||
{
|
||||
$functionName = $request->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<string, \Illuminate\Contracts\JsonSchema\JsonSchema>
|
||||
*/
|
||||
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'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -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'],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -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
|
||||
@@ -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'] }}
|
||||
```
|
||||
@@ -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
|
||||
@@ -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)
|
||||
```
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
use App\Mcp\Tools\GetCOXEventReference;
|
||||
use Laravel\Mcp\Request;
|
||||
|
||||
it('get cox event reference returns response with content', function () {
|
||||
$tool = new GetCOXEventReference;
|
||||
$request = new Request(['event_name' => '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();
|
||||
});
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
use App\Mcp\Tools\GetCOXFunction;
|
||||
use Laravel\Mcp\Request;
|
||||
|
||||
it('get cox function returns response with content', function () {
|
||||
$tool = new GetCOXFunction;
|
||||
$request = new Request(['function_name' => '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();
|
||||
});
|
||||
Reference in new issue
Block a user