Files
fivem-mcp/app/Mcp/Tools/Prodigy/Server/GetProdigyServerExport.php
T
Adrian Mejias 0bedeed913 Add Prodigy event and export documentation views, enhance tests
- Created new Blade views for displaying Prodigy events and exports, including detailed references.
- Implemented tests for Prodigy client and server event/export reference tools to ensure correct functionality.
- Updated Pest test configuration for improved test case handling.
2026-03-28 15:07:29 -05:00

429 lines
26 KiB
PHP

<?php
namespace App\Mcp\Tools\Prodigy\Server;
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('prp-bridge server-side exports reference. Covers Cooldowns, Allowlist, Groups, UniQueue, Sell Shops, and Cases exports provided by the Prodigy Studios bridge resource.')]
class GetProdigyServerExport extends Tool
{
/**
* Handle the tool request.
*/
public function handle(Request $request): Response
{
$exportName = $request->get('export_name', '');
$exports = $this->getServerExports();
if ($exportName) {
$export = collect($exports)->firstWhere('name', $exportName);
if ($export) {
return Response::text($this->formatExport($export));
}
return Response::text("prp-bridge server export '$exportName' not found. See https://docs.prodigyrp.net/prp-bridge/exports.html for the full reference.");
}
return Response::text($this->formatExportList($exports));
}
/**
* Get all prp-bridge server-side exports.
*/
protected function getServerExports(): array
{
return [
// Cooldowns
[
'name' => 'startGlobalCooldown',
'category' => 'Cooldowns',
'side' => 'server',
'description' => 'Starts a global cooldown for all players for the given duration.',
'parameters' => [
['name' => 'cooldownKey', 'type' => 'string', 'description' => 'Unique key identifying this cooldown'],
['name' => 'minutes', 'type' => 'number', 'description' => 'Duration of the cooldown in minutes'],
],
'returns' => ['type' => 'void', 'description' => 'No return value'],
'lua_example' => "exports['prp-bridge']:startGlobalCooldown('bank_robbery', 30)",
],
[
'name' => 'startCooldownByIdentifier',
'category' => 'Cooldowns',
'side' => 'server',
'description' => 'Starts a cooldown for a specific player identifier.',
'parameters' => [
['name' => 'identifier', 'type' => 'string', 'description' => 'The player state ID / identifier'],
['name' => 'cooldownKey', 'type' => 'string', 'description' => 'Unique key identifying this cooldown'],
['name' => 'minutes', 'type' => 'number', 'description' => 'Duration of the cooldown in minutes'],
['name' => 'applyToAllCharacters', 'type' => 'boolean?', 'description' => 'Apply to all characters of the same player (optional)'],
],
'returns' => ['type' => 'void', 'description' => 'No return value'],
'lua_example' => "local identifier = GetPlayerIdentifier(source, 0)\nexports['prp-bridge']:startCooldownByIdentifier(identifier, 'drug_run', 15)",
],
[
'name' => 'startCooldownByPlayerId',
'category' => 'Cooldowns',
'side' => 'server',
'description' => 'Starts a cooldown for a player by their server ID.',
'parameters' => [
['name' => 'playerId', 'type' => 'number', 'description' => 'The player server ID'],
['name' => 'cooldownKey', 'type' => 'string', 'description' => 'Unique key identifying this cooldown'],
['name' => 'minutes', 'type' => 'number', 'description' => 'Duration of the cooldown in minutes'],
['name' => 'applyToAllCharacters', 'type' => 'boolean?', 'description' => 'Apply to all characters of the same player (optional)'],
],
'returns' => ['type' => 'void', 'description' => 'No return value'],
'lua_example' => "exports['prp-bridge']:startCooldownByPlayerId(source, 'drug_run', 15)",
],
[
'name' => 'isCooldownActive',
'category' => 'Cooldowns',
'side' => 'server',
'description' => 'Returns whether a global cooldown is currently active.',
'parameters' => [
['name' => 'cooldownKey', 'type' => 'string', 'description' => 'The cooldown key to check'],
],
'returns' => ['type' => 'boolean', 'description' => 'true if the global cooldown is active, false otherwise'],
'lua_example' => "if exports['prp-bridge']:isCooldownActive('bank_robbery') then\n -- notify player to wait\nend",
],
[
'name' => 'isCooldownActiveForIdentifier',
'category' => 'Cooldowns',
'side' => 'server',
'description' => 'Returns whether a cooldown is active for a specific player identifier.',
'parameters' => [
['name' => 'identifier', 'type' => 'string', 'description' => 'The player state ID / identifier'],
['name' => 'cooldownKey', 'type' => 'string', 'description' => 'The cooldown key to check'],
],
'returns' => ['type' => 'boolean', 'description' => 'true if the cooldown is active for that identifier, false otherwise'],
'lua_example' => "local identifier = GetPlayerIdentifier(source, 0)\nif exports['prp-bridge']:isCooldownActiveForIdentifier(identifier, 'drug_run') then\n -- notify player they must wait\nend",
],
// Allowlist
[
'name' => 'GetAllowlist',
'category' => 'Allowlist',
'side' => 'server',
'description' => 'Returns all allowlists for a character by their state ID.',
'parameters' => [
['name' => 'stateId', 'type' => 'string', 'description' => 'The character state ID'],
],
'returns' => ['type' => 'table<string, boolean>', 'description' => 'Map of allowlist name to whether the character has it'],
'lua_example' => "local allowlists = exports['prp-bridge']:GetAllowlist(stateId)\nfor name, has in pairs(allowlists) do\n print(name, has)\nend",
],
[
'name' => 'HasAllowlist',
'category' => 'Allowlist',
'side' => 'server',
'description' => 'Returns whether a character has a specific allowlist.',
'parameters' => [
['name' => 'stateId', 'type' => 'string', 'description' => 'The character state ID'],
['name' => 'allowlist', 'type' => 'string', 'description' => 'The allowlist key to check'],
],
'returns' => ['type' => 'boolean', 'description' => 'true if the character has the allowlist'],
'lua_example' => "if exports['prp-bridge']:HasAllowlist(stateId, 'police_allowlist') then\n print('Has police allowlist')\nend",
],
[
'name' => 'AddAllowlist',
'category' => 'Allowlist',
'side' => 'server',
'description' => 'Adds an allowlist to a character.',
'parameters' => [
['name' => 'source', 'type' => 'number', 'description' => 'The player server ID'],
['name' => 'stateId', 'type' => 'string', 'description' => 'The character state ID'],
['name' => 'allowlist', 'type' => 'string', 'description' => 'The allowlist key to add'],
],
'returns' => ['type' => 'boolean', 'description' => 'true if successfully added'],
'lua_example' => "local success = exports['prp-bridge']:AddAllowlist(source, stateId, 'police_allowlist')\nif not success then\n print('Failed to add allowlist')\nend",
],
[
'name' => 'RemoveAllowlist',
'category' => 'Allowlist',
'side' => 'server',
'description' => 'Removes an allowlist from a character.',
'parameters' => [
['name' => 'source', 'type' => 'number', 'description' => 'The player server ID'],
['name' => 'stateId', 'type' => 'string', 'description' => 'The character state ID'],
['name' => 'allowlist', 'type' => 'string', 'description' => 'The allowlist key to remove'],
],
'returns' => ['type' => 'boolean', 'description' => 'true if successfully removed'],
'lua_example' => "local success = exports['prp-bridge']:RemoveAllowlist(source, stateId, 'police_allowlist')\nif not success then\n print('Failed to remove allowlist')\nend",
],
// Groups
[
'name' => 'CreateGroup',
'category' => 'Groups',
'side' => 'server',
'description' => 'Creates a new group with the specified player as the leader.',
'parameters' => [
['name' => 'leaderSrc', 'type' => 'number', 'description' => 'Server ID of the player who will be the group leader'],
],
'returns' => ['type' => 'table<{ success, error?, group? }>', 'description' => 'Result table with success boolean, optional error string, and optional group object'],
'lua_example' => "local result = exports['prp-bridge']:CreateGroup(source)\nif result.success then\n local group = result.group\n print('Group created:', group.getUUID())\nelse\n print('Error:', result.error)\nend",
],
[
'name' => 'GetGroupFromMember',
'category' => 'Groups',
'side' => 'server',
'description' => 'Returns the group a player is currently in, or nil.',
'parameters' => [
['name' => 'src', 'type' => 'number', 'description' => 'The player server ID'],
],
'returns' => ['type' => 'Group?', 'description' => 'The Group object if the player is in a group, nil otherwise'],
'lua_example' => "local group = exports['prp-bridge']:GetGroupFromMember(source)\nif group then\n print('Player is in group:', group.getUUID())\nend",
],
[
'name' => 'GetGroupFromMemberByIdentifier',
'category' => 'Groups',
'side' => 'server',
'description' => 'Returns the group a player identifier is currently in, or nil.',
'parameters' => [
['name' => 'identifier', 'type' => 'string', 'description' => 'The player state ID / identifier'],
],
'returns' => ['type' => 'Group?', 'description' => 'The Group object if found, nil otherwise'],
'lua_example' => "local identifier = GetPlayerIdentifier(source, 0)\nlocal group = exports['prp-bridge']:GetGroupFromMemberByIdentifier(identifier)\nif group then\n print('Group UUID:', group.getUUID())\nend",
],
[
'name' => 'GetGroupByUuid',
'category' => 'Groups',
'side' => 'server',
'description' => 'Returns a group by its unique identifier.',
'parameters' => [
['name' => 'uuid', 'type' => 'string', 'description' => 'The group UUID'],
],
'returns' => ['type' => 'Group?', 'description' => 'The Group object, or nil if not found'],
'lua_example' => "local group = exports['prp-bridge']:GetGroupByUuid('some-group-uuid')\nif group then\n print('Found group with', #group.getMembers(), 'members')\nend",
],
[
'name' => 'GetGroupByPartyUuid',
'category' => 'Groups',
'side' => 'server',
'description' => 'Returns a group by its linked UniQueue party UUID.',
'parameters' => [
['name' => 'uuid', 'type' => 'string', 'description' => 'The UniQueue party UUID linked to the group'],
],
'returns' => ['type' => 'Group?', 'description' => 'The Group object, or nil if not found'],
'lua_example' => "local group = exports['prp-bridge']:GetGroupByPartyUuid(partyUuid)\nif group then\n print('Found group for party:', group.getUUID())\nend",
],
[
'name' => 'GetGroupIdFromMember',
'category' => 'Groups',
'side' => 'server',
'description' => 'Returns the group UUID for a player if they are in a group.',
'parameters' => [
['name' => 'src', 'type' => 'number', 'description' => 'The player server ID'],
],
'returns' => ['type' => 'string?', 'description' => 'The group UUID string, or nil if not in a group'],
'lua_example' => "local groupId = exports['prp-bridge']:GetGroupIdFromMember(source)\nif groupId then\n print('Group ID:', groupId)\nend",
],
[
'name' => 'GetGroupIdFromMemberByIdentifier',
'category' => 'Groups',
'side' => 'server',
'description' => 'Returns the group UUID for a player identifier if they are in a group.',
'parameters' => [
['name' => 'identifier', 'type' => 'string', 'description' => 'The player state ID / identifier'],
],
'returns' => ['type' => 'string?', 'description' => 'The group UUID string, or nil if not in a group'],
'lua_example' => "local identifier = GetPlayerIdentifier(source, 0)\nlocal groupId = exports['prp-bridge']:GetGroupIdFromMemberByIdentifier(identifier)\nif groupId then\n print('Group ID:', groupId)\nend",
],
[
'name' => 'GetGroupPlayerIds',
'category' => 'Groups',
'side' => 'server',
'description' => 'Returns all player server IDs currently in the specified group.',
'parameters' => [
['name' => 'uuid', 'type' => 'string', 'description' => 'The group UUID'],
],
'returns' => ['type' => 'number[]', 'description' => 'Array of player server IDs in the group'],
'lua_example' => "local playerIds = exports['prp-bridge']:GetGroupPlayerIds(groupUuid)\nfor _, playerId in ipairs(playerIds) do\n TriggerClientEvent('myresource:start', playerId)\nend",
],
// UniQueue
[
'name' => 'CreateQueue',
'category' => 'UniQueue',
'side' => 'server',
'description' => 'Creates a new mission queue. Most Prodigy resources create their own queues — use this only when building custom missions.',
'parameters' => [
['name' => 'name', 'type' => 'string', 'description' => 'Unique queue identifier'],
['name' => 'type', 'type' => 'string', 'description' => 'Queue type: "civ" (civilian missions) or "crime" (crime missions)'],
['name' => 'requiredPolicePower', 'type' => 'number', 'description' => 'Police power required to run the mission'],
['name' => 'maxConcurrentTasks', 'type' => 'number', 'description' => 'Max missions from this queue running simultaneously'],
['name' => 'cooldown', 'type' => 'number', 'description' => 'Seconds after a task ends before the next mission can run'],
],
'returns' => ['type' => 'UniQueue', 'description' => 'The created queue object'],
'lua_example' => "local queue = exports['prp-bridge']:CreateQueue(\n 'my_mission',\n 'crime',\n 4, -- requires 4 police power\n 1, -- max 1 concurrent task\n 60 -- 60 second cooldown\n)\n\nqueue.setExecFunction(function(queueName, partyUuid, partyMembers, taskId)\n startMission(partyMembers, taskId)\nend)",
],
[
'name' => 'GetQueue',
'category' => 'UniQueue',
'side' => 'server',
'description' => 'Returns a queue by its name, or nil if not found.',
'parameters' => [
['name' => 'name', 'type' => 'string', 'description' => 'The queue name used in CreateQueue'],
],
'returns' => ['type' => 'UniQueue?', 'description' => 'The queue object, or nil if not found'],
'lua_example' => "local queue = exports['prp-bridge']:GetQueue('my_mission')\nif queue then\n print('Queue position count:', queue.getNumOfExecTasks())\nend",
],
[
'name' => 'GetQueuesByType',
'category' => 'UniQueue',
'side' => 'server',
'description' => 'Returns all queue names of a given type.',
'parameters' => [
['name' => 'type', 'type' => 'string', 'description' => 'Queue type: "civ" or "crime"'],
],
'returns' => ['type' => 'table<string, boolean>', 'description' => 'Map of queue names of that type'],
'lua_example' => "local crimeQueues = exports['prp-bridge']:GetQueuesByType('crime')\nfor queueName in pairs(crimeQueues) do\n print('Crime queue:', queueName)\nend",
],
[
'name' => 'CreateParty',
'category' => 'UniQueue',
'side' => 'server',
'description' => 'Creates a new UniQueue party of the given type.',
'parameters' => [
['name' => 'type', 'type' => 'string', 'description' => 'Party type: "civ" or "crime"'],
],
'returns' => ['type' => 'UniQueueParty', 'description' => 'The created party object'],
'lua_example' => "local party = exports['prp-bridge']:CreateParty('crime')\n\n-- Add members by identifier\nlocal identifier = GetPlayerIdentifier(source, 0)\nlocal result = party.addMember(identifier)\nif not result.success then\n print('Error:', result.error)\nend\n\n-- Join a queue\nlocal queue = exports['prp-bridge']:GetQueue('my_mission')\nqueue.add(party)",
],
[
'name' => 'GetParty',
'category' => 'UniQueue',
'side' => 'server',
'description' => 'Returns a party by its UUID, or nil if not found.',
'parameters' => [
['name' => 'uuid', 'type' => 'string', 'description' => 'The party UUID'],
],
'returns' => ['type' => 'UniQueueParty?', 'description' => 'The party object, or nil if not found'],
'lua_example' => "local party = exports['prp-bridge']:GetParty(partyUuid)\nif party then\n local members = party.getMembersAsArray()\n print('Party has', #members, 'members')\nend",
],
[
'name' => 'GetPartiesByType',
'category' => 'UniQueue',
'side' => 'server',
'description' => 'Returns all party UUIDs of a given type.',
'parameters' => [
['name' => 'queueType', 'type' => 'string', 'description' => 'Party type: "civ" or "crime"'],
],
'returns' => ['type' => 'table<string, boolean>', 'description' => 'Map of party UUIDs of that type'],
'lua_example' => "local crimeParties = exports['prp-bridge']:GetPartiesByType('crime')\nfor partyUuid in pairs(crimeParties) do\n print('Crime party:', partyUuid)\nend",
],
[
'name' => 'GetPartiesFromPlayer',
'category' => 'UniQueue',
'side' => 'server',
'description' => 'Returns all party UUIDs the player identifier is currently in.',
'parameters' => [
['name' => 'identifier', 'type' => 'string', 'description' => 'The player state ID / identifier'],
],
'returns' => ['type' => 'table<string, boolean>', 'description' => 'Map of party UUIDs the player is in'],
'lua_example' => "local identifier = GetPlayerIdentifier(source, 0)\nlocal parties = exports['prp-bridge']:GetPartiesFromPlayer(identifier)\nfor partyUuid in pairs(parties) do\n print('In party:', partyUuid)\nend",
],
// Sell Shops
[
'name' => 'RegisterSellShop',
'category' => 'Sell Shops',
'side' => 'server',
'description' => 'Registers a sell shop with accepted items and prices. Safe to call repeatedly — re-registering the same ID does nothing.',
'parameters' => [
['name' => 'id', 'type' => 'string', 'description' => 'Unique shop identifier'],
['name' => 'payload', 'type' => 'table', 'description' => 'Config table with label, items (keyed by item name with label and price), optional reason and coords'],
],
'returns' => ['type' => 'void', 'description' => 'No return value'],
'lua_example' => "exports['prp-bridge']:RegisterSellShop('pawn_shop', {\n label = 'Pawn Shop',\n items = {\n ['gold_bar'] = { label = 'Gold Bar', price = 500 },\n ['silver_bar'] = { label = 'Silver Bar', price = 200 },\n ['diamond'] = { label = 'Diamond', price = 1000 },\n }\n})",
],
[
'name' => 'OpenSellShop',
'category' => 'Sell Shops',
'side' => 'server',
'description' => 'Opens a registered sell shop for a player. The player sees a stash UI and items are consumed for cash on move.',
'parameters' => [
['name' => 'source', 'type' => 'number', 'description' => 'The player server ID'],
['name' => 'id', 'type' => 'string', 'description' => 'The shop ID registered with RegisterSellShop'],
],
'returns' => ['type' => 'void', 'description' => 'No return value'],
'lua_example' => "RegisterNetEvent('myresource:openShop', function()\n local src = source\n -- Register before opening (safe to call every time)\n exports['prp-bridge']:RegisterSellShop('pawn_shop', {\n label = 'Pawn Shop',\n items = {\n ['gold_bar'] = { label = 'Gold Bar', price = 500 },\n }\n })\n exports['prp-bridge']:OpenSellShop(src, 'pawn_shop')\nend)",
],
// Cases
[
'name' => 'CreateCase',
'category' => 'Cases',
'side' => 'server',
'description' => 'Registers a new loot case type. All item chances must sum to exactly 100.',
'parameters' => [
['name' => 'caseId', 'type' => 'string', 'description' => 'Unique case identifier (e.g. "CRIME_CASE")'],
['name' => 'payload', 'type' => 'table', 'description' => 'Config table with optional label and required items array (each with name, chance, optional min/max/metadata/image)'],
],
'returns' => ['type' => 'boolean', 'description' => 'true on success; false if item chances do not sum to 100'],
'lua_example' => "local success = exports['prp-bridge']:CreateCase('ROBBERY_CASE', {\n label = 'Robbery Loot',\n items = {\n { name = 'money', chance = 50, min = 200, max = 800 },\n { name = 'lockpick', chance = 30 },\n { name = 'weapon_pistol', chance = 20 },\n }\n})\nif not success then\n print('Item chances must sum to 100!')\nend",
],
[
'name' => 'GetCase',
'category' => 'Cases',
'side' => 'server',
'description' => 'Gets case data by its unique identifier.',
'parameters' => [
['name' => 'caseId', 'type' => 'string', 'description' => 'The case ID used in CreateCase'],
],
'returns' => ['type' => 'CaseDataPayload?', 'description' => 'The case data table, or nil if not found'],
'lua_example' => "local caseData = exports['prp-bridge']:GetCase('ROBBERY_CASE')\nif caseData then\n print('Case label:', caseData.label)\n print('Item count:', #caseData.items)\nend",
],
[
'name' => 'GetAllCases',
'category' => 'Cases',
'side' => 'server',
'description' => 'Returns all registered case types and their item data.',
'parameters' => [],
'returns' => ['type' => 'CaseDataPayload[]', 'description' => 'Array of all registered case data tables'],
'lua_example' => "local allCases = exports['prp-bridge']:GetAllCases()\nfor _, caseData in ipairs(allCases) do\n print('Case:', caseData.label or 'unlabelled', '- Items:', #caseData.items)\nend",
],
];
}
/**
* Format a single export for output.
*/
protected function formatExport(array $export): string
{
return view('mcp.prodigy.prodigy-export-reference', [
'export' => $export,
])->render();
}
/**
* Format full export listing grouped by category.
*/
protected function formatExportList(array $exports): string
{
$byCategory = collect($exports)->groupBy('category')->toArray();
return view('mcp.prodigy.prodigy-export-list', [
'exportsByCategory' => $byCategory,
])->render();
}
/**
* Get the tool's input schema.
*
* @return array<string, JsonSchema>
*/
public function schema(JsonSchema $schema): array
{
return [
'export_name' => $schema
->string()
->description('The prp-bridge server export name to look up (e.g. "startGlobalCooldown", "CreateGroup", "RegisterSellShop"). Omit to list all server exports.')
->default(''),
];
}
}