Skip to content

Third-party plugin API

McProgression exposes Java APIs for plugins that need to award progression or issue timed modifier keys. This page documents the key-grant API. The API is available through Bukkit's ServicesManager; integrations should not cast McProgression's plugin main class or call its internal services.

Compile against the API

McProgression is currently distributed privately rather than through a public Maven repository. Add the matching McProgression release JAR to your development environment and declare it as compileOnly (Gradle) or provided (Maven). Do not shade or bundle McProgression classes into your plugin. At runtime, install McProgression on the server and declare it as a soft dependency if your plugin can operate without it:

softdepend: [McProgression]

The API package is dev.mcprogression.api. Check for the service at runtime because the plugin may be absent or disabled:

ProgressionKeyGrantApi keyApi = Bukkit.getServicesManager()
        .load(ProgressionKeyGrantApi.class);
if (keyApi == null) {
    // McProgression is not available; skip the integration or report it as disabled.
}

Grant a custom key

Create a KeyDefinition for the signed key's effect and appearance, then submit it with a recipient and a stable request ID:

import dev.mcprogression.api.KeyDefinition;
import dev.mcprogression.api.KeyGrantRequest;
import dev.mcprogression.api.KeyGrantResult;
import dev.mcprogression.api.ProgressionKeyGrantApi;

import java.util.List;
import java.util.Set;
import java.util.UUID;

ProgressionKeyGrantApi keyApi = Bukkit.getServicesManager()
        .load(ProgressionKeyGrantApi.class);
if (keyApi != null) {
    KeyDefinition effect = new KeyDefinition(
            "quest-mining-boost",       // stable key/template ID
            "AMETHYST_SHARD",           // Bukkit material name
            "&bMiner's Favor",
            List.of("&7A reward for completing the quarry quest."),
            15 * 60 * 1000L,             // duration in milliseconds
            2.0,                         // XP multiplier
            0, "", "",                  // minimum score, minimum tier, maximum tier
            false,                       // include General XP
            Set.of("mining"),            // skill IDs; use Set.of("*") for all enabled skills
            Set.of(),                    // optional activity IDs
            false                        // false = personal; true = server-wide
    );
    KeyGrantRequest request = new KeyGrantRequest(
            playerUuid,
            playerName,                  // optional/fallback display name for offline players
            effect,
            "MyQuestPlugin",             // source plugin name
            "quarry-complete:" + questCompletionUuid // stable unique reward ID
    );

    keyApi.grant(request).thenAccept(result -> {
        if (result.status() == KeyGrantResult.Status.GRANTED) {
            // The key was durably queued for the recipient.
        } else if (result.status() == KeyGrantResult.Status.ALREADY_GRANTED) {
            // This request ID was already processed; do not issue another reward.
        } else {
            // Rejected; inspect result.message() for a human-readable reason.
        }
    });
}

The recipient UUID identifies the player's mailbox, while serverWide selects the key's activation audience. A server-wide key is still delivered to one recipient, who can claim and activate it. Server-wide activation is rejected unless award-modifiers.server-wide-boosting-enabled: true is set in McProgression's config.yml.

grant returns a CompletionStage; do not block the server thread waiting for it. The result reports GRANTED, ALREADY_GRANTED, or REJECTED. A successful result means the key is durably queued for the recipient, not that it has been claimed or activated. The player can retrieve it through /mcp keys or /mcp keys claim.

Inspect or retract a grant

inspectGrant(recipientId, sourcePlugin, requestId) checks the stored grant state using the same source, recipient, and request ID as the original request. It returns NOT_FOUND, PENDING, PROCESSED_NOT_PENDING, or UNAVAILABLE, along with a delivery ID and key ID when available. PROCESSED_NOT_PENDING means the delivery is no longer in the mailbox; the key store does not distinguish whether it was claimed or retracted.

Keep the deliveryId returned by KeyGrantResult when a grant succeeds. revokePendingGrant(recipientId, deliveryId, sourcePlugin) retracts that one delivery only while it is still pending and checks that the UUID owns it. It does not remove an item already claimed into a player's inventory. Claims and retractions are serialized within one McProgression process; if a claim may already have inserted an item, retraction can return CHANGED_CONCURRENTLY. This coordination does not cover multiple Paper instances sharing MySQL or direct repository writers. Uncertain-ack markers are in memory, so a crash after inventory insertion but before the mailbox acknowledgement can leave the delivery row pending after restart; the claim command may report the item as delivered even when its acknowledgement failed. These guarantees are best-effort under storage failure. Use one server writer per key database until distributed claim coordination exists. Revocation can return REVOKED, NOT_FOUND, CHANGED_CONCURRENTLY, or REJECTED.

Read or revoke active modifiers

activeModifiers(recipientId) asynchronously returns read-only, best-effort snapshots for that player's active personal modifier and the active server-wide modifier, if present. The values come from McProgression's current modifier state/cache and can change as keys activate or expire. A snapshot includes its key ID, display name, multiplier, effect scope, activation/expiry timestamps, observation time, and remaining milliseconds. A server-wide snapshot has a null recipientId and serverWide set to true.

revokeActiveModifier(recipientId, keyId, sourcePlugin) revokes the player's active modifier only when its key ID matches. revokeServerWideModifier(keyId, sourcePlugin) does the same for the server-wide modifier. These methods return KeyOperationResult with REVOKED, NOT_FOUND, CHANGED_CONCURRENTLY, or REJECTED; they do not remove pending mailbox deliveries. sourcePlugin is an audit label, not an authorization identity.

keyApi.inspectGrant(playerUuid, "MyQuestPlugin", requestId).thenAccept(status -> {
    if (status.state() == KeyGrantStatus.State.PENDING) {
        keyApi.revokePendingGrant(playerUuid, status.deliveryId(), "MyQuestPlugin");
    }
});

keyApi.activeModifiers(playerUuid).thenAccept(modifiers ->
        modifiers.forEach(modifier -> Bukkit.getLogger().info(
                modifier.keyId() + " has " + modifier.remainingMillis() + " ms remaining")));

keyApi.revokeActiveModifier(playerUuid, "quest-mining-boost", "MyQuestPlugin");
keyApi.revokeServerWideModifier("weekend-boost", "MyQuestPlugin");

Award General XP

awardGeneralXp(recipientId, sourceId, amount, eventId, sourcePlugin) awards uncategorized progression through McProgression's configured adapter-source policy. The recipient must be online when the award is processed, and sourceId must identify a source explicitly enabled in general-xp.yml. Supply a stable eventId for a one-time event so ordinary retries are deduplicated. The source plugin name is included in the event key and Discord webhook action label; XP history is attributed to the configured source ID. The label is caller-supplied and does not authenticate the caller. GeneralXpAwardResult reports AWARDED or REJECTED, the amount awarded, the resulting total, and a reason when rejected; a rejection can mean either policy failure or a duplicate event. Configured per-award, daily, source, and cooldown limits still apply. Event claims are persisted through the player's progression profile snapshot, not atomically with the award, so a crash before the next save can leave a window where a retry is not recognized. This is retry deduplication, not crash-proof exactly-once delivery.

keyApi.awardGeneralXp(playerUuid, "quest_reward", 5.0,
        "quest-complete:" + questCompletionUuid, "MyQuestPlugin")
    .thenAccept(result -> {
        if (result.status() == GeneralXpAwardResult.Status.AWARDED) {
            // result.awarded() is the applied amount after configured caps.
        } else {
            // result.reason() explains the policy rejection or duplicate event.
        }
    });

Request IDs and retries

Use a stable request ID for each logical reward, such as a quest completion UUID or event record ID. The source plugin, recipient UUID, and request ID together form the idempotency key: retries for that reward do not create duplicate mailbox entries. Reuse the same key definition and recipient payload on retries; a previously processed request ID will not create a replacement key if you change its definition. Request IDs must be 1–128 characters. Do not generate a new request ID for each retry. Use a distinct request ID for each separate reward, even if its recipient and key effect are identical. Source plugin names must match [A-Za-z0-9][A-Za-z0-9_.-]{0,63}. They are caller-supplied labels, not authenticated identities; only trusted server-side plugins should receive access to this API.

Key effect fields and validation

KeyDefinition contains the key ID, Bukkit item material, display name, lore, duration in milliseconds, multiplier, optional minimum Global Rank score and tier bounds, General XP scope, skill IDs, activity IDs, and activation audience. Key IDs, skill IDs, activity IDs, and tier IDs must use lowercase letters, digits, _, or - (IDs start with a letter or digit). Set.of("*") selects all enabled skills; activity filters must match at least one selected skill. Include at least General XP or one skill in the effect scope.

McProgression validates the definition against its current configuration before issuing it. Display names are limited to 80 characters and lore to 10 lines of 100 characters each. Materials must be valid inventory items; durations are limited to 1 second through 30 days; multipliers must be greater than 1 and at most 10; rank gates require Global Rank to be enabled and must reference configured tiers. Invalid definitions, unavailable scope IDs, or a disabled server-wide setting produce REJECTED; check the returned message and server log for details.

Each issued item stores a signed snapshot of the definition. Editing or removing a key template later does not rewrite already-issued items. Protect third-party grant calls as carefully as any command that can award progression rewards.

General XP adapter

For third-party sources that award uncategorized progression directly, see General XP's adapter guidance. That path records XP rather than issuing a claimable item.

RCON and command execution

McProgression does not expose an HTTP or generic RCON API. Minecraft RCON is a server-level remote console: an external tool can send a command that the server console is allowed to execute, such as an administrative McProgression command. Whether a command is available depends on the server's permission configuration; McProgression's admin commands still check their permission nodes. This is command execution, not the Java ServicesManager API documented above; it does not provide typed request objects, CompletionStage results, or plugin-to-plugin service discovery. Use the Java API when integrating another installed Bukkit/Paper plugin. Keep RCON credentials and network access restricted.