"""
FiveM Documentation MCP Server
Provides tools for looking up GTA V natives, QBCore events, and ox_lib exports.
Built with FastMCP for easy deployment.
"""
from fastmcp import FastMCP
from typing import Optional
import httpx
import re
# Initialize FastMCP server
mcp = FastMCP(
name="FiveM Docs",
instructions="""
This MCP server provides documentation lookup for FiveM development:
- GTA V native functions (from docs.fivem.net/natives)
- QBCore framework events and functions
- ox_lib exports and utilities
- fxmanifest.lua template generation
Use these tools when developing FiveM scripts with QBCore and ox framework.
"""
)
# GitHub raw content base URLs
NATIVES_BASE = "https://raw.githubusercontent.com/citizenfx/natives/master"
QBDOCS_BASE = "https://raw.githubusercontent.com/qbcore-framework/qb-docs/main"
OXDOCS_BASE = "https://raw.githubusercontent.com/overextended/overextended.github.io/main/pages"
@mcp.tool()
async def lookup_native(
query: str,
category: Optional[str] = None
) -> str:
"""
Search GTA V native functions by name, hash, or description.
Args:
query: Native name (e.g., 'GetPlayerPed'), partial name, or keyword to search
category: Optional category filter (e.g., 'PLAYER', 'VEHICLE', 'PED', 'ENTITY', 'AUDIO')
Returns:
Native function documentation including signature and description
"""
# Common native categories
categories = [
"APP", "AUDIO", "BRAIN", "CAM", "CLOCK", "CUTSCENE", "DATAFILE",
"DECORATOR", "DLC", "ENTITY", "EVENT", "FILES", "FIRE", "GRAPHICS",
"HUD", "INTERIOR", "ITEMSET", "LOADINGSCREEN", "LOCALIZATION",
"MISC", "MOBILE", "MONEY", "NETSHOPPING", "NETWORK", "OBJECT",
"PAD", "PATHFIND", "PED", "PHYSICS", "PLAYER", "RECORDING",
"REPLAY", "SAVEMIGRATION", "SCRIPT", "SECURITY", "SHAPETEST",
"SOCIALCLUB", "STATS", "STREAMING", "TASK", "VEHICLE", "WATER",
"WEAPON", "ZONE"
]
if category:
categories = [cat for cat in categories if category.upper() in cat]
results = []
query_lower = query.lower()
async with httpx.AsyncClient(timeout=30.0) as client:
# Search through native categories
for cat in categories[:10]: # Limit to prevent timeout
try:
# Try to fetch the native file directly if query looks like a native name
if query.upper() == query or '_' in query:
url = f"{NATIVES_BASE}/{cat}/{query.upper()}.md"
resp = await client.get(url)
if resp.status_code == 200:
content = resp.text
results.append({
"name": query.upper(),
"category": cat,
"content": content[:1500]
})
break
except:
continue
# If no direct match, search the FiveM docs API
if not results:
try:
async with httpx.AsyncClient(timeout=30.0) as client:
# Use FiveM natives search
search_url = f"https://docs.fivem.net/natives/?search={query}"
# Provide common natives info based on query
common_natives = get_common_natives(query_lower)
if common_natives:
return common_natives
except Exception as e:
pass
if results:
output = f"## Native Search Results for '{query}'\n\n"
for r in results:
output += f"### {r['name']}\n"
output += f"**Category:** {r['category']}\n\n"
output += f"{r['content']}\n\n"
return output
return f"""## Native Search: '{query}'
No direct match found. Here are some suggestions:
1. **Search on FiveM Docs:** https://docs.fivem.net/natives/?search={query}
2. **Common Categories:**
- PLAYER - Player-related functions (GetPlayerPed, GetPlayerName, etc.)
- VEHICLE - Vehicle functions (CreateVehicle, SetVehicleColours, etc.)
- PED - Pedestrian functions (CreatePed, SetPedComponentVariation, etc.)
- ENTITY - Entity functions (GetEntityCoords, SetEntityHeading, etc.)
- WEAPON - Weapon functions (GiveWeaponToPed, GetSelectedPedWeapon, etc.)
3. **Try searching with:**
- Full native name: `GET_PLAYER_PED`
- Partial name: `PlayerPed`
- Category filter: use category='PLAYER'
"""
def get_common_natives(query: str) -> Optional[str]:
"""Return documentation for commonly used natives."""
natives_db = {
"getplayerped": """## GetPlayerPed
**Category:** PLAYER
```lua
ped = GetPlayerPed(playerId)
```
**Parameters:**
- `playerId` (int): The player ID (-1 for local player)
**Returns:**
- `ped` (Ped): The ped handle for the player
**Example:**
```lua
local playerPed = GetPlayerPed(-1)
local coords = GetEntityCoords(playerPed)
print("Player position:", coords.x, coords.y, coords.z)
```
""",
"getentitycoords": """## GetEntityCoords
**Category:** ENTITY
```lua
vector3 = GetEntityCoords(entity, alive)
```
**Parameters:**
- `entity` (Entity): The entity handle
- `alive` (bool): Whether to get coords of alive entity only
**Returns:**
- `vector3`: The entity coordinates (x, y, z)
**Example:**
```lua
local ped = GetPlayerPed(-1)
local coords = GetEntityCoords(ped, true)
print(coords.x, coords.y, coords.z)
```
""",
"createvehicle": """## CreateVehicle
**Category:** VEHICLE
```lua
vehicle = CreateVehicle(modelHash, x, y, z, heading, isNetwork, netMissionEntity)
```
**Parameters:**
- `modelHash` (Hash): Vehicle model hash
- `x, y, z` (float): Spawn coordinates
- `heading` (float): Spawn heading
- `isNetwork` (bool): Whether to create as network entity
- `netMissionEntity` (bool): Whether it's a mission entity
**Example:**
```lua
local model = GetHashKey("adder")
RequestModel(model)
while not HasModelLoaded(model) do Wait(0) end
local vehicle = CreateVehicle(model, coords.x, coords.y, coords.z, 0.0, true, false)
```
""",
"triggerevent": """## TriggerEvent / TriggerServerEvent / TriggerClientEvent
**Client to Server:**
```lua
TriggerServerEvent(eventName, ...)
```
**Server to Client:**
```lua
TriggerClientEvent(eventName, playerId, ...)
```
**Local Event:**
```lua
TriggerEvent(eventName, ...)
```
**Example:**
```lua
-- Client
TriggerServerEvent('myResource:doSomething', data)
-- Server
TriggerClientEvent('myResource:response', source, result)
```
""",
"registernetevent": """## RegisterNetEvent / AddEventHandler
```lua
RegisterNetEvent('eventName', function(...)
-- handler code
end)
```
**Or separated:**
```lua
RegisterNetEvent('eventName')
AddEventHandler('eventName', function(...)
-- handler code
end)
```
**Example:**
```lua
RegisterNetEvent('myResource:notify', function(message)
print("Received:", message)
end)
```
"""
}
for key, value in natives_db.items():
if key in query or query in key:
return value
return None
@mcp.tool()
async def lookup_qb_event(
query: str,
side: Optional[str] = "both"
) -> str:
"""
Search QBCore framework events and functions.
Args:
query: Event name or keyword to search (e.g., 'playerloaded', 'getplayer')
side: Filter by 'client', 'server', or 'both' (default: 'both')
Returns:
QBCore event/function documentation with usage examples
"""
query_lower = query.lower()
# QBCore common events and functions database
qb_docs = {
"playerloaded": {
"name": "QBCore:Client:OnPlayerLoaded",
"side": "client",
"description": "Triggered when player data is fully loaded",
"usage": """```lua
RegisterNetEvent('QBCore:Client:OnPlayerLoaded', function()
local PlayerData = QBCore.Functions.GetPlayerData()
print("Player loaded:", PlayerData.charinfo.firstname)
end)
```"""
},
"onplayerunload": {
"name": "QBCore:Client:OnPlayerUnload",
"side": "client",
"description": "Triggered when player is unloading (disconnect/character switch)",
"usage": """```lua
RegisterNetEvent('QBCore:Client:OnPlayerUnload', function()
-- Cleanup code here
end)
```"""
},
"getplayer": {
"name": "QBCore.Functions.GetPlayer",
"side": "server",
"description": "Get player object by server ID",
"usage": """```lua
local Player = QBCore.Functions.GetPlayer(source)
if Player then
local money = Player.PlayerData.money.cash
local job = Player.PlayerData.job.name
end
```"""
},
"getplayerdata": {
"name": "QBCore.Functions.GetPlayerData",
"side": "client",
"description": "Get local player's data on client side",
"usage": """```lua
local PlayerData = QBCore.Functions.GetPlayerData()
print(PlayerData.charinfo.firstname)
print(PlayerData.job.name)
print(PlayerData.money.cash)
```"""
},
"addmoney": {
"name": "Player.Functions.AddMoney",
"side": "server",
"description": "Add money to player",
"usage": """```lua
local Player = QBCore.Functions.GetPlayer(source)
Player.Functions.AddMoney('cash', 1000, 'salary')
Player.Functions.AddMoney('bank', 5000, 'deposit')
```"""
},
"removemoney": {
"name": "Player.Functions.RemoveMoney",
"side": "server",
"description": "Remove money from player",
"usage": """```lua
local Player = QBCore.Functions.GetPlayer(source)
if Player.Functions.RemoveMoney('cash', 500, 'purchase') then
-- Money removed successfully
end
```"""
},
"additem": {
"name": "Player.Functions.AddItem",
"side": "server",
"description": "Add item to player inventory",
"usage": """```lua
local Player = QBCore.Functions.GetPlayer(source)
Player.Functions.AddItem('water', 5)
TriggerClientEvent('inventory:client:ItemBox', source, QBCore.Shared.Items['water'], 'add')
```"""
},
"removeitem": {
"name": "Player.Functions.RemoveItem",
"side": "server",
"description": "Remove item from player inventory",
"usage": """```lua
local Player = QBCore.Functions.GetPlayer(source)
if Player.Functions.RemoveItem('water', 1) then
TriggerClientEvent('inventory:client:ItemBox', source, QBCore.Shared.Items['water'], 'remove')
end
```"""
},
"getitembyname": {
"name": "Player.Functions.GetItemByName",
"side": "server",
"description": "Get item from player inventory by name",
"usage": """```lua
local Player = QBCore.Functions.GetPlayer(source)
local item = Player.Functions.GetItemByName('water')
if item then
print("Has", item.amount, "water")
end
```"""
},
"notify": {
"name": "QBCore.Functions.Notify",
"side": "client",
"description": "Show notification to player",
"usage": """```lua
-- Client side
QBCore.Functions.Notify('Hello World', 'success', 5000)
-- Types: 'success', 'error', 'primary', 'warning'
```"""
},
"callback": {
"name": "QBCore.Functions.CreateCallback / TriggerCallback",
"side": "both",
"description": "Server-client callback system",
"usage": """```lua
-- Server: Create callback
QBCore.Functions.CreateCallback('myresource:getdata', function(source, cb)
local Player = QBCore.Functions.GetPlayer(source)
cb(Player.PlayerData)
end)
-- Client: Trigger callback
QBCore.Functions.TriggerCallback('myresource:getdata', function(data)
print(data.charinfo.firstname)
end)
```"""
},
"setjob": {
"name": "Player.Functions.SetJob",
"side": "server",
"description": "Set player's job",
"usage": """```lua
local Player = QBCore.Functions.GetPlayer(source)
Player.Functions.SetJob('police', 1) -- job name, grade
```"""
},
"progressbar": {
"name": "QBCore.Functions.Progressbar",
"side": "client",
"description": "Show progress bar",
"usage": """```lua
QBCore.Functions.Progressbar('unique_id', 'Doing something...', 5000, false, true, {
disableMovement = true,
disableCarMovement = true,
disableMouse = false,
disableCombat = true,
}, {}, {}, {}, function() -- Done
print('Finished!')
end, function() -- Cancel
print('Cancelled!')
end)
```"""
}
}
results = []
for key, doc in qb_docs.items():
if query_lower in key or query_lower in doc["name"].lower():
if side == "both" or side == doc["side"]:
results.append(doc)
if results:
output = f"## QBCore Search Results for '{query}'\n\n"
for r in results:
output += f"### {r['name']}\n"
output += f"**Side:** {r['side']}\n"
output += f"**Description:** {r['description']}\n\n"
output += f"{r['usage']}\n\n"
return output
return f"""## QBCore Search: '{query}'
No direct match found. Here are common QBCore patterns:
### Getting QBCore Object
```lua
-- Client
local QBCore = exports['qb-core']:GetCoreObject()
-- Server
local QBCore = exports['qb-core']:GetCoreObject()
```
### Common Events
- `QBCore:Client:OnPlayerLoaded` - Player data loaded
- `QBCore:Client:OnPlayerUnload` - Player disconnecting
- `QBCore:Client:OnJobUpdate` - Job changed
- `QBCore:Client:OnMoneyChange` - Money updated
- `QBCore:Server:OnPlayerLoaded` - Server-side player loaded
### Common Functions
- `QBCore.Functions.GetPlayer(source)` - Get player object (server)
- `QBCore.Functions.GetPlayerData()` - Get player data (client)
- `QBCore.Functions.Notify(msg, type)` - Show notification
- `QBCore.Functions.TriggerCallback(name, cb)` - Trigger callback
**Full docs:** https://docs.qbcore.org/
"""
@mcp.tool()
async def lookup_ox_export(
query: str,
module: Optional[str] = None
) -> str:
"""
Search ox_lib exports and functions.
Args:
query: Export name or keyword (e.g., 'notify', 'progressbar', 'callback')
module: Optional module filter (e.g., 'interface', 'callback', 'cache')
Returns:
ox_lib function documentation with usage examples
"""
query_lower = query.lower()
# ox_lib common exports database
ox_docs = {
"notify": {
"name": "lib.notify",
"module": "interface",
"description": "Display a notification",
"usage": """```lua
lib.notify({
title = 'Notification',
description = 'This is a notification',
type = 'success', -- 'success', 'error', 'warning', 'info'
duration = 5000,
position = 'top-right' -- 'top', 'top-right', 'top-left', 'bottom', 'bottom-right', 'bottom-left'
})
```"""
},
"progressbar": {
"name": "lib.progressBar / lib.progressCircle",
"module": "interface",
"description": "Display a progress bar or circle",
"usage": """```lua
-- Progress Bar
if lib.progressBar({
duration = 5000,
label = 'Doing something',
useWhileDead = false,
canCancel = true,
disable = {
car = true,
move = true,
combat = true
},
anim = {
dict = 'amb@prop_human_parking_meter@male@idle_a',
clip = 'idle_a'
},
}) then
print('Progress completed')
else
print('Progress cancelled')
end
-- Progress Circle
lib.progressCircle({
duration = 5000,
label = 'Loading...',
position = 'bottom',
useWhileDead = false,
canCancel = true,
})
```"""
},
"callback": {
"name": "lib.callback / lib.callback.await",
"module": "callback",
"description": "Synchronized callbacks between client and server",
"usage": """```lua
-- Server: Register callback
lib.callback.register('myresource:getData', function(source, arg1)
return { data = 'value', arg = arg1 }
end)
-- Client: Trigger callback (async)
lib.callback('myresource:getData', false, function(result)
print(result.data)
end, 'argument1')
-- Client: Trigger callback (sync/await)
local result = lib.callback.await('myresource:getData', false, 'argument1')
print(result.data)
```"""
},
"context": {
"name": "lib.registerContext / lib.showContext",
"module": "interface",
"description": "Context menu system",
"usage": """```lua
lib.registerContext({
id = 'my_context_menu',
title = 'My Menu',
options = {
{
title = 'Option 1',
description = 'First option',
icon = 'circle',
onSelect = function()
print('Selected option 1')
end
},
{
title = 'Option 2',
description = 'Second option',
arrow = true,
menu = 'submenu_id'
}
}
})
lib.showContext('my_context_menu')
```"""
},
"menu": {
"name": "lib.registerMenu / lib.showMenu",
"module": "interface",
"description": "List-based menu system",
"usage": """```lua
lib.registerMenu({
id = 'my_menu',
title = 'My Menu',
position = 'top-left',
options = {
{ label = 'Option 1', icon = 'hashtag' },
{ label = 'Option 2', icon = 'circle' },
{ label = 'Option 3', icon = 'square' },
}
}, function(selected, scrollIndex, args)
print('Selected:', selected)
end)
lib.showMenu('my_menu')
```"""
},
"input": {
"name": "lib.inputDialog",
"module": "interface",
"description": "Input dialog for user data entry",
"usage": """```lua
local input = lib.inputDialog('Player Info', {
{ type = 'input', label = 'Name', required = true },
{ type = 'number', label = 'Age', min = 18, max = 100 },
{ type = 'select', label = 'Gender', options = {
{ value = 'male', label = 'Male' },
{ value = 'female', label = 'Female' },
}},
{ type = 'checkbox', label = 'Agree to terms' },
})
if input then
print('Name:', input[1])
print('Age:', input[2])
print('Gender:', input[3])
print('Agreed:', input[4])
end
```"""
},
"target": {
"name": "exports.ox_target",
"module": "target",
"description": "Targeting system (requires ox_target)",
"usage": """```lua
-- Add target to entity
exports.ox_target:addLocalEntity(entity, {
{
name = 'my_option',
icon = 'fa-solid fa-hand',
label = 'Interact',
onSelect = function(data)
print('Interacted with', data.entity)
end
}
})
-- Add target zone
exports.ox_target:addBoxZone({
coords = vec3(0, 0, 0),
size = vec3(2, 2, 2),
rotation = 0,
debug = true,
options = {
{
name = 'zone_option',
icon = 'fa-solid fa-circle',
label = 'Zone Action',
onSelect = function()
print('Zone triggered')
end
}
}
})
```"""
},
"cache": {
"name": "cache",
"module": "cache",
"description": "Cached player data (auto-updates)",
"usage": """```lua
-- Cached values (automatically updated)
local ped = cache.ped
local playerId = cache.playerId
local serverId = cache.serverId
local vehicle = cache.vehicle
local seat = cache.seat
local weapon = cache.weapon
-- Listen for cache changes
lib.onCache('vehicle', function(value)
print('Vehicle changed to:', value)
end)
```"""
},
"zones": {
"name": "lib.zones",
"module": "zones",
"description": "Poly and box zones",
"usage": """```lua
local zone = lib.zones.box({
coords = vec3(0, 0, 0),
size = vec3(10, 10, 5),
rotation = 45,
debug = true,
onEnter = function()
print('Entered zone')
end,
onExit = function()
print('Exited zone')
end,
inside = function()
print('Inside zone')
end
})
-- Remove zone
zone:remove()
```"""
},
"points": {
"name": "lib.points",
"module": "points",
"description": "Distance-based points with callbacks",
"usage": """```lua
local point = lib.points.new({
coords = vec3(0, 0, 0),
distance = 50,
onEnter = function(self)
print('Nearby point')
end,
onExit = function(self)
print('Left point area')
end,
nearby = function(self)
-- Called every frame when in range
DrawMarker(...)
end
})
-- Remove point
point:remove()
```"""
},
"alertdialog": {
"name": "lib.alertDialog",
"module": "interface",
"description": "Alert/confirmation dialog",
"usage": """```lua
local alert = lib.alertDialog({
header = 'Confirmation',
content = 'Are you sure you want to do this?',
centered = true,
cancel = true
})
if alert == 'confirm' then
print('Confirmed!')
end
```"""
}
}
results = []
for key, doc in ox_docs.items():
if query_lower in key or query_lower in doc["name"].lower():
if module is None or module.lower() in doc["module"].lower():
results.append(doc)
if results:
output = f"## ox_lib Search Results for '{query}'\n\n"
for r in results:
output += f"### {r['name']}\n"
output += f"**Module:** {r['module']}\n"
output += f"**Description:** {r['description']}\n\n"
output += f"{r['usage']}\n\n"
return output
return f"""## ox_lib Search: '{query}'
No direct match found. Here are common ox_lib features:
### Setup
```lua
-- In fxmanifest.lua
shared_scripts {{
'@ox_lib/init.lua',
}}
```
### Common Modules
- **Interface:** notify, progressBar, context menus, input dialogs
- **Callback:** lib.callback for client-server communication
- **Cache:** Cached player data (cache.ped, cache.vehicle, etc.)
- **Zones:** Box and poly zones with enter/exit callbacks
- **Points:** Distance-based points with nearby callbacks
### Quick Reference
- `lib.notify(options)` - Notifications
- `lib.progressBar(options)` - Progress bars
- `lib.callback.await(name)` - Sync callbacks
- `lib.inputDialog(title, options)` - Input forms
- `lib.registerContext(options)` - Context menus
**Full docs:** https://overextended.dev/ox_lib
"""
@mcp.tool()
async def search_docs(
query: str,
source: Optional[str] = "all"
) -> str:
"""
Full-text search across FiveM documentation.
Args:
query: Search query
source: Limit search to 'all', 'natives', 'qbcore', 'ox', or 'fivem'
Returns:
Relevant documentation excerpts and links
"""
query_lower = query.lower()
results = []
# Search natives
if source in ["all", "natives"]:
native_result = await lookup_native(query)
if "No direct match" not in native_result:
results.append(("Natives", native_result))
# Search QBCore
if source in ["all", "qbcore"]:
qb_result = await lookup_qb_event(query)
if "No direct match" not in qb_result:
results.append(("QBCore", qb_result))
# Search ox_lib
if source in ["all", "ox"]:
ox_result = await lookup_ox_export(query)
if "No direct match" not in ox_result:
results.append(("ox_lib", ox_result))
if results:
output = f"## Documentation Search: '{query}'\n\n"
for src, content in results:
output += f"---\n### From {src}:\n{content}\n"
return output
return f"""## No results found for '{query}'
### Helpful Resources:
- **FiveM Natives:** https://docs.fivem.net/natives/
- **QBCore Docs:** https://docs.qbcore.org/
- **ox_lib Docs:** https://overextended.dev/ox_lib
- **FiveM Forums:** https://forum.cfx.re/
### Search Tips:
- Use specific function names (e.g., 'GetPlayerPed' instead of 'get player')
- Try different variations (e.g., 'notify', 'notification', 'alert')
- Filter by source for more targeted results
"""
@mcp.tool()
def get_fxmanifest(
name: str,
framework: Optional[str] = "qbcore",
has_ui: Optional[bool] = False,
use_ox_lib: Optional[bool] = True
) -> str:
"""
Generate an fxmanifest.lua template for a new FiveM resource.
Args:
name: Resource name
framework: Framework to use - 'qbcore', 'ox', or 'standalone' (default: 'qbcore')
has_ui: Whether the resource has NUI (default: False)
use_ox_lib: Whether to include ox_lib dependency (default: True)
Returns:
Complete fxmanifest.lua template with folder structure
"""
fw = framework or "qbcore"
include_ui = has_ui or False
include_ox_lib = use_ox_lib if use_ox_lib is not None else True
dependencies = []
# Add framework dependencies
if fw == "qbcore":
dependencies.append("qb-core")
elif fw == "ox":
dependencies.append("ox_core")
# Add ox_lib if requested
if include_ox_lib:
dependencies.append("ox_lib")
# Add oxmysql for database
dependencies.append("oxmysql")
manifest = f"""fx_version 'cerulean'
game 'gta5'
author 'Your Name'
description '{name} - FiveM Resource'
version '1.0.0'
lua54 'yes'
"""
# Add dependencies
if dependencies:
manifest += "dependencies {\n"
for dep in dependencies:
manifest += f" '{dep}',\n"
manifest += "}\n\n"
# Add shared scripts
manifest += "shared_scripts {\n"
if include_ox_lib:
manifest += " '@ox_lib/init.lua',\n"
manifest += " 'config.lua',\n"
manifest += "}\n\n"
# Add client scripts
manifest += "client_scripts {\n 'client/*.lua',\n}\n\n"
# Add server scripts
manifest += "server_scripts {\n"
manifest += " '@oxmysql/lib/MySQL.lua',\n"
manifest += " 'server/*.lua',\n"
manifest += "}\n\n"
# Add UI if requested
if include_ui:
manifest += "ui_page 'web/dist/index.html'\n\n"
manifest += "files {\n"
manifest += " 'web/dist/index.html',\n"
manifest += " 'web/dist/**/*',\n"
manifest += "}\n"
# Generate folder structure
folder_structure = f"""
## Recommended Folder Structure
```
{name}/
├── fxmanifest.lua
├── config.lua
├── client/
│ └── main.lua
├── server/
│ └── main.lua"""
if include_ui:
folder_structure += """
├── web/
│ ├── package.json
│ ├── src/
│ └── dist/"""
folder_structure += """
└── README.md
```
"""
# Generate starter files
config_template = """
## config.lua
```lua
Config = {}
Config.Debug = false
-- Add your configuration options here
Config.Options = {
-- example = true,
}
```
"""
client_template = f"""
## client/main.lua
```lua
"""
if fw == "qbcore":
client_template += "local QBCore = exports['qb-core']:GetCoreObject()\n\n"
if include_ox_lib:
client_template += """-- ox_lib is available via '@ox_lib/init.lua'
-- Use lib.notify(), lib.progressBar(), etc.
"""
client_template += f"""CreateThread(function()
print('{name} client started')
end)
-- Example: Player loaded event
"""
if fw == "qbcore":
client_template += """RegisterNetEvent('QBCore:Client:OnPlayerLoaded', function()
local PlayerData = QBCore.Functions.GetPlayerData()
print('Player loaded:', PlayerData.charinfo.firstname)
end)
```
"""
else:
client_template += """-- Add your initialization code here
```
"""
server_template = f"""
## server/main.lua
```lua
"""
if fw == "qbcore":
server_template += "local QBCore = exports['qb-core']:GetCoreObject()\n\n"
server_template += f"""CreateThread(function()
print('{name} server started')
end)
-- Example: Callback
"""
if include_ox_lib:
server_template += """lib.callback.register('""" + name + """:getData', function(source)
local Player = QBCore.Functions.GetPlayer(source)
return Player.PlayerData
end)
```
"""
else:
server_template += """-- Add your server code here
```
"""
return f"""# Generated fxmanifest.lua for {name}
**Framework:** {fw.upper()}
**Features:** {"With NUI" if include_ui else "No NUI"}, {"Uses ox_lib" if include_ox_lib else "No ox_lib"}
## fxmanifest.lua
```lua
{manifest}```
{folder_structure}
{config_template}
{client_template}
{server_template}
"""
def main():
"""Run the MCP server."""
mcp.run()
if __name__ == "__main__":
main()