@lzimul/minecraft-mcp 1.0.2 → 1.0.4

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 (88) hide show
  1. package/README.md +219 -168
  2. package/SECURITY.md +38 -21
  3. package/build/config/server-info.js +3 -2
  4. package/build/config/world-limits.js +4 -1
  5. package/build/data/entities.js +1581 -0
  6. package/build/enums/entity-kind.enum.js +5 -0
  7. package/build/enums/inventory-section.enum.js +5 -0
  8. package/build/index.js +4 -3
  9. package/build/interfaces/entity-inventory.interface.js +1 -0
  10. package/build/interfaces/entity-type.interface.js +1 -0
  11. package/build/interfaces/entity.interface.js +1 -0
  12. package/build/resources/dimension-resources.js +23 -6
  13. package/build/resources/entity-resources.js +17 -0
  14. package/build/resources/index.js +2 -0
  15. package/build/schemas/coordinate-schema.js +5 -0
  16. package/build/schemas/dimension-schema.js +1 -0
  17. package/build/schemas/entity-schema.js +95 -0
  18. package/build/tools/block-tools.js +14 -23
  19. package/build/tools/entity-tools.js +325 -0
  20. package/build/tools/index.js +4 -0
  21. package/build/tools/result.js +3 -5
  22. package/build/tools/world-tools.js +139 -0
  23. package/build/utils/logger.js +53 -0
  24. package/build/world/block-spec.js +6 -6
  25. package/build/world/block-states.js +23 -4
  26. package/build/world/entity-inventory.js +84 -0
  27. package/build/world/item-spec.js +25 -0
  28. package/build/world/simulated-dimension.js +187 -13
  29. package/build/world/stores/file-world-store.js +67 -20
  30. package/build/world/stores/memory-world-store.js +13 -5
  31. package/build/world/stores/serialized-block.js +9 -14
  32. package/build/world/stores/serialized-entity.js +74 -0
  33. package/build/world/world.js +33 -1
  34. package/lib/config/server-info.d.ts.map +1 -1
  35. package/lib/config/world-limits.d.ts +3 -0
  36. package/lib/config/world-limits.d.ts.map +1 -1
  37. package/lib/data/entities.d.ts +5 -0
  38. package/lib/data/entities.d.ts.map +1 -0
  39. package/lib/enums/entity-kind.enum.d.ts +5 -0
  40. package/lib/enums/entity-kind.enum.d.ts.map +1 -0
  41. package/lib/enums/inventory-section.enum.d.ts +5 -0
  42. package/lib/enums/inventory-section.enum.d.ts.map +1 -0
  43. package/lib/interfaces/entity-inventory.interface.d.ts +9 -0
  44. package/lib/interfaces/entity-inventory.interface.d.ts.map +1 -0
  45. package/lib/interfaces/entity-type.interface.d.ts +8 -0
  46. package/lib/interfaces/entity-type.interface.d.ts.map +1 -0
  47. package/lib/interfaces/entity.interface.d.ts +17 -0
  48. package/lib/interfaces/entity.interface.d.ts.map +1 -0
  49. package/lib/interfaces/world-store.interface.d.ts +7 -2
  50. package/lib/interfaces/world-store.interface.d.ts.map +1 -1
  51. package/lib/resources/dimension-resources.d.ts.map +1 -1
  52. package/lib/resources/entity-resources.d.ts +3 -0
  53. package/lib/resources/entity-resources.d.ts.map +1 -0
  54. package/lib/resources/index.d.ts.map +1 -1
  55. package/lib/schemas/coordinate-schema.d.ts +5 -0
  56. package/lib/schemas/coordinate-schema.d.ts.map +1 -1
  57. package/lib/schemas/dimension-schema.d.ts +1 -0
  58. package/lib/schemas/dimension-schema.d.ts.map +1 -1
  59. package/lib/schemas/entity-schema.d.ts +134 -0
  60. package/lib/schemas/entity-schema.d.ts.map +1 -0
  61. package/lib/tools/block-tools.d.ts.map +1 -1
  62. package/lib/tools/entity-tools.d.ts +4 -0
  63. package/lib/tools/entity-tools.d.ts.map +1 -0
  64. package/lib/tools/index.d.ts.map +1 -1
  65. package/lib/tools/result.d.ts +2 -6
  66. package/lib/tools/result.d.ts.map +1 -1
  67. package/lib/tools/world-tools.d.ts +4 -0
  68. package/lib/tools/world-tools.d.ts.map +1 -0
  69. package/lib/utils/logger.d.ts +3 -0
  70. package/lib/utils/logger.d.ts.map +1 -0
  71. package/lib/world/block-states.d.ts +2 -0
  72. package/lib/world/block-states.d.ts.map +1 -1
  73. package/lib/world/entity-inventory.d.ts +16 -0
  74. package/lib/world/entity-inventory.d.ts.map +1 -0
  75. package/lib/world/item-spec.d.ts +10 -0
  76. package/lib/world/item-spec.d.ts.map +1 -0
  77. package/lib/world/simulated-dimension.d.ts +29 -0
  78. package/lib/world/simulated-dimension.d.ts.map +1 -1
  79. package/lib/world/stores/file-world-store.d.ts +3 -3
  80. package/lib/world/stores/file-world-store.d.ts.map +1 -1
  81. package/lib/world/stores/memory-world-store.d.ts +3 -4
  82. package/lib/world/stores/memory-world-store.d.ts.map +1 -1
  83. package/lib/world/stores/serialized-block.d.ts.map +1 -1
  84. package/lib/world/stores/serialized-entity.d.ts +24 -0
  85. package/lib/world/stores/serialized-entity.d.ts.map +1 -0
  86. package/lib/world/world.d.ts +3 -0
  87. package/lib/world/world.d.ts.map +1 -1
  88. package/package.json +113 -109
@@ -0,0 +1,5 @@
1
+ export var EEntityKind;
2
+ (function (EEntityKind) {
3
+ EEntityKind["Entity"] = "entity";
4
+ EEntityKind["Player"] = "player";
5
+ })(EEntityKind || (EEntityKind = {}));
@@ -0,0 +1,5 @@
1
+ export var EInventorySection;
2
+ (function (EInventorySection) {
3
+ EInventorySection["Hotbar"] = "hotbar";
4
+ EInventorySection["Inventory"] = "inventory";
5
+ })(EInventorySection || (EInventorySection = {}));
package/build/index.js CHANGED
@@ -3,6 +3,7 @@ import { serveStdio } from '@modelcontextprotocol/server/stdio';
3
3
  import { createServer } from './app/server.js';
4
4
  import { WORLD_STORAGE } from './config/storage.js';
5
5
  import { toErrorMessage } from './utils/error-message.js';
6
+ import { logger } from './utils/logger.js';
6
7
  import { createWorldStore } from './world/stores/index.js';
7
8
  import { World } from './world/world.js';
8
9
  async function main() {
@@ -19,14 +20,14 @@ async function main() {
19
20
  await world.flush();
20
21
  }
21
22
  catch (error) {
22
- console.error(`[lzimul-minecraft-mcp] Failed to flush world state on shutdown: ${toErrorMessage(error)}`);
23
+ logger.error(`Failed to flush world state on shutdown: ${toErrorMessage(error)}`);
23
24
  code = 1;
24
25
  }
25
26
  try {
26
27
  await handle.close();
27
28
  }
28
29
  catch (error) {
29
- console.error(`[lzimul-minecraft-mcp] Failed to close the MCP transport: ${toErrorMessage(error)}`);
30
+ logger.error(`Failed to close the MCP transport: ${toErrorMessage(error)}`);
30
31
  code = 1;
31
32
  }
32
33
  process.exit(code);
@@ -42,6 +43,6 @@ try {
42
43
  await main();
43
44
  }
44
45
  catch (error) {
45
- console.error(`[lzimul-minecraft-mcp] Fatal: ${toErrorMessage(error)}`);
46
+ logger.error(`Fatal: ${toErrorMessage(error)}`);
46
47
  process.exitCode = 1;
47
48
  }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -1,6 +1,22 @@
1
1
  import { toBlockInfo } from '../schemas/block-schema.js';
2
+ import { toEntityInfo } from '../schemas/entity-schema.js';
2
3
  import { errorResource } from './error-content.js';
3
4
  import { World } from '../world/world.js';
5
+ function dimensionSummary(world, dimension) {
6
+ const simulated = world.getDimension(dimension.type);
7
+ return {
8
+ key: dimension.key,
9
+ type: dimension.type,
10
+ displayName: dimension.displayName,
11
+ minY: dimension.minY,
12
+ maxY: dimension.maxY,
13
+ hasSkylight: dimension.hasSkylight,
14
+ modifiedBlockCount: simulated.modifiedBlockCount,
15
+ entityCount: simulated.entityCount,
16
+ playerCount: simulated.playerCount,
17
+ uri: `lzimul://minecraft/dimensions/${dimension.type}`
18
+ };
19
+ }
4
20
  function dimensionState(world, dimension) {
5
21
  const simulated = world.getDimension(dimension.type);
6
22
  return {
@@ -11,21 +27,22 @@ function dimensionState(world, dimension) {
11
27
  maxY: dimension.maxY,
12
28
  hasSkylight: dimension.hasSkylight,
13
29
  modifiedBlockCount: simulated.modifiedBlockCount,
14
- placedBlocks: simulated.placedBlocks
15
- .map(placed => ({
30
+ entityCount: simulated.entityCount,
31
+ playerCount: simulated.playerCount,
32
+ placedBlocks: simulated.sortedPlacedBlocks.map(placed => ({
16
33
  x: placed.x,
17
34
  y: placed.y,
18
35
  z: placed.z,
19
36
  block: toBlockInfo(placed.block),
20
37
  states: placed.states
21
- }))
22
- .toSorted((a, b) => a.x - b.x || a.y - b.y || a.z - b.z)
38
+ })),
39
+ entities: simulated.sortedPlacedEntities.map(toEntityInfo)
23
40
  };
24
41
  }
25
42
  export function registerDimensionIndexResource(server, world) {
26
43
  server.registerResource('minecraft/dimensions', 'lzimul://minecraft/dimensions', {
27
44
  title: 'Minecraft Dimension List',
28
- description: 'Index of the persisted Minecraft dimensions',
45
+ description: 'Index of the persisted Minecraft dimensions with their block counts (read the per-dimension resource for placed blocks)',
29
46
  mimeType: 'application/json'
30
47
  }, async (uri) => {
31
48
  try {
@@ -34,7 +51,7 @@ export function registerDimensionIndexResource(server, world) {
34
51
  {
35
52
  uri: uri.href,
36
53
  mimeType: 'application/json',
37
- text: JSON.stringify(World.listDimensions().map(dimension => dimensionState(world, dimension)), null, 2)
54
+ text: JSON.stringify(World.listDimensions().map(dimension => dimensionSummary(world, dimension)), null, 2)
38
55
  }
39
56
  ]
40
57
  };
@@ -0,0 +1,17 @@
1
+ import { MINECRAFT_ENTITY_COUNT, MINECRAFT_ENTITY_TYPES } from '../data/entities.js';
2
+ import { MINECRAFT_DATA_VERSION } from '../data/version.js';
3
+ import { registerDataEntryResources, registerDataIndexResource } from './data-resources.js';
4
+ export function registerEntityResources(server) {
5
+ registerDataIndexResource(server, {
6
+ name: 'minecraft/entities',
7
+ uri: 'lzimul://minecraft/entities',
8
+ title: 'Minecraft Entity Type List',
9
+ description: `Index of all ${MINECRAFT_ENTITY_COUNT} Minecraft entity types (Java Edition ${MINECRAFT_DATA_VERSION})`,
10
+ entries: MINECRAFT_ENTITY_TYPES
11
+ });
12
+ registerDataEntryResources(server, {
13
+ pathPrefix: 'entities',
14
+ category: 'entity type',
15
+ entries: MINECRAFT_ENTITY_TYPES
16
+ });
17
+ }
@@ -1,9 +1,11 @@
1
1
  import { registerBlockResources } from './block-resources.js';
2
2
  import { registerDimensionIndexResource, registerDimensionResources } from './dimension-resources.js';
3
+ import { registerEntityResources } from './entity-resources.js';
3
4
  import { registerItemResources } from './item-resources.js';
4
5
  export function registerResources(server, world) {
5
6
  registerBlockResources(server);
6
7
  registerItemResources(server);
8
+ registerEntityResources(server);
7
9
  registerDimensionIndexResource(server, world);
8
10
  registerDimensionResources(server, world);
9
11
  }
@@ -10,3 +10,8 @@ export const verticalCoordinate = z
10
10
  .int()
11
11
  .min(WORLD_LIMITS.minVertical)
12
12
  .max(WORLD_LIMITS.maxVertical);
13
+ export const blockPositionShape = {
14
+ x: horizontalCoordinate.describe('X coordinate (-30000000 to 30000000)'),
15
+ y: verticalCoordinate.describe('Y coordinate, within the dimension height range'),
16
+ z: horizontalCoordinate.describe('Z coordinate (-30000000 to 30000000)')
17
+ };
@@ -4,3 +4,4 @@ export const dimensionSchema = z
4
4
  .enum(EDimensionType)
5
5
  .default(EDimensionType.Overworld);
6
6
  export const dimensionTypeSchema = z.enum(EDimensionType);
7
+ export const dimensionField = dimensionSchema.describe('Dimension: "overworld" (default), "the_nether" or "the_end"');
@@ -0,0 +1,95 @@
1
+ import * as z from 'zod/v4';
2
+ import { EEntityKind } from '../enums/entity-kind.enum.js';
3
+ import { EInventorySection } from '../enums/inventory-section.enum.js';
4
+ import { HOTBAR_SLOT_COUNT, INVENTORY_SLOT_COUNT, inventoryUsedSlots, sectionUsedSlots, TOTAL_SLOT_COUNT } from '../world/entity-inventory.js';
5
+ export const itemStackSchema = z.object({
6
+ item: z.string(),
7
+ count: z.number().int().min(1)
8
+ });
9
+ export const entityInventorySchema = z.object({
10
+ hotbar: z.array(itemStackSchema.nullable()),
11
+ inventory: z.array(itemStackSchema.nullable())
12
+ });
13
+ export const entitySummarySchema = z.object({
14
+ id: z.string(),
15
+ kind: z.enum(EEntityKind),
16
+ type: z.string(),
17
+ typeDisplayName: z.string(),
18
+ category: z.string(),
19
+ name: z.string(),
20
+ x: z.number(),
21
+ y: z.number(),
22
+ z: z.number(),
23
+ dimension: z.string()
24
+ });
25
+ export const entityInfoSchema = entitySummarySchema.extend({
26
+ createdAt: z.string(),
27
+ usedSlots: z.number().int(),
28
+ totalSlots: z.number().int(),
29
+ inventory: entityInventorySchema
30
+ });
31
+ export const inventoryViewSchema = z.object({
32
+ entity: entitySummarySchema,
33
+ hotbar: z.array(itemStackSchema.nullable()),
34
+ inventory: z.array(itemStackSchema.nullable()),
35
+ hotbarUsed: z.number().int(),
36
+ inventoryUsed: z.number().int(),
37
+ usedSlots: z.number().int(),
38
+ totalSlots: z.number().int()
39
+ });
40
+ export const inventoryChangeSchema = inventoryViewSchema.extend({
41
+ section: z.enum(EInventorySection),
42
+ slot: z.number().int(),
43
+ previous: itemStackSchema.nullable(),
44
+ item: itemStackSchema.nullable()
45
+ });
46
+ export function toItemStackInfo(stack) {
47
+ return { item: stack.item, count: stack.count };
48
+ }
49
+ function toNullableItemStackInfo(stack) {
50
+ return stack ? toItemStackInfo(stack) : null;
51
+ }
52
+ export function toEntityInventoryInfo(entity) {
53
+ return {
54
+ hotbar: entity.inventory.hotbar.map(toNullableItemStackInfo),
55
+ inventory: entity.inventory.inventory.map(toNullableItemStackInfo)
56
+ };
57
+ }
58
+ export function toEntitySummary(entity) {
59
+ return {
60
+ id: entity.id,
61
+ kind: entity.kind,
62
+ type: entity.type.key,
63
+ typeDisplayName: entity.type.displayName,
64
+ category: entity.type.category,
65
+ name: entity.name,
66
+ x: entity.x,
67
+ y: entity.y,
68
+ z: entity.z,
69
+ dimension: entity.dimension
70
+ };
71
+ }
72
+ export function toEntityInfo(entity) {
73
+ return {
74
+ ...toEntitySummary(entity),
75
+ createdAt: entity.createdAt,
76
+ usedSlots: inventoryUsedSlots(entity.inventory),
77
+ totalSlots: TOTAL_SLOT_COUNT,
78
+ inventory: toEntityInventoryInfo(entity)
79
+ };
80
+ }
81
+ export function toInventoryView(entity) {
82
+ return {
83
+ entity: toEntitySummary(entity),
84
+ hotbar: entity.inventory.hotbar.map(toNullableItemStackInfo),
85
+ inventory: entity.inventory.inventory.map(toNullableItemStackInfo),
86
+ hotbarUsed: sectionUsedSlots(entity.inventory, EInventorySection.Hotbar),
87
+ inventoryUsed: sectionUsedSlots(entity.inventory, EInventorySection.Inventory),
88
+ usedSlots: inventoryUsedSlots(entity.inventory),
89
+ totalSlots: TOTAL_SLOT_COUNT
90
+ };
91
+ }
92
+ export const INVENTORY_LIMITS = {
93
+ hotbar: HOTBAR_SLOT_COUNT,
94
+ inventory: INVENTORY_SLOT_COUNT
95
+ };
@@ -1,25 +1,20 @@
1
1
  import * as z from 'zod/v4';
2
2
  import { WORLD_LIMITS } from '../config/world-limits.js';
3
3
  import { blockInfoSchema, toBlockInfo } from '../schemas/block-schema.js';
4
- import { horizontalCoordinate, verticalCoordinate } from '../schemas/coordinate-schema.js';
5
- import { dimensionSchema } from '../schemas/dimension-schema.js';
4
+ import { blockPositionShape } from '../schemas/coordinate-schema.js';
5
+ import { dimensionField } from '../schemas/dimension-schema.js';
6
6
  import { toErrorMessage } from '../utils/error-message.js';
7
7
  import { formatStates } from '../world/block-states.js';
8
8
  import { parseBlockSpec } from '../world/block-spec.js';
9
- import { toolError } from './result.js';
9
+ import { coordinateRangeError, toolError } from './result.js';
10
10
  const blockStatesSchema = z.record(z.string(), z.string());
11
- function rangeError(dimension, x, y, z) {
12
- return toolError(`Coordinates (${x}, ${y}, ${z}) are out of range in ${dimension.displayName} (Y: ${dimension.minY} to ${dimension.maxY - 1}).`);
13
- }
14
11
  export function registerBlockTools(server, world) {
15
12
  server.registerTool('getBlock', {
16
13
  title: 'Get Block',
17
14
  description: 'Get the block at the given coordinates in the given dimension, including its properties (name, hardness, light level, etc.) and its block states (for example facing=south). Each dimension has its own persisted world state; unmodified coordinates return air (minecraft:air).',
18
15
  inputSchema: z.object({
19
- x: horizontalCoordinate.describe('X coordinate (-30000000 to 30000000)'),
20
- y: verticalCoordinate.describe('Y coordinate, within the dimension height range'),
21
- z: horizontalCoordinate.describe('Z coordinate (-30000000 to 30000000)'),
22
- dimension: dimensionSchema.describe('Dimension: "overworld" (default), "the_nether" or "the_end"')
16
+ ...blockPositionShape,
17
+ dimension: dimensionField
23
18
  }),
24
19
  outputSchema: z.object({
25
20
  x: z.number(),
@@ -38,10 +33,9 @@ export function registerBlockTools(server, world) {
38
33
  try {
39
34
  const dimension = world.getDimension(dimensionType);
40
35
  if (!dimension.contains(x, y, z)) {
41
- return rangeError(dimension.info, x, y, z);
36
+ return coordinateRangeError(dimension.info, x, y, z);
42
37
  }
43
- const block = dimension.getBlock(x, y, z);
44
- const states = dimension.getStates(x, y, z);
38
+ const { block, states } = dimension.getBlockState(x, y, z);
45
39
  return {
46
40
  content: [
47
41
  {
@@ -67,15 +61,13 @@ export function registerBlockTools(server, world) {
67
61
  title: 'Set Block',
68
62
  description: 'Place a block at the given coordinates in the given dimension and return the replaced block. Block states are supported: "minecraft:oak_stairs[facing=south,half=bottom]" or "oak_stairs[facing=south]". Each dimension has its own persisted world state; setting air removes the block. blockType is a block id such as "minecraft:stone" or "stone"; see the resource lzimul://minecraft/blocks for the full list.',
69
63
  inputSchema: z.object({
70
- x: horizontalCoordinate.describe('X coordinate (-30000000 to 30000000)'),
71
- y: verticalCoordinate.describe('Y coordinate, within the dimension height range'),
72
- z: horizontalCoordinate.describe('Z coordinate (-30000000 to 30000000)'),
64
+ ...blockPositionShape,
73
65
  blockType: z
74
66
  .string()
75
67
  .min(1)
76
68
  .max(256)
77
69
  .describe('Block id with optional states, such as "minecraft:stone" or "minecraft:oak_stairs[facing=south]"'),
78
- dimension: dimensionSchema.describe('Dimension: "overworld" (default), "the_nether" or "the_end"')
70
+ dimension: dimensionField
79
71
  }),
80
72
  outputSchema: z.object({
81
73
  x: z.number(),
@@ -96,7 +88,7 @@ export function registerBlockTools(server, world) {
96
88
  try {
97
89
  const dimension = world.getDimension(dimensionType);
98
90
  if (!dimension.contains(x, y, z)) {
99
- return rangeError(dimension.info, x, y, z);
91
+ return coordinateRangeError(dimension.info, x, y, z);
100
92
  }
101
93
  const spec = parseBlockSpec(blockType);
102
94
  if (!spec.ok) {
@@ -110,8 +102,7 @@ export function registerBlockTools(server, world) {
110
102
  if (!isAir && !dimension.canSetBlock(x, y, z)) {
111
103
  return toolError(`The ${dimension.info.displayName} world already holds the maximum of ${WORLD_LIMITS.maxModifiedBlocks} modified blocks. Remove blocks with "minecraft:air" before adding more.`);
112
104
  }
113
- const previousBlock = dimension.getBlock(x, y, z);
114
- const previousStates = dimension.getStates(x, y, z);
105
+ const previous = dimension.getBlockState(x, y, z);
115
106
  dimension.setBlock(x, y, z, spec.block, spec.states);
116
107
  try {
117
108
  await dimension.flush();
@@ -123,7 +114,7 @@ export function registerBlockTools(server, world) {
123
114
  content: [
124
115
  {
125
116
  type: 'text',
126
- text: `Block set at (${x}, ${y}, ${z}) in ${dimension.info.displayName}: ${previousBlock.displayName} (${previousBlock.key})${formatStates(previousStates)} -> ${spec.block.displayName} (${spec.block.key})${formatStates(spec.states)}`
117
+ text: `Block set at (${x}, ${y}, ${z}) in ${dimension.info.displayName}: ${previous.block.displayName} (${previous.block.key})${formatStates(previous.states)} -> ${spec.block.displayName} (${spec.block.key})${formatStates(spec.states)}`
127
118
  }
128
119
  ],
129
120
  structuredContent: {
@@ -133,8 +124,8 @@ export function registerBlockTools(server, world) {
133
124
  dimension: dimension.info.key,
134
125
  block: toBlockInfo(spec.block),
135
126
  states: spec.states,
136
- previous: toBlockInfo(previousBlock),
137
- previousStates
127
+ previous: toBlockInfo(previous.block),
128
+ previousStates: previous.states
138
129
  }
139
130
  };
140
131
  }