diff --git a/patches/api/Adventure.patch b/patches/api/Adventure.patch
index 3a417e3690..1a42c3c676 100644
--- a/patches/api/Adventure.patch
+++ b/patches/api/Adventure.patch
@@ -14,7 +14,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      withJavadocJar()
  }
  
-+val adventureVersion = "4.12.0"
++val adventureVersion = "4.13.0"
 +val apiAndDocs: Configuration by configurations.creating {
 +    attributes {
 +        attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.DOCUMENTATION))
diff --git a/patches/server/Add-debug-for-sync-chunk-loads.patch b/patches/server/Add-debug-for-sync-chunk-loads.patch
index 93cb7ce941..bb3ecf2b70 100644
--- a/patches/server/Add-debug-for-sync-chunk-loads.patch
+++ b/patches/server/Add-debug-for-sync-chunk-loads.patch
@@ -197,14 +197,6 @@ diff --git a/src/main/java/io/papermc/paper/command/PaperCommand.java b/src/main
 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 --- a/src/main/java/io/papermc/paper/command/PaperCommand.java
 +++ b/src/main/java/io/papermc/paper/command/PaperCommand.java
-@@ -0,0 +0,0 @@ import io.papermc.paper.command.subcommands.EntityCommand;
- import io.papermc.paper.command.subcommands.FixLightCommand;
- import io.papermc.paper.command.subcommands.HeapDumpCommand;
- import io.papermc.paper.command.subcommands.ReloadCommand;
-+import io.papermc.paper.command.subcommands.SyncLoadInfoCommand;
- import io.papermc.paper.command.subcommands.VersionCommand;
- import io.papermc.paper.command.subcommands.DumpPluginsCommand;
- import it.unimi.dsi.fastutil.Pair;
 @@ -0,0 +0,0 @@ public final class PaperCommand extends Command {
          commands.put(Set.of("dumpplugins"), new DumpPluginsCommand());
          commands.put(Set.of("fixlight"), new FixLightCommand());
diff --git a/patches/server/Add-paper-dumplisteners-command.patch b/patches/server/Add-paper-dumplisteners-command.patch
index 85f8f25c0b..125268c076 100644
--- a/patches/server/Add-paper-dumplisteners-command.patch
+++ b/patches/server/Add-paper-dumplisteners-command.patch
@@ -9,14 +9,6 @@ diff --git a/src/main/java/io/papermc/paper/command/PaperCommand.java b/src/main
 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 --- a/src/main/java/io/papermc/paper/command/PaperCommand.java
 +++ b/src/main/java/io/papermc/paper/command/PaperCommand.java
-@@ -0,0 +0,0 @@ package io.papermc.paper.command;
- 
- import io.papermc.paper.command.subcommands.ChunkDebugCommand;
- import io.papermc.paper.command.subcommands.DumpItemCommand;
-+import io.papermc.paper.command.subcommands.DumpListenersCommand;
- import io.papermc.paper.command.subcommands.EntityCommand;
- import io.papermc.paper.command.subcommands.FixLightCommand;
- import io.papermc.paper.command.subcommands.HeapDumpCommand;
 @@ -0,0 +0,0 @@ public final class PaperCommand extends Command {
          commands.put(Set.of("syncloadinfo"), new SyncLoadInfoCommand());
          commands.put(Set.of("dumpitem"), new DumpItemCommand());
diff --git a/patches/server/Add-paper-mobcaps-and-paper-playermobcaps.patch b/patches/server/Add-paper-mobcaps-and-paper-playermobcaps.patch
index ee67f767be..58b49f9790 100644
--- a/patches/server/Add-paper-mobcaps-and-paper-playermobcaps.patch
+++ b/patches/server/Add-paper-mobcaps-and-paper-playermobcaps.patch
@@ -13,14 +13,6 @@ diff --git a/src/main/java/io/papermc/paper/command/PaperCommand.java b/src/main
 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 --- a/src/main/java/io/papermc/paper/command/PaperCommand.java
 +++ b/src/main/java/io/papermc/paper/command/PaperCommand.java
-@@ -0,0 +0,0 @@ import io.papermc.paper.command.subcommands.DumpItemCommand;
- import io.papermc.paper.command.subcommands.EntityCommand;
- import io.papermc.paper.command.subcommands.FixLightCommand;
- import io.papermc.paper.command.subcommands.HeapDumpCommand;
-+import io.papermc.paper.command.subcommands.MobcapsCommand;
- import io.papermc.paper.command.subcommands.ReloadCommand;
- import io.papermc.paper.command.subcommands.SyncLoadInfoCommand;
- import io.papermc.paper.command.subcommands.VersionCommand;
 @@ -0,0 +0,0 @@ public final class PaperCommand extends Command {
          commands.put(Set.of("debug", "chunkinfo", "holderinfo"), new ChunkDebugCommand());
          commands.put(Set.of("syncloadinfo"), new SyncLoadInfoCommand());
diff --git a/patches/server/Adventure.patch b/patches/server/Adventure.patch
index 50cd8e1ffa..2ea9cd3f11 100644
--- a/patches/server/Adventure.patch
+++ b/patches/server/Adventure.patch
@@ -1169,6 +1169,109 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        return net.minecraft.network.chat.Component.Serializer.fromJson(GsonComponentSerializer.gson().serializer().toJsonTree(component));
 +    }
 +}
+diff --git a/src/main/java/io/papermc/paper/adventure/providers/ClickCallbackProviderImpl.java b/src/main/java/io/papermc/paper/adventure/providers/ClickCallbackProviderImpl.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/adventure/providers/ClickCallbackProviderImpl.java
+@@ -0,0 +0,0 @@
++package io.papermc.paper.adventure.providers;
++
++import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
++import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
++import net.kyori.adventure.audience.Audience;
++import net.kyori.adventure.text.event.ClickCallback;
++import net.kyori.adventure.text.event.ClickEvent;
++import org.jetbrains.annotations.NotNull;
++
++import java.util.Queue;
++import java.util.concurrent.ConcurrentLinkedQueue;
++import java.util.concurrent.atomic.AtomicLong;
++
++@SuppressWarnings("UnstableApiUsage") // permitted provider
++public class ClickCallbackProviderImpl implements ClickCallback.Provider {
++
++    public static final CallbackManager CALLBACK_MANAGER = new CallbackManager();
++
++    @Override
++    public @NotNull ClickEvent create(final @NotNull ClickCallback<Audience> callback, final ClickCallback.@NotNull Options options) {
++        return ClickEvent.runCommand("/paper callback " + CALLBACK_MANAGER.addCallback(callback, options));
++    }
++
++    public static final class CallbackManager {
++
++        private final Long2ObjectMap<StoredCallback> callbacks = new Long2ObjectOpenHashMap<>();
++        private final Queue<StoredCallback> queue = new ConcurrentLinkedQueue<>();
++        private final AtomicLong current = new AtomicLong();
++
++        private CallbackManager() {
++        }
++
++        public long addCallback(final @NotNull ClickCallback<Audience> callback, final ClickCallback.@NotNull Options options) {
++            final long id = current.getAndIncrement();
++            this.queue.add(new StoredCallback(callback, options, id));
++            return id;
++        }
++
++        public void handleQueue(final int currentTick) {
++            // Evict expired entries
++            if (currentTick % 100 == 0) {
++                this.callbacks.values().removeIf(callback -> !callback.valid());
++            }
++
++            // Add entries from queue
++            StoredCallback callback;
++            while ((callback = this.queue.poll()) != null) {
++                this.callbacks.put(callback.id(), callback);
++            }
++        }
++
++        public void runCallback(final @NotNull Audience audience, final long id) {
++            final StoredCallback callback = this.callbacks.get(id);
++            if (callback != null && callback.valid()) { //TODO Message if expired/invalid?
++                callback.takeUse();
++                callback.callback.accept(audience);
++            }
++        }
++    }
++
++    private static final class StoredCallback {
++        private final long startedAt = System.nanoTime();
++        private final ClickCallback<Audience> callback;
++        private final long lifetime;
++        private final long id;
++        private int remainingUses;
++
++        private StoredCallback(final @NotNull ClickCallback<Audience> callback, final ClickCallback.@NotNull Options options, final long id) {
++            this.callback = callback;
++            this.lifetime = options.lifetime().toNanos();
++            this.remainingUses = options.uses();
++            this.id = id;
++        }
++
++        public void takeUse() {
++            if (this.remainingUses != ClickCallback.UNLIMITED_USES) {
++                this.remainingUses--;
++            }
++        }
++
++        public boolean hasRemainingUses() {
++            return this.remainingUses == ClickCallback.UNLIMITED_USES || this.remainingUses > 0;
++        }
++
++        public boolean expired() {
++            return System.nanoTime() - this.startedAt >= this.lifetime;
++        }
++
++        public boolean valid() {
++            return hasRemainingUses() && !expired();
++        }
++
++        public long id() {
++            return this.id;
++        }
++    }
++}
 diff --git a/src/main/java/io/papermc/paper/adventure/providers/ComponentLoggerProviderImpl.java b/src/main/java/io/papermc/paper/adventure/providers/ComponentLoggerProviderImpl.java
 new file mode 100644
 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
@@ -2023,6 +2126,14 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
      private int playerIdleTimeout;
      public final long[] tickTimes;
      @Nullable
+@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+         SpigotTimings.schedulerTimer.startTiming(); // Spigot
+         this.server.getScheduler().mainThreadHeartbeat(this.tickCount); // CraftBukkit
+         SpigotTimings.schedulerTimer.stopTiming(); // Spigot
++        io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.CALLBACK_MANAGER.handleQueue(this.tickCount); // Paper
+         this.profiler.push("commandFunctions");
+         SpigotTimings.commandFunctionsTimer.startTiming(); // Spigot
+         this.getFunctions().tick();
 @@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
          return this.motd;
      }
@@ -4692,6 +4803,13 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          HashSet<Player> reference = new HashSet<Player>(players.size());
          for (ServerPlayer player : players) {
              reference.add(player.getBukkitEntity());
+diff --git a/src/main/resources/META-INF/services/net.kyori.adventure.text.event.ClickCallback$Provider b/src/main/resources/META-INF/services/net.kyori.adventure.text.event.ClickCallback$Provider
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/main/resources/META-INF/services/net.kyori.adventure.text.event.ClickCallback$Provider
+@@ -0,0 +1 @@
++io.papermc.paper.adventure.providers.ClickCallbackProviderImpl
 diff --git a/src/main/resources/META-INF/services/net.kyori.adventure.text.logger.slf4j.ComponentLoggerProvider b/src/main/resources/META-INF/services/net.kyori.adventure.text.logger.slf4j.ComponentLoggerProvider
 new file mode 100644
 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
diff --git a/patches/server/Paper-Plugins.patch b/patches/server/Paper-Plugins.patch
index 5b42eaa05d..ebc8c453af 100644
--- a/patches/server/Paper-Plugins.patch
+++ b/patches/server/Paper-Plugins.patch
@@ -8,18 +8,10 @@ diff --git a/src/main/java/io/papermc/paper/command/PaperCommand.java b/src/main
 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 --- a/src/main/java/io/papermc/paper/command/PaperCommand.java
 +++ b/src/main/java/io/papermc/paper/command/PaperCommand.java
-@@ -0,0 +0,0 @@ import io.papermc.paper.command.subcommands.EntityCommand;
- import io.papermc.paper.command.subcommands.HeapDumpCommand;
- import io.papermc.paper.command.subcommands.ReloadCommand;
- import io.papermc.paper.command.subcommands.VersionCommand;
-+import io.papermc.paper.command.subcommands.DumpPluginsCommand;
- import it.unimi.dsi.fastutil.Pair;
- import java.util.ArrayList;
- import java.util.Arrays;
 @@ -0,0 +0,0 @@ public final class PaperCommand extends Command {
-         commands.put(Set.of("entity"), new EntityCommand());
          commands.put(Set.of("reload"), new ReloadCommand());
          commands.put(Set.of("version"), new VersionCommand());
+         commands.put(Set.of("callback"), new CallbackCommand());
 +        commands.put(Set.of("dumpplugins"), new DumpPluginsCommand());
  
          return commands.entrySet().stream()
diff --git a/patches/server/Paper-command.patch b/patches/server/Paper-command.patch
index 53655b2faf..ea409ca871 100644
--- a/patches/server/Paper-command.patch
+++ b/patches/server/Paper-command.patch
@@ -87,10 +87,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 @@ -0,0 +0,0 @@
 +package io.papermc.paper.command;
 +
-+import io.papermc.paper.command.subcommands.EntityCommand;
-+import io.papermc.paper.command.subcommands.HeapDumpCommand;
-+import io.papermc.paper.command.subcommands.ReloadCommand;
-+import io.papermc.paper.command.subcommands.VersionCommand;
++import io.papermc.paper.command.subcommands.*;
 +import it.unimi.dsi.fastutil.Pair;
 +import java.util.ArrayList;
 +import java.util.Arrays;
@@ -127,11 +124,13 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        commands.put(Set.of("entity"), new EntityCommand());
 +        commands.put(Set.of("reload"), new ReloadCommand());
 +        commands.put(Set.of("version"), new VersionCommand());
++        commands.put(Set.of("callback"), new CallbackCommand());
 +
 +        return commands.entrySet().stream()
 +            .flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue())))
 +            .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
 +    });
++    private static final Set<String> COMPLETABLE_SUBCOMMANDS = SUBCOMMANDS.entrySet().stream().filter(entry -> entry.getValue().tabCompletes()).map(Map.Entry::getKey).collect(Collectors.toSet());
 +    // alias -> subcommand label
 +    private static final Map<String, String> ALIASES = Util.make(() -> {
 +        final Map<String, Set<String>> aliases = new HashMap<>();
@@ -173,7 +172,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +        final @Nullable Location location
 +    ) throws IllegalArgumentException {
 +        if (args.length <= 1) {
-+            return CommandUtil.getListMatchingLast(sender, args, SUBCOMMANDS.keySet());
++            return CommandUtil.getListMatchingLast(sender, args, COMPLETABLE_SUBCOMMANDS);
 +        }
 +
 +        final @Nullable Pair<String, PaperSubcommand> subCommand = resolveCommand(args[0]);
@@ -284,6 +283,53 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +    default List<String> tabComplete(final CommandSender sender, final String subCommand, final String[] args) {
 +        return Collections.emptyList();
 +    }
++
++    default boolean tabCompletes() {
++        return true;
++    }
++}
+diff --git a/src/main/java/io/papermc/paper/command/subcommands/CallbackCommand.java b/src/main/java/io/papermc/paper/command/subcommands/CallbackCommand.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/command/subcommands/CallbackCommand.java
+@@ -0,0 +0,0 @@
++package io.papermc.paper.command.subcommands;
++
++import io.papermc.paper.adventure.providers.ClickCallbackProviderImpl;
++import io.papermc.paper.command.PaperSubcommand;
++import net.kyori.adventure.text.Component;
++import net.kyori.adventure.text.event.ClickCallback;
++import net.kyori.adventure.text.event.ClickEvent;
++import org.bukkit.command.CommandSender;
++import org.checkerframework.checker.nullness.qual.NonNull;
++import org.checkerframework.framework.qual.DefaultQualifier;
++
++import java.time.Duration;
++
++@DefaultQualifier(NonNull.class)
++public final class CallbackCommand implements PaperSubcommand {
++    @Override
++    public boolean execute(final CommandSender sender, final String subCommand, final String[] args) {
++        if (args.length != 1) {
++            return false;
++        }
++
++        final long id;
++        try {
++            id = Long.parseLong(args[0]);
++        } catch (final NumberFormatException ignored) {
++            return false;
++        }
++
++        ClickCallbackProviderImpl.CALLBACK_MANAGER.runCallback(sender, id);
++        return true;
++    }
++
++    @Override
++    public boolean tabCompletes() {
++        return false;
++    }
 +}
 diff --git a/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java b/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java
 new file mode 100644
diff --git a/patches/server/Paper-dumpitem-command.patch b/patches/server/Paper-dumpitem-command.patch
index 2ed9c43bfe..8608deff59 100644
--- a/patches/server/Paper-dumpitem-command.patch
+++ b/patches/server/Paper-dumpitem-command.patch
@@ -9,14 +9,6 @@ diff --git a/src/main/java/io/papermc/paper/command/PaperCommand.java b/src/main
 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 --- a/src/main/java/io/papermc/paper/command/PaperCommand.java
 +++ b/src/main/java/io/papermc/paper/command/PaperCommand.java
-@@ -0,0 +0,0 @@
- package io.papermc.paper.command;
- 
- import io.papermc.paper.command.subcommands.ChunkDebugCommand;
-+import io.papermc.paper.command.subcommands.DumpItemCommand;
- import io.papermc.paper.command.subcommands.EntityCommand;
- import io.papermc.paper.command.subcommands.FixLightCommand;
- import io.papermc.paper.command.subcommands.HeapDumpCommand;
 @@ -0,0 +0,0 @@ public final class PaperCommand extends Command {
          commands.put(Set.of("fixlight"), new FixLightCommand());
          commands.put(Set.of("debug", "chunkinfo", "holderinfo"), new ChunkDebugCommand());
diff --git a/patches/server/Rewrite-chunk-system.patch b/patches/server/Rewrite-chunk-system.patch
index 46f6ef1db4..1972f89dba 100644
--- a/patches/server/Rewrite-chunk-system.patch
+++ b/patches/server/Rewrite-chunk-system.patch
@@ -11558,15 +11558,8 @@ diff --git a/src/main/java/io/papermc/paper/command/PaperCommand.java b/src/main
 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 --- a/src/main/java/io/papermc/paper/command/PaperCommand.java
 +++ b/src/main/java/io/papermc/paper/command/PaperCommand.java
-@@ -0,0 +0,0 @@
- package io.papermc.paper.command;
- 
-+import io.papermc.paper.command.subcommands.ChunkDebugCommand;
- import io.papermc.paper.command.subcommands.EntityCommand;
- import io.papermc.paper.command.subcommands.FixLightCommand;
- import io.papermc.paper.command.subcommands.HeapDumpCommand;
 @@ -0,0 +0,0 @@ public final class PaperCommand extends Command {
-         commands.put(Set.of("version"), new VersionCommand());
+         commands.put(Set.of("callback"), new CallbackCommand());
          commands.put(Set.of("dumpplugins"), new DumpPluginsCommand());
          commands.put(Set.of("fixlight"), new FixLightCommand());
 +        commands.put(Set.of("debug", "chunkinfo", "holderinfo"), new ChunkDebugCommand());
diff --git a/patches/server/Starlight.patch b/patches/server/Starlight.patch
index 925f76aa8e..68e8f830e9 100644
--- a/patches/server/Starlight.patch
+++ b/patches/server/Starlight.patch
@@ -4334,17 +4334,9 @@ diff --git a/src/main/java/io/papermc/paper/command/PaperCommand.java b/src/main
 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 --- a/src/main/java/io/papermc/paper/command/PaperCommand.java
 +++ b/src/main/java/io/papermc/paper/command/PaperCommand.java
-@@ -0,0 +0,0 @@
- package io.papermc.paper.command;
- 
- import io.papermc.paper.command.subcommands.EntityCommand;
-+import io.papermc.paper.command.subcommands.FixLightCommand;
- import io.papermc.paper.command.subcommands.HeapDumpCommand;
- import io.papermc.paper.command.subcommands.ReloadCommand;
- import io.papermc.paper.command.subcommands.VersionCommand;
 @@ -0,0 +0,0 @@ public final class PaperCommand extends Command {
-         commands.put(Set.of("reload"), new ReloadCommand());
          commands.put(Set.of("version"), new VersionCommand());
+         commands.put(Set.of("callback"), new CallbackCommand());
          commands.put(Set.of("dumpplugins"), new DumpPluginsCommand());
 +        commands.put(Set.of("fixlight"), new FixLightCommand());
  
diff --git a/patches/server/Timings-v2.patch b/patches/server/Timings-v2.patch
index 8347c8a379..980872ccf5 100644
--- a/patches/server/Timings-v2.patch
+++ b/patches/server/Timings-v2.patch
@@ -842,6 +842,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
          this.server.getScheduler().mainThreadHeartbeat(this.tickCount); // CraftBukkit
 -        SpigotTimings.schedulerTimer.stopTiming(); // Spigot
 +        MinecraftTimings.bukkitSchedulerTimer.stopTiming(); // Spigot // Paper
+         io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.CALLBACK_MANAGER.handleQueue(this.tickCount); // Paper
          this.profiler.push("commandFunctions");
 -        SpigotTimings.commandFunctionsTimer.startTiming(); // Spigot
 +        MinecraftTimings.commandFunctionsTimer.startTiming(); // Spigot // Paper