Commands
Declare the command tree the client uses for tab completion and syntax highlighting, without hand-numbering nodes.
The Commands packet tells the client which commands exist and how each argument parses. It is a flat array of nodes that reference each other by index, with flags packed into a byte and a parser ID plus parser-specific properties per argument. helpers.createCommandTree takes a nested description and produces that array.
import { helpers } from "mc-protocol";
const { literal, argument, createCommandTree } = helpers;
await client.sendPacket("Commands", createCommandTree([
literal("ping", { executable: true }),
literal("tp", {
children: [argument("target", "entity", { single: true, playersOnly: true, executable: true })],
}),
literal("say", {
children: [argument("message", "string", { type: "greedy", executable: true, suggestions: "ask_server" })],
}),
literal("run", { redirect: "root" }),
]));
This declares the syntax. Executing commands is still yours: listen for the serverbound ChatCommand packet and parse data.command.
createCommandTree(commands)
Takes the root’s children and returns { nodes, rootIndex: 0 }, ready for sendPacket("Commands", …). Nodes are numbered breadth-first from the root, so the order of your array is the order of the root’s children. A node object that appears under several parents is written once and shared.
Errors are thrown before anything is encoded when:
- a
redirectpoints at a node that is not reachable from the root (RangeError) - two children of the same node share a name (
RangeError) - children form a cycle; loops such as
/execute run …must useredirect(RangeError) - an element is not a node from
literal()orargument()(TypeError)
literal(name, options?)
A fixed keyword. Options:
| Option | Type | Wire effect |
|---|---|---|
children |
CommandNode[] |
child indices |
executable |
boolean |
flag 0x04; the path up to this node is a complete command |
restricted |
boolean |
flag 0x20; requires permission level above 0 |
redirect |
CommandNode | "root" |
flag 0x08 and the target’s index |
argument(name, parser, options?)
A typed argument. parser is any 26.2 parser name, with or without its prefix: "integer" and "brigadier:integer" are the same, as are "entity" and "minecraft:entity". Unknown names throw; the parser’s numeric ID is resolved by the wire codec. Argument nodes take the literal options plus suggestions, a suggestion provider: "ask_server", "all_recipes", "available_sounds", "summonable_entities", or a namespaced identifier.
Parsers with properties take them in the same options object. TypeScript narrows the accepted keys by parser name.
| Parser | Properties | Notes |
|---|---|---|
float, double, integer |
min?, max? |
Only the bounds you pass are written. integer bounds must be 32-bit integers. |
long |
min?, max? |
number or bigint, 64-bit signed. |
string |
type? |
"word" (default), "phrase" (quotable), or "greedy" (rest of the line). |
entity |
single?, playersOnly? |
Both default to false. |
score_holder |
multiple? |
Defaults to false. |
time |
min? |
Minimum ticks, defaults to 0. |
resource, resource_key, resource_or_tag, resource_or_tag_key, resource_selector |
registry |
Required. "entity_type" becomes "minecraft:entity_type". |
Every other parser (block_pos, vec3, game_profile, uuid, component, …) takes no properties. Any key a parser does not take throws, so a typo like mni is caught. The full list is helpers.COMMAND_PARSERS. The wiki’s minecraft:nbt_compound_tag is spelled minecraft:nbt in the bundled codec; nbt_compound_tag, nbt and their prefixed forms are all accepted and encode as parser 21.
argument("amount", "integer", { min: 1, max: 64, executable: true });
argument("delay", "time", { min: 20 });
argument("biome", "resource_or_tag", { registry: "worldgen/biome", executable: true });
argument("mode", "gamemode", { executable: true });
Sharing and redirects
Nodes are plain objects. Keep a reference to share a subtree or to point a redirect at it:
const amount = argument("amount", "integer", { min: 1, executable: true });
const give = literal("give", { children: [amount] });
const take = literal("take", { children: [amount] });
const again = literal("again", { redirect: give }); // continue parsing at /give
createCommandTree([give, take, again]); // 5 nodes: root, give, take, again, amount
redirect: "root" is how vanilla builds /execute run <command>: after run, parsing restarts at the root.
What you get back
createCommandTree([literal("ping", { executable: true })]);
// {
// nodes: [
// { flags: { unused: 0, allows_restricted: 0, has_custom_suggestions: 0, has_redirect_node: 0, has_command: 0, command_node_type: 0 }, children: [1] },
// { flags: { …, has_command: 1, command_node_type: 1 }, children: [], extraNodeData: { name: "ping" } },
// ],
// rootIndex: 0,
// }
The flags object uses the codec’s bit-field names. Argument nodes carry extraNodeData: { name, parser, properties, suggestionType }. Decoded packets return enums as { value, translation }, so a node read from the wire has parser: { value: 6, translation: "minecraft:entity" } rather than the bare string.
Types
All exported through helpers: CommandNode, CommandNodeWire, CommandsPacket, CommandNodeOptions, ArgumentNodeOptions, CommandParser, CommandParserName, ParserProperties<P>, CommandSuggestions.