diff --git a/patches/server/initial-work-on-native-Adventure-codecs.patch b/patches/server/initial-work-on-native-Adventure-codecs.patch
new file mode 100644
index 0000000000..ef1a08a374
--- /dev/null
+++ b/patches/server/initial-work-on-native-Adventure-codecs.patch
@@ -0,0 +1,301 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <jake.m.potrebic@gmail.com>
+Date: Tue, 5 Dec 2023 16:47:40 -0800
+Subject: [PATCH] initial work on native Adventure codecs
+
+== AT ==
+public net.minecraft.network.chat.HoverEvent$ItemStackInfo item
+public net.minecraft.network.chat.HoverEvent$ItemStackInfo count
+public net.minecraft.network.chat.HoverEvent$ItemStackInfo tag
+public net.minecraft.network.chat.contents.TranslatableContents filterAllowedArguments(Ljava/lang/Object;)Lcom/mojang/serialization/DataResult;
+
+diff --git a/src/main/java/io/papermc/paper/adventure/AdventureCodecs.java b/src/main/java/io/papermc/paper/adventure/AdventureCodecs.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/adventure/AdventureCodecs.java
+@@ -0,0 +0,0 @@
++package io.papermc.paper.adventure;
++
++import com.mojang.datafixers.util.Either;
++import com.mojang.serialization.Codec;
++import com.mojang.serialization.DataResult;
++import com.mojang.serialization.MapCodec;
++import com.mojang.serialization.codecs.RecordCodecBuilder;
++import java.io.IOException;
++import java.util.Collections;
++import java.util.List;
++import java.util.Optional;
++import java.util.function.Consumer;
++import java.util.function.Function;
++import net.kyori.adventure.key.Key;
++import net.kyori.adventure.text.Component;
++import net.kyori.adventure.text.KeybindComponent;
++import net.kyori.adventure.text.ScoreComponent;
++import net.kyori.adventure.text.TextComponent;
++import net.kyori.adventure.text.TranslatableComponent;
++import net.kyori.adventure.text.event.ClickEvent;
++import net.kyori.adventure.text.event.HoverEvent;
++import net.kyori.adventure.text.format.NamedTextColor;
++import net.kyori.adventure.text.format.Style;
++import net.kyori.adventure.text.format.TextColor;
++import net.kyori.adventure.text.format.TextDecoration;
++import net.minecraft.core.UUIDUtil;
++import net.minecraft.core.registries.BuiltInRegistries;
++import net.minecraft.nbt.CompoundTag;
++import net.minecraft.network.chat.ComponentSerialization;
++import net.minecraft.network.chat.contents.KeybindContents;
++import net.minecraft.network.chat.contents.ScoreContents;
++import net.minecraft.network.chat.contents.TranslatableContents;
++import net.minecraft.util.ExtraCodecs;
++import net.minecraft.util.StringRepresentable;
++import net.minecraft.world.item.Item;
++import net.minecraft.world.item.ItemStack;
++import org.checkerframework.checker.nullness.qual.NonNull;
++import org.checkerframework.checker.nullness.qual.Nullable;
++import org.checkerframework.framework.qual.DefaultQualifier;
++import org.intellij.lang.annotations.Subst;
++
++import static java.util.Objects.requireNonNull;
++import static net.kyori.adventure.text.Component.text;
++import static net.minecraft.util.ExtraCodecs.strictOptionalField;
++
++@DefaultQualifier(NonNull.class)
++public final class AdventureCodecs {
++
++    public static final Codec<Component> COMPONENT_CODEC = ExtraCodecs.recursive("adventure Component", AdventureCodecs::createCodec);
++
++    private static final Codec<TextColor> TEXT_COLOR_CODEC = Codec.STRING.comapFlatMap(s -> {
++        if (s.startsWith("#")) {
++            @Nullable TextColor value = TextColor.fromHexString(s);
++            return value != null ? DataResult.success(value) : DataResult.error(() -> "Cannot convert " + s + " to adventure TextColor");
++        } else {
++            final @Nullable NamedTextColor value = NamedTextColor.NAMES.value(s);
++            return value != null ? DataResult.success(value) : DataResult.error(() -> "Cannot convert " + s + " to adventure NamedTextColor");
++        }
++    }, TextColor::asHexString);
++
++    private static final Codec<Key> KEY_CODEC = Codec.STRING.comapFlatMap(s -> {
++        return Key.parseable(s) ? DataResult.success(Key.key(s)) : DataResult.error(() -> "Cannot convert " + s + " to adventure Key");
++    }, Key::asString);
++
++    private static final Codec<ClickEvent.Action> CLICK_EVENT_ACTION_CODEC = Codec.STRING.comapFlatMap(s -> {
++        final ClickEvent.@Nullable Action value = ClickEvent.Action.NAMES.value(s);
++        return value != null ? DataResult.success(value) : DataResult.error(() -> "Cannot convert " + s + " to adventure ClickEvent$Action");
++    }, ClickEvent.Action.NAMES::keyOrThrow);
++    private static final Codec<ClickEvent> CLICK_EVENT_CODEC = RecordCodecBuilder.create((instance) -> {
++        return instance.group(
++            CLICK_EVENT_ACTION_CODEC.fieldOf("action").forGetter(ClickEvent::action),
++            Codec.STRING.fieldOf("value").forGetter(ClickEvent::value)
++        ).apply(instance, ClickEvent::clickEvent);
++    });
++
++    private static final Codec<HoverEvent.ShowEntity> SHOW_ENTITY_CODEC = RecordCodecBuilder.create((instance) -> {
++        return instance.group(
++            KEY_CODEC.fieldOf("type").forGetter(HoverEvent.ShowEntity::type),
++            UUIDUtil.LENIENT_CODEC.fieldOf("id").forGetter(HoverEvent.ShowEntity::id),
++            strictOptionalField(COMPONENT_CODEC, "name").forGetter(he -> Optional.ofNullable(he.name()))
++        ).apply(instance, (key, uuid, component) -> {
++            return HoverEvent.ShowEntity.showEntity(key, uuid, component.orElse(null));
++        });
++    });
++
++    private static final Codec<HoverEvent.ShowItem> SHOW_ITEM_CODEC = net.minecraft.network.chat.HoverEvent.ItemStackInfo.CODEC.xmap(isi -> {
++        @Subst("key") final String typeKey = BuiltInRegistries.ITEM.getKey(isi.item).toString();
++        return HoverEvent.ShowItem.showItem(Key.key(typeKey), isi.count, PaperAdventure.asBinaryTagHolder(isi.tag.orElse(null)));
++    }, si -> {
++        final Item itemType = BuiltInRegistries.ITEM.get(PaperAdventure.asVanilla(si.item()));
++        final ItemStack stack;
++        try {
++            final @Nullable CompoundTag tag = si.nbt() != null ? si.nbt().get(PaperAdventure.NBT_CODEC) : null;
++            stack = new ItemStack(BuiltInRegistries.ITEM.wrapAsHolder(itemType), si.count(), Optional.ofNullable(tag));
++        } catch (IOException e) {
++            throw new RuntimeException(e);
++        }
++        return new net.minecraft.network.chat.HoverEvent.ItemStackInfo(stack);
++    });
++
++    // TODO legacies
++    private static final HoverEventType<HoverEvent.ShowEntity> SHOW_ENTITY_HOVER_EVENT_TYPE = new HoverEventType<>(SHOW_ENTITY_CODEC, HoverEvent.Action.SHOW_ENTITY, "show_entity");
++    private static final HoverEventType<HoverEvent.ShowItem> SHOW_ITEM_HOVER_EVENT_TYPE = new HoverEventType<>(SHOW_ITEM_CODEC, HoverEvent.Action.SHOW_ITEM, "show_item");
++    private static final HoverEventType<Component> SHOW_TEXT_HOVER_EVENT_TYPE = new HoverEventType<>(COMPONENT_CODEC, HoverEvent.Action.SHOW_TEXT, "show_text");
++    private static final Codec<HoverEventType<?>> HOVER_EVENT_TYPE_CODEC = StringRepresentable.fromValues(() -> new HoverEventType<?>[]{ SHOW_ENTITY_HOVER_EVENT_TYPE, SHOW_ITEM_HOVER_EVENT_TYPE, SHOW_TEXT_HOVER_EVENT_TYPE });
++
++    private record HoverEventType<V>(Codec<HoverEvent<V>> codec, String id) implements StringRepresentable {
++        private HoverEventType(final Codec<V> contentCodec, final HoverEvent.Action<V> action, final String id) {
++            this(contentCodec.xmap(v -> {
++                return HoverEvent.hoverEvent(action, v);
++            }, HoverEvent::value), id);
++        }
++        @Override
++        public String getSerializedName() {
++            return this.id;
++        }
++    }
++
++    private static final MapCodec<HoverEvent<?>> HOVER_EVENT_MAP_CODEC = HOVER_EVENT_TYPE_CODEC.dispatchMap("action", he -> {
++        if (he.action() == HoverEvent.Action.SHOW_ENTITY) {
++            return SHOW_ENTITY_HOVER_EVENT_TYPE;
++        } else if (he.action() == HoverEvent.Action.SHOW_ITEM) {
++            return SHOW_ITEM_HOVER_EVENT_TYPE;
++        } else if (he.action() == HoverEvent.Action.SHOW_TEXT) {
++            return SHOW_TEXT_HOVER_EVENT_TYPE;
++        } else {
++            throw new IllegalStateException();
++        }
++    }, HoverEventType::codec);
++
++    public static final MapCodec<Style> STYLE_CODEC = RecordCodecBuilder.mapCodec((instance) -> {
++        return instance.group(
++            strictOptionalField(TEXT_COLOR_CODEC, "color").forGetter(styleGetter(Style::color)),
++            strictOptionalField(Codec.BOOL, "bold").forGetter(decorationGetter(TextDecoration.BOLD)),
++            strictOptionalField(Codec.BOOL, "italic").forGetter(decorationGetter(TextDecoration.ITALIC)),
++            strictOptionalField(Codec.BOOL, "underlined").forGetter(decorationGetter(TextDecoration.UNDERLINED)),
++            strictOptionalField(Codec.BOOL, "strikethrough").forGetter(decorationGetter(TextDecoration.STRIKETHROUGH)),
++            strictOptionalField(Codec.BOOL, "obfuscated").forGetter(decorationGetter(TextDecoration.OBFUSCATED)),
++            strictOptionalField(CLICK_EVENT_CODEC, "clickEvent").forGetter(styleGetter(Style::clickEvent)),
++            strictOptionalField(HOVER_EVENT_MAP_CODEC.codec(), "hoverEvent").forGetter(styleGetter(Style::hoverEvent)),
++            strictOptionalField(Codec.STRING, "insertion").forGetter(styleGetter(Style::insertion)),
++            strictOptionalField(KEY_CODEC, "font").forGetter(styleGetter(Style::font))
++            ).apply(instance, (textColor, bold, italic, underlined, strikethrough, obfuscated, clickEvent, hoverEvent, insertion, font) -> {
++                return Style.style(builder -> {
++                    textColor.ifPresent(builder::color);
++                    bold.ifPresent(styleBooleanConsumer(builder, TextDecoration.BOLD));
++                    italic.ifPresent(styleBooleanConsumer(builder, TextDecoration.ITALIC));
++                    underlined.ifPresent(styleBooleanConsumer(builder, TextDecoration.UNDERLINED));
++                    strikethrough.ifPresent(styleBooleanConsumer(builder, TextDecoration.STRIKETHROUGH));
++                    obfuscated.ifPresent(styleBooleanConsumer(builder, TextDecoration.OBFUSCATED));
++                    clickEvent.ifPresent(builder::clickEvent);
++                    hoverEvent.ifPresent(builder::hoverEvent);
++                    insertion.ifPresent(builder::insertion);
++                    font.ifPresent(builder::font);
++                });
++        });
++    });
++    private static Consumer<Boolean> styleBooleanConsumer(final Style.Builder builder, final TextDecoration decoration) {
++        return b -> builder.decoration(decoration, b);
++    }
++
++    private static Function<Style, Optional<Boolean>> decorationGetter(final TextDecoration decoration) {
++        return style -> Optional.ofNullable(style.decoration(decoration) == TextDecoration.State.NOT_SET ? null : style.decoration(decoration) == TextDecoration.State.TRUE);
++    }
++
++    private static <T> Function<Style, Optional<T>> styleGetter(final Function<Style, @Nullable T> getter) {
++        return style -> Optional.ofNullable(getter.apply(style));
++    }
++
++    private static final MapCodec<TextComponent> TEXT_COMPONENT_CODEC = RecordCodecBuilder.mapCodec((instance) -> {
++        return instance.group(Codec.STRING.fieldOf("text").forGetter(TextComponent::content)).apply(instance, Component::text);
++    });
++    private static final Codec<Object> PRIMITIVE_ARG_CODEC = ExtraCodecs.validate(ExtraCodecs.JAVA, TranslatableContents::filterAllowedArguments);
++    private static final Codec<Component> ARG_CODEC = Codec.either(PRIMITIVE_ARG_CODEC, COMPONENT_CODEC).xmap((either) -> {
++        return either.map((object) -> {
++            if (object instanceof Integer integer) {
++                return text(integer);
++            } else if (object instanceof Long l) {
++                return text(l);
++            } else if (object instanceof String s) {
++                return text(s);
++            } else if (object instanceof Boolean bool) {
++                return text(bool);
++            } else if (object instanceof Float f) {
++                return text(f);
++            } else if (object instanceof Double d) {
++                return text(d);
++            } else if (object instanceof Short s) {
++                return text(s);
++            } else {
++                throw new IllegalStateException();
++            }
++        }, (text) -> text);
++    }, Either::right);
++    private static final MapCodec<TranslatableComponent> TRANSLATABLE_COMPONENT_CODEC = RecordCodecBuilder.mapCodec((instance) -> {
++        return instance.group(
++            Codec.STRING.fieldOf("translate").forGetter(TranslatableComponent::key),
++            Codec.STRING.fieldOf("fallback").forGetter(TranslatableComponent::fallback),
++            strictOptionalField(ARG_CODEC.listOf(), "with").forGetter(c -> c.args().isEmpty() ? Optional.empty() : Optional.of(c.args()))
++        ).apply(instance, (key, fallback, components) -> {
++            return Component.translatable(key, components.orElse(Collections.emptyList())).fallback(fallback);
++        });
++    });
++    private static final MapCodec<KeybindComponent> KEYBIND_COMPONENT_CODEC = KeybindContents.CODEC.xmap(k -> Component.keybind(k.getName()), k -> new KeybindContents(k.keybind()));
++    private static final MapCodec<ScoreComponent> SCORE_COMPONENT_CODEC = ScoreContents.INNER_CODEC.xmap(s -> Component.score(s.getName(), s.getObjective()), s -> new ScoreContents(s.name(), s.objective()));
++
++    private record ComponentType<C extends Component>(MapCodec<C> codec, String id) implements StringRepresentable {
++        @Override
++        public String getSerializedName() {
++            return this.id;
++        }
++    }
++
++    private static final ComponentType<TextComponent> PLAIN = new ComponentType<>(TEXT_COMPONENT_CODEC, "text");
++    private static final ComponentType<TranslatableComponent> TRANSLATABLE = new ComponentType<>(TRANSLATABLE_COMPONENT_CODEC, "translatable");
++    private static final ComponentType<ScoreComponent> SCORE = new ComponentType<>(SCORE_COMPONENT_CODEC, "score");
++    private static final ComponentType<KeybindComponent> KEYBIND = new ComponentType<>(KEYBIND_COMPONENT_CODEC, "keybind");
++
++    private static Codec<Component> createCodec(final Codec<Component> selfCodec) {
++        final ComponentType<?>[] types = new ComponentType<?>[]{PLAIN, TRANSLATABLE, SCORE};
++        final MapCodec<Component> legacyCodec = ComponentSerialization.createLegacyComponentMatcher(types, ComponentType::codec, component -> {
++            if (component instanceof TextComponent) {
++                return PLAIN;
++            } else if (component instanceof TranslatableComponent) {
++                return TRANSLATABLE;
++            } else if (component instanceof KeybindComponent) {
++                return KEYBIND;
++            } else if (component instanceof ScoreComponent) {
++                return SCORE;
++            } else {
++                throw new IllegalStateException();
++            }
++        }, "type");
++
++        final Codec<Component> codec = RecordCodecBuilder.create((instance) -> {
++            return instance.group(legacyCodec.forGetter(Function.identity()), ExtraCodecs.strictOptionalField(ExtraCodecs.nonEmptyList(selfCodec.listOf()), "extra", List.of()).forGetter(Component::children), STYLE_CODEC.forGetter(Component::style)).apply(instance, (component, children, style) -> {
++                return component.style(style).children(children);
++            });
++        });
++        return Codec.either(Codec.either(Codec.STRING, ExtraCodecs.nonEmptyList(selfCodec.listOf())), codec).xmap((either) -> {
++            return either.map((either2) -> {
++                return either2.map(Component::text, AdventureCodecs::createFromList);
++            }, (text) -> {
++                return text;
++            });
++        }, (text) -> {
++            final @Nullable String string = tryCollapseToString(text);
++            return string != null ? Either.left(Either.left(string)) : Either.right(text);
++        });
++    }
++
++    private static @Nullable String tryCollapseToString(final Component component) {
++        if (component instanceof final TextComponent textComponent) {
++            if (component.children().isEmpty() && component.style().isEmpty()) {
++                return textComponent.content();
++            }
++        }
++        return null;
++    }
++
++    private static Component createFromList(final List<Component> components) {
++        Component component = components.get(0);
++        for (int i = 1; i < components.size(); i++) {
++            component = component.append(components.get(i));
++        }
++        return component;
++    }
++
++    private AdventureCodecs() {
++    }
++}
+diff --git a/src/main/java/io/papermc/paper/adventure/PaperAdventure.java b/src/main/java/io/papermc/paper/adventure/PaperAdventure.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/io/papermc/paper/adventure/PaperAdventure.java
++++ b/src/main/java/io/papermc/paper/adventure/PaperAdventure.java
+@@ -0,0 +0,0 @@ public final class PaperAdventure {
+ 
+     public static final ANSIComponentSerializer ANSI_SERIALIZER = ANSIComponentSerializer.builder().flattener(FLATTENER).build();
+ 
+-    private static final Codec<CompoundTag, String, IOException, IOException> NBT_CODEC = new Codec<CompoundTag, String, IOException, IOException>() {
++    public static final Codec<CompoundTag, String, IOException, IOException> NBT_CODEC = new Codec<CompoundTag, String, IOException, IOException>() {
+         @Override
+         public @NotNull CompoundTag decode(final @NotNull String encoded) throws IOException {
+             try {