The PocketMine-MP bridge for shared services provided by EasyLibraryAgent.
EasyLibraryAgentBridge connects a PocketMine-MP server to the external EasyLibraryAgent service. It gives plugins a shared server registry, PubSub, reliable delivery tracking, namespaced KV storage, network flags, locks, RPC, server state and allowlisted compute services.
The bridge is API-first. Plugin integrations use:
imperazim\agent\api\*
imperazim\agent\constant\*
imperazim\agent\event\*
/easyagent exists for diagnostics and controlled administration. Normal
plugin behavior should use the public APIs and PocketMine event listeners.
EasyLibraryAgentBridge is not a Minecraft proxy. Players continue to connect directly to PocketMine-MP or through the proxy already used by the network. The Agent is a separate service for communication and shared state.
- Server registry: Track online, stale and offline PMMP servers through heartbeats.
- Server metadata: Publish a display name, type, group and custom tags for routing and discovery.
- Target-aware PubSub: Send events to the entire network, one server, several servers, a group, a server type or a tag.
- Built-in network events: Broadcast messages, notices, staff alerts, maintenance messages, titles and actionbars.
- Reliable delivery: Inspect acknowledgements, execution results, pending targets, failures, expiration and retries.
- Namespaced KV storage: Share temporary JSON-compatible values between servers with TTL support.
- Atomic KV operations: Increment, decrement, set-if-absent and compare-and-set.
- Distributed locks: Coordinate network-wide operations with owner-aware expiring locks.
- Network flags: Store shared states such as maintenance or double XP with reason, owner and TTL metadata.
- Server administration state: Mark servers as maintenance or draining and expose whether they are eligible for routing.
- Allowlisted RPC: Request status, players, TPS, memory, worlds, flags and KV data from targeted servers.
- Safe compute services: Run fixed, allowlisted tasks such as leaderboard sorting, JSON diff and region intersection outside PMMP.
- Protected command dispatch: Execute explicitly allowed commands on selected servers with blocklists, length limits and dry-run support.
- Runtime events: React to messages, flags, KV changes, RPC responses, compute results and delivery activity through normal PMMP listeners.
- LibPlaceholder integration: Expose Agent, registry, flag, KV and server
values through
%agent_*%placeholders. - Standalone and package-backed distributions: Use the same public namespace through either this plugin or an EasyLibrary-managed package.
- Failure isolation: Disabled or unreachable Agent services return safe failure responses so consumer plugins can keep local gameplay running.
Plugin code
|
| imperazim\agent\api\*
v
EasyLibraryAgentBridge
|
| authenticated TCP/JSON requests
v
EasyLibraryAgent
|
+-- server registry and heartbeats
+-- PubSub and reliable delivery
+-- KV, flags and locks
+-- RPC and compute services
|
v
Other PocketMine-MP servers running the bridge
The external Agent owns network state and routing. The bridge translates that protocol into PHP APIs, configuration, placeholders and PocketMine events.
| Requirement | Purpose |
|---|---|
| PocketMine-MP API 5.0.0+ | Server runtime |
| PHP 8.2+ | Language runtime |
| EasyLibraryAgent | External shared-service process |
| LibCommand | Optional; registers /easyagent in standalone mode |
| LibPlaceholder | Optional; registers %agent_*% placeholders |
The Agent Bridge is disabled by default. A normal installation does not start
heartbeats, PubSub polling, commands, placeholders or Agent event handling
until agent.enabled is set to true.
Download or build the external service from:
https://github.com/ImperaZim/EasyLibraryAgent
Initialize its runtime directories and configure a strong shared secret:
./easylib-agent --init
./easylib-agent --config config/agent.tomlThe default local development address is 127.0.0.1:8787. The bridge address
must match the Agent bind value, and both sides must use the same secret.
Keep the Agent port on a private network, VPN or restricted firewall whenever possible.
Place EasyLibraryAgentBridge.phar in the PocketMine-MP plugins/ directory.
Install LibCommand when you want /easyagent, and install LibPlaceholder
when you want the Agent placeholder expansion.
Start the server once to generate:
plugin_data/EasyLibraryAgentBridge/config.yml
Minimal local configuration:
agent:
enabled: true
address: "127.0.0.1:8787"
secret: "CHANGE_ME_REPLACE_WITH_THE_AGENT_SECRET"
server-id: "lobby-1"
required: falseEvery PMMP server connected to the same Agent must use a unique server-id.
Add registry metadata when the network uses groups, types or tag targeting:
agent:
metadata:
display-name: "Main Lobby"
type: "lobby"
group: "public"
tags:
- "fallback"
- "na-east"Restart PocketMine-MP after enabling the bridge. With LibCommand installed, validate the connection using:
/easyagent status
/easyagent health
/easyagent doctor
The complete configuration is available in
resources/config.yml.
Plugins that require this standalone plugin can declare:
depend:
- EasyLibraryAgentBridgeThe same API can also be hosted by EasyLibrary. Plugins that support either distribution should declare both as optional and check the runtime:
softdepend:
- EasyLibraryAgentBridge
- EasyLibraryuse imperazim\agent\api\AgentRuntimeAPI;
if (
!class_exists(AgentRuntimeAPI::class) ||
!AgentRuntimeAPI::isAvailable()
) {
// Keep the plugin working locally or disable only the network feature.
return;
}When both distributions are installed, the standalone bridge is the preferred runtime. EasyLibrary does not start a duplicate manager, heartbeat, polling loop, command or placeholder expansion.
| API | Responsibility |
|---|---|
AgentRuntimeAPI |
Availability, connection, health and diagnostics |
AgentPubSubAPI |
Messages, network events and target routing |
AgentRegistryAPI |
Servers, metadata buckets and target resolution |
AgentDeliveryAPI |
ACK, result, retry and delivery diagnostics |
AgentRpcAPI |
Request/response calls to selected servers |
AgentKVAPI |
Namespaced values and atomic operations |
AgentFlagsAPI |
Shared network flags |
AgentLocksAPI |
Expiring distributed locks |
AgentServerStateAPI |
Maintenance, draining and routing state |
AgentCommandDispatchAPI |
Protected remote command automation |
AgentPlaceholderAPI |
Bridge metadata for placeholder integration |
AgentComputeAPI |
Safe allowlisted external compute tasks |
Use constants for protocol-owned strings:
use imperazim\agent\constant\AgentChannels;
use imperazim\agent\constant\AgentComputeMethods;
use imperazim\agent\constant\AgentNetworkFlags;
use imperazim\agent\constant\AgentRpcMethods;
use imperazim\agent\constant\AgentTargetTypes;Custom PubSub channels and plugin-owned flag names are supported. Prefix them with your plugin name to avoid collisions:
myplugin.cache.invalidate
myplugin.event.active
use imperazim\agent\api\AgentRuntimeAPI;
if (!AgentRuntimeAPI::isAvailable()) {
return;
}
$serverId = AgentRuntimeAPI::serverId();
$connected = AgentRuntimeAPI::isConnected();
$health = AgentRuntimeAPI::health();isAvailable() checks whether a bridge runtime is enabled. isConnected()
performs an Agent ping and should not be called repeatedly in same-tick
gameplay logic.
use imperazim\agent\api\AgentKVAPI;
AgentKVAPI::set(
namespace: 'myplugin',
key: 'current_event',
value: ['id' => 'mining_frenzy', 'multiplier' => 2],
ttlSeconds: 3600
);
$event = AgentKVAPI::get('myplugin', 'current_event');
AgentKVAPI::incr('myplugin', 'global_kills', 1, 86400);Values must be JSON-compatible. KV entries expire after their TTL and should not be treated as permanent database records.
use imperazim\agent\api\AgentLocksAPI;
$lock = AgentLocksAPI::lock(
namespace: 'myplugin.locks',
key: 'daily_reset',
ttlSeconds: 60,
reason: 'lobby-1'
);
if (($lock['locked'] ?? false) === true) {
try {
// Only one server should perform the global reset.
} finally {
AgentLocksAPI::unlock('myplugin.locks', 'daily_reset');
}
}Locks are coordination helpers, not a replacement for durable transactions.
Always use a bounded TTL and release the lock in a finally block.
use imperazim\agent\api\AgentFlagsAPI;
use imperazim\agent\constant\AgentNetworkFlags;
AgentFlagsAPI::set(
AgentNetworkFlags::DOUBLE_XP,
true,
3600,
'Weekend event'
);
$flag = AgentFlagsAPI::get(AgentNetworkFlags::DOUBLE_XP);
$active = (($flag['flag']['value']['active'] ?? false) === true);Common constants include maintenance, double XP, global events and global boosters. Plugins may also define namespaced flags they own.
Publish a custom event to every connected server:
use imperazim\agent\api\AgentPubSubAPI;
AgentPubSubAPI::toNetwork(
'myplugin.cache.invalidate',
'Invalidate profile cache',
['player' => 'Steve']
);Target a registry group with a built-in network event:
use imperazim\agent\api\AgentPubSubAPI;
use imperazim\agent\constant\AgentChannels;
AgentPubSubAPI::toGroup(
'public',
AgentChannels::NETWORK_NOTICE,
'The network will restart in five minutes.'
);Listen through PocketMine's event system:
use imperazim\agent\api\AgentPubSubAPI;
use imperazim\agent\event\AgentMessageEvent;
use pocketmine\event\Listener;
final class NetworkListener implements Listener {
public function onAgentMessage(AgentMessageEvent $event): void {
if (!AgentPubSubAPI::eventTargetsThisServer($event)) {
return;
}
if ($event->getChannel() !== 'myplugin.cache.invalidate') {
return;
}
$playerName = $event->getDataValue('player');
if (!is_string($playerName)) {
return;
}
// Invalidate the local cache entry.
}
}use imperazim\agent\api\AgentRegistryAPI;
use imperazim\agent\api\AgentServerStateAPI;
$servers = AgentRegistryAPI::servers();
$lobby = AgentRegistryAPI::info('lobby-1');
$publicGroup = AgentRegistryAPI::resolveGroup('public');
AgentServerStateAPI::draining(
'lobby-1',
true,
'Scheduled restart'
);Registry state can power server selectors, routing previews, network player counts and maintenance displays.
use imperazim\agent\api\AgentRpcAPI;
use imperazim\agent\constant\AgentRpcMethods;
$request = AgentRpcAPI::toServer(
'survival-1',
AgentRpcMethods::SERVER_STATUS,
[],
10
);
$requestId = (int) ($request['request_id'] ?? 0);Handle the eventual response:
use imperazim\agent\event\AgentRpcResponseReceivedEvent;
use pocketmine\event\Listener;
final class RpcListener implements Listener {
public function onRpcResponse(AgentRpcResponseReceivedEvent $event): void {
if (!$event->isOk()) {
$error = $event->getError();
return;
}
$result = $event->getResult();
// Consume the response without blocking the main server tick.
}
}Use RPC when an answer is required. Use PubSub when the sender only needs to notify other servers.
use imperazim\agent\api\AgentComputeAPI;
use imperazim\agent\constant\AgentComputeMethods;
$task = AgentComputeAPI::runAsync(
AgentComputeMethods::LEADERBOARD_SORT,
[
'entries' => [
['name' => 'Alice', 'score' => 120],
['name' => 'Bob', 'score' => 300],
],
'score_key' => 'score',
'limit' => 10,
'descending' => true,
],
5
);Compute methods are closed and allowlisted. The current built-ins are:
| Constant | Purpose |
|---|---|
AgentComputeMethods::PING |
Check compute availability |
AgentComputeMethods::ECHO |
Return a diagnostic payload |
AgentComputeMethods::MATH_BASIC |
Run a basic math batch |
AgentComputeMethods::JSON_DIFF |
Compare JSON-like values |
AgentComputeMethods::REGION_INTERSECTS |
Test two 3D boxes |
AgentComputeMethods::LEADERBOARD_SORT |
Sort and limit score entries |
Do not use compute for permission checks, tiny same-tick calculations, direct world mutation or arbitrary code execution.
use imperazim\agent\api\AgentCommandDispatchAPI;
$response = AgentCommandDispatchAPI::toServerReliable(
'lobby-1',
'say Scheduled restart in five minutes'
);Command dispatch is intended for trusted administration and automation. The receiving server validates the command against its allowlist, blocklist, maximum length and dry-run settings.
Prefer PubSub, flags, KV or RPC for plugin-to-plugin gameplay behavior.
| Event | Fired for |
|---|---|
AgentMessageEvent |
Incoming PubSub message |
AgentPubSubPublishedEvent |
Local publish result |
AgentNetworkEventHandledEvent |
Built-in network event handled locally |
AgentNetworkEventSkippedEvent |
Built-in network event skipped |
AgentCommandDispatchResultEvent |
Remote command execution or rejection |
AgentKVChangedEvent |
Local KV mutation result |
AgentNetworkFlagChangedEvent |
Local network flag mutation |
AgentServerStateChangedEvent |
Maintenance or draining state update |
AgentRpcRequestReceivedEvent |
Incoming RPC request |
AgentRpcResponseReceivedEvent |
Incoming RPC response |
AgentComputeResultEvent |
Completed compute task |
AgentComputeFailedEvent |
Failed compute task |
AgentComputeExpiredEvent |
Expired compute task |
Register listeners normally:
$this->getServer()
->getPluginManager()
->registerEvents(new NetworkListener(), $this);Events only fire while the Agent runtime is enabled.
When LibPlaceholder is available and agent.placeholders.enabled is true,
the bridge registers the agent expansion.
| Placeholder | Value |
|---|---|
%agent_bridge_version% |
Bridge API version |
%agent_connected% |
1 when the Agent ping succeeds |
%agent_server_id% |
Local server ID |
%agent_metadata_display_name% |
Local registry display name |
%agent_metadata_type% |
Local server type |
%agent_metadata_group% |
Local server group |
%agent_metadata_tags% |
Comma-separated local tags |
%agent_servers_online% |
Online server count |
%agent_servers_stale% |
Stale server count |
%agent_servers_offline% |
Offline server count |
%agent_servers_maintenance% |
Servers in maintenance |
%agent_servers_draining% |
Draining server count |
%agent_network_online% |
Players online across the network |
%agent_network_max% |
Combined maximum player capacity |
%agent_network_average_tps% |
Average network TPS |
%agent_flag_<flag>%
%agent_flag_<flag>_reason%
%agent_flag_<flag>_ttl%
%agent_flag_<flag>_owner%
%agent_kv_<namespace>_<key>%
%agent_kv_<namespace>_<key>_exists%
%agent_kv_<namespace>_<key>_ttl%
%agent_kv_<namespace>_<key>_owner%
%agent_server_<server-id>_online%
%agent_server_<server-id>_players%
%agent_server_<server-id>_max_players%
%agent_server_<server-id>_tps%
%agent_server_<server-id>_state%
%agent_server_<server-id>_maintenance%
%agent_server_<server-id>_draining%
%agent_server_<server-id>_routing_eligible%
Use /easyagent placeholders to inspect the available patterns and
/easyagent placeholders test <placeholder> to test one value.
The command is registered only when:
- the Agent bridge is enabled;
agent.commands.enabledistrue;- LibCommand is available in standalone mode.
Main command:
/easyagent
Aliases:
/eagent
/libraryagent
/elagent
| Command | Purpose |
|---|---|
/easyagent status |
Runtime, heartbeat, PubSub and connection summary |
/easyagent health |
Agent health response |
/easyagent doctor |
Connection and configuration diagnostics |
/easyagent report [section] |
Network, registry, delivery, RPC and readiness report |
/easyagent reload |
Reload the bridge configuration and tasks |
/easyagent config doctor |
Preview missing or migrated config entries |
/easyagent config repair |
Repair and save the configuration |
| Group | Main operations |
|---|---|
/easyagent pubsub |
Status, cursor, last event, poll, channels, test and publish |
/easyagent servers |
List, registry, info, groups, types, tags, states and target resolution |
/easyagent kv |
Get, set, delete, list, increment, decrement, set-if-absent, CAS and locks |
/easyagent flags |
List, get, set and clear network flags |
/easyagent delivery |
Status, event details, pending, recent, retry and cleanup |
/easyagent rpc |
Targeted requests, methods, status, pending, recent and summary |
/easyagent command |
Network, server, group, type and tag command dispatch |
/easyagent placeholders |
Placeholder patterns and value testing |
/easyagent network-events |
Built-in handler status |
Recommended production command policy:
agent:
commands:
enabled: true
advanced: false
debug: falseThis keeps status and diagnostic tools available without exposing write and testing commands during normal operation.
| Permission | Default | Purpose |
|---|---|---|
easylibrary.command.agent |
OP | Use /easyagent |
easylibrary.agent.staff |
OP | Receive built-in staff alerts |
The bridge receives a channel when it matches an exact configured channel or one of the configured prefixes:
agent:
pubsub:
channels:
- "network.notice"
- "myplugin.event"
channel-prefixes:
- "network."
- "myplugin."Set receive-own-events: true only when a server should also process events
it published itself.
Built-in handlers convert official channels into local PMMP actions:
network.broadcast
network.notice
network.staff-alert
network.maintenance
network.title
network.actionbar
network.command
Each handler can be disabled independently. Plugins may also listen to
AgentMessageEvent directly for custom behavior.
Review these settings before enabling remote command automation:
agent:
command-dispatch:
enabled: true
dry-run: false
allow-console: true
max-command-length: 256
allowed-commands:
- say
- tell
- title
blocked-commands:
- stop
- restart
- op
- banThe blocklist remains authoritative even when a command also appears in the allowlist.
The bridge only responds to methods listed in
agent.rpc.allowed-methods. Built-in methods include server ping, status,
players, TPS, memory, worlds, flags, KV lookup and method discovery.
There is no arbitrary PHP execution endpoint.
Consumer plugins must treat Agent-backed features as optional and fallible.
$response = AgentFlagsAPI::get('myplugin.event.active');
if (($response['ok'] ?? false) !== true) {
// Use cached/local state or skip only this optional feature.
return;
}Expected safe behavior:
- disabled bridge: API availability checks return false;
- offline Agent: calls return false or
ok=false; - wrong secret: requests are rejected without printing the secret;
- expired KV or flags: values become absent after TTL;
- offline target: target resolution excludes it from normal routing;
- RPC timeout: the request becomes failed or expired;
- invalid compute method: PHP and the Agent both reject it;
- stale PubSub cursor: the bridge accepts the Agent cursor reset response.
Do not block the PMMP main thread waiting for network state. Prefer events, status polling with sensible intervals and local fallbacks.
- Use a long random secret and keep it identical on the bridge and Agent.
- Never publish the real secret in logs, screenshots, issues or source code.
- Rotate the secret immediately if it is exposed.
- Keep the Agent port private or firewall-restricted.
- Keep advanced and debug commands disabled in production unless needed.
- Use command allowlists and blocklists conservatively.
- Keep RPC and compute methods allowlisted.
- Do not send sensitive player or authentication data through custom PubSub payloads without an explicit data policy.
See SECURITY.md and
docs/bridge-security.md for the complete security
guidance.
docs/bridge-api.md- public API overview.docs/bridge-api-contracts.md- response and compatibility contracts.docs/bridge-command-surface.md- command policy.docs/bridge-compute.md- compute services.docs/bridge-constants.md- official constants.docs/bridge-failure-modes.md- expected failure behavior.docs/bridge-opt-in-runtime.md- disabled and enabled runtime states.docs/bridge-runtime-events.md- event reference.docs/bridge-security.md- deployment security.examples/agent/- integration examples.
Install development dependencies:
composer installRun the repository quality checks:
composer run qualityThe quality command runs PHP lint, PHPStan and the configured test runner.
Pull requests should target development. Read
CONTRIBUTING.md before submitting changes.
- EasyLibraryAgent - external Go service used by the bridge.
- EasyLibrary - complete PocketMine library bundle with the embedded bridge option.
- LibCommand - command framework
used by
/easyagent. - LibPlaceholder - placeholder
system used by
%agent_*%.
EasyLibraryAgentBridge is licensed under the
MIT License.
EasyLibraryAgentBridge can also be published as an EasyLibrary 3.x bridge package asset. The release workflow now builds EasyLibraryAgentBridge-1.0.0.easylib.zip, package.yml and checksums.txt in addition to the standalone PHAR. During migration, keep the standalone plugin installed when you want it to own the runtime; EasyLibrary's standalone shadow guard prevents the internal package from autoloading while the standalone plugin is active.