@mairwunnx/minecraft-mcp 0.0.0-stage → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,219 @@
1
+ import { z } from "zod";
2
+ import { unwrap } from "../client.js";
3
+ import { tool } from "../tool.js";
4
+ import { cursor, format, limit, location, world } from "./common.js";
5
+ export function entityTools(ctx) {
6
+ const { api, token } = ctx;
7
+ const one = (args) => ({ params: { path: { world: args.world, uuid: args.uuid } } });
8
+ tool(ctx, {
9
+ name: "list_entities",
10
+ untrusted: true,
11
+ title: "Entities",
12
+ description: "Loaded entities in a world, filtered by type and by a radius around x, y, z. Only loaded chunks count: entities far from players aren't listed.",
13
+ scope: "read",
14
+ effect: "read",
15
+ input: {
16
+ world,
17
+ types: z.array(z.string()).optional().describe('Entity keys, such as ["minecraft:zombie"]'),
18
+ x: z.number().optional(),
19
+ y: z.number().optional(),
20
+ z: z.number().optional(),
21
+ radius: z.number().positive().optional().describe("Blocks around x, y, z; give all three with it"),
22
+ limit,
23
+ cursor,
24
+ },
25
+ run: (args) => unwrap(api.GET("/worlds/{world}/entities", {
26
+ params: {
27
+ path: { world: args.world },
28
+ query: {
29
+ type: args.types?.join(","),
30
+ x: args.x,
31
+ y: args.y,
32
+ z: args.z,
33
+ radius: args.radius,
34
+ limit: args.limit,
35
+ cursor: args.cursor,
36
+ },
37
+ },
38
+ }), token),
39
+ });
40
+ tool(ctx, {
41
+ name: "get_entity",
42
+ untrusted: true,
43
+ title: "Entity",
44
+ description: "One entity in detail: position, health, equipment, effects, owner, tags.",
45
+ scope: "read",
46
+ effect: "read",
47
+ input: { world, uuid: z.string() },
48
+ run: (args) => unwrap(api.GET("/worlds/{world}/entities/{uuid}", one(args)), token),
49
+ });
50
+ tool(ctx, {
51
+ name: "spawn_entity",
52
+ title: "Spawn entities",
53
+ description: "Spawn one or more entities of a type, such as `minecraft:cow`, at a location. The server caps the count (50 by default).",
54
+ scope: "write:world",
55
+ effect: "write",
56
+ input: {
57
+ world,
58
+ type: z.string().min(1),
59
+ location,
60
+ count: z.number().int().min(1).default(1),
61
+ customName: z.string().optional(),
62
+ format,
63
+ ai: z.boolean().optional(),
64
+ persistent: z.boolean().optional().describe("Don't despawn"),
65
+ glowing: z.boolean().optional(),
66
+ },
67
+ run: ({ world: name, ...body }) => unwrap(api.POST("/worlds/{world}/entities", { params: { path: { world: name } }, body }), token),
68
+ });
69
+ tool(ctx, {
70
+ name: "update_entity",
71
+ title: "Change an entity",
72
+ description: "Rename, toggle AI, glowing, gravity, invulnerability, set health or fire, teleport, push, or tag an entity. Not for players.",
73
+ scope: "write:world",
74
+ effect: "write",
75
+ input: {
76
+ world,
77
+ uuid: z.string(),
78
+ customName: z.string().optional(),
79
+ format,
80
+ customNameVisible: z.boolean().optional(),
81
+ glowing: z.boolean().optional(),
82
+ silent: z.boolean().optional(),
83
+ invulnerable: z.boolean().optional(),
84
+ gravity: z.boolean().optional(),
85
+ persistent: z.boolean().optional(),
86
+ ai: z.boolean().optional(),
87
+ health: z.number().min(0).optional(),
88
+ fireTicks: z.number().int().optional(),
89
+ location: location.optional().describe("Teleport here"),
90
+ velocity: z.object({ x: z.number(), y: z.number(), z: z.number() }).optional(),
91
+ addTags: z.array(z.string()).optional(),
92
+ removeTags: z.array(z.string()).optional(),
93
+ },
94
+ run: ({ world: name, uuid, ...body }) => unwrap(api.PATCH("/worlds/{world}/entities/{uuid}", {
95
+ params: { path: { world: name, uuid } },
96
+ body: { ...body, addTags: body.addTags ?? [], removeTags: body.removeTags ?? [] },
97
+ }), token),
98
+ });
99
+ tool(ctx, {
100
+ name: "remove_entity",
101
+ title: "Remove entities",
102
+ description: "Remove entities without drops: one by uuid, or many by types, such as dropped items, arrows, or a mob farm's overflow, optionally within a radius of x, y, z. Many at once never touches players, skips named mobs and tamed pets unless includeNamed is true, takes the nearest first up to max, and dryRun only counts. Try dryRun first and tell the user what would go.",
103
+ scope: "write:world",
104
+ effect: "destructive",
105
+ input: {
106
+ world,
107
+ uuid: z.string().optional().describe("One entity"),
108
+ types: z
109
+ .array(z.string().min(1))
110
+ .optional()
111
+ .describe('Entity keys, such as ["minecraft:item", "minecraft:arrow"]'),
112
+ x: z.number().optional(),
113
+ y: z.number().optional(),
114
+ z: z.number().optional(),
115
+ radius: z.number().positive().optional().describe("Blocks around x, y, z"),
116
+ max: z
117
+ .number()
118
+ .int()
119
+ .min(1)
120
+ .optional()
121
+ .describe("Remove at most this many; the server caps it (1000 by default)"),
122
+ includeNamed: z.boolean().optional().describe("Also remove named mobs and tamed pets"),
123
+ dryRun: z.boolean().optional().describe("Only count what would be removed"),
124
+ },
125
+ run: (args) => {
126
+ if (args.uuid)
127
+ return unwrap(api.DELETE("/worlds/{world}/entities/{uuid}", one({ world: args.world, uuid: args.uuid })), token);
128
+ if (!args.types?.length)
129
+ throw new Error("Give uuid, or types for many at once");
130
+ return unwrap(api.DELETE("/worlds/{world}/entities", {
131
+ params: {
132
+ path: { world: args.world },
133
+ query: {
134
+ type: args.types.join(","),
135
+ x: args.x,
136
+ y: args.y,
137
+ z: args.z,
138
+ radius: args.radius,
139
+ max: args.max,
140
+ includeNamed: args.includeNamed,
141
+ dryRun: args.dryRun,
142
+ },
143
+ },
144
+ }), token);
145
+ },
146
+ });
147
+ tool(ctx, {
148
+ name: "world_effect",
149
+ title: "Lightning, explosions, sounds, particles",
150
+ description: "Make something happen at a location. `lightning` strikes (effectOnly makes it harmless). `explosion` blows up with power (TNT is 4); breakBlocks destroys terrain and fire sets things alight. `sound` plays a sound such as `minecraft:entity.ender_dragon.growl` for everyone nearby. `particles` shows particles such as `minecraft:heart` or `minecraft:flame`; ones that need extra data (dust, block) aren't supported.",
151
+ scope: "write:world",
152
+ effect: "destructive",
153
+ input: {
154
+ world,
155
+ kind: z.enum(["lightning", "explosion", "sound", "particles"]),
156
+ location,
157
+ effectOnly: z.boolean().optional().describe("lightning: only the flash and the sound"),
158
+ power: z.number().min(0).optional().describe("explosion: 4 by default"),
159
+ fire: z.boolean().optional().describe("explosion"),
160
+ breakBlocks: z.boolean().optional().describe("explosion"),
161
+ sound: z.string().optional().describe("sound: the sound key"),
162
+ volume: z.number().min(0).max(16).optional().describe("sound"),
163
+ pitch: z.number().min(0.5).max(2).optional().describe("sound"),
164
+ category: z.string().optional().describe("sound: `master` by default"),
165
+ particle: z.string().optional().describe("particles: the particle key"),
166
+ count: z.number().int().min(1).optional().describe("particles: 10 by default"),
167
+ offset: z.object({ x: z.number(), y: z.number(), z: z.number() }).optional().describe("particles: spread"),
168
+ speed: z.number().min(0).optional().describe("particles"),
169
+ },
170
+ run: (args) => {
171
+ const params = { params: { path: { world: args.world } } };
172
+ const location = args.location;
173
+ switch (args.kind) {
174
+ case "lightning":
175
+ return unwrap(api.POST("/worlds/{world}/lightning", {
176
+ ...params,
177
+ body: { location, effectOnly: args.effectOnly ?? false },
178
+ }), token);
179
+ case "explosion":
180
+ return unwrap(api.POST("/worlds/{world}/explosions", {
181
+ ...params,
182
+ body: {
183
+ location,
184
+ power: args.power ?? 4,
185
+ fire: args.fire ?? false,
186
+ breakBlocks: args.breakBlocks ?? false,
187
+ },
188
+ }), token);
189
+ case "sound":
190
+ if (!args.sound)
191
+ throw new Error("kind sound needs sound");
192
+ return unwrap(api.POST("/worlds/{world}/sounds", {
193
+ ...params,
194
+ body: {
195
+ sound: args.sound,
196
+ location,
197
+ volume: args.volume ?? 1,
198
+ pitch: args.pitch ?? 1,
199
+ category: args.category ?? "master",
200
+ },
201
+ }), token);
202
+ case "particles":
203
+ if (!args.particle)
204
+ throw new Error("kind particles needs particle");
205
+ return unwrap(api.POST("/worlds/{world}/particles", {
206
+ ...params,
207
+ body: {
208
+ particle: args.particle,
209
+ location,
210
+ count: args.count ?? 10,
211
+ offset: args.offset,
212
+ speed: args.speed ?? 0,
213
+ },
214
+ }), token);
215
+ }
216
+ },
217
+ });
218
+ }
219
+ //# sourceMappingURL=entities.js.map
@@ -0,0 +1,272 @@
1
+ import { z } from "zod";
2
+ import { unwrap } from "../client.js";
3
+ import { tool } from "../tool.js";
4
+ import { player as anyPlayer, cursor, format, item, limit, location, OFFLINE_NOTE, playerOrMe, SLOT_LAYOUT, } from "./common.js";
5
+ const container = z.enum(["inventory", "enderchest"]).default("inventory");
6
+ export function playerTools(ctx) {
7
+ const { api, token } = ctx;
8
+ const who = (name) => ({ params: { path: { player: name } } });
9
+ const player = playerOrMe(ctx.player);
10
+ tool(ctx, {
11
+ name: "list_players",
12
+ title: "Players",
13
+ description: "Everyone who has played here: online state, first and last seen, playtime in seconds.",
14
+ scope: "read",
15
+ effect: "read",
16
+ input: {
17
+ online: z.boolean().optional(),
18
+ sort: z.enum(["name", "last_seen", "playtime"]).optional(),
19
+ limit,
20
+ cursor,
21
+ },
22
+ run: (args) => unwrap(api.GET("/players", { params: { query: args } }), token),
23
+ });
24
+ tool(ctx, {
25
+ name: "list_online_players",
26
+ untrusted: true,
27
+ title: "Online players",
28
+ description: "Who is playing now, with world, game mode, health, food, level, ping, idle time, and location.",
29
+ scope: "read",
30
+ effect: "read",
31
+ input: {},
32
+ run: () => unwrap(api.GET("/players/online"), token),
33
+ });
34
+ tool(ctx, {
35
+ name: "get_player",
36
+ untrusted: true,
37
+ title: "Player",
38
+ description: "A player's profile and, while online, full state: game mode, health, food, XP, location, flight, speed, ping, client. Offline players show their last location.",
39
+ scope: "read",
40
+ effect: "read",
41
+ input: { player },
42
+ run: (args) => unwrap(api.GET("/players/{player}", who(args.player)), token),
43
+ });
44
+ tool(ctx, {
45
+ name: "get_player_inventory",
46
+ untrusted: true,
47
+ title: "Inventory or ender chest",
48
+ description: `Main inventory, armor, and offhand with full item data, or the ender chest. ${SLOT_LAYOUT} ${OFFLINE_NOTE}`,
49
+ scope: "read",
50
+ effect: "read",
51
+ input: { player, container },
52
+ run: (args) => args.container === "enderchest"
53
+ ? unwrap(api.GET("/players/{player}/enderchest", who(args.player)), token)
54
+ : unwrap(api.GET("/players/{player}/inventory", who(args.player)), token),
55
+ });
56
+ tool(ctx, {
57
+ name: "get_player_effects",
58
+ title: "Effects",
59
+ description: `Active potion effects. ${OFFLINE_NOTE}`,
60
+ scope: "read",
61
+ effect: "read",
62
+ input: { player },
63
+ run: (args) => unwrap(api.GET("/players/{player}/effects", who(args.player)), token),
64
+ });
65
+ tool(ctx, {
66
+ name: "get_player_statistics",
67
+ title: "Statistics",
68
+ description: "Vanilla statistics by category (custom, mined, broken, crafted, used, picked_up, dropped, killed, killed_by); live while online. play_time is in ticks (20 per second); distances in centimeters.",
69
+ scope: "read",
70
+ effect: "read",
71
+ input: { player, category: z.string().optional().describe("Only one category, such as `mined`") },
72
+ run: (args) => unwrap(api.GET("/players/{player}/statistics", {
73
+ params: { path: { player: args.player }, query: { category: args.category } },
74
+ }), token),
75
+ });
76
+ tool(ctx, {
77
+ name: "get_player_advancements",
78
+ title: "Advancements",
79
+ description: "Every advancement on the server with its title, description, frame, and parent, done or not, and when it was finished. done false lists what's left: with the parents, it shows what to go for next.",
80
+ scope: "read",
81
+ effect: "read",
82
+ input: { player, done: z.boolean().optional().describe("Only finished (true) or unfinished (false)") },
83
+ run: (args) => unwrap(api.GET("/players/{player}/advancements", {
84
+ params: { path: { player: args.player }, query: { done: args.done } },
85
+ }), token),
86
+ });
87
+ tool(ctx, {
88
+ name: "get_player_sessions",
89
+ title: "Play sessions",
90
+ description: "When the player joined and left, newest first, as WorldAPI recorded them.",
91
+ scope: "read",
92
+ effect: "read",
93
+ input: { player, limit, cursor },
94
+ run: (args) => unwrap(api.GET("/players/{player}/sessions", {
95
+ params: { path: { player: args.player }, query: { limit: args.limit, cursor: args.cursor } },
96
+ }), token),
97
+ });
98
+ tool(ctx, {
99
+ name: "check_permission",
100
+ title: "Permissions",
101
+ description: "Whether a player is an operator and, if online, whether they have a permission node.",
102
+ scope: "read",
103
+ effect: "read",
104
+ input: { player, node: z.string().optional().describe("Such as `worldedit.wand`") },
105
+ run: (args) => unwrap(api.GET("/players/{player}/permissions", {
106
+ params: { path: { player: args.player }, query: { node: args.node } },
107
+ }), token),
108
+ });
109
+ tool(ctx, {
110
+ name: "teleport_player",
111
+ title: "Teleport a player",
112
+ description: `Teleport to coordinates, to another player, or to a world's spawn; give exactly one. Check get_highest_block first to land on solid ground. ${OFFLINE_NOTE}`,
113
+ scope: "write:players",
114
+ effect: "write",
115
+ input: {
116
+ player,
117
+ location: location.optional(),
118
+ toPlayer: z.string().optional().describe("Another player's name or UUID"),
119
+ worldSpawn: z.string().optional().describe("A world name"),
120
+ },
121
+ run: (args) => unwrap(api.POST("/players/{player}/teleport", {
122
+ ...who(args.player),
123
+ body: { location: args.location, player: args.toPlayer, worldSpawn: args.worldSpawn },
124
+ }), token),
125
+ });
126
+ tool(ctx, {
127
+ name: "set_player_property",
128
+ title: "Change a player",
129
+ description: `Set game mode, health, food (0–20), saturation, XP level or progress, flight, walk or fly speed (-1 to 1; defaults 0.2 and 0.1), display or tab-list name, invulnerability, fire, or air. ${OFFLINE_NOTE}`,
130
+ scope: "write:players",
131
+ effect: "write",
132
+ input: {
133
+ player,
134
+ gameMode: z.enum(["survival", "creative", "adventure", "spectator"]).optional(),
135
+ health: z.number().min(0).optional(),
136
+ food: z.number().int().min(0).max(20).optional(),
137
+ saturation: z.number().min(0).max(20).optional(),
138
+ exhaustion: z.number().min(0).optional(),
139
+ level: z.number().int().min(0).optional(),
140
+ exp: z.number().min(0).max(1).optional().describe("Progress to the next level"),
141
+ allowFlight: z.boolean().optional(),
142
+ flying: z.boolean().optional(),
143
+ walkSpeed: z.number().min(-1).max(1).optional(),
144
+ flySpeed: z.number().min(-1).max(1).optional(),
145
+ displayName: z.string().optional(),
146
+ listName: z.string().optional(),
147
+ format,
148
+ invulnerable: z.boolean().optional(),
149
+ fireTicks: z.number().int().optional(),
150
+ remainingAir: z.number().int().optional(),
151
+ },
152
+ run: ({ player: name, ...body }) => unwrap(api.PATCH("/players/{player}", { ...who(name), body }), token),
153
+ });
154
+ tool(ctx, {
155
+ name: "send_message",
156
+ title: "Message a player",
157
+ description: `Send one player a chat message, title, or action bar text, optionally with a sound. ${OFFLINE_NOTE}`,
158
+ scope: "write:players",
159
+ effect: "write",
160
+ input: {
161
+ player,
162
+ message: z.string().min(1),
163
+ kind: z.enum(["chat", "title", "actionbar"]).default("chat"),
164
+ subtitle: z.string().optional(),
165
+ sound: z.string().optional().describe("Such as `minecraft:entity.experience_orb.pickup`"),
166
+ format,
167
+ },
168
+ run: ({ player: name, ...body }) => unwrap(api.POST("/players/{player}/message", { ...who(name), body }), token),
169
+ });
170
+ tool(ctx, {
171
+ name: "kick_player",
172
+ title: "Kick a player",
173
+ description: "Disconnect a player with an optional reason.",
174
+ scope: "admin",
175
+ effect: "destructive",
176
+ input: { player: anyPlayer, reason: z.string().optional(), format },
177
+ run: ({ player: name, ...body }) => unwrap(api.POST("/players/{player}/kick", { ...who(name), body }), token),
178
+ });
179
+ tool(ctx, {
180
+ name: "give_item",
181
+ title: "Give an item",
182
+ description: `Add an item to a player's inventory (what doesn't fit drops at their feet) or ender chest. ${OFFLINE_NOTE}`,
183
+ scope: "write:players",
184
+ effect: "write",
185
+ input: { player, item, container },
186
+ run: (args) => unwrap(args.container === "enderchest"
187
+ ? api.POST("/players/{player}/enderchest/give", { ...who(args.player), body: { item: args.item } })
188
+ : api.POST("/players/{player}/inventory/give", { ...who(args.player), body: { item: args.item } }), token),
189
+ });
190
+ tool(ctx, {
191
+ name: "edit_inventory",
192
+ title: "Edit an inventory",
193
+ description: `Change a player's inventory or ender chest: \`set\` puts an item in a slot, replacing what was there; \`clear_slot\` empties one slot; \`clear\` empties everything, armor and offhand included. Cleared items are gone. To add items without picking a slot, use give_item. ${SLOT_LAYOUT} ${OFFLINE_NOTE}`,
194
+ scope: "write:players",
195
+ effect: "destructive",
196
+ input: {
197
+ player,
198
+ action: z.enum(["set", "clear_slot", "clear"]),
199
+ slot: z.number().int().min(0).optional().describe("For set and clear_slot"),
200
+ item: item.optional().describe("For set"),
201
+ container,
202
+ },
203
+ run: (args) => {
204
+ const ender = args.container === "enderchest";
205
+ if (args.action === "clear")
206
+ return unwrap(ender
207
+ ? api.DELETE("/players/{player}/enderchest", who(args.player))
208
+ : api.DELETE("/players/{player}/inventory", who(args.player)), token);
209
+ if (args.slot === undefined)
210
+ throw new Error(`${args.action} needs slot`);
211
+ const params = { params: { path: { player: args.player, slot: args.slot } } };
212
+ if (args.action === "clear_slot")
213
+ return unwrap(ender
214
+ ? api.DELETE("/players/{player}/enderchest/slots/{slot}", params)
215
+ : api.DELETE("/players/{player}/inventory/slots/{slot}", params), token);
216
+ if (!args.item)
217
+ throw new Error("set needs item");
218
+ return unwrap(ender
219
+ ? api.PATCH("/players/{player}/enderchest/slots/{slot}", { ...params, body: args.item })
220
+ : api.PATCH("/players/{player}/inventory/slots/{slot}", { ...params, body: args.item }), token);
221
+ },
222
+ });
223
+ const effect = z.object({
224
+ type: z.string().min(1).describe("Such as `minecraft:speed`"),
225
+ durationSeconds: z.number().int().positive().optional().describe("Leave out for an infinite effect"),
226
+ amplifier: z.number().int().min(0).max(255).default(0).describe("0 is level I"),
227
+ ambient: z.boolean().default(false),
228
+ particles: z.boolean().default(true),
229
+ icon: z.boolean().default(true),
230
+ });
231
+ tool(ctx, {
232
+ name: "set_effects",
233
+ title: "Change potion effects",
234
+ description: `Give, take away, or clear a player's potion effects, in that order: clear first, then remove, then add. Answers with the effects the player has afterwards. ${OFFLINE_NOTE}`,
235
+ scope: "write:players",
236
+ effect: "write",
237
+ input: {
238
+ player,
239
+ add: z.array(effect).optional(),
240
+ remove: z.array(z.string().min(1)).optional().describe("Effect keys to take away"),
241
+ clear: z.boolean().optional().describe("Take away every effect first"),
242
+ },
243
+ run: async (args) => {
244
+ if (args.clear)
245
+ await unwrap(api.DELETE("/players/{player}/effects", who(args.player)), token);
246
+ for (const type of args.remove ?? [])
247
+ await unwrap(api.DELETE("/players/{player}/effects/{effect}", { params: { path: { player: args.player, effect: type } } }), token);
248
+ for (const body of args.add ?? [])
249
+ await unwrap(api.POST("/players/{player}/effects", { ...who(args.player), body }), token);
250
+ return unwrap(api.GET("/players/{player}/effects", who(args.player)), token);
251
+ },
252
+ });
253
+ tool(ctx, {
254
+ name: "set_advancement",
255
+ title: "Grant or revoke an advancement",
256
+ description: `Complete every criterion of an advancement for a player (done true), or take them all back (done false). ${OFFLINE_NOTE}`,
257
+ scope: "write:players",
258
+ effect: "idempotent",
259
+ input: {
260
+ player,
261
+ key: z.string().min(1).describe("Advancement key, such as `minecraft:story/mine_diamond`"),
262
+ done: z.boolean(),
263
+ },
264
+ run: (args) => {
265
+ const params = { params: { path: { player: args.player, key: args.key } } };
266
+ return unwrap(args.done
267
+ ? api.POST("/players/{player}/advancements/{key}", { ...params, body: {} })
268
+ : api.DELETE("/players/{player}/advancements/{key}", params), token);
269
+ },
270
+ });
271
+ }
272
+ //# sourceMappingURL=players.js.map
@@ -0,0 +1,81 @@
1
+ import { z } from "zod";
2
+ import { unwrap } from "../client.js";
3
+ import { tool } from "../tool.js";
4
+ const API = [
5
+ "Scripts are Java, compiled and run inside the Minecraft server. This is arbitrary code: it needs the token's `code` scope and scripts.enabled in WorldAPI's config.",
6
+ "Only Java and the Paper/Bukkit API are visible (plus Adventure, Guava, Brigadier, JOML); process, file, reflection, and class-loading calls are blocked at compile time.",
7
+ "Interface: `com.mairwunnx.worldapi.script.api.WorldApiScript` with `Object run(ScriptContext ctx)`, `void enable(ScriptContext ctx)`, `void disable()`.",
8
+ "ScriptContext: `server()`, `log(String)`, `on(EventClass.class, e -> …)`, `on(type, priority, ignoreCancelled, handler)`, `later(ticks, task)`, `every(delay, period, task)`, `async(task)`, `sync(callable)`, `memory()`.",
9
+ ].join(" ");
10
+ export function scriptTools(ctx) {
11
+ const { api, token } = ctx;
12
+ tool(ctx, {
13
+ name: "execute_java",
14
+ untrusted: true,
15
+ title: "Run Java once",
16
+ description: `${API} Send either a full public class implementing WorldApiScript, or just the body of run: \`ctx\` and \`server\` are in scope, and org.bukkit.*, org.bukkit.entity.*, org.bukkit.block.*, org.bukkit.inventory.*, java.util.*, and java.util.stream.* are imported. End with \`return <value>;\`: maps, lists, and primitives come back as JSON. Example: \`return server.getOnlinePlayers().stream().map(Player::getName).toList();\`. thread \`main\` (default) may touch the world but blocks the tick while it runs; \`async\` runs beside the server and must use ctx.sync(...) for world access. The script unloads as soon as it returns, with anything it registered.`,
17
+ scope: "code",
18
+ effect: "destructive",
19
+ input: { source: z.string().min(1), thread: z.enum(["main", "async"]).default("main") },
20
+ run: (args) => unwrap(api.POST("/scripts/run", { body: args }), token),
21
+ });
22
+ tool(ctx, {
23
+ name: "load_java_script",
24
+ title: "Load a Java script",
25
+ description: `${API} Loads a resident script: a full public class implementing WorldApiScript whose enable(ctx) runs once on the server thread. Register listeners with ctx.on(...) and tasks with ctx.later/every/async — only those are removed on unload, so never register through Bukkit directly. persistent keeps it across restarts; replace swaps a script with the same id.`,
26
+ scope: "code",
27
+ effect: "destructive",
28
+ input: {
29
+ id: z
30
+ .string()
31
+ .regex(/^[a-z0-9_-]{1,40}$/)
32
+ .describe("Lowercase letters, digits, dashes, underscores"),
33
+ source: z.string().min(1),
34
+ persistent: z.boolean().default(false),
35
+ replace: z.boolean().default(false),
36
+ },
37
+ run: (args) => unwrap(api.POST("/scripts", { body: args }), token),
38
+ });
39
+ tool(ctx, {
40
+ name: "list_java_scripts",
41
+ title: "Loaded scripts",
42
+ description: "Resident scripts with their listener and task counts and error count.",
43
+ scope: "code",
44
+ effect: "read",
45
+ input: {},
46
+ run: () => unwrap(api.GET("/scripts"), token),
47
+ });
48
+ tool(ctx, {
49
+ name: "get_java_script",
50
+ untrusted: true,
51
+ title: "Script and its log",
52
+ description: "One resident script with its source, plus the latest lines it wrote with ctx.log and errors from its handlers and tasks. logs false leaves the log out.",
53
+ scope: "code",
54
+ effect: "read",
55
+ input: {
56
+ id: z.string(),
57
+ logs: z.boolean().default(true),
58
+ limit: z.number().int().min(1).max(10000).optional().describe("Log lines to return"),
59
+ },
60
+ run: async (args) => {
61
+ const path = { id: args.id };
62
+ const [script, logs] = await Promise.all([
63
+ unwrap(api.GET("/scripts/{id}", { params: { path } }), token),
64
+ args.logs
65
+ ? unwrap(api.GET("/scripts/{id}/logs", { params: { path, query: { limit: args.limit } } }), token)
66
+ : undefined,
67
+ ]);
68
+ return logs ? { script, logs } : { script };
69
+ },
70
+ });
71
+ tool(ctx, {
72
+ name: "unload_java_script",
73
+ title: "Unload a script",
74
+ description: "Run the script's disable(), remove its listeners and tasks, and close its class loader. A persistent script's saved copy is deleted too, unless keepSaved is true; then it loads again on the next server start.",
75
+ scope: "code",
76
+ effect: "destructive",
77
+ input: { id: z.string(), keepSaved: z.boolean().optional() },
78
+ run: (args) => unwrap(api.DELETE("/scripts/{id}", { params: { path: { id: args.id }, query: { keepSaved: args.keepSaved } } }), token),
79
+ });
80
+ }
81
+ //# sourceMappingURL=scripts.js.map