diff --git a/README.md b/README.md index 590d869..215cfa0 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ The FiveM MCP Server enables seamless integration with AI coding assistants (Cla ### Live Site -A live instance is available at: **https://fivem-mcp.kingsoflossantos.com/fivem** +A live instance is available at: **https://fivem-mcp.kingsoflossantos.com** This site includes interactive documentation, tool descriptions, and integration guides. @@ -44,6 +44,18 @@ npm run build ### Running the Server +**Live AI Assistants (Claude/Client):** +```json +{ + "servers": { + "fivem": { + "url": "https://fivem-mcp.kingsoflossantos.com/fivem", + "type": "http" + } + } +} +``` + **Local Testing:** ```bash php artisan mcp:inspector fivem diff --git a/app/Mcp/Servers/FiveMServer.php b/app/Mcp/Servers/FiveMServer.php index 1b44c24..23f1eb9 100644 --- a/app/Mcp/Servers/FiveMServer.php +++ b/app/Mcp/Servers/FiveMServer.php @@ -24,6 +24,9 @@ use App\Mcp\Tools\FiveM\Server\GetEventServerReference; use App\Mcp\Tools\FiveM\Server\GetNativeServerFunction; use App\Mcp\Tools\QBCore\Client\GetQBCoreClientEventReference; use App\Mcp\Tools\QBCore\Client\GetQBCoreClientFunction; +use App\Mcp\Tools\QBCore\GetQBCoreConfig; +use App\Mcp\Tools\QBCore\GetQBCorePlayers; +use App\Mcp\Tools\QBCore\GetQBCoreSharedData; use App\Mcp\Tools\QBCore\Server\GetQBCoreServerEventReference; use App\Mcp\Tools\QBCore\Server\GetQBCoreServerFunction; use Laravel\Mcp\Server; @@ -60,6 +63,9 @@ class FiveMServer extends Server GetQBCoreClientFunction::class, GetQBCoreServerEventReference::class, GetQBCoreClientEventReference::class, + GetQBCoreSharedData::class, + GetQBCoreConfig::class, + GetQBCorePlayers::class, GetCOXEventReference::class, GetCOXFunction::class, GetInventoryServerFunction::class, diff --git a/app/Mcp/Tools/QBCore/Client/GetQBCoreClientFunction.php b/app/Mcp/Tools/QBCore/Client/GetQBCoreClientFunction.php index f062aaf..bf9f498 100644 --- a/app/Mcp/Tools/QBCore/Client/GetQBCoreClientFunction.php +++ b/app/Mcp/Tools/QBCore/Client/GetQBCoreClientFunction.php @@ -113,6 +113,101 @@ class GetQBCoreClientFunction extends Tool 'lua_example' => "RegisterNetEvent('QBCore:Client:OnPlayerLoad')\nAddEventHandler('QBCore:Client:OnPlayerLoad', function()\n local playerData = QBCore.Functions.GetPlayerData()\n print('Player loaded: ' .. playerData.charinfo.firstname)\nend)", 'js_example' => "RegisterNetEvent('QBCore:Client:OnPlayerLoad');\nAddEventHandler('QBCore:Client:OnPlayerLoad', function() {\n const playerData = QBCore.Functions.GetPlayerData();\n console.log(`Player loaded: \${playerData.charinfo.firstname}`);\n});", ], + 'TriggerServerEvent' => [ + 'namespace' => 'FiveM Core', + 'description' => 'Trigger a server-side event from the client (client-side only)', + 'side' => 'client', + 'parameters' => [ + ['name' => 'eventName', 'type' => 'string', 'description' => 'Event name to trigger on server'], + ['name' => '...', 'type' => 'any', 'description' => 'Arguments to pass to server event'], + ], + 'returns' => ['type' => 'void', 'description' => ''], + 'lua_example' => "TriggerServerEvent('myResource:serverEvent', 'arg1', 'arg2')\n-- Received on server with:\n-- RegisterNetEvent('myResource:serverEvent')\n-- AddEventHandler('myResource:serverEvent', function(arg1, arg2) end)", + 'js_example' => "TriggerServerEvent('myResource:serverEvent', 'arg1', 'arg2');\n// Received on server with:\n// on('myResource:serverEvent', (arg1, arg2) => {})", + ], + 'Citizen.Wait' => [ + 'namespace' => 'FiveM Core', + 'description' => 'Wait/sleep for a specified duration in milliseconds (client-side)', + 'side' => 'client', + 'parameters' => [ + ['name' => 'ms', 'type' => 'number', 'description' => 'Duration in milliseconds'], + ], + 'returns' => ['type' => 'void', 'description' => ''], + 'lua_example' => "print('Before wait')\nCitizen.Wait(5000) -- Wait 5 seconds\nprint('After wait')", + 'js_example' => "console.log('Before wait');\nawait Citizen.Wait(5000); // Wait 5 seconds\nconsole.log('After wait');", + ], + 'Citizen.CreateThread' => [ + 'namespace' => 'FiveM Core', + 'description' => 'Create an async thread (client-side)', + 'side' => 'client', + 'parameters' => [ + ['name' => 'callback', 'type' => 'function', 'description' => 'Function to execute as thread'], + ], + 'returns' => ['type' => 'number', 'description' => 'Thread ID'], + 'lua_example' => "Citizen.CreateThread(function()\n while true do\n Citizen.Wait(1000) -- Repeat every second\n print('This runs repeatedly')\n end\nend)", + 'js_example' => "Citizen.CreateThread(async () => {\n while (true) {\n await Citizen.Wait(1000); // Repeat every second\n console.log('This runs repeatedly');\n }\n});", + ], + 'PlayerPedId' => [ + 'namespace' => 'FiveM Core', + 'description' => 'Get the local player ped entity (client-side only)', + 'side' => 'client', + 'parameters' => [], + 'returns' => ['type' => 'number', 'description' => 'Player ped entity ID'], + 'lua_example' => "local playerPed = PlayerPedId()\nprint('My ped ID: ' .. playerPed)", + 'js_example' => "const playerPed = PlayerPedId();\nconsole.log(`My ped ID: \${playerPed}`);", + ], + 'GetEntityCoords' => [ + 'namespace' => 'FiveM Core', + 'description' => 'Get the coordinates of an entity (client-side)', + 'side' => 'client', + 'parameters' => [ + ['name' => 'entity', 'type' => 'number', 'description' => 'Entity ID'], + ['name' => 'alive', 'type' => 'boolean|nil', 'description' => 'Whether to get alive coords (default true)'], + ], + 'returns' => ['type' => 'vector3', 'description' => 'X, Y, Z coordinates'], + 'lua_example' => "local playerPed = PlayerPedId()\nlocal playerCoords = GetEntityCoords(playerPed)\nprint('Player at: ' .. playerCoords.x .. ', ' .. playerCoords.y .. ', ' .. playerCoords.z)", + 'js_example' => "const playerPed = PlayerPedId();\nconst playerCoords = GetEntityCoords(playerPed);\nconsole.log(`Player at: \${playerCoords.x}, \${playerCoords.y}, \${playerCoords.z}`);", + ], + 'SetEntityCoords' => [ + 'namespace' => 'FiveM Core', + 'description' => 'Set the coordinates of an entity (client-side)', + 'side' => 'client', + 'parameters' => [ + ['name' => 'entity', 'type' => 'number', 'description' => 'Entity ID'], + ['name' => 'x', 'type' => 'number', 'description' => 'X coordinate'], + ['name' => 'y', 'type' => 'number', 'description' => 'Y coordinate'], + ['name' => 'z', 'type' => 'number', 'description' => 'Z coordinate'], + ['name' => 'alive', 'type' => 'boolean|nil', 'description' => 'Whether alive (default true)'], + ['name' => 'animate', 'type' => 'boolean|nil', 'description' => 'Whether to animate movement (default false)'], + ], + 'returns' => ['type' => 'void', 'description' => ''], + 'lua_example' => "local playerPed = PlayerPedId()\nSetEntityCoords(playerPed, 100.0, 200.0, 50.0, false, false, false, false)\nprint('Teleported!')", + 'js_example' => "const playerPed = PlayerPedId();\nSetEntityCoords(playerPed, 100.0, 200.0, 50.0, false, false, false, false);\nconsole.log('Teleported!');", + ], + 'QBCore.Functions.Notify' => [ + 'namespace' => 'UI', + 'description' => 'Send a notification to the local player on client (client-side only)', + 'side' => 'client', + 'parameters' => [ + ['name' => 'text', 'type' => 'string', 'description' => 'Notification text'], + ['name' => 'type', 'type' => 'string', 'description' => 'Notification type: error, success, primary, warning'], + ['name' => 'duration', 'type' => 'number|nil', 'description' => 'Duration in milliseconds (default 5000)'], + ], + 'returns' => ['type' => 'void', 'description' => ''], + 'lua_example' => "QBCore.Functions.Notify('Welcome to the server!', 'success', 5000)", + 'js_example' => "QBCore.Functions.Notify('Welcome to the server!', 'success', 5000);", + ], + 'QBCore.Functions.GetResourceMeta' => [ + 'namespace' => 'Core', + 'description' => 'Get a resource metadata value (client-side)', + 'side' => 'client', + 'parameters' => [ + ['name' => 'key', 'type' => 'string', 'description' => 'Metadata key to retrieve'], + ], + 'returns' => ['type' => 'any', 'description' => 'The metadata value or nil'], + 'lua_example' => "local version = QBCore.Functions.GetResourceMeta('version')\nif version then\n print('QBCore Version: ' .. version)\nend", + 'js_example' => "const version = QBCore.Functions.GetResourceMeta('version');\nif (version) {\n console.log(`QBCore Version: \${version}`);\n}", + ], ]; $nameLower = strtolower($name); @@ -147,7 +242,7 @@ class GetQBCoreClientFunction extends Tool return [ 'function_name' => $schema ->string() - ->description('The name of the QBCore client function to look up (e.g., "QBCore.Functions.GetPlayerData", "QBCore.Commands.Add")') + ->description('The name of the QBCore client function to look up (e.g., "QBCore.Functions.GetPlayerData", "QBCore.Commands.Add", "TriggerServerEvent", "Citizen.Wait", "PlayerPedId")') ->required(), 'language' => $schema ->string() diff --git a/app/Mcp/Tools/QBCore/GetQBCoreConfig.php b/app/Mcp/Tools/QBCore/GetQBCoreConfig.php new file mode 100644 index 0000000..f66d504 --- /dev/null +++ b/app/Mcp/Tools/QBCore/GetQBCoreConfig.php @@ -0,0 +1,159 @@ +get('config_type'); + $language = $request->get('language', 'lua'); + + $config = $this->findConfigType($configType); + + if (! $config) { + return Response::text(sprintf("QBCore config '%s' not found. Available types: Player, Framework, Database, Callbacks, Database, Features", $configType)); + } + + $output = $this->formatConfigInfo($config, $language); + + return Response::text($output); + } + + /** + * Find a QBCore config type. + */ + protected function findConfigType(string $name): ?array + { + $configs = [ + 'Player' => [ + 'category' => 'Player', + 'description' => 'Default player configuration including character generation and initial data setup.', + 'location' => 'qb-core/config.lua', + 'config_options' => [ + ['name' => 'PlayerDefaults', 'type' => 'table', 'description' => 'Default values for new player characters (money, metadata, inventory)'], + ['name' => 'Metadata', 'type' => 'table', 'description' => 'Default metadata values like hunger, thirst, stress, temperature'], + ['name' => 'Money', 'type' => 'table', 'description' => 'Default starting money (cash, bank, crypto)'], + ['name' => 'Licenses', 'type' => 'table', 'description' => 'Default licenses a player starts with'], + ], + 'lua_example' => "local QBConfig = exports['qb-core']:GetCoreObject().Config\nif QBConfig.PlayerDefaults then\n print('Default metadata: ' .. json.encode(QBConfig.PlayerDefaults.metadata))\nend", + 'js_example' => "const QBConfig = exports['qb-core']:GetCoreObject().Config;\nif (QBConfig.PlayerDefaults) {\n console.log('Default metadata: ' + JSON.stringify(QBConfig.PlayerDefaults.metadata));\n}", + ], + 'Framework' => [ + 'category' => 'Framework', + 'description' => 'Core framework settings including server name, version, and general options.', + 'location' => 'qb-core/config.lua', + 'config_options' => [ + ['name' => 'ServerName', 'type' => 'string', 'description' => 'Name of your QBCore server'], + ['name' => 'Version', 'type' => 'string', 'description' => 'QBCore framework version'], + ['name' => 'Timezone', 'type' => 'string', 'description' => 'Server timezone (e.g., "America/New_York")'], + ['name' => 'Debug', 'type' => 'boolean', 'description' => 'Enable debug logging'], + ], + 'lua_example' => "local QBConfig = exports['qb-core']:GetCoreObject().Config\nprint('Server Name: ' .. QBConfig.ServerName)\nprint('Version: ' .. QBConfig.Version)\nprint('Debug Mode: ' .. tostring(QBConfig.Debug))", + 'js_example' => "const QBConfig = exports['qb-core']:GetCoreObject().Config;\nconsole.log(`Server Name: \${QBConfig.ServerName}`);\nconsole.log(`Version: \${QBConfig.Version}`);\nconsole.log(`Debug Mode: \${QBConfig.Debug}`);", + ], + 'Database' => [ + 'category' => 'Database', + 'description' => 'Database configuration and connection settings.', + 'location' => 'qb-core/config.lua', + 'config_options' => [ + ['name' => 'Backend', 'type' => 'string', 'description' => 'Database backend (ox_mysql, mysql-async, oxmysql)'], + ['name' => 'Host', 'type' => 'string', 'description' => 'Database server address'], + ['name' => 'Port', 'type' => 'number', 'description' => 'Database server port'], + ['name' => 'Username', 'type' => 'string', 'description' => 'Database username'], + ['name' => 'Password', 'type' => 'string', 'description' => 'Database password (set in .env or server.cfg)'], + ['name' => 'Database', 'type' => 'string', 'description' => 'Database name'], + ], + 'lua_example' => "-- Database configuration is typically in environment variables\n-- Access through resource config if exposed\nprint('Database configuration is set in server.cfg or environment variables')", + 'js_example' => "// Database configuration is typically in environment variables\n// Access through resource config if exposed\nconsole.log('Database configuration is set in server.cfg or environment variables');", + ], + 'Callbacks' => [ + 'category' => 'Callbacks', + 'description' => 'Server-side callbacks that resources can register to respond to client requests.', + 'location' => 'qb-core/server/callbacks.lua', + 'config_options' => [ + ['name' => 'RegisterCallback', 'type' => 'function', 'description' => 'Register a server callback that client scripts can call'], + ['name' => 'TriggerCallback', 'type' => 'function', 'description' => 'Trigger a callback from client to server with response'], + ], + 'lua_example' => "-- Server-side callback registration\nQBCore.Functions.CreateCallback('myResource:getData', function(source, cb, data)\n -- Process data from client\n cb({success = true, message = 'Data received'})\nend)\n\n-- Client-side callback usage\nQBCore.Functions.TriggerCallback('myResource:getData', function(result)\n print('Server response: ' .. result.message)\nend, myData)", + 'js_example' => "// Server-side callback registration\nQBCore.Functions.CreateCallback('myResource:getData', (source, cb, data) => {\n // Process data from client\n cb({success: true, message: 'Data received'});\n});\n\n// Client-side callback usage\nQBCore.Functions.TriggerCallback('myResource:getData', (result) => {\n console.log(`Server response: \${result.message}`);\n}, myData);", + ], + 'Features' => [ + 'category' => 'Features', + 'description' => 'Framework feature flags and optional feature configurations.', + 'location' => 'qb-core/config.lua', + 'config_options' => [ + ['name' => 'Encryption', 'type' => 'boolean', 'description' => 'Enable data encryption'], + ['name' => 'Logging', 'type' => 'boolean', 'description' => 'Enable comprehensive logging'], + ['name' => 'PerformanceMonitor', 'type' => 'boolean', 'description' => 'Monitor resource performance'], + ['name' => 'AntiCheat', 'type' => 'boolean', 'description' => 'Enable anti-cheat systems'], + ], + 'lua_example' => "local QBConfig = exports['qb-core']:GetCoreObject().Config\nif QBConfig.Encryption then\n print('Data encryption is enabled')\nend\nif QBConfig.AntiCheat then\n print('Anti-cheat is enabled')\nend", + 'js_example' => "const QBConfig = exports['qb-core']:GetCoreObject().Config;\nif (QBConfig.Encryption) {\n console.log('Data encryption is enabled');\n}\nif (QBConfig.AntiCheat) {\n console.log('Anti-cheat is enabled');\n}", + ], + 'RepTypes' => [ + 'category' => 'RepTypes', + 'description' => 'Reputation types available for player progression. Defined in config for use with AddRep/RemoveRep.', + 'location' => 'qb-core/config.lua', + 'config_options' => [ + ['name' => 'Examples', 'type' => 'table', 'description' => 'Common rep types: selling, heist, cartheft, etc.'], + ['name' => 'Custom', 'type' => 'string', 'description' => 'Add custom rep types in config for your resources'], + ], + 'lua_example' => "-- Add rep to player (types defined in config)\nPlayer.Functions.AddRep('selling', 10)\nPlayer.Functions.AddRep('heist', 5)\nPlayer.Functions.AddRep('cartheft', 15)\n\n-- Get current rep\nlocal sellingRep = Player.Functions.GetRep('selling')\nprint('Selling Rep: ' .. sellingRep)", + 'js_example' => "// Add rep to player (types defined in config)\nPlayer.Functions.AddRep('selling', 10);\nPlayer.Functions.AddRep('heist', 5);\nPlayer.Functions.AddRep('cartheft', 15);\n\n// Get current rep\nconst sellingRep = Player.Functions.GetRep('selling');\nconsole.log(`Selling Rep: \${sellingRep}`);", + ], + ]; + + $nameLower = strtolower($name); + + foreach ($configs as $configName => $configData) { + if (strtolower($configName) === $nameLower) { + return $configData; + } + } + + return null; + } + + /** + * Format config info. + */ + protected function formatConfigInfo(array $config, string $language): string + { + return view('mcp.qbcore.qbcore-config', [ + 'config' => $config, + 'language' => $language, + ])->render(); + } + + /** + * Get the tool's input schema. + * + * @return array + */ + public function schema(JsonSchema $schema): array + { + return [ + 'config_type' => $schema + ->string() + ->description('The type of QBCore configuration to look up (Player, Framework, Database, Callbacks, Features, RepTypes)') + ->enum(['Player', 'Framework', 'Database', 'Callbacks', 'Features', 'RepTypes']) + ->required(), + 'language' => $schema + ->string() + ->enum(['lua', 'js']) + ->description('The scripting language for code examples') + ->default('lua'), + ]; + } +} diff --git a/app/Mcp/Tools/QBCore/GetQBCorePlayers.php b/app/Mcp/Tools/QBCore/GetQBCorePlayers.php new file mode 100644 index 0000000..6db8e59 --- /dev/null +++ b/app/Mcp/Tools/QBCore/GetQBCorePlayers.php @@ -0,0 +1,170 @@ +get('info_type'); + $language = $request->get('language', 'lua'); + + $info = $this->findPlayerInfo($infoType); + + if (! $info) { + return Response::text(sprintf("QBCore player info '%s' not found. Available types: Structure, PlayerData, Methods, Events, Examples", $infoType)); + } + + $output = $this->formatPlayerInfo($info, $language); + + return Response::text($output); + } + + /** + * Find QBCore player information. + */ + protected function findPlayerInfo(string $name): ?array + { + $playerInfo = [ + 'Structure' => [ + 'type' => 'Structure', + 'title' => 'QBCore.Players Table Structure', + 'description' => 'The QBCore.Players table on the server contains all currently connected players indexed by their source (net ID).', + 'properties' => [ + ['name' => 'QBCore.Players[source]', 'type' => 'table', 'description' => 'Player object for given source ID'], + ['name' => 'PlayerData', 'type' => 'table', 'description' => 'All player data (citizenid, job, gang, money, items, etc.)'], + ['name' => 'Functions', 'type' => 'table', 'description' => 'Methods available on player object (AddItem, RemoveItem, etc.)'], + ['name' => 'Metadata', 'type' => 'table', 'description' => 'Player metadata (hunger, thirst, stress, etc.)'], + ['name' => 'Inventory', 'type' => 'table', 'description' => 'Current inventory items and slots'], + ], + 'lua_example' => "-- Access a player by source (server-side)\nlocal Player = QBCore.Players[source]\nif Player then\n print('Player found: ' .. Player.PlayerData.charinfo.firstname)\nend\n\n-- Better to use the function instead:\nlocal Player = QBCore.Functions.GetPlayer(source)\nif Player then\n print('Player: ' .. Player.Functions.GetName())\nend", + 'js_example' => "// Access a player by source (server-side)\nconst Player = QBCore.Players[global.source];\nif (Player) {\n console.log(`Player found: \${Player.PlayerData.charinfo.firstname}`);\n}\n\n// Better to use the function instead:\nconst Player = QBCore.Functions.GetPlayer(global.source);\nif (Player) {\n console.log(`Player: \${Player.Functions.GetName()}`);\n}", + ], + 'PlayerData' => [ + 'type' => 'PlayerData', + 'title' => 'Player Data Properties', + 'description' => 'Complete structure of the PlayerData table containing all player information.', + 'properties' => [ + ['name' => 'source', 'type' => 'number', 'description' => 'Player net ID (used to identify player)'], + ['name' => 'citizenid', 'type' => 'string', 'description' => 'Unique character identifier'], + ['name' => 'cid', 'type' => 'number', 'description' => 'Character ID in database'], + ['name' => 'charinfo', 'type' => 'table', 'description' => 'Character information (firstname, lastname, gender, birthdate, etc.)'], + ['name' => 'job', 'type' => 'table', 'description' => 'Job information (name, label, grade, payment, onduty)'], + ['name' => 'gang', 'type' => 'table', 'description' => 'Gang information (name, label, grade, isboss)'], + ['name' => 'money', 'type' => 'table', 'description' => 'Money table (cash, bank accounts)'], + ['name' => 'metadata', 'type' => 'table', 'description' => 'Player metadata (hunger, thirst, stress, armor, health)'], + ['name' => 'items', 'type' => 'table', 'description' => 'Inventory items'], + ['name' => 'position', 'type' => 'vector3', 'description' => 'Player position coordinates'], + ['name' => 'licenses', 'type' => 'table', 'description' => 'Licenses owned by player'], + ], + 'lua_example' => "local Player = QBCore.Functions.GetPlayer(source)\nif Player then\n local pd = Player.PlayerData\n print('Name: ' .. pd.charinfo.firstname .. ' ' .. pd.charinfo.lastname)\n print('Citizen ID: ' .. pd.citizenid)\n print('Job: ' .. pd.job.name .. ' (Grade: ' .. pd.job.grade.level .. ')')\n print('Cash: $' .. pd.money.cash .. ' | Bank: $' .. pd.money.bank)\n print('Hunger: ' .. pd.metadata.hunger)\nend", + 'js_example' => "const Player = QBCore.Functions.GetPlayer(global.source);\nif (Player) {\n const pd = Player.PlayerData;\n console.log(`Name: \${pd.charinfo.firstname} \${pd.charinfo.lastname}`);\n console.log(`Citizen ID: \${pd.citizenid}`);\n console.log(`Job: \${pd.job.name} (Grade: \${pd.job.grade.level})`);\n console.log(`Cash: \\\$\${pd.money.cash} | Bank: \\\$\${pd.money.bank}`);\n console.log(`Hunger: \${pd.metadata.hunger}`);\n}", + ], + 'Methods' => [ + 'type' => 'Methods', + 'title' => 'Player Object Methods', + 'description' => 'Functions available on the Player object for manipulating player data.', + 'methods' => [ + ['name' => 'AddItem(item, amount)', 'description' => 'Add item to player inventory'], + ['name' => 'RemoveItem(item, amount)', 'description' => 'Remove item from player inventory'], + ['name' => 'HasItem(item, amount)', 'description' => 'Check if player has item'], + ['name' => 'GetItemByName(item)', 'description' => 'Get item data by name'], + ['name' => 'AddMoney(type, amount, reason)', 'description' => 'Add money to player account'], + ['name' => 'RemoveMoney(type, amount, reason)', 'description' => 'Remove money from player account'], + ['name' => 'SetMoney(type, amount, reason)', 'description' => 'Set money to exact amount'], + ['name' => 'GetMoney(type)', 'description' => 'Get player money amount'], + ['name' => 'SetJob(job, grade)', 'description' => 'Set player job and grade'], + ['name' => 'GetJob()', 'description' => 'Get player job data'], + ['name' => 'SetJobDuty(onDuty)', 'description' => 'Set player on/off duty'], + ['name' => 'SetGang(gang, grade)', 'description' => 'Set player gang and grade'], + ['name' => 'SetPlayerData(key, value)', 'description' => 'Set player data by key'], + ['name' => 'SetMetaData(key, value)', 'description' => 'Set player metadata by key'], + ['name' => 'GetMetaData(key)', 'description' => 'Get player metadata by key'], + ['name' => 'AddRep(type, amount)', 'description' => 'Add reputation points'], + ['name' => 'RemoveRep(type, amount)', 'description' => 'Remove reputation points'], + ['name' => 'GetRep(type)', 'description' => 'Get reputation value'], + ['name' => 'Notify(text, type, duration)', 'description' => 'Send notification to player'], + ['name' => 'Save()', 'description' => 'Save player data to database'], + ['name' => 'UpdatePlayerData()', 'description' => 'Sync player data to client'], + ['name' => 'GetName()', 'description' => 'Get full player name'], + ['name' => 'Logout()', 'description' => 'Force player to logout'], + ], + 'lua_example' => "local Player = QBCore.Functions.GetPlayer(source)\nif Player then\n -- Manage inventory\n Player.Functions.AddItem('water', 5)\n Player.Functions.RemoveItem('water', 2)\n \n -- Manage money\n Player.Functions.AddMoney('cash', 1000, 'Salary')\n local cash = Player.Functions.GetMoney('cash')\n \n -- Set job\n Player.Functions.SetJob('police', 2)\n Player.Functions.SetJobDuty(true)\n \n -- Notify player\n Player.Functions.Notify('Welcome to the server!', 'success')\n \n -- Save changes\n Player.Functions.Save()\nend", + 'js_example' => "const Player = QBCore.Functions.GetPlayer(global.source);\nif (Player) {\n // Manage inventory\n Player.Functions.AddItem('water', 5);\n Player.Functions.RemoveItem('water', 2);\n \n // Manage money\n Player.Functions.AddMoney('cash', 1000, 'Salary');\n const cash = Player.Functions.GetMoney('cash');\n \n // Set job\n Player.Functions.SetJob('police', 2);\n Player.Functions.SetJobDuty(true);\n \n // Notify player\n Player.Functions.Notify('Welcome to the server!', 'success');\n \n // Save changes\n Player.Functions.Save();\n}", + ], + 'Events' => [ + 'type' => 'Events', + 'title' => 'Player-Related Events', + 'description' => 'Events triggered when player data changes or player actions occur.', + 'events' => [ + ['name' => 'QBCore:Player:SetPlayerData', 'description' => 'Triggered when player data is updated'], + ['name' => 'QBCore:Server:OnJobUpdate', 'description' => 'Triggered when player job is changed'], + ['name' => 'QBCore:Client:OnJobUpdate', 'description' => 'Client-side job update event'], + ['name' => 'QBCore:Player:PlayerLoaded', 'description' => 'Triggered when player finishes loading'], + ['name' => 'QBCore:Player:PlayerLoggedOut', 'description' => 'Triggered when player logs out'], + ], + 'lua_example' => "-- Server-side: Listen for job updates\nRegisterNetEvent('QBCore:Server:OnJobUpdate', function(source)\n local Player = QBCore.Functions.GetPlayer(source)\n if Player then\n print(Player.Functions.GetName() .. ' job updated to ' .. Player.PlayerData.job.name)\n end\nend)\n\n-- Client-side: Listen for player load\nRegisterNetEvent('QBCore:Player:SetPlayerData')\nAddEventHandler('QBCore:Player:SetPlayerData', function()\n local playerData = QBCore.Functions.GetPlayerData()\n print('Player data updated!')\nend)", + 'js_example' => "// Server-side: Listen for job updates\non('QBCore:Server:OnJobUpdate', (source) => {\n const Player = QBCore.Functions.GetPlayer(source);\n if (Player) {\n console.log(`\${Player.Functions.GetName()} job updated to \${Player.PlayerData.job.name}`);\n }\n});\n\n// Client-side: Listen for player load\non('QBCore:Player:SetPlayerData', () => {\n const playerData = QBCore.Functions.GetPlayerData();\n console.log('Player data updated!');\n});", + ], + 'Examples' => [ + 'type' => 'Examples', + 'title' => 'Common Player Usage Examples', + 'description' => 'Real-world examples of working with player objects in QBCore.', + 'lua_example' => "-- Example 1: Check if player has items before proceeding\nlocal Player = QBCore.Functions.GetPlayer(source)\nif Player then\n if Player.Functions.HasItem('police_badge', 1) then\n print('Player is a police officer')\n end\nend\n\n-- Example 2: Transfer money between players\nlocal Player1 = QBCore.Functions.GetPlayer(source)\nlocal Player2 = QBCore.Functions.GetPlayerByCitizenId(citizenid)\nif Player1 and Player2 then\n local amount = 500\n if Player1.Functions.RemoveMoney('cash', amount) then\n Player2.Functions.AddMoney('cash', amount, 'Player Transfer')\n Player1.Functions.Notify('Gave \$' .. amount .. ' to ' .. Player2.Functions.GetName(), 'success')\n Player2.Functions.Notify('Received \$' .. amount .. ' from ' .. Player1.Functions.GetName(), 'success')\n end\nend\n\n-- Example 3: Job paycheck system\nfor i = 1, #QBCore.Functions.GetPlayers() do\n local Player = QBCore.Functions.GetPlayer(QBCore.Functions.GetPlayers()[i])\n if Player and Player.PlayerData.job.onduty then\n local payment = Player.PlayerData.job.payment\n Player.Functions.AddMoney('bank', payment, 'Salary')\n Player.Functions.Notify('Received \$' .. payment .. ' salary', 'success')\n end\nend", + 'js_example' => "// Example 1: Check if player has items before proceeding\nconst Player = QBCore.Functions.GetPlayer(global.source);\nif (Player) {\n if (Player.Functions.HasItem('police_badge', 1)) {\n console.log('Player is a police officer');\n }\n}\n\n// Example 2: Transfer money between players\nconst Player1 = QBCore.Functions.GetPlayer(global.source);\nconst Player2 = QBCore.Functions.GetPlayerByCitizenId(citizenid);\nif (Player1 && Player2) {\n const amount = 500;\n if (Player1.Functions.RemoveMoney('cash', amount)) {\n Player2.Functions.AddMoney('cash', amount, 'Player Transfer');\n Player1.Functions.Notify(`Gave \\\$\${amount} to \${Player2.Functions.GetName()}`, 'success');\n Player2.Functions.Notify(`Received \\\$\${amount} from \${Player1.Functions.GetName()}`, 'success');\n }\n}\n\n// Example 3: Job paycheck system\nconst players = QBCore.Functions.GetPlayers();\nfor (const playerId of players) {\n const Player = QBCore.Functions.GetPlayer(playerId);\n if (Player && Player.PlayerData.job.onduty) {\n const payment = Player.PlayerData.job.payment;\n Player.Functions.AddMoney('bank', payment, 'Salary');\n Player.Functions.Notify(`Received \\\$\${payment} salary`, 'success');\n }\n}", + ], + ]; + + $nameLower = strtolower($name); + + foreach ($playerInfo as $infoName => $infoData) { + if (strtolower($infoName) === $nameLower) { + return $infoData; + } + } + + return null; + } + + /** + * Format player info. + */ + protected function formatPlayerInfo(array $info, string $language): string + { + return view('mcp.qbcore.qbcore-players', [ + 'info' => $info, + 'language' => $language, + ])->render(); + } + + /** + * Get the tool's input schema. + * + * @return array + */ + public function schema(JsonSchema $schema): array + { + return [ + 'info_type' => $schema + ->string() + ->description('The type of QBCore player information to look up (Structure, PlayerData, Methods, Events, Examples)') + ->enum(['Structure', 'PlayerData', 'Methods', 'Events', 'Examples']) + ->required(), + 'language' => $schema + ->string() + ->enum(['lua', 'js']) + ->description('The scripting language for code examples') + ->default('lua'), + ]; + } +} diff --git a/app/Mcp/Tools/QBCore/GetQBCoreSharedData.php b/app/Mcp/Tools/QBCore/GetQBCoreSharedData.php new file mode 100644 index 0000000..01bfc03 --- /dev/null +++ b/app/Mcp/Tools/QBCore/GetQBCoreSharedData.php @@ -0,0 +1,183 @@ +get('data_type'); + $language = $request->get('language', 'lua'); + + $data = $this->findSharedData($dataType); + + if (! $data) { + return Response::text(sprintf("QBCore shared data '%s' not found. Available types: Items, Jobs, Gangs, Vehicles, Weapons, StarterItems, Utilities", $dataType)); + } + + $output = $this->formatSharedInfo($data, $language); + + return Response::text($output); + } + + /** + * Find a QBCore shared data type. + */ + protected function findSharedData(string $name): ?array + { + $sharedData = [ + 'Items' => [ + 'type' => 'Items', + 'description' => 'Table of all items available on the server. Each item has properties like label, weight, type, image, useable, etc.', + 'location' => 'qb-core/shared/items.lua', + 'properties' => [ + ['name' => 'name', 'type' => 'string', 'description' => 'Internal item identifier'], + ['name' => 'label', 'type' => 'string', 'description' => 'Display name shown to players'], + ['name' => 'weight', 'type' => 'number', 'description' => 'Item weight (affects inventory limit)'], + ['name' => 'type', 'type' => 'string', 'description' => 'Item type (item, weapon, component, etc.)'], + ['name' => 'image', 'type' => 'string', 'description' => 'Image filename for inventory display'], + ['name' => 'unique', 'type' => 'boolean', 'description' => 'If true, item cannot be stacked'], + ['name' => 'useable', 'type' => 'boolean', 'description' => 'If true, item can be used/consumed'], + ['name' => 'shouldClose', 'type' => 'boolean', 'description' => 'If true, inventory closes when using item'], + ['name' => 'description', 'type' => 'string', 'description' => 'Item description shown in inventory'], + ], + 'lua_example' => "local QBCore = exports['qb-core']:GetCoreObject({'Shared'})\nlocal item = QBCore.Shared.Items['water_bottle']\nif item then\n print('Item: ' .. item.label .. ' (Weight: ' .. item.weight .. ')')\nend", + 'js_example' => "const QBCore = exports['qb-core']:GetCoreObject(['Shared']);\nconst item = QBCore.Shared.Items['water_bottle'];\nif (item) {\n console.log(`Item: \${item.label} (Weight: \${item.weight})`);\n}", + ], + 'Jobs' => [ + 'type' => 'Jobs', + 'description' => 'Table of all jobs available on the server. Each job has grades (ranks) and payment amounts.', + 'location' => 'qb-core/shared/jobs.lua', + 'properties' => [ + ['name' => 'label', 'type' => 'string', 'description' => 'Job display name'], + ['name' => 'type', 'type' => 'string', 'description' => 'Job type (leo, illegal, casino, etc.)'], + ['name' => 'defaultDuty', 'type' => 'boolean', 'description' => 'If true, player starts on duty'], + ['name' => 'offDutyPay', 'type' => 'boolean', 'description' => 'If true, player gets paid while off duty'], + ['name' => 'grades', 'type' => 'table', 'description' => 'Array of job grades with name and payment'], + ], + 'lua_example' => "local QBCore = exports['qb-core']:GetCoreObject({'Shared'})\nlocal job = QBCore.Shared.Jobs['police']\nif job then\n print('Job: ' .. job.label)\n for i = 1, #job.grades do\n local grade = job.grades[i]\n print(' Grade: ' .. grade.name .. ' (Payment: $' .. grade.payment .. ')')\n end\nend", + 'js_example' => "const QBCore = exports['qb-core']:GetCoreObject(['Shared']);\nconst job = QBCore.Shared.Jobs['police'];\nif (job) {\n console.log(`Job: \${job.label}`);\n job.grades.forEach((grade, i) => {\n console.log(` Grade: \${grade.name} (Payment: \\\$\${grade.payment})`);\n });\n}", + ], + 'Gangs' => [ + 'type' => 'Gangs', + 'description' => 'Table of all gangs available on the server. Similar structure to jobs with labels and grades.', + 'location' => 'qb-core/shared/gangs.lua', + 'properties' => [ + ['name' => 'label', 'type' => 'string', 'description' => 'Gang display name'], + ['name' => 'grades', 'type' => 'table', 'description' => 'Array of gang grades/ranks'], + ], + 'lua_example' => "local QBCore = exports['qb-core']:GetCoreObject({'Shared'})\nlocal gang = QBCore.Shared.Gangs['lostmc']\nif gang then\n print('Gang: ' .. gang.label)\n for i = 1, #gang.grades do\n print(' Rank: ' .. gang.grades[i].name)\n end\nend", + 'js_example' => "const QBCore = exports['qb-core']:GetCoreObject(['Shared']);\nconst gang = QBCore.Shared.Gangs['lostmc'];\nif (gang) {\n console.log(`Gang: \${gang.label}`);\n gang.grades.forEach((grade) => {\n console.log(` Rank: \${grade.name}`);\n });\n}", + ], + 'Vehicles' => [ + 'type' => 'Vehicles', + 'description' => 'Table of all vehicle models available on the server. Indexed by model name for quick lookup.', + 'location' => 'qb-core/shared/vehicles.lua', + 'properties' => [ + ['name' => 'model', 'type' => 'string', 'description' => 'Vehicle model spawn code'], + ['name' => 'name', 'type' => 'string', 'description' => 'Vehicle display name'], + ['name' => 'brand', 'type' => 'string', 'description' => 'Vehicle manufacturer/brand'], + ['name' => 'price', 'type' => 'number', 'description' => 'Vehicle resale price'], + ['name' => 'category', 'type' => 'string', 'description' => 'Vehicle category (compacts, sedans, etc.)'], + ['name' => 'type', 'type' => 'string', 'description' => 'Vehicle type (automobile, bike, helicopter, etc.)'], + ['name' => 'shop', 'type' => 'string|table', 'description' => 'Shop(s) where vehicle can be purchased'], + ], + 'lua_example' => "local QBCore = exports['qb-core']:GetCoreObject({'Shared'})\nlocal vehicle = QBCore.Shared.Vehicles['adder']\nif vehicle then\n print('Vehicle: ' .. vehicle.name .. ' by ' .. vehicle.brand)\n print('Price: $' .. vehicle.price)\nend", + 'js_example' => "const QBCore = exports['qb-core']:GetCoreObject(['Shared']);\nconst vehicle = QBCore.Shared.Vehicles['adder'];\nif (vehicle) {\n console.log(`Vehicle: \${vehicle.name} by \${vehicle.brand}`);\n console.log(`Price: \\\$\${vehicle.price}`);\n}", + ], + 'Weapons' => [ + 'type' => 'Weapons', + 'description' => 'Table of all weapons indexed by weapon hash. Used for retrieving weapon information.', + 'location' => 'qb-core/shared/weapons.lua', + 'properties' => [ + ['name' => 'name', 'type' => 'string', 'description' => 'Weapon model/spawn name'], + ['name' => 'label', 'type' => 'string', 'description' => 'Weapon display name'], + ['name' => 'weapontype', 'type' => 'string', 'description' => 'Weapon type (Pistol, Rifle, Shotgun, etc.)'], + ['name' => 'ammotype', 'type' => 'string', 'description' => 'Ammo type used by weapon'], + ['name' => 'damageReason', 'type' => 'string', 'description' => 'Death notification message for kills'], + ], + 'lua_example' => "local QBCore = exports['qb-core']:GetCoreObject({'Shared'})\nlocal weapon = QBCore.Shared.Weapons[`weapon_pistol`]\nif weapon then\n print('Weapon: ' .. weapon.label .. ' (' .. weapon.weapontype .. ')')\nend", + 'js_example' => "const QBCore = exports['qb-core']:GetCoreObject(['Shared']);\nconst weapon = QBCore.Shared.Weapons['weapon_pistol'];\nif (weapon) {\n console.log(`Weapon: \${weapon.label} (\${weapon.weapontype})`);\n}", + ], + 'StarterItems' => [ + 'type' => 'StarterItems', + 'description' => 'Table of items that new players receive when first joining. Defines item name and amount.', + 'location' => 'qb-core/shared/main.lua', + 'properties' => [ + ['name' => 'item_name', 'type' => 'string', 'description' => 'Item to give to new players'], + ['name' => 'amount', 'type' => 'number', 'description' => 'Amount of item to give'], + ], + 'lua_example' => "local QBCore = exports['qb-core']:GetCoreObject({'Shared'})\nfor item, amount in pairs(QBCore.Shared.StarterItems) do\n local itemInfo = QBCore.Shared.Items[item]\n if itemInfo then\n print('New players get ' .. amount .. 'x ' .. itemInfo.label)\n end\nend", + 'js_example' => "const QBCore = exports['qb-core']:GetCoreObject(['Shared']);\nfor (const [item, amount] of Object.entries(QBCore.Shared.StarterItems)) {\n const itemInfo = QBCore.Shared.Items[item];\n if (itemInfo) {\n console.log(`New players get \${amount}x \${itemInfo.label}`);\n }\n}", + ], + 'Utilities' => [ + 'type' => 'Utilities', + 'description' => 'Utility functions available in QBShared for common operations like string manipulation and vehicle handling.', + 'location' => 'qb-core/shared/main.lua', + 'methods' => [ + ['name' => 'RandomStr(length)', 'description' => 'Generate random string of specified length'], + ['name' => 'RandomInt(length)', 'description' => 'Generate random numeric string of specified length'], + ['name' => 'SplitStr(str, delimiter)', 'description' => 'Split string by delimiter into table'], + ['name' => 'Trim(value)', 'description' => 'Remove leading/trailing whitespace'], + ['name' => 'Round(number, decimals)', 'description' => 'Round number to specified decimal places'], + ['name' => 'ChangeVehicleExtra(vehicle, extra, enable)', 'description' => 'Toggle vehicle extra on/off'], + ['name' => 'SetDefaultVehicleExtras(vehicle, config)', 'description' => 'Set default vehicle extras from config table'], + ], + 'lua_example' => "local QBCore = exports['qb-core']:GetCoreObject({'Shared'})\nlocal randomId = QBCore.Shared.RandomStr(8)\nlocal trimmed = QBCore.Shared.Trim(' hello world ')\nlocal rounded = QBCore.Shared.Round(3.14159, 2) -- Returns 3.14\nprint('Random ID: ' .. randomId)\nprint('Trimmed: ' .. trimmed)\nprint('Rounded: ' .. rounded)", + 'js_example' => "const QBCore = exports['qb-core']:GetCoreObject(['Shared']);\nconst randomId = QBCore.Shared.RandomStr(8);\nconst trimmed = QBCore.Shared.Trim(' hello world ');\nconst rounded = QBCore.Shared.Round(3.14159, 2); // Returns 3.14\nconsole.log(`Random ID: \${randomId}`);\nconsole.log(`Trimmed: \${trimmed}`);\nconsole.log(`Rounded: \${rounded}`);", + ], + ]; + + $nameLower = strtolower($name); + + foreach ($sharedData as $typeName => $typeData) { + if (strtolower($typeName) === $nameLower) { + return $typeData; + } + } + + return null; + } + + /** + * Format shared data info. + */ + protected function formatSharedInfo(array $data, string $language): string + { + return view('mcp.qbcore.qbcore-shared-data', [ + 'data' => $data, + 'language' => $language, + ])->render(); + } + + /** + * Get the tool's input schema. + * + * @return array + */ + public function schema(JsonSchema $schema): array + { + return [ + 'data_type' => $schema + ->string() + ->description('The type of QBCore shared data to look up (Items, Jobs, Gangs, Vehicles, Weapons, StarterItems, Utilities)') + ->enum(['Items', 'Jobs', 'Gangs', 'Vehicles', 'Weapons', 'StarterItems', 'Utilities']) + ->required(), + 'language' => $schema + ->string() + ->enum(['lua', 'js']) + ->description('The scripting language for code examples') + ->default('lua'), + ]; + } +} diff --git a/app/Mcp/Tools/QBCore/Server/GetQBCoreServerFunction.php b/app/Mcp/Tools/QBCore/Server/GetQBCoreServerFunction.php index c717d00..ec1ee49 100644 --- a/app/Mcp/Tools/QBCore/Server/GetQBCoreServerFunction.php +++ b/app/Mcp/Tools/QBCore/Server/GetQBCoreServerFunction.php @@ -178,6 +178,193 @@ class GetQBCoreServerFunction extends Tool '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}", ], + 'SetGang' => [ + 'namespace' => 'Player', + 'description' => 'Set a player\'s gang and grade (server-side)', + 'side' => 'server', + 'parameters' => [ + ['name' => 'gangName', 'type' => 'string', 'description' => 'The gang name'], + ['name' => 'grade', 'type' => 'number', 'description' => 'The gang grade/rank level'], + ], + 'returns' => ['type' => 'boolean', 'description' => 'Success status'], + 'lua_example' => "local Player = QBCore.Functions.GetPlayer(source)\nif Player then\n Player.Functions.SetGang('lostmc', 1)\n TriggerClientEvent('QBCore:Notify', source, 'You joined the gang', 'success')\nend", + 'js_example' => "const Player = QBCore.Functions.GetPlayer(global.source);\nif (Player) {\n Player.Functions.SetGang('lostmc', 1);\n TriggerClientEvent('QBCore:Notify', global.source, 'You joined the gang', 'success');\n}", + ], + 'Notify' => [ + 'namespace' => 'Player', + 'description' => 'Send a notification to a player (server-side)', + 'side' => 'server', + 'parameters' => [ + ['name' => 'text', 'type' => 'string', 'description' => 'Notification text'], + ['name' => 'type', 'type' => 'string', 'description' => 'Notification type: error, success, primary, warning'], + ['name' => 'duration', 'type' => 'number|nil', 'description' => 'Duration in milliseconds (default 5000)'], + ], + 'returns' => ['type' => 'void', 'description' => ''], + 'lua_example' => "local Player = QBCore.Functions.GetPlayer(source)\nif Player then\n Player.Functions.Notify('Hello!', 'success', 5000)\nend", + 'js_example' => "const Player = QBCore.Functions.GetPlayer(global.source);\nif (Player) {\n Player.Functions.Notify('Hello!', 'success', 5000);\n}", + ], + 'HasItem' => [ + 'namespace' => 'Player', + 'description' => 'Check if a player has an item (server-side)', + 'side' => 'server', + 'parameters' => [ + ['name' => 'item', 'type' => 'string|table', 'description' => 'Item name or array of item names'], + ['name' => 'amount', 'type' => 'number', 'description' => 'Amount required (default 1)'], + ], + 'returns' => ['type' => 'boolean', 'description' => 'True if player has the item(s)'], + 'lua_example' => "local Player = QBCore.Functions.GetPlayer(source)\nif Player then\n if Player.Functions.HasItem('water_bottle', 1) then\n print('Player has water')\n end\nend", + 'js_example' => "const Player = QBCore.Functions.GetPlayer(global.source);\nif (Player) {\n if (Player.Functions.HasItem('water_bottle', 1)) {\n console.log('Player has water');\n }\n}", + ], + 'GetName' => [ + 'namespace' => 'Player', + 'description' => 'Get a player\'s full character name (server-side)', + 'side' => 'server', + 'parameters' => [], + 'returns' => ['type' => 'string', 'description' => 'Full name (first lastname)'], + 'lua_example' => "local Player = QBCore.Functions.GetPlayer(source)\nif Player then\n local fullName = Player.Functions.GetName()\n print('Player name: ' .. fullName)\nend", + 'js_example' => "const Player = QBCore.Functions.GetPlayer(global.source);\nif (Player) {\n const fullName = Player.Functions.GetName();\n console.log(`Player name: \${fullName}`);\n}", + ], + 'SetJobDuty' => [ + 'namespace' => 'Player', + 'description' => 'Set a player on/off duty for their job (server-side)', + 'side' => 'server', + 'parameters' => [ + ['name' => 'onDuty', 'type' => 'boolean', 'description' => 'True to set on duty, false to set off duty'], + ], + 'returns' => ['type' => 'void', 'description' => ''], + 'lua_example' => "local Player = QBCore.Functions.GetPlayer(source)\nif Player then\n Player.Functions.SetJobDuty(true)\n Player.Functions.Notify('You are now on duty', 'success')\nend", + 'js_example' => "const Player = QBCore.Functions.GetPlayer(global.source);\nif (Player) {\n Player.Functions.SetJobDuty(true);\n Player.Functions.Notify('You are now on duty', 'success');\n}", + ], + 'SetPlayerData' => [ + 'namespace' => 'Player', + 'description' => 'Set a player data value by key (server-side)', + 'side' => 'server', + 'parameters' => [ + ['name' => 'key', 'type' => 'string', 'description' => 'The player data key to set'], + ['name' => 'value', 'type' => 'any', 'description' => 'The value to set'], + ], + 'returns' => ['type' => 'void', 'description' => ''], + 'lua_example' => "local Player = QBCore.Functions.GetPlayer(source)\nif Player then\n Player.Functions.SetPlayerData('position', vector3(-425.3, 1123.6, 325.0))\nend", + 'js_example' => "const Player = QBCore.Functions.GetPlayer(global.source);\nif (Player) {\n Player.Functions.SetPlayerData('position', [-425.3, 1123.6, 325.0]);\n}", + ], + 'SetMetaData' => [ + 'namespace' => 'Player', + 'description' => 'Set a player metadata value by key (server-side)', + 'side' => 'server', + 'parameters' => [ + ['name' => 'key', 'type' => 'string', 'description' => 'The metadata key to set'], + ['name' => 'value', 'type' => 'any', 'description' => 'The value to set'], + ], + 'returns' => ['type' => 'void', 'description' => ''], + 'lua_example' => "local Player = QBCore.Functions.GetPlayer(source)\nif Player then\n Player.Functions.SetMetaData('hunger', 80)\n Player.Functions.SetMetaData('thirst', 70)\nend", + 'js_example' => "const Player = QBCore.Functions.GetPlayer(global.source);\nif (Player) {\n Player.Functions.SetMetaData('hunger', 80);\n Player.Functions.SetMetaData('thirst', 70);\n}", + ], + 'GetMetaData' => [ + 'namespace' => 'Player', + 'description' => 'Get a player metadata value by key (server-side)', + 'side' => 'server', + 'parameters' => [ + ['name' => 'key', 'type' => 'string', 'description' => 'The metadata key to retrieve'], + ], + 'returns' => ['type' => 'any', 'description' => 'The metadata value'], + 'lua_example' => "local Player = QBCore.Functions.GetPlayer(source)\nif Player then\n local hunger = Player.Functions.GetMetaData('hunger')\n print('Hunger Level: ' .. hunger)\nend", + 'js_example' => "const Player = QBCore.Functions.GetPlayer(global.source);\nif (Player) {\n const hunger = Player.Functions.GetMetaData('hunger');\n console.log(`Hunger Level: \${hunger}`);\n}", + ], + 'AddRep' => [ + 'namespace' => 'Player', + 'description' => 'Add reputation points to a player (server-side)', + 'side' => 'server', + 'parameters' => [ + ['name' => 'repType', 'type' => 'string', 'description' => 'Type of reputation to add (e.g., selling, heist)'], + ['name' => 'amount', 'type' => 'number', 'description' => 'Amount of reputation to add'], + ], + 'returns' => ['type' => 'void', 'description' => ''], + 'lua_example' => "local Player = QBCore.Functions.GetPlayer(source)\nif Player then\n Player.Functions.AddRep('selling', 10)\nend", + 'js_example' => "const Player = QBCore.Functions.GetPlayer(global.source);\nif (Player) {\n Player.Functions.AddRep('selling', 10);\n}", + ], + 'RemoveRep' => [ + 'namespace' => 'Player', + 'description' => 'Remove reputation points from a player (server-side)', + 'side' => 'server', + 'parameters' => [ + ['name' => 'repType', 'type' => 'string', 'description' => 'Type of reputation to remove'], + ['name' => 'amount', 'type' => 'number', 'description' => 'Amount of reputation to remove'], + ], + 'returns' => ['type' => 'void', 'description' => ''], + 'lua_example' => "local Player = QBCore.Functions.GetPlayer(source)\nif Player then\n Player.Functions.RemoveRep('selling', 5)\nend", + 'js_example' => "const Player = QBCore.Functions.GetPlayer(global.source);\nif (Player) {\n Player.Functions.RemoveRep('selling', 5);\n}", + ], + 'GetRep' => [ + 'namespace' => 'Player', + 'description' => 'Get a player\'s reputation value (server-side)', + 'side' => 'server', + 'parameters' => [ + ['name' => 'repType', 'type' => 'string', 'description' => 'Type of reputation to retrieve'], + ], + 'returns' => ['type' => 'number', 'description' => 'Reputation value'], + 'lua_example' => "local Player = QBCore.Functions.GetPlayer(source)\nif Player then\n local sellingRep = Player.Functions.GetRep('selling')\n print('Selling Rep: ' .. sellingRep)\nend", + 'js_example' => "const Player = QBCore.Functions.GetPlayer(global.source);\nif (Player) {\n const sellingRep = Player.Functions.GetRep('selling');\n console.log(`Selling Rep: \${sellingRep}`);\n}", + ], + 'SetMoney' => [ + 'namespace' => 'Player', + 'description' => 'Set a player\'s money to a specific amount (server-side)', + 'side' => 'server', + 'parameters' => [ + ['name' => 'moneyType', 'type' => 'string', 'description' => 'Type: "cash", "bank", or custom'], + ['name' => 'amount', 'type' => 'number', 'description' => 'Amount to set'], + ['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.SetMoney('cash', 5000, 'Admin Command')\n Player.Functions.Notify('Your cash has been set', 'info')\nend", + 'js_example' => "const Player = QBCore.Functions.GetPlayer(global.source);\nif (Player) {\n Player.Functions.SetMoney('cash', 5000, 'Admin Command');\n Player.Functions.Notify('Your cash has been set', 'info');\n}", + ], + 'Save' => [ + 'namespace' => 'Player', + 'description' => 'Save a player\'s data to the database (server-side)', + 'side' => 'server', + 'parameters' => [], + 'returns' => ['type' => 'void', 'description' => ''], + 'lua_example' => "local Player = QBCore.Functions.GetPlayer(source)\nif Player then\n Player.Functions.Save()\nend", + 'js_example' => "const Player = QBCore.Functions.GetPlayer(global.source);\nif (Player) {\n Player.Functions.Save();\n}", + ], + 'Logout' => [ + 'namespace' => 'Player', + 'description' => 'Force a player to logout to the character selection (server-side)', + 'side' => 'server', + 'parameters' => [], + 'returns' => ['type' => 'void', 'description' => ''], + 'lua_example' => "local Player = QBCore.Functions.GetPlayer(source)\nif Player then\n Player.Functions.Logout()\nend", + 'js_example' => "const Player = QBCore.Functions.GetPlayer(global.source);\nif (Player) {\n Player.Functions.Logout();\n}", + ], + 'UpdatePlayerData' => [ + 'namespace' => 'Player', + 'description' => 'Update the player data on client and server (server-side)', + 'side' => 'server', + 'parameters' => [], + 'returns' => ['type' => 'void', 'description' => ''], + 'lua_example' => "local Player = QBCore.Functions.GetPlayer(source)\nif Player then\n Player.Functions.UpdatePlayerData()\nend", + 'js_example' => "const Player = QBCore.Functions.GetPlayer(global.source);\nif (Player) {\n Player.Functions.UpdatePlayerData();\n}", + ], + 'GetPlayerBophone' => [ + 'namespace' => 'Core', + 'description' => 'Get a player by their phone number (server-side only)', + 'side' => 'server', + 'parameters' => [ + ['name' => 'phoneNumber', 'type' => 'string', 'description' => 'The phone number to search for'], + ], + 'returns' => ['type' => 'table', 'description' => 'QBCore player object or nil'], + 'lua_example' => "local Player = QBCore.Functions.GetPlayerByPhone('555-0001')\nif Player then\n print('Found player: ' .. Player.Functions.GetName())\nend", + 'js_example' => "const Player = QBCore.Functions.GetPlayerByPhone('555-0001');\nif (Player) {\n console.log(`Found player: \${Player.Functions.GetName()}`);\n}", + ], + 'GetPlayers' => [ + 'namespace' => 'Core', + 'description' => 'Get table of all online player net IDs (server-side only)', + 'side' => 'server', + 'parameters' => [], + 'returns' => ['type' => 'table', 'description' => 'Array of all connected player net IDs'], + 'lua_example' => "local players = QBCore.Functions.GetPlayers()\nfor i = 1, #players do\n local Player = QBCore.Functions.GetPlayer(players[i])\n if Player then\n print('Player: ' .. Player.Functions.GetName())\n end\nend", + 'js_example' => "const players = QBCore.Functions.GetPlayers();\nfor (const playerId of players) {\n const Player = QBCore.Functions.GetPlayer(playerId);\n if (Player) {\n console.log(`Player: \${Player.Functions.GetName()}`);\n }\n}", + ], ]; $nameLower = strtolower($name); @@ -212,7 +399,7 @@ class GetQBCoreServerFunction extends Tool return [ 'function_name' => $schema ->string() - ->description('The name of the QBCore server function to look up (e.g., "QBCore.Functions.GetPlayer", "AddMoney", "SetJob")') + ->description('The name of the QBCore server function to look up (e.g., "QBCore.Functions.GetPlayer", "AddMoney", "SetJob", "SetGang", "Notify", "HasItem", "SetMetaData", "GetRep")') ->required(), 'language' => $schema ->string() diff --git a/resources/views/docs/index.blade.php b/resources/views/docs/index.blade.php index c9aadb9..21e9ffc 100644 --- a/resources/views/docs/index.blade.php +++ b/resources/views/docs/index.blade.php @@ -171,11 +171,18 @@

Resources

-

Testing

+

MCP Server Configuration

- Test the MCP server locally using the inspector: + To connect to the FiveM MCP server, use the following configuration in your MCP client:

-
php artisan mcp:inspector fivem
+
{
+  "servers": {
+    "fivem": {
+      "url": "{{ route('fivem') }}",
+      "type": "http"
+    }
+  }
+}

External Links

diff --git a/resources/views/mcp/qbcore/qbcore-config.blade.php b/resources/views/mcp/qbcore/qbcore-config.blade.php new file mode 100644 index 0000000..8ec8998 --- /dev/null +++ b/resources/views/mcp/qbcore/qbcore-config.blade.php @@ -0,0 +1,28 @@ +## {{ $config['category'] }} Configuration + +**Location:** `{{ $config['location'] }}` + +{{ $config['description'] }} + +### Configuration Options +| Option | Type | Description | +|--------|------|-------------| +@foreach($config['config_options'] as $option) +| `{{ $option['name'] }}` | {{ $option['type'] }} | {{ $option['description'] }} | +@endforeach + +@if($language === 'lua') +### Lua Example +```lua +{{ $config['lua_example'] }} +``` +@else +### JavaScript Example +```javascript +{{ $config['js_example'] }} +``` +@endif + +### Learn More +- [QBCore Configuration Guide](https://docs.qbcore.org/qbcore-documentation/qb-core/configuration) +- [QBCore Repository](https://github.com/qbcore-framework/qb-core) diff --git a/resources/views/mcp/qbcore/qbcore-players.blade.php b/resources/views/mcp/qbcore/qbcore-players.blade.php new file mode 100644 index 0000000..63e3198 --- /dev/null +++ b/resources/views/mcp/qbcore/qbcore-players.blade.php @@ -0,0 +1,46 @@ +## {{ $info['title'] }} + +{{ $info['description'] }} + +@if(isset($info['properties'])) +### Properties +| Property | Type | Description | +|----------|------|-------------| +@foreach($info['properties'] as $prop) +| `{{ $prop['name'] }}` | {{ $prop['type'] }} | {{ $prop['description'] }} | +@endforeach +@endif + +@if(isset($info['methods'])) +### Available Methods +| Method | Description | +|--------|-------------| +@foreach($info['methods'] as $method) +| `{{ $method['name'] }}` | {{ $method['description'] }} | +@endforeach +@endif + +@if(isset($info['events'])) +### Events +| Event | Description | +|-------|-------------| +@foreach($info['events'] as $event) +| `{{ $event['name'] }}` | {{ $event['description'] }} | +@endforeach +@endif + +@if($language === 'lua') +### Lua Example +```lua +{{ $info['lua_example'] }} +``` +@else +### JavaScript Example +```javascript +{{ $info['js_example'] }} +``` +@endif + +### Learn More +- [QBCore Player Data Documentation](https://docs.qbcore.org/qbcore-documentation/qb-core/player-data) +- [QBCore Repository](https://github.com/qbcore-framework/qb-core) diff --git a/resources/views/mcp/qbcore/qbcore-shared-data.blade.php b/resources/views/mcp/qbcore/qbcore-shared-data.blade.php new file mode 100644 index 0000000..81a0162 --- /dev/null +++ b/resources/views/mcp/qbcore/qbcore-shared-data.blade.php @@ -0,0 +1,35 @@ +## {{ $data['type'] }} - QBCore Shared Data + +**Location:** `{{ $data['location'] }}` + +{{ $data['description'] }} + +### Properties +@if(isset($data['properties'])) +| Property | Type | Description | +|----------|------|-------------| +@foreach($data['properties'] as $prop) +| `{{ $prop['name'] }}` | {{ $prop['type'] }} | {{ $prop['description'] }} | +@endforeach +@endif + +@if(isset($data['methods'])) +### Available Methods +@foreach($data['methods'] as $method) +- **{{ $method['name'] }}** - {{ $method['description'] }} +@endforeach +@endif + +### Lua Example +```lua +{{ $data['lua_example'] }} +``` + +### JavaScript Example +```javascript +{{ $data['js_example'] }} +``` + +### Learn More +- [QBCore Shared Documentation](https://docs.qbcore.org/qbcore-documentation/qb-core/shared) +- [QBCore Repository](https://github.com/qbcore-framework/qb-core) diff --git a/tests/Feature/GetQBCoreConfigToolTest.php b/tests/Feature/GetQBCoreConfigToolTest.php new file mode 100644 index 0000000..9768688 --- /dev/null +++ b/tests/Feature/GetQBCoreConfigToolTest.php @@ -0,0 +1,72 @@ + 'Player', 'language' => 'lua']); + + $response = $tool->handle($request); + + expect((string) $response->content())->toContain('Player'); + expect((string) $response->content())->toContain('PlayerDefaults'); +}); + +test('GetQBCoreConfig returns Framework config', function () { + $tool = new GetQBCoreConfig; + $request = new Request(['config_type' => 'Framework', 'language' => 'lua']); + + $response = $tool->handle($request); + + expect((string) $response->content())->toContain('Framework'); + expect((string) $response->content())->toContain('Version'); +}); + +test('GetQBCoreConfig returns Database config', function () { + $tool = new GetQBCoreConfig; + $request = new Request(['config_type' => 'Database', 'language' => 'lua']); + + $response = $tool->handle($request); + + expect((string) $response->content())->toContain('Database'); + expect((string) $response->content())->toContain('Backend'); +}); + +test('GetQBCoreConfig returns Callbacks config', function () { + $tool = new GetQBCoreConfig; + $request = new Request(['config_type' => 'Callbacks', 'language' => 'js']); + + $response = $tool->handle($request); + + expect((string) $response->content())->toContain('Callbacks'); + expect((string) $response->content())->toContain('JavaScript Example'); +}); + +test('GetQBCoreConfig returns Features config', function () { + $tool = new GetQBCoreConfig; + $request = new Request(['config_type' => 'Features', 'language' => 'lua']); + + $response = $tool->handle($request); + + expect((string) $response->content())->toContain('Features'); +}); + +test('GetQBCoreConfig returns RepTypes config', function () { + $tool = new GetQBCoreConfig; + $request = new Request(['config_type' => 'RepTypes', 'language' => 'lua']); + + $response = $tool->handle($request); + + expect((string) $response->content())->toContain('RepTypes'); + expect((string) $response->content())->toContain('selling'); +}); + +test('GetQBCoreConfig returns not found for invalid type', function () { + $tool = new GetQBCoreConfig; + $request = new Request(['config_type' => 'InvalidType', 'language' => 'lua']); + + $response = $tool->handle($request); + + expect((string) $response->content())->toContain('not found'); +}); diff --git a/tests/Feature/GetQBCorePlayersToolTest.php b/tests/Feature/GetQBCorePlayersToolTest.php new file mode 100644 index 0000000..cc2d09c --- /dev/null +++ b/tests/Feature/GetQBCorePlayersToolTest.php @@ -0,0 +1,66 @@ + 'Structure', 'language' => 'lua']); + + $response = $tool->handle($request); + + expect((string) $response->content())->toContain('Structure'); + expect((string) $response->content())->toContain('PlayerData'); + expect((string) $response->content())->toContain('Functions'); +}); + +test('GetQBCorePlayers returns PlayerData info', function () { + $tool = new GetQBCorePlayers; + $request = new Request(['info_type' => 'PlayerData', 'language' => 'lua']); + + $response = $tool->handle($request); + + expect((string) $response->content())->toContain('PlayerData'); + expect((string) $response->content())->toContain('citizenid'); + expect((string) $response->content())->toContain('charinfo'); +}); + +test('GetQBCorePlayers returns Methods info', function () { + $tool = new GetQBCorePlayers; + $request = new Request(['info_type' => 'Methods', 'language' => 'lua']); + + $response = $tool->handle($request); + + expect((string) $response->content())->toContain('Methods'); + expect((string) $response->content())->toContain('AddItem'); + expect((string) $response->content())->toContain('SetJob'); +}); + +test('GetQBCorePlayers returns Events info', function () { + $tool = new GetQBCorePlayers; + $request = new Request(['info_type' => 'Events', 'language' => 'js']); + + $response = $tool->handle($request); + + expect((string) $response->content())->toContain('Events'); + expect((string) $response->content())->toContain('JavaScript Example'); +}); + +test('GetQBCorePlayers returns Examples info', function () { + $tool = new GetQBCorePlayers; + $request = new Request(['info_type' => 'Examples', 'language' => 'lua']); + + $response = $tool->handle($request); + + expect((string) $response->content())->toContain('Examples'); + expect((string) $response->content())->toContain('HasItem'); +}); + +test('GetQBCorePlayers returns not found for invalid type', function () { + $tool = new GetQBCorePlayers; + $request = new Request(['info_type' => 'InvalidType', 'language' => 'lua']); + + $response = $tool->handle($request); + + expect((string) $response->content())->toContain('not found'); +}); diff --git a/tests/Feature/GetQBCoreSharedDataToolTest.php b/tests/Feature/GetQBCoreSharedDataToolTest.php new file mode 100644 index 0000000..240870b --- /dev/null +++ b/tests/Feature/GetQBCoreSharedDataToolTest.php @@ -0,0 +1,85 @@ + 'Items', 'language' => 'lua']); + + $response = $tool->handle($request); + + expect((string) $response->content())->toContain('Items'); + expect((string) $response->content())->toContain('label'); + expect((string) $response->content())->toContain('weight'); +}); + +test('GetQBCoreSharedData returns Jobs data', function () { + $tool = new GetQBCoreSharedData; + $request = new Request(['data_type' => 'Jobs', 'language' => 'lua']); + + $response = $tool->handle($request); + + expect((string) $response->content())->toContain('Jobs'); + expect((string) $response->content())->toContain('grades'); + expect((string) $response->content())->toContain('Lua Example'); +}); + +test('GetQBCoreSharedData returns Gangs data', function () { + $tool = new GetQBCoreSharedData; + $request = new Request(['data_type' => 'Gangs', 'language' => 'lua']); + + $response = $tool->handle($request); + + expect((string) $response->content())->toContain('Gangs'); +}); + +test('GetQBCoreSharedData returns Vehicles data', function () { + $tool = new GetQBCoreSharedData; + $request = new Request(['data_type' => 'Vehicles', 'language' => 'lua']); + + $response = $tool->handle($request); + + expect((string) $response->content())->toContain('Vehicles'); + expect((string) $response->content())->toContain('model'); + expect((string) $response->content())->toContain('price'); +}); + +test('GetQBCoreSharedData returns Weapons data', function () { + $tool = new GetQBCoreSharedData; + $request = new Request(['data_type' => 'Weapons', 'language' => 'js']); + + $response = $tool->handle($request); + + expect((string) $response->content())->toContain('Weapons'); + expect((string) $response->content())->toContain('JavaScript Example'); +}); + +test('GetQBCoreSharedData returns StarterItems data', function () { + $tool = new GetQBCoreSharedData; + $request = new Request(['data_type' => 'StarterItems', 'language' => 'lua']); + + $response = $tool->handle($request); + + expect((string) $response->content())->toContain('StarterItems'); +}); + +test('GetQBCoreSharedData returns Utilities data', function () { + $tool = new GetQBCoreSharedData; + $request = new Request(['data_type' => 'Utilities', 'language' => 'lua']); + + $response = $tool->handle($request); + + expect((string) $response->content())->toContain('Utilities'); + expect((string) $response->content())->toContain('RandomStr'); + expect((string) $response->content())->toContain('Trim'); +}); + +test('GetQBCoreSharedData returns not found for invalid type', function () { + $tool = new GetQBCoreSharedData; + $request = new Request(['data_type' => 'InvalidType', 'language' => 'lua']); + + $response = $tool->handle($request); + + expect((string) $response->content())->toContain('not found'); +});