8 Commits
Author SHA1 Message Date
Adrian Mejias a2591c2209 feat: Add COX MySQL tools and resources for event references and function lookups
- Implemented DatabaseQueries resource for FiveM database query examples.
- Created GetCOXEventReference tool to retrieve information about COX MySQL events.
- Developed GetCOXFunction tool to look up COX MySQL functions and methods.
- Added views for displaying event lists, event references, and function details.
- Included comprehensive CRUD operation examples for database interactions in FiveM.
- Established tests for GetCOXEventReference and GetCOXFunction tools to ensure functionality.
2026-03-01 01:20:59 -06:00
Adrian Mejias 18765b1013 Add COX MySQL documentation support to SearchFiveMDocs tool and update related documentation 2026-03-01 01:14:56 -06:00
Adrian Mejias 969e4cf0da Implement feature X to enhance user experience and fix bug Y in module Z 2026-03-01 01:12:16 -06:00
Adrian Mejias 379d6d3d2a Add QBCore event and function tools with documentation and tests
- Implement GetQBCoreEventReference tool to retrieve QBCore event information.
- Implement GetQBCoreFunction tool to look up QBCore functions and exports.
- Enhance SearchFiveMDocs to include QBCore documentation links.
- Create Blade views for displaying QBCore events and function details.
- Add tests for GetQBCoreEventReference and GetQBCoreFunction tools to ensure functionality and error handling.
2026-03-01 01:07:48 -06:00
Adrian Mejias d9f5e2d315 Enhance documentation and UI with copy buttons for code blocks, improve animations, and update URLs 2026-03-01 01:02:33 -06:00
Adrian Mejias 2d547e7fb8 Update README.md to include details about the custom VSCode extension and its features 2026-02-28 22:59:52 -06:00
Adrian Mejias 5f8d67eec9 Add web route for FiveM server in ai.php 2026-02-28 22:47:10 -06:00
Adrian Mejias 48326df8ea Implement feature X to enhance user experience and fix bug Y in module Z 2026-02-28 22:11:45 -06:00
27 changed files with 2626 additions and 1869 deletions

No files matched your search

+18
View File
@@ -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": []
}
```
+22
View File
@@ -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());
}
}
+10
View File
@@ -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 = [
+213
View File
@@ -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'),
];
}
}
+230
View File
@@ -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'),
];
}
}
+277
View File
@@ -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'),
];
}
}
+270
View File
@@ -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'),
];
}
}
+113 -1
View File
@@ -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'),
];
+164 -1275
View File
File diff suppressed because it is too large. Load diff
+60
View File
@@ -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;
}
+37
View File
@@ -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
+6 -6
View File
@@ -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>
+4 -101
View File
@@ -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>
+7 -24
View File
@@ -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)
```
+2
View File
@@ -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();
});
+54
View File
@@ -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();
});