About minecraft-server-scriptapi
A skill for AI agents developing with Minecraft Server ScriptAPI, aimed at developers working on events, entities, components, and commands. It emphasizes checking APIs against Microsoft Learn and respecting execution and timing restrictions.
Key features
- 01Task-based lookup of official API docs
- 02Patterns for events, dimensions, and components
- 03Guidance for custom commands and script events
- 04Execution permission and early-run checks
- 05Type safety and performance precautions
Use cases
- 01Implement world event subscriptions
- 02Write custom command callbacks
- 03Schedule costly work across ticks
- 04Verify API execution restrictions
MCP support
The skill specifies a Microsoft Learn MCP endpoint, using microsoft_docs_search to find documentation and microsoft_docs_fetch to retrieve full signatures and details.
Install
Copy the command and run it in a terminal.
$
npx skills add https:/ / github.com/ kaaariyaaa/ scriptapiserverskill --skill minecraft-server-scriptapiAfter installing, type/minecraft-server-scriptapiin Claude Code to confirm it loaded.
Bundled filesSKILL.ja.md
Details
From the project READMEMinecraft Server ScriptAPI
Workflow
- Scope: Identify task (events, entities, components, commands, timing). Default to latest stable version unless beta/preview requested.
- Docs: Navigate from module index to specific class/interface/event. Use Microsoft Learn as source of truth. Search via MCP when details missing.
- Output: Quote exact API names. Provide minimal working example with required imports. Verify no official equivalent exists before creating custom helpers.
- For enums like
CustomCommandParamType, always check Microsoft Learn to confirm availability. @minecraft/vanilla-datais not on Microsoft Learn, so skip MCP searches for those enums.
- For enums like
Common patterns
Events
- Subscribe/unsubscribe on
world/systemevents. Guard logic for performance.
Dimensions
- Use
world.getDimension(MinecraftDimensionTypes.<Dimension>)and pick the appropriate dimension for the task.
Components
- Check existence before access.
- Use typed IDs:
EntityComponentTypes,BlockComponentTypes,ItemComponentTypes.
Scheduling
- Use
system.run,system.runTimeout,system.runInterval,system.runJob.
Identifiers
- MUST use
@minecraft/vanilla-dataenums:MinecraftBlockTypes,MinecraftEntityTypes,MinecraftItemTypes,MinecraftDimensionTypes,MinecraftEffectTypes,potionEffect,potionDelivery,feature,enchantment,cooldownCategory,cameraPresets,biome. - Custom IDs: Must include namespace prefix (e.g.,
example:cmd). One consistent prefix per addon.
Example:
import { world, system } from "@minecraft/server";
import { MinecraftDimensionTypes, MinecraftBlockTypes } from "@minecraft/vanilla-data";
system.runInterval(() => {
const dimensions = [MinecraftDimensionTypes.Overworld, MinecraftDimensionTypes.Nether];
const blocks = [MinecraftBlockTypes.Stone, MinecraftBlockTypes.Sand, MinecraftBlockTypes.GrassBlock];
for (const dimension of dimensions) {
for (const block of blocks) {
world.getDimension(dimension).setBlockType({ x: 0, y: 0, z: 0 }, block);
}
}
});
Permission modes
Read-only
- Before simulation/events/tick start. No world mutations.
- Fix: defer to
system.run/runTimeout/runJob.
Doc verification
- When using any method/property, always check Microsoft Learn to confirm whether it is read-only safe or early-execution safe.
- Look for explicit notes in the API reference (read-only / early-execution) and follow them strictly.
- For arrow-function callbacks only, also check for restricted-execution notes like:
- "This closure is called with restricted-execution privilege."
- "This function can't be called in restricted-execution mode."
- If a restricted-execution note applies, review the arrow-function body to ensure no read-only or early-execution violations; defer with
system.runif needed.
Example (read-only deferral):
world.beforeEvents.playerInteractWithBlock.subscribe((event) => {
const player = event.player;
system.run(() => {
player.runCommand("say ok");
});
});
Early-execution
- Before world loads. Many APIs unavailable.
- Fix: defer to
world.afterEvents.worldLoadorsystem.run. - Subscribe at root to avoid missing events.
Safe in early-execution:
- Event subscriptions (
world/systembeforeEvents/afterEvents) system.clearJob,clearRun,run,runInterval,runJob,runTimeout,waitTicksBlockComponentRegistry.registerCustomComponent,ItemComponentRegistry.registerCustomComponent
Custom commands
- Interface:
CustomCommand(name,description,permissionLevel,mandatoryParameters,optionalParameters). - Parameters:
CustomCommandParameter(name,type, optionalenumName). - Param types and arrow-function argument types:
String->StringPlayerSelector->PlayerLocation->Vector3ItemType->ItemTypeInteger->NumberFloat->NumberEnum->StringEntityType->EntityTypeEntitySelector->EntityBoolean->BoolBlockType->BlockType
- Register enums:
CustomCommandRegistry.registerEnum(name, values). - Register cmd:
CustomCommandRegistry.registerCommand(customCommand, callback). - Callback:
(origin, ...args) => CustomCommandResult. - Custom command callbacks run with restricted-execution privileges, so do not call read-only-restricted methods directly; defer with
system.runif needed. - When using an arrow function, align parameter names and order with the
CustomCommandParameter.namelist; avoid mismatched names or genericargswhen parameters are defined.
Example:
import {
system,
StartupEvent,
CommandPermissionLevel,
CustomCommandParamType,
CustomCommandStatus,
} from "@minecraft/server";
system.beforeEvents.startup.subscribe((init: StartupEvent) => {
init.customCommandRegistry.registerEnum("example:mode", ["on", "off"]);
init.customCommandRegistry.registerCommand(
{
name: "example:demo",
description: "Command demo",
permissionLevel: CommandPermissionLevel.GameDirectors,
cheatsRequired: true,
mandatoryParameters: [
{ type: CustomCommandParamType.String, name: "msg", },
{ type: CustomCommandParamType.Enum, name: "example:mode"},
],
optionalParameters: [
{ type: CustomCommandParamType.Boolean, name: "silent" },
{ type: CustomCommandParamType.Integer, name: "count" },
],
},
(origin, msg, mode, silent, count) => {
const msgValue = String(msg ?? "ok");
const modeValue = String(mode ?? "off");
const silentValue = Boolean(silent ?? false);
const countValue = Number(count ?? 1);
return {
status: CustomCommandStatus.Success,
message: silentValue
? undefined
: `[${origin.sourceType}] ${msgValue} mode=${modeValue} count=${countValue}`,
};
}
);
});
Script events
- Send:
system.sendScriptEvent(id, message)(namespaced ID, payload string). - Receive:
system.afterEvents.scriptEventReceive.subscribe(callback, options?). - Event:
ScriptEventCommandMessageAfterEvent(id,message,sourceType, optionalsourceEntity/sourceBlock/initiator). - Filter:
ScriptEventMessageFilterOptions.namespaces.
Example:
import { system, world, ScriptEventSource } from "@minecraft/server";
system.afterEvents.scriptEventReceive.subscribe((event) => {
const { id, message, sourceType, initiator, sourceEntity, sourceBlock } = event;
if (id !== "example:say") return;
switch (sourceType) {
case ScriptEventSource.Block:
world.sendMessage(`sendBy:${sourceBlock?.typeId ?? "unknown"} ${message}`);
break;
case ScriptEventSource.Entity:
world.sendMessage(`sendBy:${sourceEntity?.typeId ?? "unknown"} ${message}`);
break;
case ScriptEventSource.NPCDialogue:
world.sendMessage(`sendBy:${initiator?.typeId ?? "unknown"} ${message}`);
break;
case ScriptEventSource.Server:
world.sendMessage(`sendBy:server ${message}`);
break;
}
});
Performance
- Expensive work in events: use
system.runJob(generator)to spread across ticks. - Short-circuit before iterating large sets.
system.run variants
run: next tick.runTimeout(cb, ticks): delay N ticks.0can cause tight loops if misused.runInterval(cb, ticks): repeat every N ticks untilclearRun.runJob(generator): long-running work. Keep iterations small.
Type safety
- Use
typeIdchecks and verify component existence. instanceofonly for documented@minecraft/serverclasses.
Minimal templates
Event subscription:
world.afterEvents.playerJoin.subscribe((event) => {
const player = event.player;
});
Tick loop:
system.runInterval(() => {
// tick logic
}, 1);
Get dimension:
const overworld = world.getDimension(MinecraftDimensionTypes.Overworld);
Spawn entity:
const overworld = world.getDimension(MinecraftDimensionTypes.Overworld);
overworld.spawnEntity(MinecraftEntityTypes.Zombie, { x: 0, y: 80, z: 0 });
Give item:
const item = new ItemStack(MinecraftItemTypes.Diamond, 1);
player.getComponent("inventory")?.container?.addItem(item);
Apply effect:
player.addEffect(MinecraftEffectTypes.Speed, 200, { amplifier: 1 });
Get component (typed):
const health = entity.getComponent(EntityComponentTypes.Health);
Pitfalls
- Null components: Check existence before access.
- Heavy events: Use
system.runJob. - Permissions: Avoid read-only/early-execution violations.
- Raw IDs: Use typed enums.
- Namespaces: One consistent prefix per addon.
MCP tools
- Endpoint: https://learn.microsoft.com/api/mcp
- Search:
microsoft_docs_search(module + class + task). - Fetch:
microsoft_docs_fetch(full signatures/details).
Comments
Markdown supported