@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.
- package/LICENSE +21 -0
- package/README.md +163 -2
- package/SECURITY.md +21 -0
- package/build/app/server.js +10 -0
- package/build/config/dimensions.js +27 -0
- package/build/config/server-info.js +25 -0
- package/build/config/storage.js +15 -0
- package/build/config/world-limits.js +7 -0
- package/build/data/blocks.js +17532 -0
- package/build/data/items.js +11194 -0
- package/build/data/version.js +1 -0
- package/build/enums/bounding-box.enum.js +5 -0
- package/build/enums/dimension-type.enum.js +6 -0
- package/build/enums/icon-theme.enum.js +5 -0
- package/build/index.js +47 -0
- package/build/interfaces/block-states.interface.js +1 -0
- package/build/interfaces/block.interface.js +1 -0
- package/build/interfaces/data-entry.interface.js +1 -0
- package/build/interfaces/dimension.interface.js +1 -0
- package/build/interfaces/item.interface.js +1 -0
- package/build/interfaces/placed-block.interface.js +1 -0
- package/build/interfaces/world-store.interface.js +1 -0
- package/build/resources/block-resources.js +17 -0
- package/build/resources/data-resources.js +55 -0
- package/build/resources/dimension-resources.js +70 -0
- package/build/resources/error-content.js +12 -0
- package/build/resources/index.js +9 -0
- package/build/resources/item-resources.js +17 -0
- package/build/schemas/block-schema.js +27 -0
- package/build/schemas/coordinate-schema.js +12 -0
- package/build/schemas/dimension-schema.js +6 -0
- package/build/schemas/item-schema.js +28 -0
- package/build/tools/block-tools.js +145 -0
- package/build/tools/index.js +6 -0
- package/build/tools/item-tools.js +50 -0
- package/build/tools/result.js +11 -0
- package/build/utils/error-message.js +25 -0
- package/build/world/block-spec.js +74 -0
- package/build/world/block-states.js +43 -0
- package/build/world/simulated-dimension.js +129 -0
- package/build/world/stores/file-world-store.js +103 -0
- package/build/world/stores/index.js +9 -0
- package/build/world/stores/memory-world-store.js +14 -0
- package/build/world/stores/serialized-block.js +53 -0
- package/build/world/world.js +39 -0
- package/lib/app/server.d.ts +4 -0
- package/lib/app/server.d.ts.map +1 -0
- package/lib/config/dimensions.d.ts +3 -0
- package/lib/config/dimensions.d.ts.map +1 -0
- package/lib/config/server-info.d.ts +15 -0
- package/lib/config/server-info.d.ts.map +1 -0
- package/lib/config/storage.d.ts +8 -0
- package/lib/config/storage.d.ts.map +1 -0
- package/lib/config/world-limits.d.ts +8 -0
- package/lib/config/world-limits.d.ts.map +1 -0
- package/lib/data/blocks.d.ts +5 -0
- package/lib/data/blocks.d.ts.map +1 -0
- package/lib/data/items.d.ts +5 -0
- package/lib/data/items.d.ts.map +1 -0
- package/lib/data/version.d.ts +2 -0
- package/lib/data/version.d.ts.map +1 -0
- package/lib/enums/bounding-box.enum.d.ts +5 -0
- package/lib/enums/bounding-box.enum.d.ts.map +1 -0
- package/lib/enums/dimension-type.enum.d.ts +6 -0
- package/lib/enums/dimension-type.enum.d.ts.map +1 -0
- package/lib/enums/icon-theme.enum.d.ts +5 -0
- package/lib/enums/icon-theme.enum.d.ts.map +1 -0
- package/lib/index.d.ts +3 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/interfaces/block-states.interface.d.ts +2 -0
- package/lib/interfaces/block-states.interface.d.ts.map +1 -0
- package/lib/interfaces/block.interface.d.ts +14 -0
- package/lib/interfaces/block.interface.d.ts.map +1 -0
- package/lib/interfaces/data-entry.interface.d.ts +7 -0
- package/lib/interfaces/data-entry.interface.d.ts.map +1 -0
- package/lib/interfaces/dimension.interface.d.ts +10 -0
- package/lib/interfaces/dimension.interface.d.ts.map +1 -0
- package/lib/interfaces/item.interface.d.ts +8 -0
- package/lib/interfaces/item.interface.d.ts.map +1 -0
- package/lib/interfaces/placed-block.interface.d.ts +10 -0
- package/lib/interfaces/placed-block.interface.d.ts.map +1 -0
- package/lib/interfaces/world-store.interface.d.ts +7 -0
- package/lib/interfaces/world-store.interface.d.ts.map +1 -0
- package/lib/resources/block-resources.d.ts +3 -0
- package/lib/resources/block-resources.d.ts.map +1 -0
- package/lib/resources/data-resources.d.ts +15 -0
- package/lib/resources/data-resources.d.ts.map +1 -0
- package/lib/resources/dimension-resources.d.ts +5 -0
- package/lib/resources/dimension-resources.d.ts.map +1 -0
- package/lib/resources/error-content.d.ts +8 -0
- package/lib/resources/error-content.d.ts.map +1 -0
- package/lib/resources/index.d.ts +4 -0
- package/lib/resources/index.d.ts.map +1 -0
- package/lib/resources/item-resources.d.ts +3 -0
- package/lib/resources/item-resources.d.ts.map +1 -0
- package/lib/schemas/block-schema.d.ts +17 -0
- package/lib/schemas/block-schema.d.ts.map +1 -0
- package/lib/schemas/coordinate-schema.d.ts +4 -0
- package/lib/schemas/coordinate-schema.d.ts.map +1 -0
- package/lib/schemas/dimension-schema.d.ts +5 -0
- package/lib/schemas/dimension-schema.d.ts.map +1 -0
- package/lib/schemas/item-schema.d.ts +14 -0
- package/lib/schemas/item-schema.d.ts.map +1 -0
- package/lib/tools/block-tools.d.ts +4 -0
- package/lib/tools/block-tools.d.ts.map +1 -0
- package/lib/tools/index.d.ts +4 -0
- package/lib/tools/index.d.ts.map +1 -0
- package/lib/tools/item-tools.d.ts +3 -0
- package/lib/tools/item-tools.d.ts.map +1 -0
- package/lib/tools/result.d.ts +14 -0
- package/lib/tools/result.d.ts.map +1 -0
- package/lib/utils/error-message.d.ts +2 -0
- package/lib/utils/error-message.d.ts.map +1 -0
- package/lib/world/block-spec.d.ts +12 -0
- package/lib/world/block-spec.d.ts.map +1 -0
- package/lib/world/block-states.d.ts +8 -0
- package/lib/world/block-states.d.ts.map +1 -0
- package/lib/world/simulated-dimension.d.ts +27 -0
- package/lib/world/simulated-dimension.d.ts.map +1 -0
- package/lib/world/stores/file-world-store.d.ts +14 -0
- package/lib/world/stores/file-world-store.d.ts.map +1 -0
- package/lib/world/stores/index.d.ts +3 -0
- package/lib/world/stores/index.d.ts.map +1 -0
- package/lib/world/stores/memory-world-store.d.ts +9 -0
- package/lib/world/stores/memory-world-store.d.ts.map +1 -0
- package/lib/world/stores/serialized-block.d.ts +12 -0
- package/lib/world/stores/serialized-block.d.ts.map +1 -0
- package/lib/world/world.d.ts +13 -0
- package/lib/world/world.d.ts.map +1 -0
- package/package.json +107 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const MINECRAFT_DATA_VERSION = '26.1';
|
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,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,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,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
|
+
}
|