@lzimul/minecraft-mcp 0.0.0-stage → 1.0.1

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 (130) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +163 -2
  3. package/SECURITY.md +21 -0
  4. package/build/app/server.js +10 -0
  5. package/build/config/dimensions.js +27 -0
  6. package/build/config/server-info.js +25 -0
  7. package/build/config/storage.js +15 -0
  8. package/build/config/world-limits.js +7 -0
  9. package/build/data/blocks.js +17532 -0
  10. package/build/data/items.js +11194 -0
  11. package/build/data/version.js +1 -0
  12. package/build/enums/bounding-box.enum.js +5 -0
  13. package/build/enums/dimension-type.enum.js +6 -0
  14. package/build/enums/icon-theme.enum.js +5 -0
  15. package/build/index.js +47 -0
  16. package/build/interfaces/block-states.interface.js +1 -0
  17. package/build/interfaces/block.interface.js +1 -0
  18. package/build/interfaces/data-entry.interface.js +1 -0
  19. package/build/interfaces/dimension.interface.js +1 -0
  20. package/build/interfaces/item.interface.js +1 -0
  21. package/build/interfaces/placed-block.interface.js +1 -0
  22. package/build/interfaces/world-store.interface.js +1 -0
  23. package/build/resources/block-resources.js +17 -0
  24. package/build/resources/data-resources.js +55 -0
  25. package/build/resources/dimension-resources.js +70 -0
  26. package/build/resources/error-content.js +12 -0
  27. package/build/resources/index.js +9 -0
  28. package/build/resources/item-resources.js +17 -0
  29. package/build/schemas/block-schema.js +27 -0
  30. package/build/schemas/coordinate-schema.js +12 -0
  31. package/build/schemas/dimension-schema.js +6 -0
  32. package/build/schemas/item-schema.js +28 -0
  33. package/build/tools/block-tools.js +145 -0
  34. package/build/tools/index.js +6 -0
  35. package/build/tools/item-tools.js +50 -0
  36. package/build/tools/result.js +11 -0
  37. package/build/utils/error-message.js +25 -0
  38. package/build/world/block-spec.js +74 -0
  39. package/build/world/block-states.js +43 -0
  40. package/build/world/simulated-dimension.js +129 -0
  41. package/build/world/stores/file-world-store.js +103 -0
  42. package/build/world/stores/index.js +9 -0
  43. package/build/world/stores/memory-world-store.js +14 -0
  44. package/build/world/stores/serialized-block.js +53 -0
  45. package/build/world/world.js +39 -0
  46. package/lib/app/server.d.ts +4 -0
  47. package/lib/app/server.d.ts.map +1 -0
  48. package/lib/config/dimensions.d.ts +3 -0
  49. package/lib/config/dimensions.d.ts.map +1 -0
  50. package/lib/config/server-info.d.ts +15 -0
  51. package/lib/config/server-info.d.ts.map +1 -0
  52. package/lib/config/storage.d.ts +8 -0
  53. package/lib/config/storage.d.ts.map +1 -0
  54. package/lib/config/world-limits.d.ts +8 -0
  55. package/lib/config/world-limits.d.ts.map +1 -0
  56. package/lib/data/blocks.d.ts +5 -0
  57. package/lib/data/blocks.d.ts.map +1 -0
  58. package/lib/data/items.d.ts +5 -0
  59. package/lib/data/items.d.ts.map +1 -0
  60. package/lib/data/version.d.ts +2 -0
  61. package/lib/data/version.d.ts.map +1 -0
  62. package/lib/enums/bounding-box.enum.d.ts +5 -0
  63. package/lib/enums/bounding-box.enum.d.ts.map +1 -0
  64. package/lib/enums/dimension-type.enum.d.ts +6 -0
  65. package/lib/enums/dimension-type.enum.d.ts.map +1 -0
  66. package/lib/enums/icon-theme.enum.d.ts +5 -0
  67. package/lib/enums/icon-theme.enum.d.ts.map +1 -0
  68. package/lib/index.d.ts +3 -0
  69. package/lib/index.d.ts.map +1 -0
  70. package/lib/interfaces/block-states.interface.d.ts +2 -0
  71. package/lib/interfaces/block-states.interface.d.ts.map +1 -0
  72. package/lib/interfaces/block.interface.d.ts +14 -0
  73. package/lib/interfaces/block.interface.d.ts.map +1 -0
  74. package/lib/interfaces/data-entry.interface.d.ts +7 -0
  75. package/lib/interfaces/data-entry.interface.d.ts.map +1 -0
  76. package/lib/interfaces/dimension.interface.d.ts +10 -0
  77. package/lib/interfaces/dimension.interface.d.ts.map +1 -0
  78. package/lib/interfaces/item.interface.d.ts +8 -0
  79. package/lib/interfaces/item.interface.d.ts.map +1 -0
  80. package/lib/interfaces/placed-block.interface.d.ts +10 -0
  81. package/lib/interfaces/placed-block.interface.d.ts.map +1 -0
  82. package/lib/interfaces/world-store.interface.d.ts +7 -0
  83. package/lib/interfaces/world-store.interface.d.ts.map +1 -0
  84. package/lib/resources/block-resources.d.ts +3 -0
  85. package/lib/resources/block-resources.d.ts.map +1 -0
  86. package/lib/resources/data-resources.d.ts +15 -0
  87. package/lib/resources/data-resources.d.ts.map +1 -0
  88. package/lib/resources/dimension-resources.d.ts +5 -0
  89. package/lib/resources/dimension-resources.d.ts.map +1 -0
  90. package/lib/resources/error-content.d.ts +8 -0
  91. package/lib/resources/error-content.d.ts.map +1 -0
  92. package/lib/resources/index.d.ts +4 -0
  93. package/lib/resources/index.d.ts.map +1 -0
  94. package/lib/resources/item-resources.d.ts +3 -0
  95. package/lib/resources/item-resources.d.ts.map +1 -0
  96. package/lib/schemas/block-schema.d.ts +17 -0
  97. package/lib/schemas/block-schema.d.ts.map +1 -0
  98. package/lib/schemas/coordinate-schema.d.ts +4 -0
  99. package/lib/schemas/coordinate-schema.d.ts.map +1 -0
  100. package/lib/schemas/dimension-schema.d.ts +5 -0
  101. package/lib/schemas/dimension-schema.d.ts.map +1 -0
  102. package/lib/schemas/item-schema.d.ts +14 -0
  103. package/lib/schemas/item-schema.d.ts.map +1 -0
  104. package/lib/tools/block-tools.d.ts +4 -0
  105. package/lib/tools/block-tools.d.ts.map +1 -0
  106. package/lib/tools/index.d.ts +4 -0
  107. package/lib/tools/index.d.ts.map +1 -0
  108. package/lib/tools/item-tools.d.ts +3 -0
  109. package/lib/tools/item-tools.d.ts.map +1 -0
  110. package/lib/tools/result.d.ts +14 -0
  111. package/lib/tools/result.d.ts.map +1 -0
  112. package/lib/utils/error-message.d.ts +2 -0
  113. package/lib/utils/error-message.d.ts.map +1 -0
  114. package/lib/world/block-spec.d.ts +12 -0
  115. package/lib/world/block-spec.d.ts.map +1 -0
  116. package/lib/world/block-states.d.ts +8 -0
  117. package/lib/world/block-states.d.ts.map +1 -0
  118. package/lib/world/simulated-dimension.d.ts +27 -0
  119. package/lib/world/simulated-dimension.d.ts.map +1 -0
  120. package/lib/world/stores/file-world-store.d.ts +14 -0
  121. package/lib/world/stores/file-world-store.d.ts.map +1 -0
  122. package/lib/world/stores/index.d.ts +3 -0
  123. package/lib/world/stores/index.d.ts.map +1 -0
  124. package/lib/world/stores/memory-world-store.d.ts +9 -0
  125. package/lib/world/stores/memory-world-store.d.ts.map +1 -0
  126. package/lib/world/stores/serialized-block.d.ts +12 -0
  127. package/lib/world/stores/serialized-block.d.ts.map +1 -0
  128. package/lib/world/world.d.ts +13 -0
  129. package/lib/world/world.d.ts.map +1 -0
  130. package/package.json +107 -4
@@ -0,0 +1 @@
1
+ export const MINECRAFT_DATA_VERSION = '26.1';
@@ -0,0 +1,5 @@
1
+ export var EBoundingBox;
2
+ (function (EBoundingBox) {
3
+ EBoundingBox["Empty"] = "empty";
4
+ EBoundingBox["Block"] = "block";
5
+ })(EBoundingBox || (EBoundingBox = {}));
@@ -0,0 +1,6 @@
1
+ export var EDimensionType;
2
+ (function (EDimensionType) {
3
+ EDimensionType["Overworld"] = "overworld";
4
+ EDimensionType["TheNether"] = "the_nether";
5
+ EDimensionType["TheEnd"] = "the_end";
6
+ })(EDimensionType || (EDimensionType = {}));
@@ -0,0 +1,5 @@
1
+ export var EIconTheme;
2
+ (function (EIconTheme) {
3
+ EIconTheme["Light"] = "light";
4
+ EIconTheme["Dark"] = "dark";
5
+ })(EIconTheme || (EIconTheme = {}));
package/build/index.js ADDED
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env node
2
+ import { serveStdio } from '@modelcontextprotocol/server/stdio';
3
+ import { createServer } from './app/server.js';
4
+ import { WORLD_STORAGE } from './config/storage.js';
5
+ import { toErrorMessage } from './utils/error-message.js';
6
+ import { createWorldStore } from './world/stores/index.js';
7
+ import { World } from './world/world.js';
8
+ async function main() {
9
+ const world = await World.load(createWorldStore(), WORLD_STORAGE.writeDelayMs);
10
+ const handle = serveStdio(() => createServer(world));
11
+ let shuttingDown = false;
12
+ const shutdown = async (exitCode) => {
13
+ if (shuttingDown) {
14
+ return;
15
+ }
16
+ shuttingDown = true;
17
+ let code = exitCode;
18
+ try {
19
+ await world.flush();
20
+ }
21
+ catch (error) {
22
+ console.error(`[lzimul-minecraft-mcp] Failed to flush world state on shutdown: ${toErrorMessage(error)}`);
23
+ code = 1;
24
+ }
25
+ try {
26
+ await handle.close();
27
+ }
28
+ catch (error) {
29
+ console.error(`[lzimul-minecraft-mcp] Failed to close the MCP transport: ${toErrorMessage(error)}`);
30
+ code = 1;
31
+ }
32
+ process.exit(code);
33
+ };
34
+ process.once('SIGINT', () => {
35
+ void shutdown(0);
36
+ });
37
+ process.once('SIGTERM', () => {
38
+ void shutdown(0);
39
+ });
40
+ }
41
+ try {
42
+ await main();
43
+ }
44
+ catch (error) {
45
+ console.error(`[lzimul-minecraft-mcp] Fatal: ${toErrorMessage(error)}`);
46
+ process.exitCode = 1;
47
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ import { EBoundingBox } from '../enums/bounding-box.enum.js';
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ import { EDimensionType } from '../enums/dimension-type.enum.js';
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,17 @@
1
+ import { MINECRAFT_BLOCKS, MINECRAFT_BLOCK_COUNT } from '../data/blocks.js';
2
+ import { MINECRAFT_DATA_VERSION } from '../data/version.js';
3
+ import { registerDataEntryResources, registerDataIndexResource } from './data-resources.js';
4
+ export function registerBlockResources(server) {
5
+ registerDataIndexResource(server, {
6
+ name: 'minecraft/blocks',
7
+ uri: 'lzimul://minecraft/blocks',
8
+ title: 'Minecraft Block List',
9
+ description: `Index of all ${MINECRAFT_BLOCK_COUNT} Minecraft blocks (Java Edition ${MINECRAFT_DATA_VERSION})`,
10
+ entries: MINECRAFT_BLOCKS
11
+ });
12
+ registerDataEntryResources(server, {
13
+ pathPrefix: 'blocks',
14
+ category: 'block',
15
+ entries: MINECRAFT_BLOCKS
16
+ });
17
+ }
@@ -0,0 +1,55 @@
1
+ import { errorResource } from './error-content.js';
2
+ function lazyJsonText(build) {
3
+ let cached = '';
4
+ return () => {
5
+ if (!cached) {
6
+ cached = JSON.stringify(build(), null, 2);
7
+ }
8
+ return cached;
9
+ };
10
+ }
11
+ export function registerDataIndexResource(server, config) {
12
+ const getText = lazyJsonText(() => config.entries.map(entry => ({
13
+ id: entry.id,
14
+ key: entry.key,
15
+ name: entry.name,
16
+ displayName: entry.displayName
17
+ })));
18
+ server.registerResource(config.name, config.uri, {
19
+ title: config.title,
20
+ description: config.description,
21
+ mimeType: 'application/json'
22
+ }, async (uri) => {
23
+ try {
24
+ return {
25
+ contents: [
26
+ { uri: uri.href, mimeType: 'application/json', text: getText() }
27
+ ]
28
+ };
29
+ }
30
+ catch (error) {
31
+ return errorResource(uri.href, error);
32
+ }
33
+ });
34
+ }
35
+ export function registerDataEntryResources(server, config) {
36
+ for (const entry of config.entries) {
37
+ const getText = lazyJsonText(() => entry);
38
+ server.registerResource(`minecraft/${config.pathPrefix}/${entry.name}`, `lzimul://minecraft/${config.pathPrefix}/${entry.name}`, {
39
+ title: `${entry.displayName} (${entry.key})`,
40
+ description: `Properties of the Minecraft ${config.category} ${entry.key}`,
41
+ mimeType: 'application/json'
42
+ }, async (uri) => {
43
+ try {
44
+ return {
45
+ contents: [
46
+ { uri: uri.href, mimeType: 'application/json', text: getText() }
47
+ ]
48
+ };
49
+ }
50
+ catch (error) {
51
+ return errorResource(uri.href, error);
52
+ }
53
+ });
54
+ }
55
+ }
@@ -0,0 +1,70 @@
1
+ import { toBlockInfo } from '../schemas/block-schema.js';
2
+ import { errorResource } from './error-content.js';
3
+ import { World } from '../world/world.js';
4
+ function dimensionState(world, dimension) {
5
+ const simulated = world.getDimension(dimension.type);
6
+ return {
7
+ key: dimension.key,
8
+ type: dimension.type,
9
+ displayName: dimension.displayName,
10
+ minY: dimension.minY,
11
+ maxY: dimension.maxY,
12
+ hasSkylight: dimension.hasSkylight,
13
+ modifiedBlockCount: simulated.modifiedBlockCount,
14
+ placedBlocks: simulated.placedBlocks
15
+ .map(placed => ({
16
+ x: placed.x,
17
+ y: placed.y,
18
+ z: placed.z,
19
+ block: toBlockInfo(placed.block),
20
+ states: placed.states
21
+ }))
22
+ .toSorted((a, b) => a.x - b.x || a.y - b.y || a.z - b.z)
23
+ };
24
+ }
25
+ export function registerDimensionIndexResource(server, world) {
26
+ server.registerResource('minecraft/dimensions', 'lzimul://minecraft/dimensions', {
27
+ title: 'Minecraft Dimension List',
28
+ description: 'Index of the persisted Minecraft dimensions',
29
+ mimeType: 'application/json'
30
+ }, async (uri) => {
31
+ try {
32
+ return {
33
+ contents: [
34
+ {
35
+ uri: uri.href,
36
+ mimeType: 'application/json',
37
+ text: JSON.stringify(World.listDimensions().map(dimension => dimensionState(world, dimension)), null, 2)
38
+ }
39
+ ]
40
+ };
41
+ }
42
+ catch (error) {
43
+ return errorResource(uri.href, error);
44
+ }
45
+ });
46
+ }
47
+ export function registerDimensionResources(server, world) {
48
+ for (const dimension of World.listDimensions()) {
49
+ server.registerResource(`minecraft/dimensions/${dimension.type}`, `lzimul://minecraft/dimensions/${dimension.type}`, {
50
+ title: `${dimension.displayName} (${dimension.key})`,
51
+ description: `Persisted state of the ${dimension.displayName} dimension`,
52
+ mimeType: 'application/json'
53
+ }, async (uri) => {
54
+ try {
55
+ return {
56
+ contents: [
57
+ {
58
+ uri: uri.href,
59
+ mimeType: 'application/json',
60
+ text: JSON.stringify(dimensionState(world, dimension), null, 2)
61
+ }
62
+ ]
63
+ };
64
+ }
65
+ catch (error) {
66
+ return errorResource(uri.href, error);
67
+ }
68
+ });
69
+ }
70
+ }
@@ -0,0 +1,12 @@
1
+ import { toErrorMessage } from '../utils/error-message.js';
2
+ export function errorResource(uri, error) {
3
+ return {
4
+ contents: [
5
+ {
6
+ uri,
7
+ mimeType: 'application/json',
8
+ text: JSON.stringify({ uri, error: toErrorMessage(error) }, null, 2)
9
+ }
10
+ ]
11
+ };
12
+ }
@@ -0,0 +1,9 @@
1
+ import { registerBlockResources } from './block-resources.js';
2
+ import { registerDimensionIndexResource, registerDimensionResources } from './dimension-resources.js';
3
+ import { registerItemResources } from './item-resources.js';
4
+ export function registerResources(server, world) {
5
+ registerBlockResources(server);
6
+ registerItemResources(server);
7
+ registerDimensionIndexResource(server, world);
8
+ registerDimensionResources(server, world);
9
+ }
@@ -0,0 +1,17 @@
1
+ import { MINECRAFT_ITEMS, MINECRAFT_ITEM_COUNT } from '../data/items.js';
2
+ import { MINECRAFT_DATA_VERSION } from '../data/version.js';
3
+ import { registerDataEntryResources, registerDataIndexResource } from './data-resources.js';
4
+ export function registerItemResources(server) {
5
+ registerDataIndexResource(server, {
6
+ name: 'minecraft/items',
7
+ uri: 'lzimul://minecraft/items',
8
+ title: 'Minecraft Item List',
9
+ description: `Index of all ${MINECRAFT_ITEM_COUNT} Minecraft items (Java Edition ${MINECRAFT_DATA_VERSION})`,
10
+ entries: MINECRAFT_ITEMS
11
+ });
12
+ registerDataEntryResources(server, {
13
+ pathPrefix: 'items',
14
+ category: 'item',
15
+ entries: MINECRAFT_ITEMS
16
+ });
17
+ }
@@ -0,0 +1,27 @@
1
+ import * as z from 'zod/v4';
2
+ export const blockInfoSchema = z.object({
3
+ key: z.string(),
4
+ name: z.string(),
5
+ displayName: z.string(),
6
+ hardness: z.number(),
7
+ resistance: z.number(),
8
+ diggable: z.boolean(),
9
+ transparent: z.boolean(),
10
+ emitLight: z.number(),
11
+ filterLight: z.number(),
12
+ boundingBox: z.string()
13
+ });
14
+ export function toBlockInfo(block) {
15
+ return {
16
+ key: block.key,
17
+ name: block.name,
18
+ displayName: block.displayName,
19
+ hardness: block.hardness,
20
+ resistance: block.resistance,
21
+ diggable: block.diggable,
22
+ transparent: block.transparent,
23
+ emitLight: block.emitLight,
24
+ filterLight: block.filterLight,
25
+ boundingBox: block.boundingBox
26
+ };
27
+ }
@@ -0,0 +1,12 @@
1
+ import * as z from 'zod/v4';
2
+ import { WORLD_LIMITS } from '../config/world-limits.js';
3
+ export const horizontalCoordinate = z
4
+ .number()
5
+ .int()
6
+ .min(WORLD_LIMITS.minHorizontal)
7
+ .max(WORLD_LIMITS.maxHorizontal);
8
+ export const verticalCoordinate = z
9
+ .number()
10
+ .int()
11
+ .min(WORLD_LIMITS.minVertical)
12
+ .max(WORLD_LIMITS.maxVertical);
@@ -0,0 +1,6 @@
1
+ import * as z from 'zod/v4';
2
+ import { EDimensionType } from '../enums/dimension-type.enum.js';
3
+ export const dimensionSchema = z
4
+ .enum(EDimensionType)
5
+ .default(EDimensionType.Overworld);
6
+ export const dimensionTypeSchema = z.enum(EDimensionType);
@@ -0,0 +1,28 @@
1
+ import * as z from 'zod/v4';
2
+ export const itemInfoSchema = z.object({
3
+ key: z.string(),
4
+ name: z.string(),
5
+ displayName: z.string(),
6
+ stackSize: z.number(),
7
+ maxDurability: z.number().optional(),
8
+ enchantCategories: z.array(z.string()).optional(),
9
+ repairWith: z.array(z.string()).optional()
10
+ });
11
+ export function toItemInfo(item) {
12
+ const info = {
13
+ key: item.key,
14
+ name: item.name,
15
+ displayName: item.displayName,
16
+ stackSize: item.stackSize
17
+ };
18
+ if (item.maxDurability) {
19
+ info.maxDurability = item.maxDurability;
20
+ }
21
+ if (item.enchantCategories) {
22
+ info.enchantCategories = [...item.enchantCategories];
23
+ }
24
+ if (item.repairWith) {
25
+ info.repairWith = [...item.repairWith];
26
+ }
27
+ return info;
28
+ }
@@ -0,0 +1,145 @@
1
+ import * as z from 'zod/v4';
2
+ import { WORLD_LIMITS } from '../config/world-limits.js';
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';
6
+ import { toErrorMessage } from '../utils/error-message.js';
7
+ import { formatStates } from '../world/block-states.js';
8
+ import { parseBlockSpec } from '../world/block-spec.js';
9
+ import { toolError } from './result.js';
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
+ export function registerBlockTools(server, world) {
15
+ server.registerTool('getBlock', {
16
+ title: 'Get Block',
17
+ 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
+ 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"')
23
+ }),
24
+ outputSchema: z.object({
25
+ x: z.number(),
26
+ y: z.number(),
27
+ z: z.number(),
28
+ dimension: z.string(),
29
+ block: blockInfoSchema,
30
+ states: blockStatesSchema
31
+ }),
32
+ annotations: {
33
+ readOnlyHint: true,
34
+ idempotentHint: true,
35
+ openWorldHint: false
36
+ }
37
+ }, async ({ x, y, z, dimension: dimensionType }) => {
38
+ try {
39
+ const dimension = world.getDimension(dimensionType);
40
+ if (!dimension.contains(x, y, z)) {
41
+ return rangeError(dimension.info, x, y, z);
42
+ }
43
+ const block = dimension.getBlock(x, y, z);
44
+ const states = dimension.getStates(x, y, z);
45
+ return {
46
+ content: [
47
+ {
48
+ type: 'text',
49
+ text: `Block at (${x}, ${y}, ${z}) in ${dimension.info.displayName} is ${block.displayName} (${block.key})${formatStates(states)}`
50
+ }
51
+ ],
52
+ structuredContent: {
53
+ x,
54
+ y,
55
+ z,
56
+ dimension: dimension.info.key,
57
+ block: toBlockInfo(block),
58
+ states
59
+ }
60
+ };
61
+ }
62
+ catch (error) {
63
+ return toolError(`getBlock failed: ${toErrorMessage(error)}`);
64
+ }
65
+ });
66
+ server.registerTool('setBlock', {
67
+ title: 'Set Block',
68
+ 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
+ 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)'),
73
+ blockType: z
74
+ .string()
75
+ .min(1)
76
+ .max(256)
77
+ .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"')
79
+ }),
80
+ outputSchema: z.object({
81
+ x: z.number(),
82
+ y: z.number(),
83
+ z: z.number(),
84
+ dimension: z.string(),
85
+ block: blockInfoSchema,
86
+ states: blockStatesSchema,
87
+ previous: blockInfoSchema,
88
+ previousStates: blockStatesSchema
89
+ }),
90
+ annotations: {
91
+ destructiveHint: true,
92
+ idempotentHint: true,
93
+ openWorldHint: false
94
+ }
95
+ }, async ({ x, y, z, blockType, dimension: dimensionType }) => {
96
+ try {
97
+ const dimension = world.getDimension(dimensionType);
98
+ if (!dimension.contains(x, y, z)) {
99
+ return rangeError(dimension.info, x, y, z);
100
+ }
101
+ const spec = parseBlockSpec(blockType);
102
+ if (!spec.ok) {
103
+ return toolError(spec.error);
104
+ }
105
+ const isAir = spec.block.key === 'minecraft:air';
106
+ const hasStates = Object.keys(spec.states).length > 0;
107
+ if (isAir && hasStates) {
108
+ return toolError('Air cannot carry block states. Use "minecraft:air" to remove a block.');
109
+ }
110
+ if (!isAir && !dimension.canSetBlock(x, y, z)) {
111
+ 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
+ }
113
+ const previousBlock = dimension.getBlock(x, y, z);
114
+ const previousStates = dimension.getStates(x, y, z);
115
+ dimension.setBlock(x, y, z, spec.block, spec.states);
116
+ try {
117
+ await dimension.flush();
118
+ }
119
+ catch (error) {
120
+ return toolError(`Block ${spec.block.key} was applied at (${x}, ${y}, ${z}) in ${dimension.info.displayName} but could not be persisted to the world store: ${toErrorMessage(error)}`);
121
+ }
122
+ return {
123
+ content: [
124
+ {
125
+ 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)}`
127
+ }
128
+ ],
129
+ structuredContent: {
130
+ x,
131
+ y,
132
+ z,
133
+ dimension: dimension.info.key,
134
+ block: toBlockInfo(spec.block),
135
+ states: spec.states,
136
+ previous: toBlockInfo(previousBlock),
137
+ previousStates
138
+ }
139
+ };
140
+ }
141
+ catch (error) {
142
+ return toolError(`setBlock failed: ${toErrorMessage(error)}`);
143
+ }
144
+ });
145
+ }
@@ -0,0 +1,6 @@
1
+ import { registerBlockTools } from './block-tools.js';
2
+ import { registerItemTools } from './item-tools.js';
3
+ export function registerTools(server, world) {
4
+ registerBlockTools(server, world);
5
+ registerItemTools(server);
6
+ }
@@ -0,0 +1,50 @@
1
+ import * as z from 'zod/v4';
2
+ import { getItemByName } from '../data/items.js';
3
+ import { itemInfoSchema, toItemInfo } from '../schemas/item-schema.js';
4
+ import { toErrorMessage } from '../utils/error-message.js';
5
+ import { toolError } from './result.js';
6
+ export function registerItemTools(server) {
7
+ server.registerTool('getItem', {
8
+ title: 'Get Item',
9
+ description: 'Get the properties of a Minecraft item by id (stack size, durability, enchantment categories, etc.). See the resource lzimul://minecraft/items for the full list.',
10
+ inputSchema: z.object({
11
+ item: z
12
+ .string()
13
+ .min(1)
14
+ .max(256)
15
+ .describe('Item id, such as "minecraft:diamond_sword" or "diamond_sword"')
16
+ }),
17
+ outputSchema: z.object({
18
+ item: itemInfoSchema
19
+ }),
20
+ annotations: {
21
+ readOnlyHint: true,
22
+ idempotentHint: true,
23
+ openWorldHint: false
24
+ }
25
+ }, async ({ item: itemId }) => {
26
+ try {
27
+ const item = getItemByName(itemId.trim().toLowerCase());
28
+ if (!item) {
29
+ return toolError(`Unknown item "${itemId}". Use an item id such as "minecraft:diamond_sword"; see the resource lzimul://minecraft/items for the full list.`);
30
+ }
31
+ const durability = item.maxDurability
32
+ ? `, durability ${item.maxDurability}`
33
+ : '';
34
+ return {
35
+ content: [
36
+ {
37
+ type: 'text',
38
+ text: `${item.displayName} (${item.key}): stack size ${item.stackSize}${durability}`
39
+ }
40
+ ],
41
+ structuredContent: {
42
+ item: toItemInfo(item)
43
+ }
44
+ };
45
+ }
46
+ catch (error) {
47
+ return toolError(`getItem failed: ${toErrorMessage(error)}`);
48
+ }
49
+ });
50
+ }
@@ -0,0 +1,11 @@
1
+ export function toolText(text) {
2
+ return {
3
+ content: [{ type: 'text', text }]
4
+ };
5
+ }
6
+ export function toolError(message) {
7
+ return {
8
+ isError: true,
9
+ content: [{ type: 'text', text: message }]
10
+ };
11
+ }
@@ -0,0 +1,25 @@
1
+ const FALLBACK_MESSAGE = 'Unknown error';
2
+ export function toErrorMessage(error) {
3
+ if (error === null || error === void 0) {
4
+ return FALLBACK_MESSAGE;
5
+ }
6
+ if (error instanceof Error) {
7
+ const message = error.message.trim();
8
+ return message.length > 0 ? message : error.name || FALLBACK_MESSAGE;
9
+ }
10
+ if (typeof error === 'string') {
11
+ return error.trim() || FALLBACK_MESSAGE;
12
+ }
13
+ if (typeof error === 'object') {
14
+ try {
15
+ const serialized = JSON.stringify(error);
16
+ return typeof serialized === 'string' && serialized !== '{}'
17
+ ? serialized
18
+ : FALLBACK_MESSAGE;
19
+ }
20
+ catch {
21
+ return FALLBACK_MESSAGE;
22
+ }
23
+ }
24
+ return String(error) || FALLBACK_MESSAGE;
25
+ }