Plugins are how agent_api adapts to whatever framework runs on your server. Each plugin is a single TypeScript file that exports a Plugin object. At boot the loader walks ALL_PLUGINS, runs each plugin's detect(), and either installs its tools or logs why it skipped.
What lives in src/server/plugins/
plugins/
index.ts # ALL_PLUGINS array — append your plugin here
loader.ts # detect + install + status logging
types.ts # Plugin interface
helpers.ts # isResourceStarted, safeExport, callExport
dynamic.ts # csvSet, isAllowed, listCallable, safeSerialize
esx/index.ts
oxlib/index.ts
oxmysql/index.tsBundled plugins
| Plugin | Detects | Pages |
|---|---|---|
esx | es_extended | ESX plugin |
oxlib | ox_lib | ox_lib plugin |
oxmysql | oxmysql | oxmysql plugin |
Boot-time behaviour
When agent_api starts:
[agent_api] plugin enabled : esx (+8 tools: esx_list_players, esx_get_player, esx_add_money, ...)
[agent_api] plugin enabled : oxlib (+5 tools: oxlib_notify, oxlib_trigger_client_callback, ...)
[agent_api] plugin enabled : oxmysql (+3 tools: oxmysql_query, oxmysql_scalar, oxmysql_execute)A plugin can be skipped because:
- Its target resource isn't started —
detect()returnsok:false. - The operator opted out via
agent_api_plugin_<name>_enabled false. install()threw.
You can introspect via the list_plugins MCP tool.
Common patterns
Detection
detect: () => isResourceStarted('target_resource'),Or more specific:
detect: () => {
const started = isResourceStarted('target_resource');
if (!started.ok) return started;
if (!safeExport('target_resource', 'someExport')) {
return { ok: false, reason: 'target_resource is up but missing someExport' };
}
return { ok: true };
},Readonly + blocklist gate (for reflective tools)
Use isAllowed from dynamic.ts:
const blocklist = csvSet('agent_api_plugin_yourname_blocked_methods');
const guard = isAllowed(methodName, { readonly: convars.readonly, blocklist });
if (!guard.ok) return err('COMMAND_NOT_ALLOWED', guard.reason);Safe serialization
Framework objects often contain functions, circular refs, or huge nested data. Use safeSerialize from dynamic.ts before returning anything from a reflective call.
return ok({ method, result: safeSerialize(raw) });