@lzimul/minecraft-mcp 1.0.4 → 1.0.6

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.
Files changed (94) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +297 -219
  3. package/build/app/server.js +6 -1
  4. package/build/app/tool-events.js +72 -0
  5. package/build/app/websocket.js +116 -0
  6. package/build/config/server-info.js +4 -4
  7. package/build/config/storage.js +2 -1
  8. package/build/config/websocket.js +48 -0
  9. package/build/config/world-limits.js +2 -0
  10. package/build/enums/move-direction.enum.js +9 -0
  11. package/build/index.js +19 -1
  12. package/build/interfaces/websocket.interface.js +1 -0
  13. package/build/resources/dimension-resources.js +2 -2
  14. package/build/tools/entity-tools.js +318 -5
  15. package/build/tools/world-tools.js +388 -1
  16. package/build/utils/logger.js +1 -1
  17. package/build/world/block-spec.js +3 -1
  18. package/build/world/entity-inventory.js +21 -4
  19. package/build/world/simulated-dimension.js +135 -9
  20. package/build/world/stores/file-world-store.js +47 -39
  21. package/build/world/stores/memory-world-store.js +5 -0
  22. package/build/world/stores/serialized-entity.js +4 -3
  23. package/build/world/stores/serialized-snapshot.js +35 -0
  24. package/lib/app/server.d.ts +5 -1
  25. package/lib/app/server.d.ts.map +1 -1
  26. package/lib/app/tool-events.d.ts +5 -0
  27. package/lib/app/tool-events.d.ts.map +1 -0
  28. package/lib/app/websocket.d.ts +10 -0
  29. package/lib/app/websocket.d.ts.map +1 -0
  30. package/lib/config/dimensions.d.ts.map +1 -1
  31. package/lib/config/server-info.d.ts.map +1 -1
  32. package/lib/config/storage.d.ts.map +1 -1
  33. package/lib/config/websocket.d.ts +8 -0
  34. package/lib/config/websocket.d.ts.map +1 -0
  35. package/lib/config/world-limits.d.ts +2 -0
  36. package/lib/config/world-limits.d.ts.map +1 -1
  37. package/lib/data/entities.d.ts.map +1 -1
  38. package/lib/enums/bounding-box.enum.d.ts.map +1 -1
  39. package/lib/enums/dimension-type.enum.d.ts.map +1 -1
  40. package/lib/enums/entity-kind.enum.d.ts.map +1 -1
  41. package/lib/enums/icon-theme.enum.d.ts.map +1 -1
  42. package/lib/enums/inventory-section.enum.d.ts.map +1 -1
  43. package/lib/enums/move-direction.enum.d.ts +9 -0
  44. package/lib/enums/move-direction.enum.d.ts.map +1 -0
  45. package/lib/index.d.ts +1 -1
  46. package/lib/index.d.ts.map +1 -1
  47. package/lib/interfaces/block.interface.d.ts.map +1 -1
  48. package/lib/interfaces/data-entry.interface.d.ts.map +1 -1
  49. package/lib/interfaces/dimension.interface.d.ts.map +1 -1
  50. package/lib/interfaces/entity-inventory.interface.d.ts.map +1 -1
  51. package/lib/interfaces/entity-type.interface.d.ts.map +1 -1
  52. package/lib/interfaces/entity.interface.d.ts.map +1 -1
  53. package/lib/interfaces/item.interface.d.ts.map +1 -1
  54. package/lib/interfaces/placed-block.interface.d.ts.map +1 -1
  55. package/lib/interfaces/websocket.interface.d.ts +47 -0
  56. package/lib/interfaces/websocket.interface.d.ts.map +1 -0
  57. package/lib/interfaces/world-store.interface.d.ts +1 -0
  58. package/lib/interfaces/world-store.interface.d.ts.map +1 -1
  59. package/lib/resources/block-resources.d.ts.map +1 -1
  60. package/lib/resources/data-resources.d.ts.map +1 -1
  61. package/lib/resources/dimension-resources.d.ts +3 -0
  62. package/lib/resources/dimension-resources.d.ts.map +1 -1
  63. package/lib/resources/entity-resources.d.ts.map +1 -1
  64. package/lib/resources/index.d.ts.map +1 -1
  65. package/lib/resources/item-resources.d.ts.map +1 -1
  66. package/lib/schemas/block-schema.d.ts.map +1 -1
  67. package/lib/schemas/coordinate-schema.d.ts.map +1 -1
  68. package/lib/schemas/dimension-schema.d.ts.map +1 -1
  69. package/lib/schemas/entity-schema.d.ts.map +1 -1
  70. package/lib/schemas/item-schema.d.ts.map +1 -1
  71. package/lib/tools/block-tools.d.ts.map +1 -1
  72. package/lib/tools/entity-tools.d.ts.map +1 -1
  73. package/lib/tools/index.d.ts.map +1 -1
  74. package/lib/tools/item-tools.d.ts.map +1 -1
  75. package/lib/tools/result.d.ts.map +1 -1
  76. package/lib/tools/world-tools.d.ts.map +1 -1
  77. package/lib/utils/logger.d.ts.map +1 -1
  78. package/lib/world/block-spec.d.ts.map +1 -1
  79. package/lib/world/block-states.d.ts.map +1 -1
  80. package/lib/world/entity-inventory.d.ts.map +1 -1
  81. package/lib/world/item-spec.d.ts.map +1 -1
  82. package/lib/world/simulated-dimension.d.ts +25 -0
  83. package/lib/world/simulated-dimension.d.ts.map +1 -1
  84. package/lib/world/stores/file-world-store.d.ts +2 -2
  85. package/lib/world/stores/file-world-store.d.ts.map +1 -1
  86. package/lib/world/stores/index.d.ts.map +1 -1
  87. package/lib/world/stores/memory-world-store.d.ts +1 -0
  88. package/lib/world/stores/memory-world-store.d.ts.map +1 -1
  89. package/lib/world/stores/serialized-block.d.ts.map +1 -1
  90. package/lib/world/stores/serialized-entity.d.ts.map +1 -1
  91. package/lib/world/stores/serialized-snapshot.d.ts +6 -0
  92. package/lib/world/stores/serialized-snapshot.d.ts.map +1 -0
  93. package/lib/world/world.d.ts.map +1 -1
  94. package/package.json +116 -113
@@ -2,9 +2,10 @@ import * as z from 'zod/v4';
2
2
  import { WORLD_LIMITS } from '../config/world-limits.js';
3
3
  import { getEntityTypeByName } from '../data/entities.js';
4
4
  import { EInventorySection } from '../enums/inventory-section.enum.js';
5
+ import { EMoveDirection } from '../enums/move-direction.enum.js';
5
6
  import { blockPositionShape } from '../schemas/coordinate-schema.js';
6
7
  import { dimensionField } from '../schemas/dimension-schema.js';
7
- import { entityInfoSchema, inventoryChangeSchema, inventoryViewSchema, toEntityInfo, toInventoryView, toItemStackInfo } from '../schemas/entity-schema.js';
8
+ import { entityInfoSchema, entitySummarySchema, inventoryChangeSchema, inventoryViewSchema, itemStackSchema, toEntityInfo, toEntitySummary, toInventoryView, toItemStackInfo } from '../schemas/entity-schema.js';
8
9
  import { toErrorMessage } from '../utils/error-message.js';
9
10
  import { HOTBAR_SLOT_COUNT, INVENTORY_SLOT_COUNT, slotCount } from '../world/entity-inventory.js';
10
11
  import { parseItemSpec } from '../world/item-spec.js';
@@ -13,13 +14,13 @@ const entityIdSchema = z
13
14
  .string()
14
15
  .trim()
15
16
  .min(1)
16
- .max(128)
17
+ .max(WORLD_LIMITS.maxEntityIdLength)
17
18
  .describe('Entity UUID returned by createEntity or createPlayer');
18
19
  const targetSchema = z
19
20
  .string()
20
21
  .trim()
21
22
  .min(1)
22
- .max(128)
23
+ .max(WORLD_LIMITS.maxEntityIdLength)
23
24
  .describe('Entity UUID or player name (player names are case-insensitive)');
24
25
  const playerNameSchema = z
25
26
  .string()
@@ -41,6 +42,45 @@ const itemIdSchema = z
41
42
  .min(1)
42
43
  .max(256)
43
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
+ }
44
84
  function resolveEntityType(input) {
45
85
  const normalized = input.trim().toLowerCase();
46
86
  return (getEntityTypeByName(normalized) ??
@@ -50,6 +90,9 @@ function resolveTarget(world, target) {
50
90
  const normalized = target.trim();
51
91
  return world.findEntity(normalized) ?? world.findPlayer(normalized);
52
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
+ }
53
96
  function text(message) {
54
97
  return { type: 'text', text: message };
55
98
  }
@@ -214,6 +257,161 @@ export function registerEntityTools(server, world) {
214
257
  return toolError(`getPlayer failed: ${toErrorMessage(error)}`);
215
258
  }
216
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
+ });
217
415
  server.registerTool('getInventory', {
218
416
  title: 'Get Inventory',
219
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.',
@@ -230,7 +428,7 @@ export function registerEntityTools(server, world) {
230
428
  try {
231
429
  const entity = resolveTarget(world, target);
232
430
  if (!entity) {
233
- return toolError(`Unknown entity or player "${target}". Use an entity UUID from createEntity/createPlayer or a player name.`);
431
+ return unknownTargetError(target);
234
432
  }
235
433
  const view = toInventoryView(entity);
236
434
  return {
@@ -244,6 +442,121 @@ export function registerEntityTools(server, world) {
244
442
  return toolError(`getInventory failed: ${toErrorMessage(error)}`);
245
443
  }
246
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
+ });
247
560
  server.registerTool('setInventory', {
248
561
  title: 'Set Inventory',
249
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.',
@@ -278,7 +591,7 @@ export function registerEntityTools(server, world) {
278
591
  try {
279
592
  const entity = resolveTarget(world, target);
280
593
  if (!entity) {
281
- return toolError(`Unknown entity or player "${target}". Use an entity UUID from createEntity/createPlayer or a player name.`);
594
+ return unknownTargetError(target);
282
595
  }
283
596
  const maxSlot = slotCount(section) - 1;
284
597
  if (slot > maxSlot) {