@lzimul/minecraft-mcp 1.0.3 → 1.0.5
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.
- package/LICENSE +21 -21
- package/README.md +126 -67
- package/SECURITY.md +38 -21
- package/build/config/server-info.js +5 -4
- package/build/config/storage.js +2 -1
- package/build/config/world-limits.js +6 -1
- package/build/data/entities.js +1581 -0
- package/build/enums/entity-kind.enum.js +5 -0
- package/build/enums/inventory-section.enum.js +5 -0
- package/build/enums/move-direction.enum.js +9 -0
- package/build/index.js +4 -3
- package/build/interfaces/entity-inventory.interface.js +1 -0
- package/build/interfaces/entity-type.interface.js +1 -0
- package/build/interfaces/entity.interface.js +1 -0
- package/build/resources/dimension-resources.js +24 -7
- package/build/resources/entity-resources.js +17 -0
- package/build/resources/index.js +2 -0
- package/build/schemas/coordinate-schema.js +5 -0
- package/build/schemas/dimension-schema.js +1 -0
- package/build/schemas/entity-schema.js +95 -0
- package/build/tools/block-tools.js +14 -23
- package/build/tools/entity-tools.js +638 -0
- package/build/tools/index.js +4 -0
- package/build/tools/result.js +3 -5
- package/build/tools/world-tools.js +526 -0
- package/build/utils/logger.js +53 -0
- package/build/world/block-spec.js +8 -6
- package/build/world/block-states.js +23 -4
- package/build/world/entity-inventory.js +101 -0
- package/build/world/item-spec.js +25 -0
- package/build/world/simulated-dimension.js +314 -14
- package/build/world/stores/file-world-store.js +84 -29
- package/build/world/stores/memory-world-store.js +18 -5
- package/build/world/stores/serialized-block.js +9 -14
- package/build/world/stores/serialized-entity.js +75 -0
- package/build/world/stores/serialized-snapshot.js +35 -0
- package/build/world/world.js +33 -1
- package/lib/app/server.d.ts.map +1 -1
- package/lib/config/dimensions.d.ts.map +1 -1
- package/lib/config/server-info.d.ts.map +1 -1
- package/lib/config/storage.d.ts.map +1 -1
- package/lib/config/world-limits.d.ts +5 -0
- package/lib/config/world-limits.d.ts.map +1 -1
- package/lib/data/entities.d.ts +5 -0
- package/lib/data/entities.d.ts.map +1 -0
- package/lib/enums/bounding-box.enum.d.ts.map +1 -1
- package/lib/enums/dimension-type.enum.d.ts.map +1 -1
- package/lib/enums/entity-kind.enum.d.ts +5 -0
- package/lib/enums/entity-kind.enum.d.ts.map +1 -0
- package/lib/enums/icon-theme.enum.d.ts.map +1 -1
- package/lib/enums/inventory-section.enum.d.ts +5 -0
- package/lib/enums/inventory-section.enum.d.ts.map +1 -0
- package/lib/enums/move-direction.enum.d.ts +9 -0
- package/lib/enums/move-direction.enum.d.ts.map +1 -0
- package/lib/interfaces/block.interface.d.ts.map +1 -1
- package/lib/interfaces/data-entry.interface.d.ts.map +1 -1
- package/lib/interfaces/dimension.interface.d.ts.map +1 -1
- package/lib/interfaces/entity-inventory.interface.d.ts +9 -0
- package/lib/interfaces/entity-inventory.interface.d.ts.map +1 -0
- package/lib/interfaces/entity-type.interface.d.ts +8 -0
- package/lib/interfaces/entity-type.interface.d.ts.map +1 -0
- package/lib/interfaces/entity.interface.d.ts +17 -0
- package/lib/interfaces/entity.interface.d.ts.map +1 -0
- package/lib/interfaces/item.interface.d.ts.map +1 -1
- package/lib/interfaces/placed-block.interface.d.ts.map +1 -1
- package/lib/interfaces/world-store.interface.d.ts +8 -2
- package/lib/interfaces/world-store.interface.d.ts.map +1 -1
- package/lib/resources/block-resources.d.ts.map +1 -1
- package/lib/resources/data-resources.d.ts.map +1 -1
- package/lib/resources/dimension-resources.d.ts.map +1 -1
- package/lib/resources/entity-resources.d.ts +3 -0
- package/lib/resources/entity-resources.d.ts.map +1 -0
- package/lib/resources/index.d.ts.map +1 -1
- package/lib/resources/item-resources.d.ts.map +1 -1
- package/lib/schemas/block-schema.d.ts.map +1 -1
- package/lib/schemas/coordinate-schema.d.ts +5 -0
- package/lib/schemas/coordinate-schema.d.ts.map +1 -1
- package/lib/schemas/dimension-schema.d.ts +1 -0
- package/lib/schemas/dimension-schema.d.ts.map +1 -1
- package/lib/schemas/entity-schema.d.ts +134 -0
- package/lib/schemas/entity-schema.d.ts.map +1 -0
- package/lib/schemas/item-schema.d.ts.map +1 -1
- package/lib/tools/block-tools.d.ts.map +1 -1
- package/lib/tools/entity-tools.d.ts +4 -0
- package/lib/tools/entity-tools.d.ts.map +1 -0
- package/lib/tools/index.d.ts.map +1 -1
- package/lib/tools/item-tools.d.ts.map +1 -1
- package/lib/tools/result.d.ts +2 -6
- package/lib/tools/result.d.ts.map +1 -1
- package/lib/tools/world-tools.d.ts +4 -0
- package/lib/tools/world-tools.d.ts.map +1 -0
- package/lib/utils/logger.d.ts +3 -0
- package/lib/utils/logger.d.ts.map +1 -0
- package/lib/world/block-spec.d.ts.map +1 -1
- package/lib/world/block-states.d.ts +2 -0
- package/lib/world/block-states.d.ts.map +1 -1
- package/lib/world/entity-inventory.d.ts +16 -0
- package/lib/world/entity-inventory.d.ts.map +1 -0
- package/lib/world/item-spec.d.ts +10 -0
- package/lib/world/item-spec.d.ts.map +1 -0
- package/lib/world/simulated-dimension.d.ts +54 -0
- package/lib/world/simulated-dimension.d.ts.map +1 -1
- package/lib/world/stores/file-world-store.d.ts +4 -4
- package/lib/world/stores/file-world-store.d.ts.map +1 -1
- package/lib/world/stores/index.d.ts.map +1 -1
- package/lib/world/stores/memory-world-store.d.ts +4 -4
- package/lib/world/stores/memory-world-store.d.ts.map +1 -1
- package/lib/world/stores/serialized-block.d.ts.map +1 -1
- package/lib/world/stores/serialized-entity.d.ts +24 -0
- package/lib/world/stores/serialized-entity.d.ts.map +1 -0
- package/lib/world/stores/serialized-snapshot.d.ts +6 -0
- package/lib/world/stores/serialized-snapshot.d.ts.map +1 -0
- package/lib/world/world.d.ts +3 -0
- package/lib/world/world.d.ts.map +1 -1
- package/package.json +16 -12
|
@@ -0,0 +1,638 @@
|
|
|
1
|
+
import * as z from 'zod/v4';
|
|
2
|
+
import { WORLD_LIMITS } from '../config/world-limits.js';
|
|
3
|
+
import { getEntityTypeByName } from '../data/entities.js';
|
|
4
|
+
import { EInventorySection } from '../enums/inventory-section.enum.js';
|
|
5
|
+
import { EMoveDirection } from '../enums/move-direction.enum.js';
|
|
6
|
+
import { blockPositionShape } from '../schemas/coordinate-schema.js';
|
|
7
|
+
import { dimensionField } from '../schemas/dimension-schema.js';
|
|
8
|
+
import { entityInfoSchema, entitySummarySchema, inventoryChangeSchema, inventoryViewSchema, itemStackSchema, toEntityInfo, toEntitySummary, toInventoryView, toItemStackInfo } from '../schemas/entity-schema.js';
|
|
9
|
+
import { toErrorMessage } from '../utils/error-message.js';
|
|
10
|
+
import { HOTBAR_SLOT_COUNT, INVENTORY_SLOT_COUNT, slotCount } from '../world/entity-inventory.js';
|
|
11
|
+
import { parseItemSpec } from '../world/item-spec.js';
|
|
12
|
+
import { coordinateRangeError, toolError } from './result.js';
|
|
13
|
+
const entityIdSchema = z
|
|
14
|
+
.string()
|
|
15
|
+
.trim()
|
|
16
|
+
.min(1)
|
|
17
|
+
.max(WORLD_LIMITS.maxEntityIdLength)
|
|
18
|
+
.describe('Entity UUID returned by createEntity or createPlayer');
|
|
19
|
+
const targetSchema = z
|
|
20
|
+
.string()
|
|
21
|
+
.trim()
|
|
22
|
+
.min(1)
|
|
23
|
+
.max(WORLD_LIMITS.maxEntityIdLength)
|
|
24
|
+
.describe('Entity UUID or player name (player names are case-insensitive)');
|
|
25
|
+
const playerNameSchema = z
|
|
26
|
+
.string()
|
|
27
|
+
.trim()
|
|
28
|
+
.min(1)
|
|
29
|
+
.max(WORLD_LIMITS.maxPlayerNameLength)
|
|
30
|
+
.regex(/^[A-Za-z0-9_]+$/u, 'Player names may only contain letters, digits and underscores')
|
|
31
|
+
.describe('Unique player name (letters, digits and underscores)');
|
|
32
|
+
const entityNameSchema = z
|
|
33
|
+
.string()
|
|
34
|
+
.trim()
|
|
35
|
+
.min(1)
|
|
36
|
+
.max(WORLD_LIMITS.maxEntityNameLength)
|
|
37
|
+
.optional()
|
|
38
|
+
.describe('Optional display name; defaults to the entity type display name');
|
|
39
|
+
const itemIdSchema = z
|
|
40
|
+
.string()
|
|
41
|
+
.trim()
|
|
42
|
+
.min(1)
|
|
43
|
+
.max(256)
|
|
44
|
+
.describe('Item id, such as "minecraft:diamond_sword" or "diamond_sword". Use "minecraft:air" or count 0 to clear the slot.');
|
|
45
|
+
const entityPositionShape = {
|
|
46
|
+
x: z.number().int(),
|
|
47
|
+
y: z.number().int(),
|
|
48
|
+
z: z.number().int()
|
|
49
|
+
};
|
|
50
|
+
const entityPositionSchema = z.object({
|
|
51
|
+
id: z.string(),
|
|
52
|
+
type: z.string(),
|
|
53
|
+
name: z.string(),
|
|
54
|
+
...entityPositionShape
|
|
55
|
+
});
|
|
56
|
+
const entityMovementSchema = entityPositionSchema.extend({
|
|
57
|
+
direction: z.enum(EMoveDirection),
|
|
58
|
+
previous: z.object(entityPositionShape)
|
|
59
|
+
});
|
|
60
|
+
const entityPositionChangeSchema = entityPositionSchema.extend({
|
|
61
|
+
previous: z.object(entityPositionShape)
|
|
62
|
+
});
|
|
63
|
+
const inventoryClearSchema = inventoryViewSchema.extend({
|
|
64
|
+
clearedSlots: z.number().int()
|
|
65
|
+
});
|
|
66
|
+
const inventorySwapSchema = inventoryViewSchema.extend({
|
|
67
|
+
fromSection: z.enum(EInventorySection),
|
|
68
|
+
fromSlot: z.number().int(),
|
|
69
|
+
toSection: z.enum(EInventorySection),
|
|
70
|
+
toSlot: z.number().int(),
|
|
71
|
+
previousFrom: itemStackSchema.nullable(),
|
|
72
|
+
previousTo: itemStackSchema.nullable()
|
|
73
|
+
});
|
|
74
|
+
function toEntityPosition(entity) {
|
|
75
|
+
return {
|
|
76
|
+
id: entity.id,
|
|
77
|
+
type: entity.type.key,
|
|
78
|
+
name: entity.name,
|
|
79
|
+
x: entity.x,
|
|
80
|
+
y: entity.y,
|
|
81
|
+
z: entity.z
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function resolveEntityType(input) {
|
|
85
|
+
const normalized = input.trim().toLowerCase();
|
|
86
|
+
return (getEntityTypeByName(normalized) ??
|
|
87
|
+
getEntityTypeByName(`minecraft:${normalized}`));
|
|
88
|
+
}
|
|
89
|
+
function resolveTarget(world, target) {
|
|
90
|
+
const normalized = target.trim();
|
|
91
|
+
return world.findEntity(normalized) ?? world.findPlayer(normalized);
|
|
92
|
+
}
|
|
93
|
+
function unknownTargetError(target) {
|
|
94
|
+
return toolError(`Unknown entity or player "${target}". Use an entity UUID from createEntity/createPlayer or a player name.`);
|
|
95
|
+
}
|
|
96
|
+
function text(message) {
|
|
97
|
+
return { type: 'text', text: message };
|
|
98
|
+
}
|
|
99
|
+
export function registerEntityTools(server, world) {
|
|
100
|
+
server.registerTool('createEntity', {
|
|
101
|
+
title: 'Create Entity',
|
|
102
|
+
description: 'Create a non-player entity (mob, projectile, vehicle, etc.) in a dimension with an empty 9-slot hotbar and 27-slot backpack. Entity ids come from the generated registry; see the resource lzimul://minecraft/entities for the full list. Use createPlayer for players.',
|
|
103
|
+
inputSchema: z.object({
|
|
104
|
+
type: z
|
|
105
|
+
.string()
|
|
106
|
+
.trim()
|
|
107
|
+
.min(1)
|
|
108
|
+
.max(128)
|
|
109
|
+
.describe('Entity id, such as "minecraft:zombie" or "zombie"; see lzimul://minecraft/entities'),
|
|
110
|
+
...blockPositionShape,
|
|
111
|
+
dimension: dimensionField,
|
|
112
|
+
name: entityNameSchema
|
|
113
|
+
}),
|
|
114
|
+
outputSchema: entityInfoSchema,
|
|
115
|
+
annotations: {
|
|
116
|
+
destructiveHint: false,
|
|
117
|
+
idempotentHint: false,
|
|
118
|
+
openWorldHint: false
|
|
119
|
+
}
|
|
120
|
+
}, async ({ type: typeId, x, y, z, dimension: dimensionType, name }) => {
|
|
121
|
+
try {
|
|
122
|
+
const type = resolveEntityType(typeId);
|
|
123
|
+
if (!type) {
|
|
124
|
+
return toolError(`Unknown entity type "${typeId}". Use an id such as "minecraft:zombie"; see the resource lzimul://minecraft/entities for the full list.`);
|
|
125
|
+
}
|
|
126
|
+
if (type.name === 'player') {
|
|
127
|
+
return toolError('The minecraft:player type cannot be created with createEntity. Use createPlayer instead.');
|
|
128
|
+
}
|
|
129
|
+
const dimension = world.getDimension(dimensionType);
|
|
130
|
+
if (!dimension.contains(x, y, z)) {
|
|
131
|
+
return coordinateRangeError(dimension.info, x, y, z);
|
|
132
|
+
}
|
|
133
|
+
if (!dimension.canAddEntity()) {
|
|
134
|
+
return toolError(`The ${dimension.info.displayName} world already holds the maximum of ${WORLD_LIMITS.maxEntitiesPerDimension} entities.`);
|
|
135
|
+
}
|
|
136
|
+
const entity = dimension.createEntity(type, name ?? type.displayName, x, y, z);
|
|
137
|
+
try {
|
|
138
|
+
await dimension.flush();
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
return toolError(`Entity ${type.key} "${entity.name}" was created at (${x}, ${y}, ${z}) in ${dimension.info.displayName} but could not be persisted to the world store: ${toErrorMessage(error)}`);
|
|
142
|
+
}
|
|
143
|
+
return {
|
|
144
|
+
content: [
|
|
145
|
+
text(`Created ${entity.type.displayName} "${entity.name}" (${entity.type.key}) with id ${entity.id} at (${x}, ${y}, ${z}) in ${dimension.info.displayName}`)
|
|
146
|
+
],
|
|
147
|
+
structuredContent: toEntityInfo(entity)
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
return toolError(`createEntity failed: ${toErrorMessage(error)}`);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
server.registerTool('getEntity', {
|
|
155
|
+
title: 'Get Entity',
|
|
156
|
+
description: 'Get an entity or player by UUID, including its type, position, dimension, creation time and full hotbar/backpack contents. Use getInventory or setInventory to change items.',
|
|
157
|
+
inputSchema: z.object({
|
|
158
|
+
id: entityIdSchema
|
|
159
|
+
}),
|
|
160
|
+
outputSchema: entityInfoSchema,
|
|
161
|
+
annotations: {
|
|
162
|
+
readOnlyHint: true,
|
|
163
|
+
idempotentHint: true,
|
|
164
|
+
openWorldHint: false
|
|
165
|
+
}
|
|
166
|
+
}, async ({ id }) => {
|
|
167
|
+
try {
|
|
168
|
+
const entity = world.findEntity(id);
|
|
169
|
+
if (!entity) {
|
|
170
|
+
return toolError(`Unknown entity "${id}". Use the id returned by createEntity or createPlayer; use getPlayer for a player name.`);
|
|
171
|
+
}
|
|
172
|
+
const info = toEntityInfo(entity);
|
|
173
|
+
return {
|
|
174
|
+
content: [
|
|
175
|
+
text(`${info.typeDisplayName} "${info.name}" (${info.type}, ${info.kind}) at (${info.x}, ${info.y}, ${info.z}) in ${info.dimension}; ${info.usedSlots}/${info.totalSlots} inventory slots used`)
|
|
176
|
+
],
|
|
177
|
+
structuredContent: info
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
return toolError(`getEntity failed: ${toErrorMessage(error)}`);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
server.registerTool('createPlayer', {
|
|
185
|
+
title: 'Create Player',
|
|
186
|
+
description: 'Create a uniquely named player in a dimension with an empty 9-slot hotbar and 27-slot backpack. Player names are case-insensitively unique across the whole world (all dimensions).',
|
|
187
|
+
inputSchema: z.object({
|
|
188
|
+
name: playerNameSchema,
|
|
189
|
+
...blockPositionShape,
|
|
190
|
+
dimension: dimensionField
|
|
191
|
+
}),
|
|
192
|
+
outputSchema: entityInfoSchema,
|
|
193
|
+
annotations: {
|
|
194
|
+
destructiveHint: false,
|
|
195
|
+
idempotentHint: false,
|
|
196
|
+
openWorldHint: false
|
|
197
|
+
}
|
|
198
|
+
}, async ({ name, x, y, z, dimension: dimensionType }) => {
|
|
199
|
+
try {
|
|
200
|
+
const existing = world.findPlayer(name);
|
|
201
|
+
if (existing) {
|
|
202
|
+
const where = world.getDimension(existing.dimension).info.displayName;
|
|
203
|
+
return toolError(`A player named "${name}" already exists in ${where}. Player names are case-insensitive and unique across all dimensions.`);
|
|
204
|
+
}
|
|
205
|
+
const dimension = world.getDimension(dimensionType);
|
|
206
|
+
if (!dimension.contains(x, y, z)) {
|
|
207
|
+
return coordinateRangeError(dimension.info, x, y, z);
|
|
208
|
+
}
|
|
209
|
+
if (!dimension.canAddEntity()) {
|
|
210
|
+
return toolError(`The ${dimension.info.displayName} world already holds the maximum of ${WORLD_LIMITS.maxEntitiesPerDimension} entities.`);
|
|
211
|
+
}
|
|
212
|
+
const player = dimension.createPlayer(name, x, y, z);
|
|
213
|
+
try {
|
|
214
|
+
await dimension.flush();
|
|
215
|
+
}
|
|
216
|
+
catch (error) {
|
|
217
|
+
return toolError(`Player "${name}" was created at (${x}, ${y}, ${z}) in ${dimension.info.displayName} but could not be persisted to the world store: ${toErrorMessage(error)}`);
|
|
218
|
+
}
|
|
219
|
+
return {
|
|
220
|
+
content: [
|
|
221
|
+
text(`Created player "${player.name}" with id ${player.id} at (${x}, ${y}, ${z}) in ${dimension.info.displayName}`)
|
|
222
|
+
],
|
|
223
|
+
structuredContent: toEntityInfo(player)
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
catch (error) {
|
|
227
|
+
return toolError(`createPlayer failed: ${toErrorMessage(error)}`);
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
server.registerTool('getPlayer', {
|
|
231
|
+
title: 'Get Player',
|
|
232
|
+
description: 'Get a player by name (case-insensitive), including position, dimension, creation time and full hotbar/backpack contents.',
|
|
233
|
+
inputSchema: z.object({
|
|
234
|
+
name: playerNameSchema
|
|
235
|
+
}),
|
|
236
|
+
outputSchema: entityInfoSchema,
|
|
237
|
+
annotations: {
|
|
238
|
+
readOnlyHint: true,
|
|
239
|
+
idempotentHint: true,
|
|
240
|
+
openWorldHint: false
|
|
241
|
+
}
|
|
242
|
+
}, async ({ name }) => {
|
|
243
|
+
try {
|
|
244
|
+
const player = world.findPlayer(name);
|
|
245
|
+
if (!player) {
|
|
246
|
+
return toolError(`Unknown player "${name}". Player names are case-insensitive; use createPlayer to create one.`);
|
|
247
|
+
}
|
|
248
|
+
const info = toEntityInfo(player);
|
|
249
|
+
return {
|
|
250
|
+
content: [
|
|
251
|
+
text(`Player "${info.name}" (${info.id}) at (${info.x}, ${info.y}, ${info.z}) in ${info.dimension}; ${info.usedSlots}/${info.totalSlots} inventory slots used`)
|
|
252
|
+
],
|
|
253
|
+
structuredContent: info
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
catch (error) {
|
|
257
|
+
return toolError(`getPlayer failed: ${toErrorMessage(error)}`);
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
server.registerTool('deleteEntity', {
|
|
261
|
+
title: 'Delete Entity',
|
|
262
|
+
description: 'Delete one entity or player by UUID or player name. The entity is removed from its dimension and its inventory is discarded. Player names become available again after deletion.',
|
|
263
|
+
inputSchema: z.object({
|
|
264
|
+
target: targetSchema
|
|
265
|
+
}),
|
|
266
|
+
outputSchema: entitySummarySchema,
|
|
267
|
+
annotations: {
|
|
268
|
+
destructiveHint: true,
|
|
269
|
+
idempotentHint: false,
|
|
270
|
+
openWorldHint: false
|
|
271
|
+
}
|
|
272
|
+
}, async ({ target }) => {
|
|
273
|
+
try {
|
|
274
|
+
const entity = resolveTarget(world, target);
|
|
275
|
+
if (!entity) {
|
|
276
|
+
return unknownTargetError(target);
|
|
277
|
+
}
|
|
278
|
+
const dimension = world.getDimension(entity.dimension);
|
|
279
|
+
const deleted = dimension.deleteEntity(entity.id) ?? entity;
|
|
280
|
+
try {
|
|
281
|
+
await dimension.flush();
|
|
282
|
+
}
|
|
283
|
+
catch (error) {
|
|
284
|
+
return toolError(`Entity "${deleted.name}" was deleted but could not be persisted to the world store: ${toErrorMessage(error)}`);
|
|
285
|
+
}
|
|
286
|
+
return {
|
|
287
|
+
content: [
|
|
288
|
+
text(`Deleted ${deleted.kind} "${deleted.name}" (${deleted.id}, ${deleted.type.key}) from ${dimension.info.displayName}`)
|
|
289
|
+
],
|
|
290
|
+
structuredContent: toEntitySummary(deleted)
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
catch (error) {
|
|
294
|
+
return toolError(`deleteEntity failed: ${toErrorMessage(error)}`);
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
server.registerTool('moveEntity', {
|
|
298
|
+
title: 'Move Entity',
|
|
299
|
+
description: 'Move one entity or player exactly one block in a fixed direction: forward (+Z, south), back (-Z, north), left (-X, west), right (+X, east), up (+Y) or down (-Y). The destination must stay inside the entity dimension.',
|
|
300
|
+
inputSchema: z.object({
|
|
301
|
+
id: entityIdSchema,
|
|
302
|
+
direction: z
|
|
303
|
+
.enum(EMoveDirection)
|
|
304
|
+
.describe('One-block direction: forward (+Z), back (-Z), left (-X), right (+X), up (+Y), down (-Y)')
|
|
305
|
+
}),
|
|
306
|
+
outputSchema: entityMovementSchema,
|
|
307
|
+
annotations: {
|
|
308
|
+
destructiveHint: false,
|
|
309
|
+
idempotentHint: false,
|
|
310
|
+
openWorldHint: false
|
|
311
|
+
}
|
|
312
|
+
}, async ({ id, direction }) => {
|
|
313
|
+
try {
|
|
314
|
+
const entity = world.findEntity(id);
|
|
315
|
+
if (!entity) {
|
|
316
|
+
return toolError(`Unknown entity "${id}". Use an entity UUID returned by createEntity or createPlayer.`);
|
|
317
|
+
}
|
|
318
|
+
const dimension = world.getDimension(entity.dimension);
|
|
319
|
+
const { previous, entity: moved } = dimension.moveEntity(id, direction);
|
|
320
|
+
try {
|
|
321
|
+
await dimension.flush();
|
|
322
|
+
}
|
|
323
|
+
catch (error) {
|
|
324
|
+
return toolError(`Entity "${moved.name}" was moved but could not be persisted to the world store: ${toErrorMessage(error)}`);
|
|
325
|
+
}
|
|
326
|
+
return {
|
|
327
|
+
content: [
|
|
328
|
+
text(`Moved ${moved.name} (${moved.id}) ${direction} from (${previous.x}, ${previous.y}, ${previous.z}) to (${moved.x}, ${moved.y}, ${moved.z}) in ${dimension.info.displayName}`)
|
|
329
|
+
],
|
|
330
|
+
structuredContent: {
|
|
331
|
+
...toEntityPosition(moved),
|
|
332
|
+
direction,
|
|
333
|
+
previous
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
catch (error) {
|
|
338
|
+
return toolError(`moveEntity failed: ${toErrorMessage(error)}`);
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
server.registerTool('getEntityPosition', {
|
|
342
|
+
title: 'Get Entity Position',
|
|
343
|
+
description: 'Get the UUID, entity type, name and exact x/y/z position of one entity or player.',
|
|
344
|
+
inputSchema: z.object({
|
|
345
|
+
id: entityIdSchema
|
|
346
|
+
}),
|
|
347
|
+
outputSchema: entityPositionSchema,
|
|
348
|
+
annotations: {
|
|
349
|
+
readOnlyHint: true,
|
|
350
|
+
idempotentHint: true,
|
|
351
|
+
openWorldHint: false
|
|
352
|
+
}
|
|
353
|
+
}, async ({ id }) => {
|
|
354
|
+
try {
|
|
355
|
+
const entity = world.findEntity(id);
|
|
356
|
+
if (!entity) {
|
|
357
|
+
return toolError(`Unknown entity "${id}". Use an entity UUID returned by createEntity or createPlayer.`);
|
|
358
|
+
}
|
|
359
|
+
const position = toEntityPosition(entity);
|
|
360
|
+
return {
|
|
361
|
+
content: [
|
|
362
|
+
text(`${position.name} (${position.type}, ${position.id}) is at (${position.x}, ${position.y}, ${position.z})`)
|
|
363
|
+
],
|
|
364
|
+
structuredContent: position
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
catch (error) {
|
|
368
|
+
return toolError(`getEntityPosition failed: ${toErrorMessage(error)}`);
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
server.registerTool('setEntityPosition', {
|
|
372
|
+
title: 'Set Entity Position',
|
|
373
|
+
description: 'Set the exact x/y/z position of one entity or player. The position must stay inside the entity dimension.',
|
|
374
|
+
inputSchema: z.object({
|
|
375
|
+
id: entityIdSchema,
|
|
376
|
+
position: z.object(blockPositionShape)
|
|
377
|
+
}),
|
|
378
|
+
outputSchema: entityPositionChangeSchema,
|
|
379
|
+
annotations: {
|
|
380
|
+
destructiveHint: false,
|
|
381
|
+
idempotentHint: true,
|
|
382
|
+
openWorldHint: false
|
|
383
|
+
}
|
|
384
|
+
}, async ({ id, position }) => {
|
|
385
|
+
try {
|
|
386
|
+
const entity = world.findEntity(id);
|
|
387
|
+
if (!entity) {
|
|
388
|
+
return toolError(`Unknown entity "${id}". Use an entity UUID returned by createEntity or createPlayer.`);
|
|
389
|
+
}
|
|
390
|
+
const dimension = world.getDimension(entity.dimension);
|
|
391
|
+
if (!dimension.contains(position.x, position.y, position.z)) {
|
|
392
|
+
return coordinateRangeError(dimension.info, position.x, position.y, position.z);
|
|
393
|
+
}
|
|
394
|
+
const { previous, entity: updated } = dimension.setEntityPosition(id, position.x, position.y, position.z);
|
|
395
|
+
try {
|
|
396
|
+
await dimension.flush();
|
|
397
|
+
}
|
|
398
|
+
catch (error) {
|
|
399
|
+
return toolError(`Entity "${updated.name}" was moved but could not be persisted to the world store: ${toErrorMessage(error)}`);
|
|
400
|
+
}
|
|
401
|
+
return {
|
|
402
|
+
content: [
|
|
403
|
+
text(`Set ${updated.name} (${updated.id}) position from (${previous.x}, ${previous.y}, ${previous.z}) to (${updated.x}, ${updated.y}, ${updated.z}) in ${dimension.info.displayName}`)
|
|
404
|
+
],
|
|
405
|
+
structuredContent: {
|
|
406
|
+
...toEntityPosition(updated),
|
|
407
|
+
previous
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
catch (error) {
|
|
412
|
+
return toolError(`setEntityPosition failed: ${toErrorMessage(error)}`);
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
server.registerTool('getInventory', {
|
|
416
|
+
title: 'Get Inventory',
|
|
417
|
+
description: 'Get the full inventory of an entity or player: the 9 hotbar slots and the 27 backpack slots, plus used-slot counts. Empty slots are null. The target is an entity UUID or a player name.',
|
|
418
|
+
inputSchema: z.object({
|
|
419
|
+
target: targetSchema
|
|
420
|
+
}),
|
|
421
|
+
outputSchema: inventoryViewSchema,
|
|
422
|
+
annotations: {
|
|
423
|
+
readOnlyHint: true,
|
|
424
|
+
idempotentHint: true,
|
|
425
|
+
openWorldHint: false
|
|
426
|
+
}
|
|
427
|
+
}, async ({ target }) => {
|
|
428
|
+
try {
|
|
429
|
+
const entity = resolveTarget(world, target);
|
|
430
|
+
if (!entity) {
|
|
431
|
+
return unknownTargetError(target);
|
|
432
|
+
}
|
|
433
|
+
const view = toInventoryView(entity);
|
|
434
|
+
return {
|
|
435
|
+
content: [
|
|
436
|
+
text(`${entity.name} (${entity.kind}, ${entity.type.key}): ${view.usedSlots}/${view.totalSlots} inventory slots used (hotbar ${view.hotbarUsed}/${HOTBAR_SLOT_COUNT}, backpack ${view.inventoryUsed}/${INVENTORY_SLOT_COUNT})`)
|
|
437
|
+
],
|
|
438
|
+
structuredContent: view
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
catch (error) {
|
|
442
|
+
return toolError(`getInventory failed: ${toErrorMessage(error)}`);
|
|
443
|
+
}
|
|
444
|
+
});
|
|
445
|
+
server.registerTool('clearInventory', {
|
|
446
|
+
title: 'Clear Inventory',
|
|
447
|
+
description: 'Remove every item from the 9 hotbar slots and 27 backpack slots of one entity or player. The target is an entity UUID or a player name. The cleared slot count is returned.',
|
|
448
|
+
inputSchema: z.object({
|
|
449
|
+
target: targetSchema
|
|
450
|
+
}),
|
|
451
|
+
outputSchema: inventoryClearSchema,
|
|
452
|
+
annotations: {
|
|
453
|
+
destructiveHint: true,
|
|
454
|
+
idempotentHint: true,
|
|
455
|
+
openWorldHint: false
|
|
456
|
+
}
|
|
457
|
+
}, async ({ target }) => {
|
|
458
|
+
try {
|
|
459
|
+
const entity = resolveTarget(world, target);
|
|
460
|
+
if (!entity) {
|
|
461
|
+
return unknownTargetError(target);
|
|
462
|
+
}
|
|
463
|
+
const dimension = world.getDimension(entity.dimension);
|
|
464
|
+
const { clearedSlots, entity: updated } = dimension.clearInventory(entity.id);
|
|
465
|
+
if (clearedSlots > 0) {
|
|
466
|
+
try {
|
|
467
|
+
await dimension.flush();
|
|
468
|
+
}
|
|
469
|
+
catch (error) {
|
|
470
|
+
return toolError(`The inventory of "${updated.name}" was cleared but could not be persisted to the world store: ${toErrorMessage(error)}`);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
return {
|
|
474
|
+
content: [
|
|
475
|
+
text(clearedSlots > 0
|
|
476
|
+
? `Cleared ${clearedSlots} inventory slot(s) of "${updated.name}"`
|
|
477
|
+
: `The inventory of "${updated.name}" was already empty`)
|
|
478
|
+
],
|
|
479
|
+
structuredContent: {
|
|
480
|
+
...toInventoryView(updated),
|
|
481
|
+
clearedSlots
|
|
482
|
+
}
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
catch (error) {
|
|
486
|
+
return toolError(`clearInventory failed: ${toErrorMessage(error)}`);
|
|
487
|
+
}
|
|
488
|
+
});
|
|
489
|
+
server.registerTool('swapInventorySlots', {
|
|
490
|
+
title: 'Swap Inventory Slots',
|
|
491
|
+
description: 'Swap two inventory slots of one entity or player. section is "hotbar" (slots 0-8) or "inventory" (backpack, slots 0-26). Both slots may be in the same or different sections. The target is an entity UUID or a player name.',
|
|
492
|
+
inputSchema: z.object({
|
|
493
|
+
target: targetSchema,
|
|
494
|
+
fromSection: z
|
|
495
|
+
.enum(EInventorySection)
|
|
496
|
+
.describe('"hotbar" or "inventory" for the first slot'),
|
|
497
|
+
fromSlot: z
|
|
498
|
+
.number()
|
|
499
|
+
.int()
|
|
500
|
+
.min(0)
|
|
501
|
+
.max(INVENTORY_SLOT_COUNT - 1)
|
|
502
|
+
.describe('First slot index (hotbar 0-8, backpack 0-26)'),
|
|
503
|
+
toSection: z
|
|
504
|
+
.enum(EInventorySection)
|
|
505
|
+
.describe('"hotbar" or "inventory" for the second slot'),
|
|
506
|
+
toSlot: z
|
|
507
|
+
.number()
|
|
508
|
+
.int()
|
|
509
|
+
.min(0)
|
|
510
|
+
.max(INVENTORY_SLOT_COUNT - 1)
|
|
511
|
+
.describe('Second slot index (hotbar 0-8, backpack 0-26)')
|
|
512
|
+
}),
|
|
513
|
+
outputSchema: inventorySwapSchema,
|
|
514
|
+
annotations: {
|
|
515
|
+
destructiveHint: false,
|
|
516
|
+
idempotentHint: false,
|
|
517
|
+
openWorldHint: false
|
|
518
|
+
}
|
|
519
|
+
}, async ({ target, fromSection, fromSlot, toSection, toSlot }) => {
|
|
520
|
+
try {
|
|
521
|
+
const entity = resolveTarget(world, target);
|
|
522
|
+
if (!entity) {
|
|
523
|
+
return unknownTargetError(target);
|
|
524
|
+
}
|
|
525
|
+
const fromMax = slotCount(fromSection) - 1;
|
|
526
|
+
if (fromSlot > fromMax) {
|
|
527
|
+
return toolError(`Slot ${fromSlot} is out of range for the ${fromSection} section (0-${fromMax}).`);
|
|
528
|
+
}
|
|
529
|
+
const toMax = slotCount(toSection) - 1;
|
|
530
|
+
if (toSlot > toMax) {
|
|
531
|
+
return toolError(`Slot ${toSlot} is out of range for the ${toSection} section (0-${toMax}).`);
|
|
532
|
+
}
|
|
533
|
+
const dimension = world.getDimension(entity.dimension);
|
|
534
|
+
const { previousFrom, previousTo, entity: updated } = dimension.swapInventorySlots(entity.id, fromSection, fromSlot, toSection, toSlot);
|
|
535
|
+
try {
|
|
536
|
+
await dimension.flush();
|
|
537
|
+
}
|
|
538
|
+
catch (error) {
|
|
539
|
+
return toolError(`The inventory slots of "${updated.name}" were swapped but could not be persisted to the world store: ${toErrorMessage(error)}`);
|
|
540
|
+
}
|
|
541
|
+
return {
|
|
542
|
+
content: [
|
|
543
|
+
text(`Swapped ${fromSection} slot ${fromSlot} with ${toSection} slot ${toSlot} of "${updated.name}"`)
|
|
544
|
+
],
|
|
545
|
+
structuredContent: {
|
|
546
|
+
...toInventoryView(updated),
|
|
547
|
+
fromSection,
|
|
548
|
+
fromSlot,
|
|
549
|
+
toSection,
|
|
550
|
+
toSlot,
|
|
551
|
+
previousFrom: previousFrom ? toItemStackInfo(previousFrom) : null,
|
|
552
|
+
previousTo: previousTo ? toItemStackInfo(previousTo) : null
|
|
553
|
+
}
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
catch (error) {
|
|
557
|
+
return toolError(`swapInventorySlots failed: ${toErrorMessage(error)}`);
|
|
558
|
+
}
|
|
559
|
+
});
|
|
560
|
+
server.registerTool('setInventory', {
|
|
561
|
+
title: 'Set Inventory',
|
|
562
|
+
description: 'Set one inventory slot of an entity or player. section is "hotbar" (slots 0-8) or "inventory" (backpack, slots 0-26). The item is validated against the item registry and the count must fit the item stack size. Use "minecraft:air" or count 0 to clear a slot. The target is an entity UUID or a player name.',
|
|
563
|
+
inputSchema: z.object({
|
|
564
|
+
target: targetSchema,
|
|
565
|
+
section: z
|
|
566
|
+
.enum(EInventorySection)
|
|
567
|
+
.default(EInventorySection.Inventory)
|
|
568
|
+
.describe('"hotbar" for the 9 hotbar slots or "inventory" for the 27 backpack slots'),
|
|
569
|
+
slot: z
|
|
570
|
+
.number()
|
|
571
|
+
.int()
|
|
572
|
+
.min(0)
|
|
573
|
+
.max(INVENTORY_SLOT_COUNT - 1)
|
|
574
|
+
.describe('Slot index within the section (hotbar 0-8, backpack 0-26)'),
|
|
575
|
+
item: itemIdSchema,
|
|
576
|
+
count: z
|
|
577
|
+
.number()
|
|
578
|
+
.int()
|
|
579
|
+
.min(0)
|
|
580
|
+
.max(64)
|
|
581
|
+
.default(1)
|
|
582
|
+
.describe('Stack count (1 to the item stack size); 0 clears the slot')
|
|
583
|
+
}),
|
|
584
|
+
outputSchema: inventoryChangeSchema,
|
|
585
|
+
annotations: {
|
|
586
|
+
destructiveHint: true,
|
|
587
|
+
idempotentHint: true,
|
|
588
|
+
openWorldHint: false
|
|
589
|
+
}
|
|
590
|
+
}, async ({ target, section, slot, item, count }) => {
|
|
591
|
+
try {
|
|
592
|
+
const entity = resolveTarget(world, target);
|
|
593
|
+
if (!entity) {
|
|
594
|
+
return unknownTargetError(target);
|
|
595
|
+
}
|
|
596
|
+
const maxSlot = slotCount(section) - 1;
|
|
597
|
+
if (slot > maxSlot) {
|
|
598
|
+
return toolError(`Slot ${slot} is out of range for the ${section} section (0-${maxSlot}).`);
|
|
599
|
+
}
|
|
600
|
+
const spec = parseItemSpec(item);
|
|
601
|
+
if (!spec.ok) {
|
|
602
|
+
return toolError(spec.error);
|
|
603
|
+
}
|
|
604
|
+
const isAir = spec.item.key === 'minecraft:air';
|
|
605
|
+
let stack = null;
|
|
606
|
+
if (!isAir && count > 0) {
|
|
607
|
+
if (count > spec.item.stackSize) {
|
|
608
|
+
return toolError(`${spec.item.displayName} stacks up to ${spec.item.stackSize} per slot; received ${count}.`);
|
|
609
|
+
}
|
|
610
|
+
stack = { item: spec.item.key, count };
|
|
611
|
+
}
|
|
612
|
+
const dimension = world.getDimension(entity.dimension);
|
|
613
|
+
const { previous, entity: updated } = dimension.setInventorySlot(entity.id, section, slot, stack);
|
|
614
|
+
try {
|
|
615
|
+
await dimension.flush();
|
|
616
|
+
}
|
|
617
|
+
catch (error) {
|
|
618
|
+
return toolError(`The ${section} slot ${slot} of "${updated.name}" was changed but could not be persisted to the world store: ${toErrorMessage(error)}`);
|
|
619
|
+
}
|
|
620
|
+
const message = stack
|
|
621
|
+
? `Set ${section} slot ${slot} of "${updated.name}" to ${spec.item.displayName} x${stack.count}`
|
|
622
|
+
: `Cleared ${section} slot ${slot} of "${updated.name}"`;
|
|
623
|
+
return {
|
|
624
|
+
content: [text(message)],
|
|
625
|
+
structuredContent: {
|
|
626
|
+
...toInventoryView(updated),
|
|
627
|
+
section,
|
|
628
|
+
slot,
|
|
629
|
+
previous: previous ? toItemStackInfo(previous) : null,
|
|
630
|
+
item: stack ? toItemStackInfo(stack) : null
|
|
631
|
+
}
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
catch (error) {
|
|
635
|
+
return toolError(`setInventory failed: ${toErrorMessage(error)}`);
|
|
636
|
+
}
|
|
637
|
+
});
|
|
638
|
+
}
|
package/build/tools/index.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { registerBlockTools } from './block-tools.js';
|
|
2
|
+
import { registerEntityTools } from './entity-tools.js';
|
|
2
3
|
import { registerItemTools } from './item-tools.js';
|
|
4
|
+
import { registerWorldTools } from './world-tools.js';
|
|
3
5
|
export function registerTools(server, world) {
|
|
4
6
|
registerBlockTools(server, world);
|
|
5
7
|
registerItemTools(server);
|
|
8
|
+
registerWorldTools(server, world);
|
|
9
|
+
registerEntityTools(server, world);
|
|
6
10
|
}
|
package/build/tools/result.js
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
export function toolText(text) {
|
|
2
|
-
return {
|
|
3
|
-
content: [{ type: 'text', text }]
|
|
4
|
-
};
|
|
5
|
-
}
|
|
6
1
|
export function toolError(message) {
|
|
7
2
|
return {
|
|
8
3
|
isError: true,
|
|
9
4
|
content: [{ type: 'text', text: message }]
|
|
10
5
|
};
|
|
11
6
|
}
|
|
7
|
+
export function coordinateRangeError(dimension, x, y, z) {
|
|
8
|
+
return toolError(`Coordinates (${x}, ${y}, ${z}) are out of range in ${dimension.displayName} (Y: ${dimension.minY} to ${dimension.maxY - 1}).`);
|
|
9
|
+
}
|