Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a2591c2209 | ||
|
|
18765b1013 | ||
|
|
969e4cf0da | ||
|
|
379d6d3d2a | ||
|
|
d9f5e2d315 | ||
|
|
2d547e7fb8 | ||
|
|
5f8d67eec9 | ||
|
|
48326df8ea |
No files matched your search
@@ -1,3 +1,21 @@
|
||||
# FiveM MCP
|
||||
|
||||
This repository contains a custom implementation of a Modular Command Protocol (MCP) server for FiveM, built using Laravel. The MCP server provides an interface for handling various commands and events related to FiveM development, allowing developers to easily access information about native functions, events, and more.
|
||||
|
||||
## VSCode Extension
|
||||
|
||||
A custom VSCode extension is included in this repository, which allows developers to interact with the MCP server directly from their code editor. The extension provides features such as command execution, event handling, and more.
|
||||
|
||||
`.vscode/settings.json`
|
||||
|
||||
```json
|
||||
{
|
||||
"servers": {
|
||||
"fivem": {
|
||||
"url": "https://fivem-mcp.kingsoflossantos.com/fivem",
|
||||
"type": "http"
|
||||
}
|
||||
},
|
||||
"inputs": []
|
||||
}
|
||||
```
|
||||
@@ -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'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
<?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 QBCore framework events including player, job, inventory, and vehicle events.')]
|
||||
class GetQBCoreEventReference extends Tool
|
||||
{
|
||||
/**
|
||||
* Handle the tool request.
|
||||
*/
|
||||
public function handle(Request $request): Response
|
||||
{
|
||||
$eventName = $request->get('event_name');
|
||||
$eventCategory = $request->get('event_category', 'all');
|
||||
$language = $request->get('language', 'lua');
|
||||
|
||||
if ($eventName) {
|
||||
return $this->getEventInfo($eventName, $language);
|
||||
}
|
||||
|
||||
return $this->listEvents($eventCategory, $language);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get specific event information.
|
||||
*/
|
||||
protected function getEventInfo(string $eventName, string $language): Response
|
||||
{
|
||||
$events = $this->getQBCoreEventsDatabase();
|
||||
|
||||
foreach ($events as $event) {
|
||||
if (strtolower($event['name']) === strtolower($eventName)) {
|
||||
return Response::text($this->formatEventInfo($event, $language));
|
||||
}
|
||||
}
|
||||
|
||||
return Response::text(sprintf("QBCore event '%s' not found in the database.", $eventName));
|
||||
}
|
||||
|
||||
/**
|
||||
* List events by category.
|
||||
*/
|
||||
protected function listEvents(string $category, string $language): Response
|
||||
{
|
||||
$events = $this->getQBCoreEventsDatabase();
|
||||
|
||||
if ($category !== 'all') {
|
||||
$events = array_filter($events, fn ($e) => $e['category'] === $category);
|
||||
}
|
||||
|
||||
$eventsByCategory = [];
|
||||
foreach ($events as $event) {
|
||||
$eventsByCategory[$event['category']][] = $event;
|
||||
}
|
||||
|
||||
return Response::text(
|
||||
view('mcp.qbcore-event-list', [
|
||||
'eventsByCategory' => $eventsByCategory,
|
||||
])->render()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format event information.
|
||||
*/
|
||||
protected function formatEventInfo(array $event, string $language): string
|
||||
{
|
||||
return view('mcp.qbcore-event-reference', [
|
||||
'event' => $event,
|
||||
'language' => $language,
|
||||
])->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get QBCore events database.
|
||||
*/
|
||||
protected function getQBCoreEventsDatabase(): array
|
||||
{
|
||||
return [
|
||||
// Player Events
|
||||
[
|
||||
'name' => 'QBCore:Client:OnPlayerLoaded',
|
||||
'category' => 'player',
|
||||
'side' => 'client',
|
||||
'description' => 'Triggered when a player\'s data is loaded on the client.',
|
||||
'parameters' => [],
|
||||
'lua_example' => "RegisterNetEvent('QBCore:Client:OnPlayerLoaded', function()\n local PlayerData = QBCore.Functions.GetPlayerData()\n print('Player loaded: ' .. PlayerData.charinfo.firstname)\nend)",
|
||||
'js_example' => "RegisterNetEvent('QBCore:Client:OnPlayerLoaded', () => {\n const PlayerData = QBCore.Functions.GetPlayerData();\n console.log('Player loaded: ' + PlayerData.charinfo.firstname);\n});",
|
||||
'documentation' => 'https://docs.qbcore.org/qbcore-documentation/core-concepts/player',
|
||||
],
|
||||
[
|
||||
'name' => 'QBCore:Client:OnPlayerUnload',
|
||||
'category' => 'player',
|
||||
'side' => 'client',
|
||||
'description' => 'Triggered when a player\'s data is unloaded (before character switch or logout).',
|
||||
'parameters' => [],
|
||||
'lua_example' => "RegisterNetEvent('QBCore:Client:OnPlayerUnload', function()\n print('Player unloaded')\nend)",
|
||||
'js_example' => "RegisterNetEvent('QBCore:Client:OnPlayerUnload', () => {\n console.log('Player unloaded');\n});",
|
||||
'documentation' => 'https://docs.qbcore.org/qbcore-documentation/core-concepts/player',
|
||||
],
|
||||
[
|
||||
'name' => 'QBCore:Server:OnPlayerLoaded',
|
||||
'category' => 'player',
|
||||
'side' => 'server',
|
||||
'description' => 'Triggered on the server when a player\'s data is successfully loaded.',
|
||||
'parameters' => [
|
||||
['name' => 'source', 'type' => 'int', 'description' => 'The player server ID'],
|
||||
],
|
||||
'lua_example' => "RegisterNetEvent('QBCore:Server:OnPlayerLoaded', function()\n local Player = QBCore.Functions.GetPlayer(source)\n if Player then\n print('Server: Player loaded - ' .. Player.PlayerData.charinfo.firstname)\n end\nend)",
|
||||
'js_example' => "RegisterNetEvent('QBCore:Server:OnPlayerLoaded', () => {\n const Player = QBCore.Functions.GetPlayer(global.source);\n if (Player) {\n console.log('Server: Player loaded - ' + Player.PlayerData.charinfo.firstname);\n }\n});",
|
||||
'documentation' => 'https://docs.qbcore.org/qbcore-documentation/core-concepts/player',
|
||||
],
|
||||
|
||||
// Job Events
|
||||
[
|
||||
'name' => 'QBCore:Client:OnJobUpdate',
|
||||
'category' => 'job',
|
||||
'side' => 'client',
|
||||
'description' => 'Triggered when a player\'s job is updated.',
|
||||
'parameters' => [
|
||||
['name' => 'job', 'type' => 'table', 'description' => 'The job data table'],
|
||||
],
|
||||
'lua_example' => "RegisterNetEvent('QBCore:Client:OnJobUpdate', function(job)\n PlayerData.job = job\n print('Job changed to: ' .. job.name .. ' - Grade: ' .. job.grade.name)\nend)",
|
||||
'js_example' => "RegisterNetEvent('QBCore:Client:OnJobUpdate', (job) => {\n PlayerData.job = job;\n console.log(`Job changed to: \${job.name} - Grade: \${job.grade.name}`);\n});",
|
||||
'documentation' => 'https://docs.qbcore.org/qbcore-documentation/core-concepts/jobs',
|
||||
],
|
||||
[
|
||||
'name' => 'QBCore:Server:SetJob',
|
||||
'category' => 'job',
|
||||
'side' => 'server',
|
||||
'description' => 'Set a player\'s job on the server (requires permission).',
|
||||
'parameters' => [
|
||||
['name' => 'source', 'type' => 'int', 'description' => 'The player server ID'],
|
||||
['name' => 'job', 'type' => 'string', 'description' => 'The job name'],
|
||||
['name' => 'grade', 'type' => 'int', 'description' => 'The job grade level'],
|
||||
],
|
||||
'lua_example' => "TriggerServerEvent('QBCore:Server:SetJob', source, 'police', 2)",
|
||||
'js_example' => "TriggerServerEvent('QBCore:Server:SetJob', global.source, 'police', 2);",
|
||||
'documentation' => 'https://docs.qbcore.org/qbcore-documentation/core-concepts/jobs',
|
||||
],
|
||||
|
||||
// Inventory Events
|
||||
[
|
||||
'name' => 'QBCore:Client:OnItemAdd',
|
||||
'category' => 'inventory',
|
||||
'side' => 'client',
|
||||
'description' => 'Triggered when an item is added to the player inventory.',
|
||||
'parameters' => [
|
||||
['name' => 'itemName', 'type' => 'string', 'description' => 'The item name'],
|
||||
['name' => 'amount', 'type' => 'int', 'description' => 'The amount added'],
|
||||
],
|
||||
'lua_example' => "RegisterNetEvent('QBCore:Client:OnItemAdd', function(itemName, amount)\n print('Added ' .. amount .. 'x ' .. itemName)\nend)",
|
||||
'js_example' => "RegisterNetEvent('QBCore:Client:OnItemAdd', (itemName, amount) => {\n console.log(`Added \${amount}x \${itemName}`);\n});",
|
||||
'documentation' => 'https://docs.qbcore.org/qbcore-documentation/core-concepts/items',
|
||||
],
|
||||
[
|
||||
'name' => 'QBCore:Client:OnItemRemove',
|
||||
'category' => 'inventory',
|
||||
'side' => 'client',
|
||||
'description' => 'Triggered when an item is removed from the player inventory.',
|
||||
'parameters' => [
|
||||
['name' => 'itemName', 'type' => 'string', 'description' => 'The item name'],
|
||||
['name' => 'amount', 'type' => 'int', 'description' => 'The amount removed'],
|
||||
],
|
||||
'lua_example' => "RegisterNetEvent('QBCore:Client:OnItemRemove', function(itemName, amount)\n print('Removed ' .. amount .. 'x ' .. itemName)\nend)",
|
||||
'js_example' => "RegisterNetEvent('QBCore:Client:OnItemRemove', (itemName, amount) => {\n console.log(`Removed \${amount}x \${itemName}`);\n});",
|
||||
'documentation' => 'https://docs.qbcore.org/qbcore-documentation/core-concepts/items',
|
||||
],
|
||||
[
|
||||
'name' => 'QBCore:Server:AddItem',
|
||||
'category' => 'inventory',
|
||||
'side' => 'server',
|
||||
'description' => 'Add an item to a player\'s inventory.',
|
||||
'parameters' => [
|
||||
['name' => 'source', 'type' => 'int', 'description' => 'The player server ID'],
|
||||
['name' => 'item', 'type' => 'string', 'description' => 'The item name'],
|
||||
['name' => 'amount', 'type' => 'int', 'description' => 'The amount to add'],
|
||||
['name' => 'slot', 'type' => 'int', 'description' => 'Optional inventory slot'],
|
||||
['name' => 'info', 'type' => 'table', 'description' => 'Optional item info'],
|
||||
],
|
||||
'lua_example' => "exports['qb-core']:GetPlayer(source):AddItem('water', 1)",
|
||||
'js_example' => "exports['qb-core']:GetPlayer(global.source).AddItem('water', 1);",
|
||||
'documentation' => 'https://docs.qbcore.org/qbcore-documentation/core-concepts/items',
|
||||
],
|
||||
|
||||
// Vehicle Events
|
||||
[
|
||||
'name' => 'QBCore:Server:AddVehicle',
|
||||
'category' => 'vehicle',
|
||||
'side' => 'server',
|
||||
'description' => 'Add a vehicle to a player\'s garage.',
|
||||
'parameters' => [
|
||||
['name' => 'source', 'type' => 'int', 'description' => 'The player server ID'],
|
||||
['name' => 'modelName', 'type' => 'string', 'description' => 'The vehicle model name'],
|
||||
['name' => 'plate', 'type' => 'string', 'description' => 'The vehicle license plate'],
|
||||
],
|
||||
'lua_example' => "TriggerServerEvent('QBCore:Server:AddVehicle', 'adder', 'QBTEST123')",
|
||||
'js_example' => "TriggerServerEvent('QBCore:Server:AddVehicle', 'adder', 'QBTEST123');",
|
||||
'documentation' => 'https://docs.qbcore.org/qbcore-documentation/core-concepts/vehicles',
|
||||
],
|
||||
[
|
||||
'name' => 'QBCore:Server:DeleteVehicle',
|
||||
'category' => 'vehicle',
|
||||
'side' => 'server',
|
||||
'description' => 'Remove a vehicle from a player\'s garage.',
|
||||
'parameters' => [
|
||||
['name' => 'source', 'type' => 'int', 'description' => 'The player server ID'],
|
||||
['name' => 'plate', 'type' => 'string', 'description' => 'The vehicle license plate'],
|
||||
],
|
||||
'lua_example' => "TriggerServerEvent('QBCore:Server:DeleteVehicle', 'QBTEST123')",
|
||||
'js_example' => "TriggerServerEvent('QBCore:Server:DeleteVehicle', 'QBTEST123');",
|
||||
'documentation' => 'https://docs.qbcore.org/qbcore-documentation/core-concepts/vehicles',
|
||||
],
|
||||
|
||||
// Money Events
|
||||
[
|
||||
'name' => 'QBCore:Client:OnMoneyChange',
|
||||
'category' => 'money',
|
||||
'side' => 'client',
|
||||
'description' => 'Triggered when a player\'s money amount changes.',
|
||||
'parameters' => [
|
||||
['name' => 'type', 'type' => 'string', 'description' => 'Money type (cash, bank, etc.)'],
|
||||
['name' => 'amount', 'type' => 'int', 'description' => 'The new amount'],
|
||||
['name' => 'difference', 'type' => 'int', 'description' => 'The amount changed'],
|
||||
],
|
||||
'lua_example' => "RegisterNetEvent('QBCore:Client:OnMoneyChange', function(type, amount, difference)\n print('Money changed: ' .. type .. ' = ' .. amount)\nend)",
|
||||
'js_example' => "RegisterNetEvent('QBCore:Client:OnMoneyChange', (type, amount, difference) => {\n console.log(`Money changed: \${type} = \${amount}`);\n});",
|
||||
'documentation' => 'https://docs.qbcore.org/qbcore-documentation/core-concepts/player',
|
||||
],
|
||||
[
|
||||
'name' => 'QBCore:Server:AddMoney',
|
||||
'category' => 'money',
|
||||
'side' => 'server',
|
||||
'description' => 'Add money to a player\'s account.',
|
||||
'parameters' => [
|
||||
['name' => 'source', 'type' => 'int', 'description' => 'The player server ID'],
|
||||
['name' => 'moneyType', 'type' => 'string', 'description' => 'Money type (cash, bank)'],
|
||||
['name' => 'amount', 'type' => 'int', 'description' => 'The amount to add'],
|
||||
['name' => 'reason', 'type' => 'string', 'description' => 'Optional reason for log'],
|
||||
],
|
||||
'lua_example' => "exports['qb-core']:AddMoney(source, 'cash', 500, 'Job Payment')",
|
||||
'js_example' => "exports['qb-core']:AddMoney(global.source, 'cash', 500, 'Job Payment');",
|
||||
'documentation' => 'https://docs.qbcore.org/qbcore-documentation/core-concepts/player',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 QBCore event name to look up (optional, leave empty to list all events)'),
|
||||
'event_category' => $schema
|
||||
->string()
|
||||
->enum(['all', 'player', 'job', 'inventory', 'vehicle', 'money'])
|
||||
->description('Filter events by category')
|
||||
->default('all'),
|
||||
'language' => $schema
|
||||
->string()
|
||||
->enum(['lua', 'js'])
|
||||
->description('The scripting language for code examples')
|
||||
->default('lua'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
<?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 QBCore functions and exports by name. Returns function signature, parameters, return type, and usage examples.')]
|
||||
class GetQBCoreFunction 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("QBCore function '%s' not found. Check QBCore documentation at https://docs.qbcore.org/", $functionName));
|
||||
}
|
||||
|
||||
$output = $this->formatFunctionInfo($function, $language);
|
||||
|
||||
return Response::text($output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a QBCore function.
|
||||
*/
|
||||
protected function findFunction(string $name): ?array
|
||||
{
|
||||
$functions = [
|
||||
// Player Functions
|
||||
'QBCore.Functions.GetPlayer' => [
|
||||
'namespace' => 'Core',
|
||||
'description' => 'Get a QBCore player object from server ID (server-side only)',
|
||||
'side' => 'server',
|
||||
'parameters' => [
|
||||
['name' => 'source', 'type' => 'int', 'description' => 'The player server ID'],
|
||||
],
|
||||
'returns' => ['type' => 'table', 'description' => 'QBCore player object or nil'],
|
||||
'lua_example' => "local Player = QBCore.Functions.GetPlayer(source)\nif Player then\n print('Player: ' .. Player.PlayerData.charinfo.firstname)\nend",
|
||||
'js_example' => "const Player = QBCore.Functions.GetPlayer(global.source);\nif (Player) {\n console.log('Player: ' + Player.PlayerData.charinfo.firstname);\n}",
|
||||
],
|
||||
'QBCore.Functions.GetPlayerByCitizenId' => [
|
||||
'namespace' => 'Core',
|
||||
'description' => 'Get a player object by their citizen ID (server-side only)',
|
||||
'side' => 'server',
|
||||
'parameters' => [
|
||||
['name' => 'citizenId', 'type' => 'string', 'description' => 'The player citizen ID'],
|
||||
],
|
||||
'returns' => ['type' => 'table', 'description' => 'QBCore player object or nil'],
|
||||
'lua_example' => "local Player = QBCore.Functions.GetPlayerByCitizenId('ABC12345')\nif Player then\n TriggerClientEvent('QBCore:Notify', Player.PlayerData.source, 'Hello!')\nend",
|
||||
'js_example' => "const Player = QBCore.Functions.GetPlayerByCitizenId('ABC12345');\nif (Player) {\n TriggerClientEvent('QBCore:Notify', Player.PlayerData.source, 'Hello!');\n}",
|
||||
],
|
||||
'QBCore.Functions.GetPlayerData' => [
|
||||
'namespace' => 'Core',
|
||||
'description' => 'Get the current player data (client-side only)',
|
||||
'side' => 'client',
|
||||
'parameters' => [],
|
||||
'returns' => ['type' => 'table', 'description' => 'Current player data table'],
|
||||
'lua_example' => "local PlayerData = QBCore.Functions.GetPlayerData()\nprint('Job: ' .. PlayerData.job.name)",
|
||||
'js_example' => "const PlayerData = QBCore.Functions.GetPlayerData();\nconsole.log('Job: ' + PlayerData.job.name);",
|
||||
],
|
||||
|
||||
// Item Functions
|
||||
'AddItem' => [
|
||||
'namespace' => 'Player',
|
||||
'description' => 'Add an item to a player\'s inventory',
|
||||
'side' => 'server',
|
||||
'parameters' => [
|
||||
['name' => 'item', 'type' => 'string', 'description' => 'Item name'],
|
||||
['name' => 'amount', 'type' => 'int', 'description' => 'Amount to add (default 1)'],
|
||||
['name' => 'slot', 'type' => 'int|nil', 'description' => 'Optional specific slot'],
|
||||
['name' => 'info', 'type' => 'table|nil', 'description' => 'Optional item metadata/info'],
|
||||
],
|
||||
'returns' => ['type' => 'boolean', 'description' => 'Success status'],
|
||||
'lua_example' => "local Player = QBCore.Functions.GetPlayer(source)\nif Player then\n local success = Player.Functions.AddItem('water', 1)\n if success then\n TriggerClientEvent('QBCore:Notify', source, 'Item added')\n end\nend",
|
||||
'js_example' => "const Player = QBCore.Functions.GetPlayer(global.source);\nif (Player) {\n const success = Player.Functions.AddItem('water', 1);\n if (success) {\n TriggerClientEvent('QBCore:Notify', global.source, 'Item added');\n }\n}",
|
||||
],
|
||||
'RemoveItem' => [
|
||||
'namespace' => 'Player',
|
||||
'description' => 'Remove an item from a player\'s inventory',
|
||||
'side' => 'server',
|
||||
'parameters' => [
|
||||
['name' => 'item', 'type' => 'string', 'description' => 'Item name'],
|
||||
['name' => 'amount', 'type' => 'int', 'description' => 'Amount to remove (default 1)'],
|
||||
['name' => 'slot', 'type' => 'int|nil', 'description' => 'Optional specific slot'],
|
||||
],
|
||||
'returns' => ['type' => 'boolean', 'description' => 'Success status'],
|
||||
'lua_example' => "local Player = QBCore.Functions.GetPlayer(source)\nif Player then\n local success = Player.Functions.RemoveItem('water', 1)\n if success then\n TriggerClientEvent('QBCore:Notify', source, 'Item removed')\n end\nend",
|
||||
'js_example' => "const Player = QBCore.Functions.GetPlayer(global.source);\nif (Player) {\n const success = Player.Functions.RemoveItem('water', 1);\n if (success) {\n TriggerClientEvent('QBCore:Notify', global.source, 'Item removed');\n }\n}",
|
||||
],
|
||||
'GetItemByName' => [
|
||||
'namespace' => 'Player',
|
||||
'description' => 'Get item data from a player\'s inventory',
|
||||
'side' => 'server',
|
||||
'parameters' => [
|
||||
['name' => 'item', 'type' => 'string', 'description' => 'Item name to search for'],
|
||||
],
|
||||
'returns' => ['type' => 'table|nil', 'description' => 'Item data table or nil if not found'],
|
||||
'lua_example' => "local Player = QBCore.Functions.GetPlayer(source)\nif Player then\n local item = Player.Functions.GetItemByName('water')\n if item then\n print('Found ' .. item.amount .. 'x ' .. item.name)\n end\nend",
|
||||
'js_example' => "const Player = QBCore.Functions.GetPlayer(global.source);\nif (Player) {\n const item = Player.Functions.GetItemByName('water');\n if (item) {\n console.log(`Found \${item.amount}x \${item.name}`);\n }\n}",
|
||||
],
|
||||
|
||||
// Money Functions
|
||||
'AddMoney' => [
|
||||
'namespace' => 'Player',
|
||||
'description' => 'Add money to a player\'s account',
|
||||
'side' => 'server',
|
||||
'parameters' => [
|
||||
['name' => 'moneyType', 'type' => 'string', 'description' => 'Type: "cash", "bank", or custom'],
|
||||
['name' => 'amount', 'type' => 'int', 'description' => 'Amount to add'],
|
||||
['name' => 'reason', 'type' => 'string|nil', 'description' => 'Optional reason for logging'],
|
||||
],
|
||||
'returns' => ['type' => 'boolean', 'description' => 'Success status'],
|
||||
'lua_example' => "local Player = QBCore.Functions.GetPlayer(source)\nif Player then\n Player.Functions.AddMoney('cash', 500, 'Job Payment')\n TriggerClientEvent('QBCore:Notify', source, 'You earned $500')\nend",
|
||||
'js_example' => "const Player = QBCore.Functions.GetPlayer(global.source);\nif (Player) {\n Player.Functions.AddMoney('cash', 500, 'Job Payment');\n TriggerClientEvent('QBCore:Notify', global.source, 'You earned $500');\n}",
|
||||
],
|
||||
'RemoveMoney' => [
|
||||
'namespace' => 'Player',
|
||||
'description' => 'Remove money from a player\'s account',
|
||||
'side' => 'server',
|
||||
'parameters' => [
|
||||
['name' => 'moneyType', 'type' => 'string', 'description' => 'Type: "cash", "bank", or custom'],
|
||||
['name' => 'amount', 'type' => 'int', 'description' => 'Amount to remove'],
|
||||
['name' => 'reason', 'type' => 'string|nil', 'description' => 'Optional reason for logging'],
|
||||
],
|
||||
'returns' => ['type' => 'boolean', 'description' => 'Success status'],
|
||||
'lua_example' => "local Player = QBCore.Functions.GetPlayer(source)\nif Player then\n Player.Functions.RemoveMoney('cash', 500, 'Shop Purchase')\n TriggerClientEvent('QBCore:Notify', source, 'Payment successful')\nend",
|
||||
'js_example' => "const Player = QBCore.Functions.GetPlayer(global.source);\nif (Player) {\n Player.Functions.RemoveMoney('cash', 500, 'Shop Purchase');\n TriggerClientEvent('QBCore:Notify', global.source, 'Payment successful');\n}",
|
||||
],
|
||||
'GetMoney' => [
|
||||
'namespace' => 'Player',
|
||||
'description' => 'Get a player\'s money amount',
|
||||
'side' => 'server',
|
||||
'parameters' => [
|
||||
['name' => 'moneyType', 'type' => 'string', 'description' => 'Type: "cash", "bank", or custom'],
|
||||
],
|
||||
'returns' => ['type' => 'int', 'description' => 'Money amount'],
|
||||
'lua_example' => "local Player = QBCore.Functions.GetPlayer(source)\nif Player then\n local cashAmount = Player.Functions.GetMoney('cash')\n print('Cash: $' .. cashAmount)\nend",
|
||||
'js_example' => "const Player = QBCore.Functions.GetPlayer(global.source);\nif (Player) {\n const cashAmount = Player.Functions.GetMoney('cash');\n console.log('Cash: $' + cashAmount);\n}",
|
||||
],
|
||||
|
||||
// Job Functions
|
||||
'SetJob' => [
|
||||
'namespace' => 'Player',
|
||||
'description' => 'Set a player\'s job',
|
||||
'side' => 'server',
|
||||
'parameters' => [
|
||||
['name' => 'jobName', 'type' => 'string', 'description' => 'The job name'],
|
||||
['name' => 'grade', 'type' => 'int', 'description' => 'The job grade (usually 0 for basic)'],
|
||||
],
|
||||
'returns' => ['type' => 'boolean', 'description' => 'Success status'],
|
||||
'lua_example' => "local Player = QBCore.Functions.GetPlayer(source)\nif Player then\n Player.Functions.SetJob('police', 0)\n TriggerClientEvent('QBCore:Notify', source, 'You are now a police officer', 'success')\nend",
|
||||
'js_example' => "const Player = QBCore.Functions.GetPlayer(global.source);\nif (Player) {\n Player.Functions.SetJob('police', 0);\n TriggerClientEvent('QBCore:Notify', global.source, 'You are now a police officer', 'success');\n}",
|
||||
],
|
||||
'GetJob' => [
|
||||
'namespace' => 'Player',
|
||||
'description' => 'Get a player\'s job information',
|
||||
'side' => 'server',
|
||||
'parameters' => [],
|
||||
'returns' => ['type' => 'table', 'description' => 'Job data table with name, label, grade, etc.'],
|
||||
'lua_example' => "local Player = QBCore.Functions.GetPlayer(source)\nif Player then\n local job = Player.Functions.GetJob()\n print('Job: ' .. job.name .. ' (' .. job.grade.name .. ')')\nend",
|
||||
'js_example' => "const Player = QBCore.Functions.GetPlayer(global.source);\nif (Player) {\n const job = Player.Functions.GetJob();\n console.log(`Job: \${job.name} (\${job.grade.name})`);\n}",
|
||||
],
|
||||
|
||||
// Vehicle Functions
|
||||
'AddVehicle' => [
|
||||
'namespace' => 'Player',
|
||||
'description' => 'Add a vehicle to a player\'s garage',
|
||||
'side' => 'server',
|
||||
'parameters' => [
|
||||
['name' => 'modelName', 'type' => 'string', 'description' => 'Vehicle model name (e.g., "adder")'],
|
||||
['name' => 'plate', 'type' => 'string', 'description' => 'License plate (usually unique identifier)'],
|
||||
['name' => 'properties', 'type' => 'table|nil', 'description' => 'Optional vehicle properties/customization'],
|
||||
],
|
||||
'returns' => ['type' => 'boolean', 'description' => 'Success status'],
|
||||
'lua_example' => "local Player = QBCore.Functions.GetPlayer(source)\nif Player then\n Player.Functions.AddVehicle('adder', 'QBTEST')\n TriggerClientEvent('QBCore:Notify', source, 'Vehicle added to garage')\nend",
|
||||
'js_example' => "const Player = QBCore.Functions.GetPlayer(global.source);\nif (Player) {\n Player.Functions.AddVehicle('adder', 'QBTEST');\n TriggerClientEvent('QBCore:Notify', global.source, 'Vehicle added to garage');\n}",
|
||||
],
|
||||
'DeleteVehicle' => [
|
||||
'namespace' => 'Player',
|
||||
'description' => 'Remove a vehicle from a player\'s garage',
|
||||
'side' => 'server',
|
||||
'parameters' => [
|
||||
['name' => 'plate', 'type' => 'string', 'description' => 'License plate of vehicle to remove'],
|
||||
],
|
||||
'returns' => ['type' => 'boolean', 'description' => 'Success status'],
|
||||
'lua_example' => "local Player = QBCore.Functions.GetPlayer(source)\nif Player then\n Player.Functions.DeleteVehicle('QBTEST')\n TriggerClientEvent('QBCore:Notify', source, 'Vehicle removed from garage')\nend",
|
||||
'js_example' => "const Player = QBCore.Functions.GetPlayer(global.source);\nif (Player) {\n Player.Functions.DeleteVehicle('QBTEST');\n TriggerClientEvent('QBCore:Notify', global.source, 'Vehicle removed from garage');\n}",
|
||||
],
|
||||
|
||||
// Utility Functions
|
||||
'Notify' => [
|
||||
'namespace' => 'Utility',
|
||||
'description' => 'Send a notification to a player',
|
||||
'side' => 'client',
|
||||
'parameters' => [
|
||||
['name' => 'source', 'type' => 'int', 'description' => 'Player server ID'],
|
||||
['name' => 'message', 'type' => 'string', 'description' => 'Notification message'],
|
||||
['name' => 'type', 'type' => 'string', 'description' => 'Type: "success", "error", "info" (default: info)'],
|
||||
['name' => 'length', 'type' => 'int|nil', 'description' => 'Duration in milliseconds'],
|
||||
],
|
||||
'returns' => ['type' => 'void', 'description' => 'No return value'],
|
||||
'lua_example' => "TriggerClientEvent('QBCore:Notify', source, 'Hello Player!', 'success', 5000)",
|
||||
'js_example' => "TriggerClientEvent('QBCore:Notify', global.source, 'Hello Player!', 'success', 5000);",
|
||||
],
|
||||
'HasItem' => [
|
||||
'namespace' => 'Player',
|
||||
'description' => 'Check if a player has an item',
|
||||
'side' => 'server',
|
||||
'parameters' => [
|
||||
['name' => 'item', 'type' => 'string', 'description' => 'Item name to check'],
|
||||
['name' => 'amount', 'type' => 'int|nil', 'description' => 'Minimum amount required'],
|
||||
],
|
||||
'returns' => ['type' => 'boolean', 'description' => 'True if player has item'],
|
||||
'lua_example' => "local Player = QBCore.Functions.GetPlayer(source)\nif Player then\n if Player.Functions.GetItemByName('water') then\n print('Has water')\n end\nend",
|
||||
'js_example' => "const Player = QBCore.Functions.GetPlayer(global.source);\nif (Player) {\n if (Player.Functions.GetItemByName('water')) {\n console.log('Has water');\n }\n}",
|
||||
],
|
||||
];
|
||||
|
||||
$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.qbcore-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 QBCore function to look up (e.g., "QBCore.Functions.GetPlayer", "AddMoney")')
|
||||
->required(),
|
||||
'language' => $schema
|
||||
->string()
|
||||
->enum(['lua', 'js'])
|
||||
->description('The scripting language for code examples')
|
||||
->default('lua'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -110,6 +110,118 @@ class SearchFiveMDocs extends Tool
|
||||
'keywords' => ['structure', 'folder', 'organization', 'resource', 'files'],
|
||||
],
|
||||
],
|
||||
'qbcore' => [
|
||||
[
|
||||
'title' => 'QBCore Documentation',
|
||||
'url' => 'https://docs.qbcore.org/qbcore-documentation/',
|
||||
'description' => 'Official QBCore framework documentation and guides',
|
||||
'keywords' => ['qbcore', 'framework', 'documentation', 'guide'],
|
||||
],
|
||||
[
|
||||
'title' => 'QBCore Getting Started',
|
||||
'url' => 'https://docs.qbcore.org/qbcore-documentation/get-started',
|
||||
'description' => 'Getting started with QBCore framework setup and basics',
|
||||
'keywords' => ['qbcore', 'getting started', 'setup', 'installation', 'begin'],
|
||||
],
|
||||
[
|
||||
'title' => 'QBCore Callbacks & Exports',
|
||||
'url' => 'https://docs.qbcore.org/qbcore-documentation/core-concepts/callbacks-exports',
|
||||
'description' => 'Learn about QBCore callbacks and exports for inter-resource communication',
|
||||
'keywords' => ['qbcore', 'callback', 'export', 'function', 'communication'],
|
||||
],
|
||||
[
|
||||
'title' => 'QBCore Items & Inventory',
|
||||
'url' => 'https://docs.qbcore.org/qbcore-documentation/core-concepts/items',
|
||||
'description' => 'Item system and inventory management in QBCore',
|
||||
'keywords' => ['qbcore', 'items', 'inventory', 'item', 'database'],
|
||||
],
|
||||
[
|
||||
'title' => 'QBCore Jobs & Grades',
|
||||
'url' => 'https://docs.qbcore.org/qbcore-documentation/core-concepts/jobs',
|
||||
'description' => 'Job system and player grades in QBCore',
|
||||
'keywords' => ['qbcore', 'job', 'grades', 'employment', 'boss'],
|
||||
],
|
||||
[
|
||||
'title' => 'QBCore Player Management',
|
||||
'url' => 'https://docs.qbcore.org/qbcore-documentation/core-concepts/player',
|
||||
'description' => 'Managing player data and interactions in QBCore',
|
||||
'keywords' => ['qbcore', 'player', 'data', 'management', 'character'],
|
||||
],
|
||||
[
|
||||
'title' => 'QBCore Vehicles',
|
||||
'url' => 'https://docs.qbcore.org/qbcore-documentation/core-concepts/vehicles',
|
||||
'description' => 'Vehicle system and ownership in QBCore',
|
||||
'keywords' => ['qbcore', 'vehicle', 'car', 'ownership', 'garage'],
|
||||
],
|
||||
[
|
||||
'title' => 'QBCore Events',
|
||||
'url' => 'https://docs.qbcore.org/qbcore-documentation/core-concepts/events',
|
||||
'description' => 'QBCore framework events for player and game actions',
|
||||
'keywords' => ['qbcore', 'event', 'trigger', 'listener'],
|
||||
],
|
||||
],
|
||||
'coxdocs' => [
|
||||
[
|
||||
'title' => 'COX Docs - MySQL',
|
||||
'url' => 'https://coxdocs.dev/',
|
||||
'description' => 'Complete MySQL documentation for FiveM - required component for database operations',
|
||||
'keywords' => ['cox', 'mysql', 'database', 'query', 'sql', 'required'],
|
||||
],
|
||||
[
|
||||
'title' => 'COX Docs - Getting Started',
|
||||
'url' => 'https://coxdocs.dev/getting-started',
|
||||
'description' => 'Getting started with COX MySQL library setup and configuration',
|
||||
'keywords' => ['cox', 'mysql', 'getting started', 'setup', 'installation'],
|
||||
],
|
||||
[
|
||||
'title' => 'COX Docs - Query Methods',
|
||||
'url' => 'https://coxdocs.dev/methods',
|
||||
'description' => 'COX library query methods for database operations (SELECT, INSERT, UPDATE, DELETE)',
|
||||
'keywords' => ['cox', 'mysql', 'query', 'method', 'select', 'insert', 'update', 'delete'],
|
||||
],
|
||||
[
|
||||
'title' => 'COX Docs - Events',
|
||||
'url' => 'https://coxdocs.dev/events',
|
||||
'description' => 'COX library events and callbacks for database operations',
|
||||
'keywords' => ['cox', 'mysql', 'event', 'callback', 'listener'],
|
||||
],
|
||||
[
|
||||
'title' => 'COX Docs - Transactions',
|
||||
'url' => 'https://coxdocs.dev/transactions',
|
||||
'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'],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$results = [];
|
||||
@@ -161,7 +273,7 @@ class SearchFiveMDocs extends Tool
|
||||
->required(),
|
||||
'category' => $schema
|
||||
->string()
|
||||
->enum(['all', 'scripting', 'natives', 'networking', 'resources'])
|
||||
->enum(['all', 'scripting', 'natives', 'networking', 'resources', 'qbcore', 'coxdocs'])
|
||||
->description('Filter results by documentation category')
|
||||
->default('all'),
|
||||
];
|
||||
|
||||
Generated
+164
-1275
File diff suppressed because it is too large.
Load diff
@@ -9,3 +9,63 @@
|
||||
--font-sans: 'Instrument Sans', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
|
||||
'Segoe UI Symbol', 'Noto Color Emoji';
|
||||
}
|
||||
|
||||
.tab-button {
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
animation: fadeIn 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.code-block-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.copy-button {
|
||||
position: absolute;
|
||||
top: 0.5rem;
|
||||
right: 0.5rem;
|
||||
padding: 0.375rem 0.75rem;
|
||||
background-color: rgba(55, 65, 81, 0.8);
|
||||
color: white;
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
border-radius: 0.375rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
opacity: 0;
|
||||
transition: all 0.2s ease-in-out;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.code-block-wrapper:hover .copy-button {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.copy-button:hover {
|
||||
background-color: rgba(55, 65, 81, 1);
|
||||
}
|
||||
|
||||
.copy-button.copied {
|
||||
background-color: rgba(16, 185, 129, 0.9);
|
||||
border-color: rgba(16, 185, 129, 1);
|
||||
}
|
||||
|
||||
.copy-button svg {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
@@ -1 +1,38 @@
|
||||
import './bootstrap';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const codeBlocks = document.querySelectorAll('pre');
|
||||
|
||||
codeBlocks.forEach((pre) => {
|
||||
if (!pre.parentElement.classList.contains('code-block-wrapper')) {
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.className = 'code-block-wrapper';
|
||||
pre.parentNode.insertBefore(wrapper, pre);
|
||||
wrapper.appendChild(pre);
|
||||
}
|
||||
|
||||
const wrapper = pre.parentElement;
|
||||
const copyButton = document.createElement('button');
|
||||
copyButton.className = 'copy-button';
|
||||
copyButton.innerHTML = `<svg class="copy-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg> <span class="copy-text">Copy</span>`;
|
||||
|
||||
copyButton.addEventListener('click', () => {
|
||||
const code = pre.querySelector('code');
|
||||
const text = code ? code.textContent : pre.textContent;
|
||||
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
copyButton.classList.add('copied');
|
||||
copyButton.innerHTML = `<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path></svg> <span>Copied!</span>`;
|
||||
|
||||
setTimeout(() => {
|
||||
copyButton.classList.remove('copied');
|
||||
copyButton.innerHTML = `<svg class="copy-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg><span class="copy-text">Copy</span>`;
|
||||
}, 2000);
|
||||
}).catch((err) => {
|
||||
console.error('Failed to copy:', err);
|
||||
});
|
||||
});
|
||||
|
||||
wrapper.appendChild(copyButton);
|
||||
});
|
||||
});
|
||||
File diff suppressed because it is too large.
Load diff
@@ -22,7 +22,7 @@
|
||||
<div class="bg-white overflow-hidden shadow rounded-lg">
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<div class="flex items-center">
|
||||
<div class="flex-shrink-0 bg-indigo-500 rounded-md p-3">
|
||||
<div class="shrink-0 bg-indigo-500 rounded-md p-3">
|
||||
<svg class="h-6 w-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
|
||||
</svg>
|
||||
@@ -43,7 +43,7 @@
|
||||
<div class="bg-white overflow-hidden shadow rounded-lg">
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<div class="flex items-center">
|
||||
<div class="flex-shrink-0 bg-indigo-500 rounded-md p-3">
|
||||
<div class="shrink-0 bg-indigo-500 rounded-md p-3">
|
||||
<svg class="h-6 w-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"></path>
|
||||
</svg>
|
||||
@@ -64,7 +64,7 @@
|
||||
<div class="bg-white overflow-hidden shadow rounded-lg">
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<div class="flex items-center">
|
||||
<div class="flex-shrink-0 bg-indigo-500 rounded-md p-3">
|
||||
<div class="shrink-0 bg-indigo-500 rounded-md p-3">
|
||||
<svg class="h-6 w-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
|
||||
</svg>
|
||||
@@ -85,7 +85,7 @@
|
||||
<div class="bg-white overflow-hidden shadow rounded-lg">
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<div class="flex items-center">
|
||||
<div class="flex-shrink-0 bg-indigo-500 rounded-md p-3">
|
||||
<div class="shrink-0 bg-indigo-500 rounded-md p-3">
|
||||
<svg class="h-6 w-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path>
|
||||
</svg>
|
||||
@@ -106,7 +106,7 @@
|
||||
<div class="bg-white overflow-hidden shadow rounded-lg">
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<div class="flex items-center">
|
||||
<div class="flex-shrink-0 bg-indigo-500 rounded-md p-3">
|
||||
<div class="shrink-0 bg-indigo-500 rounded-md p-3">
|
||||
<svg class="h-6 w-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 5a1 1 0 011-1h14a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM4 13a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H5a1 1 0 01-1-1v-6zM16 13a1 1 0 011-1h2a1 1 0 011 1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-6z"></path>
|
||||
</svg>
|
||||
@@ -127,7 +127,7 @@
|
||||
<div class="bg-white overflow-hidden shadow rounded-lg">
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<div class="flex items-center">
|
||||
<div class="flex-shrink-0 bg-indigo-500 rounded-md p-3">
|
||||
<div class="shrink-0 bg-indigo-500 rounded-md p-3">
|
||||
<svg class="h-6 w-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z"></path>
|
||||
</svg>
|
||||
|
||||
@@ -5,43 +5,6 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>@yield('title', 'FiveM MCP Server Documentation')</title>
|
||||
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
||||
<style>
|
||||
.code-block-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
.copy-button {
|
||||
position: absolute;
|
||||
top: 0.5rem;
|
||||
right: 0.5rem;
|
||||
padding: 0.375rem 0.75rem;
|
||||
background-color: rgba(55, 65, 81, 0.8);
|
||||
color: white;
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
border-radius: 0.375rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
opacity: 0;
|
||||
transition: all 0.2s ease-in-out;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
.code-block-wrapper:hover .copy-button {
|
||||
opacity: 1;
|
||||
}
|
||||
.copy-button:hover {
|
||||
background-color: rgba(55, 65, 81, 1);
|
||||
}
|
||||
.copy-button.copied {
|
||||
background-color: rgba(16, 185, 129, 0.9);
|
||||
border-color: rgba(16, 185, 129, 1);
|
||||
}
|
||||
.copy-button svg {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="h-full bg-gray-50">
|
||||
<div class="min-h-full">
|
||||
@@ -50,17 +13,17 @@
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex justify-between h-16">
|
||||
<div class="flex">
|
||||
<div class="flex-shrink-0 flex items-center">
|
||||
<div class="shrink-0 flex items-center">
|
||||
<h1 class="text-xl font-bold text-gray-900">FiveM MCP Server</h1>
|
||||
</div>
|
||||
<div class="hidden sm:ml-6 sm:flex sm:space-x-8">
|
||||
<a href="{{ route('docs.index') }}" class="@if(request()->routeIs('docs.index')) border-indigo-500 text-gray-900 @else border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 @endif inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">
|
||||
<a href="{{ route('docs.index') }}" class="{{ request()->routeIs('docs.index') ? 'border-indigo-500 text-gray-900' : 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700' }} inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">
|
||||
Home
|
||||
</a>
|
||||
<a href="{{ route('docs.quickstart') }}" class="@if(request()->routeIs('docs.quickstart')) border-indigo-500 text-gray-900 @else border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 @endif inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">
|
||||
<a href="{{ route('docs.quickstart') }}" class="{{ request()->routeIs('docs.quickstart') ? 'border-indigo-500 text-gray-900' : 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700' }} inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">
|
||||
Quick Start
|
||||
</a>
|
||||
<a href="{{ route('docs.documentation') }}" class="@if(request()->routeIs('docs.documentation')) border-indigo-500 text-gray-900 @else border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 @endif inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">
|
||||
<a href="{{ route('docs.documentation') }}" class="{{ request()->routeIs('docs.documentation') ? 'border-indigo-500 text-gray-900' : 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700' }} inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">
|
||||
Documentation
|
||||
</a>
|
||||
</div>
|
||||
@@ -83,65 +46,5 @@
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Add copy buttons to all code blocks
|
||||
const codeBlocks = document.querySelectorAll('pre');
|
||||
|
||||
codeBlocks.forEach(function(pre) {
|
||||
// Wrap the pre in a div if not already wrapped
|
||||
if (!pre.parentElement.classList.contains('code-block-wrapper')) {
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.className = 'code-block-wrapper';
|
||||
pre.parentNode.insertBefore(wrapper, pre);
|
||||
wrapper.appendChild(pre);
|
||||
}
|
||||
|
||||
const wrapper = pre.parentElement;
|
||||
|
||||
// Create copy button
|
||||
const copyButton = document.createElement('button');
|
||||
copyButton.className = 'copy-button';
|
||||
copyButton.innerHTML = `
|
||||
<svg class="copy-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path>
|
||||
</svg>
|
||||
<span class="copy-text">Copy</span>
|
||||
`;
|
||||
|
||||
copyButton.addEventListener('click', function() {
|
||||
const code = pre.querySelector('code');
|
||||
const text = code ? code.textContent : pre.textContent;
|
||||
|
||||
navigator.clipboard.writeText(text).then(function() {
|
||||
// Show success state
|
||||
copyButton.classList.add('copied');
|
||||
copyButton.innerHTML = `
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
||||
</svg>
|
||||
<span>Copied!</span>
|
||||
`;
|
||||
|
||||
// Reset after 2 seconds
|
||||
setTimeout(function() {
|
||||
copyButton.classList.remove('copied');
|
||||
copyButton.innerHTML = `
|
||||
<svg class="copy-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path>
|
||||
</svg>
|
||||
<span class="copy-text">Copy</span>
|
||||
`;
|
||||
}, 2000);
|
||||
}).catch(function(err) {
|
||||
console.error('Failed to copy:', err);
|
||||
});
|
||||
});
|
||||
|
||||
wrapper.appendChild(copyButton);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -3,27 +3,6 @@
|
||||
@section('title', 'Quick Start Guide - FiveM MCP Server')
|
||||
|
||||
@section('content')
|
||||
<style>
|
||||
.tab-button {
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
animation: fadeIn 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="max-w-4xl mx-auto py-12 px-4 sm:px-6 lg:px-8">
|
||||
<div class="bg-white shadow rounded-lg p-8">
|
||||
<h1 class="text-3xl font-extrabold text-gray-900 mb-6">FiveM MCP Quick Start Guide</h1>
|
||||
@@ -94,7 +73,11 @@
|
||||
<p class="text-gray-700 mb-3">
|
||||
Ask your server administrator for the MCP server URL. It will look like:
|
||||
</p>
|
||||
<pre class="bg-gray-100 rounded p-3 text-sm"><code>https://your-domain.com/mcp</code></pre>
|
||||
<pre class="bg-gray-100 rounded p-3 text-sm"><code>https://your-domain.com/fivem</code></pre>
|
||||
<p class="text-gray-700 mb-3 mt-4">
|
||||
If you're using the official hosted server, use this URL:
|
||||
</p>
|
||||
<pre class="bg-gray-100 rounded p-3 text-sm"><code>{{ route('fivem') }}</code></pre>
|
||||
</div>
|
||||
|
||||
<!-- Step 2 -->
|
||||
@@ -110,7 +93,7 @@
|
||||
<pre class="bg-gray-900 text-gray-100 rounded p-4 overflow-x-auto"><code>{
|
||||
"mcpServers": {
|
||||
"fivem": {
|
||||
"url": "https://your-domain.com/mcp"
|
||||
"url": "https://your-domain.com/fivem"
|
||||
}
|
||||
}
|
||||
}</code></pre>
|
||||
@@ -121,7 +104,7 @@
|
||||
"mcp": {
|
||||
"servers": {
|
||||
"fivem": {
|
||||
"url": "https://your-domain.com/mcp"
|
||||
"url": "https://your-domain.com/fivem"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,10 @@
|
||||
# QBCore Events
|
||||
|
||||
@foreach($eventsByCategory as $category => $events)
|
||||
## {{ ucfirst($category) }} Events
|
||||
|
||||
@foreach($events as $event)
|
||||
- **{{ $event['name'] }}** ({{ ucfirst($event['side']) }}) - {{ $event['description'] }}
|
||||
@endforeach
|
||||
|
||||
@endforeach
|
||||
@@ -0,0 +1,35 @@
|
||||
# QBCore Event Reference
|
||||
|
||||
## {{ $event['name'] }}
|
||||
|
||||
**Category:** {{ $event['category'] }}
|
||||
**Side:** {{ ucfirst($event['side']) }}
|
||||
|
||||
### 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'] }}
|
||||
```
|
||||
|
||||
@if(isset($event['documentation']))
|
||||
### Documentation
|
||||
[View at docs.qbcore.org]({{ $event['documentation'] }})
|
||||
@endif
|
||||
@@ -0,0 +1,48 @@
|
||||
# QBCore 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)
|
||||
```
|
||||
@@ -4,3 +4,5 @@ use App\Mcp\Servers\FiveMServer;
|
||||
use Laravel\Mcp\Facades\Mcp;
|
||||
|
||||
Mcp::local('fivem', FiveMServer::class);
|
||||
|
||||
Mcp::web('fivem', FiveMServer::class)->name('fivem');
|
||||
@@ -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();
|
||||
});
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
use App\Mcp\Tools\GetQBCoreEventReference;
|
||||
use Laravel\Mcp\Request;
|
||||
|
||||
it('get qbcore event reference returns response with content', function () {
|
||||
$tool = new GetQBCoreEventReference();
|
||||
$request = new Request(['event_name' => 'QBCore:Client:OnPlayerLoaded']);
|
||||
|
||||
$response = $tool->handle($request);
|
||||
|
||||
expect($response)->not()->toBeNull();
|
||||
expect($response->content())->not()->toBeNull();
|
||||
});
|
||||
|
||||
it('get qbcore event reference handles unknown event', function () {
|
||||
$tool = new GetQBCoreEventReference();
|
||||
$request = new Request(['event_name' => 'NonExistentQBCoreEvent123']);
|
||||
|
||||
$response = $tool->handle($request);
|
||||
$content = (string) $response->content();
|
||||
|
||||
expect($content)->toContain('not found') || expect($content)->toContain('QBCore event');
|
||||
});
|
||||
|
||||
it('get qbcore event reference filters by category', function () {
|
||||
$tool = new GetQBCoreEventReference();
|
||||
$request = new Request(['event_category' => 'player']);
|
||||
|
||||
$response = $tool->handle($request);
|
||||
|
||||
expect($response)->not()->toBeNull();
|
||||
expect($response->content())->not()->toBeNull();
|
||||
});
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
use App\Mcp\Tools\GetQBCoreFunction;
|
||||
use Laravel\Mcp\Request;
|
||||
|
||||
it('get qbcore function returns response with content', function () {
|
||||
$tool = new GetQBCoreFunction();
|
||||
$request = new Request(['function_name' => 'QBCore.Functions.GetPlayer']);
|
||||
|
||||
$response = $tool->handle($request);
|
||||
|
||||
expect($response)->not()->toBeNull();
|
||||
expect($response->content())->not()->toBeNull();
|
||||
});
|
||||
|
||||
it('get qbcore function handles unknown function', function () {
|
||||
$tool = new GetQBCoreFunction();
|
||||
$request = new Request(['function_name' => 'NonExistentFunction123']);
|
||||
|
||||
$response = $tool->handle($request);
|
||||
$content = (string) $response->content();
|
||||
|
||||
expect($content)->toContain('not found') || expect($content)->toContain('QBCore');
|
||||
});
|
||||
|
||||
it('get qbcore function works with shorthand names', function () {
|
||||
$tool = new GetQBCoreFunction();
|
||||
$request = new Request(['function_name' => 'AddMoney']);
|
||||
|
||||
$response = $tool->handle($request);
|
||||
|
||||
expect($response)->not()->toBeNull();
|
||||
expect($response->content())->not()->toBeNull();
|
||||
});
|
||||
|
||||
it('get qbcore function supports javascript examples', function () {
|
||||
$tool = new GetQBCoreFunction();
|
||||
$request = new Request(['function_name' => 'AddMoney', 'language' => 'js']);
|
||||
|
||||
$response = $tool->handle($request);
|
||||
|
||||
expect($response)->not()->toBeNull();
|
||||
expect($response->content())->not()->toBeNull();
|
||||
});
|
||||
Reference in new issue
Block a user