@lzimul/minecraft-mcp 1.0.5 → 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.
package/README.md CHANGED
@@ -10,6 +10,7 @@ Minecraft Model Context Protocol (MCP) server. It exposes the complete Minecraft
10
10
  - Dimension tools for metadata, paginated block access, entity listings and raw JSON snapshots
11
11
  - Entity and player tools with deletion, movement, exact position control, and a 9-slot hotbar plus 27-slot backpack per entity, managed through `getInventory`, `setInventory`, `clearInventory` and `swapInventorySlots`
12
12
  - World state (blocks, entities, players and inventories) is persisted per dimension as JSON and survives restarts
13
+ - Parallel WebSocket service that broadcasts every tool call (name, arguments, result, error and timing) to all connected clients, with a full world snapshot sent on connect and on demand (`{"type":"world-request"}`)
13
14
  - log4js logging to stderr with chalk colours (error red, warn yellow, info green); stdout stays clean for the MCP transport
14
15
  - Structured tool output (`outputSchema` / `structuredContent`) plus human readable text
15
16
  - Every failure path returns a readable message instead of an unhandled exception
@@ -125,11 +126,15 @@ Each snapshot looks like this:
125
126
 
126
127
  ### Configuration
127
128
 
128
- | Variable | Default | Description |
129
- | ------------------- | -------------- | ------------------------------------------------------- |
130
- | `MCP_WORLD_STORAGE` | `file` | `file` for JSON snapshots, `memory` for volatile state |
131
- | `MCP_WORLD_DIR` | `./app/dimensions` | Directory that holds the per-dimension snapshots |
132
- | `MCP_LOG_LEVEL` | `info` | log4js level: `trace`, `debug`, `info`, `warn`, `error` |
129
+ | Variable | Default | Description |
130
+ | ------------------- | ------------------ | -------------------------------------------------------- |
131
+ | `MCP_WORLD_STORAGE` | `file` | `file` for JSON snapshots, `memory` for volatile state |
132
+ | `MCP_WORLD_DIR` | `./app/dimensions` | Directory that holds the per-dimension snapshots |
133
+ | `MCP_LOG_LEVEL` | `info` | log4js level: `trace`, `debug`, `info`, `warn`, `error` |
134
+ | `MCP_WS_ENABLED` | `true` | Start the WebSocket fan-out service (`false` to disable) |
135
+ | `MCP_WS_HOST` | `127.0.0.1` | Host/interface the WebSocket service binds to |
136
+ | `MCP_WS_PORT` | `8765` | TCP port for the WebSocket service |
137
+ | `MCP_WS_PATH` | `/` | URL path the WebSocket service listens on |
133
138
 
134
139
  A corrupt or oversized snapshot is moved aside as `<dimension>.json.corrupt-<uuid>` and the dimension starts empty, so a bad file never blocks startup.
135
140
 
@@ -148,6 +153,58 @@ State is accessed through `IWorldStore` (`src/interfaces/world-store.interface.t
148
153
  | `FileWorldStore` | stdio (`npm start`) | Atomic per-dimension JSON files under `MCP_WORLD_DIR` |
149
154
  | `MemoryWorldStore` | tests, `memory` | Volatile; useful for throwaway sessions |
150
155
 
156
+ ### WebSocket service
157
+
158
+ The server runs a WebSocket fan-out service alongside the stdio MCP transport. It is enabled by default on `127.0.0.1:8765` and can be reconfigured or disabled with the `MCP_WS_*` variables above. The MCP transport is unaffected: stdout stays reserved for JSON-RPC, and a WebSocket listen failure is logged but never crashes the process.
159
+
160
+ Each connected client immediately receives a **world snapshot**, then a **tool-call event** for every MCP tool invocation:
161
+
162
+ ```jsonc
163
+ // On connect (and on {"type":"world-request"}):
164
+ { "type": "world-snapshot", "timestamp": 1730000000000, "dimensions": [ /* full per-dimension state: counts, placedBlocks, entities */ ] }
165
+
166
+ // Broadcast to every client after each tool call:
167
+ {
168
+ "type": "tool-call",
169
+ "tool": "getBlock",
170
+ "arguments": { "dimension": "overworld", "x": 0, "y": 320, "z": 0 },
171
+ "result": { "isError": true, "content": [ /* ... */ ] },
172
+ "isError": true,
173
+ "error": "Coordinates (0, 320, 0) are out of range in Overworld (Y: -64 to 319).",
174
+ "startedAt": 1730000000000,
175
+ "durationMs": 1
176
+ }
177
+ ```
178
+
179
+ A client may send `{"type":"world-request"}` (or `{"type":"world"}`) to request a fresh snapshot. The service observes tool calls by wrapping `McpServer.registerTool`, so tools added in the future are broadcast automatically.
180
+
181
+ #### WebSocket TypeScript types
182
+
183
+ Package consumers can import the protocol types (published via `lib/index.d.ts`) instead of hand-writing them:
184
+
185
+ ```ts
186
+ import type {
187
+ IWorldSnapshotMessage, // server -> client: full world state on connect / world-request
188
+ IDimensionSnapshot, // one dimension: counts, placedBlocks, entities
189
+ ISnapshotPlacedBlock, // { x, y, z, block: BlockInfo, states: BlockStates }
190
+ IToolCallEvent, // server -> client: one tool invocation
191
+ IWebSocketServerMessage, // union of IWorldSnapshotMessage | IToolCallEvent
192
+ IWebSocketClientMessage, // client -> server: { type: 'world-request' | 'world' }
193
+ ToolCallListener,
194
+ } from "@lzimul/minecraft-mcp";
195
+
196
+ // Discriminate incoming messages by `msg.type`:
197
+ function handle(msg: IWebSocketServerMessage): void {
198
+ if (msg.type === "world-snapshot") {
199
+ for (const dimension of msg.dimensions) {
200
+ renderDimension(dimension);
201
+ }
202
+ } else if (msg.type === "tool-call") {
203
+ applyToolCall(msg); // msg.tool, msg.arguments, msg.result, msg.isError, msg.error, msg.startedAt, msg.durationMs
204
+ }
205
+ }
206
+ ```
207
+
151
208
  ## Resources
152
209
 
153
210
  | URI | Description |
@@ -189,7 +246,7 @@ Measured on Node.js 26 (results vary by hardware):
189
246
  src/
190
247
  index.ts Entry point
191
248
  app/ Server assembly
192
- config/ Server metadata, dimensions, storage, world limits
249
+ config/ Server metadata, dimensions, storage, world limits, WebSocket
193
250
  data/ Generated registry data (blocks, items, entities, version)
194
251
  enums/ Enumerations (EBoundingBox, EIconTheme, EDimensionType, EEntityKind, EInventorySection, EMoveDirection)
195
252
  interfaces/ Interfaces (IBlock, IItem, IEntityType, IEntity, IEntityInventory, IDimension, IPlacedBlock, IWorldSnapshot, IWorldStore)
@@ -203,6 +260,7 @@ scripts/
203
260
  logger.mjs Coloured stdout logger for developer scripts
204
261
  generate-data.mjs Regenerates src/data from minecraft-data
205
262
  smoke-test.mjs End-to-end smoke test (blocks, dimensions, entities, inventories)
263
+ websocket-test.mjs WebSocket fan-out test (snapshots, tool-call broadcast)
206
264
  persistence-test.mjs Restart test for blocks, entities and inventories
207
265
  error-test.mjs Error fallbacks, sanitizing and storage limit tests
208
266
  stress-test.mjs Parallel write, coalescing and restart stress test
@@ -211,17 +269,18 @@ scripts/
211
269
 
212
270
  ## Scripts
213
271
 
214
- | Command | Description |
215
- | ----------------------- | ----------------------------------------------------- |
216
- | `npm run build` | Lint, compile and generate docs |
217
- | `npm run dev` | Build then start the server |
218
- | `npm start` | Start the server (stdio) |
219
- | `npm test` | Run the smoke, persistence, security and stress tests |
220
- | `npm run test:stress` | Run only the parallel write stress test |
221
- | `npm run generate-data` | Regenerate registry data (see below) |
222
- | `npm run clean` | Remove build output |
223
- | `npm run lint` | Lint without modifying files |
224
- | `npm run lint:fix` | Lint and auto-fix |
272
+ | Command | Description |
273
+ | ------------------------ | ---------------------------------------------------------------- |
274
+ | `npm run build` | Lint, compile and generate docs |
275
+ | `npm run dev` | Build then start the server |
276
+ | `npm start` | Start the server (stdio) |
277
+ | `npm test` | Run the smoke, WebSocket, persistence, security and stress tests |
278
+ | `npm run test:websocket` | Run only the WebSocket fan-out test |
279
+ | `npm run test:stress` | Run only the parallel write stress test |
280
+ | `npm run generate-data` | Regenerate registry data (see below) |
281
+ | `npm run clean` | Remove build output |
282
+ | `npm run lint` | Lint without modifying files |
283
+ | `npm run lint:fix` | Lint and auto-fix |
225
284
 
226
285
  ## Data
227
286
 
@@ -2,8 +2,13 @@ import { McpServer } from '@modelcontextprotocol/server';
2
2
  import { SERVER_INFO } from '../config/server-info.js';
3
3
  import { registerResources } from '../resources/index.js';
4
4
  import { registerTools } from '../tools/index.js';
5
- export function createServer(world) {
5
+ import { instrumentToolCalls } from './tool-events.js';
6
+ export function createServer(world, options) {
6
7
  const server = new McpServer(SERVER_INFO);
8
+ const onToolCall = options?.onToolCall;
9
+ if (onToolCall) {
10
+ instrumentToolCalls(server, onToolCall);
11
+ }
7
12
  registerTools(server, world);
8
13
  registerResources(server, world);
9
14
  return server;
@@ -0,0 +1,72 @@
1
+ import { toErrorMessage } from '../utils/error-message.js';
2
+ import { logger } from '../utils/logger.js';
3
+ function readIsError(result) {
4
+ if (typeof result !== 'object' || result === null) {
5
+ return false;
6
+ }
7
+ return result.isError === true;
8
+ }
9
+ function extractErrorMessage(result) {
10
+ if (typeof result !== 'object' || result === null) {
11
+ return 'Tool call failed';
12
+ }
13
+ const content = result.content;
14
+ if (Array.isArray(content)) {
15
+ const first = content.find((entry) => typeof entry === 'object' &&
16
+ entry !== null &&
17
+ entry.type === 'text' &&
18
+ typeof entry.text === 'string');
19
+ if (first) {
20
+ return first.text;
21
+ }
22
+ }
23
+ return 'Tool call failed';
24
+ }
25
+ function safeNotify(listener, event) {
26
+ try {
27
+ listener(event);
28
+ }
29
+ catch (error) {
30
+ logger.warn(`Tool-call listener failed: ${toErrorMessage(error)}`);
31
+ }
32
+ }
33
+ export function instrumentToolCalls(server, listener) {
34
+ const host = server;
35
+ const original = host.registerTool.bind(server);
36
+ host.registerTool = (name, config, cb) => {
37
+ const wrapped = async (args, extra) => {
38
+ const startedAt = Date.now();
39
+ try {
40
+ const result = await cb(args, extra);
41
+ const durationMs = Date.now() - startedAt;
42
+ const isError = readIsError(result);
43
+ safeNotify(listener, {
44
+ type: 'tool-call',
45
+ tool: name,
46
+ arguments: args,
47
+ result,
48
+ isError,
49
+ error: isError ? extractErrorMessage(result) : null,
50
+ startedAt,
51
+ durationMs
52
+ });
53
+ return result;
54
+ }
55
+ catch (error) {
56
+ const durationMs = Date.now() - startedAt;
57
+ safeNotify(listener, {
58
+ type: 'tool-call',
59
+ tool: name,
60
+ arguments: args,
61
+ result: null,
62
+ isError: true,
63
+ error: toErrorMessage(error),
64
+ startedAt,
65
+ durationMs
66
+ });
67
+ throw error;
68
+ }
69
+ };
70
+ return original(name, config, wrapped);
71
+ };
72
+ }
@@ -0,0 +1,116 @@
1
+ import { WebSocket, WebSocketServer } from 'ws';
2
+ import { toErrorMessage } from '../utils/error-message.js';
3
+ import { logger } from '../utils/logger.js';
4
+ import { dimensionState } from '../resources/dimension-resources.js';
5
+ import { World } from '../world/world.js';
6
+ function buildWorldSnapshot(world) {
7
+ return {
8
+ type: 'world-snapshot',
9
+ timestamp: Date.now(),
10
+ dimensions: World.listDimensions().map(dimension => dimensionState(world, dimension))
11
+ };
12
+ }
13
+ function sendText(socket, text) {
14
+ if (socket.readyState !== WebSocket.OPEN) {
15
+ return;
16
+ }
17
+ try {
18
+ socket.send(text);
19
+ }
20
+ catch (error) {
21
+ logger.warn(`WebSocket send failed: ${toErrorMessage(error)}`);
22
+ }
23
+ }
24
+ function sendObject(socket, payload) {
25
+ try {
26
+ sendText(socket, JSON.stringify(payload));
27
+ }
28
+ catch (error) {
29
+ logger.warn(`WebSocket serialize failed: ${toErrorMessage(error)}`);
30
+ }
31
+ }
32
+ function isClientMessage(value) {
33
+ if (typeof value !== 'object' || value === null) {
34
+ return false;
35
+ }
36
+ const type = value.type;
37
+ return type === 'world-request' || type === 'world';
38
+ }
39
+ function handleClientMessage(world, socket, data) {
40
+ const text = data.toString().trim();
41
+ if (text.length === 0) {
42
+ return;
43
+ }
44
+ let request;
45
+ try {
46
+ request = JSON.parse(text);
47
+ }
48
+ catch {
49
+ return;
50
+ }
51
+ if (isClientMessage(request)) {
52
+ sendObject(socket, buildWorldSnapshot(world));
53
+ }
54
+ }
55
+ export function startWebSocketService(world, config) {
56
+ const clients = new Set();
57
+ const wss = new WebSocketServer({
58
+ host: config.host,
59
+ port: config.port,
60
+ path: config.path
61
+ });
62
+ wss.on('error', error => {
63
+ logger.error(`WebSocket service error: ${toErrorMessage(error)}`);
64
+ });
65
+ wss.on('connection', socket => {
66
+ clients.add(socket);
67
+ logger.info(`WebSocket client connected (${clients.size} client(s))`);
68
+ sendObject(socket, buildWorldSnapshot(world));
69
+ socket.on('message', data => {
70
+ handleClientMessage(world, socket, data);
71
+ });
72
+ socket.on('close', () => {
73
+ clients.delete(socket);
74
+ logger.info(`WebSocket client disconnected (${clients.size} client(s))`);
75
+ });
76
+ socket.on('error', error => {
77
+ logger.warn(`WebSocket client error: ${toErrorMessage(error)}`);
78
+ clients.delete(socket);
79
+ });
80
+ });
81
+ function broadcast(event) {
82
+ if (clients.size === 0) {
83
+ return;
84
+ }
85
+ let text;
86
+ try {
87
+ text = JSON.stringify(event);
88
+ }
89
+ catch (error) {
90
+ logger.warn(`WebSocket tool-call serialize failed: ${toErrorMessage(error)}`);
91
+ return;
92
+ }
93
+ for (const socket of clients) {
94
+ sendText(socket, text);
95
+ }
96
+ }
97
+ function clientCount() {
98
+ return clients.size;
99
+ }
100
+ async function close() {
101
+ for (const socket of clients) {
102
+ try {
103
+ socket.close(1001, 'Server shutting down');
104
+ }
105
+ catch {
106
+ }
107
+ }
108
+ clients.clear();
109
+ await new Promise(resolve => {
110
+ wss.close(() => {
111
+ resolve();
112
+ });
113
+ });
114
+ }
115
+ return { broadcast, clientCount, close };
116
+ }
@@ -7,7 +7,7 @@ export const SERVER_INFO = {
7
7
  name: '@lzimul/minecraft-mcp',
8
8
  title: 'lZiMUl Minecraft MCP',
9
9
  description: `Minecraft Model Context Protocol server (Java Edition ${MINECRAFT_DATA_VERSION}): exposes all ${MINECRAFT_BLOCK_COUNT} blocks, ${MINECRAFT_ITEM_COUNT} items and ${MINECRAFT_ENTITY_COUNT} entity types with their properties as resources, plus tools to read, place, query and fill blocks, inspect dimensions, create, move and delete entities and players, set entity positions, and manage their hotbar and backpack inventories.`,
10
- version: '1.0.5',
10
+ version: '1.0.6',
11
11
  websiteUrl: 'https://projects.lzimul.com/minecraft-mcp',
12
12
  icons: [
13
13
  {
@@ -0,0 +1,48 @@
1
+ function envString(name) {
2
+ const raw = process.env[name];
3
+ if (typeof raw !== 'string') {
4
+ return null;
5
+ }
6
+ const trimmed = raw.trim();
7
+ return trimmed.length > 0 ? trimmed : null;
8
+ }
9
+ function envFlag(name, fallback) {
10
+ const raw = envString(name);
11
+ if (raw === null) {
12
+ return fallback;
13
+ }
14
+ const normalized = raw.toLowerCase();
15
+ if (['false', '0', 'no', 'off'].includes(normalized)) {
16
+ return false;
17
+ }
18
+ if (['true', '1', 'yes', 'on'].includes(normalized)) {
19
+ return true;
20
+ }
21
+ return fallback;
22
+ }
23
+ function envPort(name, fallback) {
24
+ const raw = envString(name);
25
+ if (raw === null) {
26
+ return fallback;
27
+ }
28
+ const parsed = Number.parseInt(raw, 10);
29
+ return Number.isInteger(parsed) && parsed > 0 && parsed <= 65535
30
+ ? parsed
31
+ : fallback;
32
+ }
33
+ function envHost(name, fallback) {
34
+ return envString(name) ?? fallback;
35
+ }
36
+ function envPath(name, fallback) {
37
+ const raw = envString(name);
38
+ if (raw === null) {
39
+ return fallback;
40
+ }
41
+ return raw.startsWith('/') ? raw : `/${raw}`;
42
+ }
43
+ export const WEBSOCKET_CONFIG = {
44
+ enabled: envFlag('MCP_WS_ENABLED', true),
45
+ host: envHost('MCP_WS_HOST', '127.0.0.1'),
46
+ port: envPort('MCP_WS_PORT', 8765),
47
+ path: envPath('MCP_WS_PATH', '/')
48
+ };
package/build/index.js CHANGED
@@ -1,6 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import { serveStdio } from '@modelcontextprotocol/server/stdio';
3
3
  import { createServer } from './app/server.js';
4
+ import { startWebSocketService } from './app/websocket.js';
5
+ import { WEBSOCKET_CONFIG } from './config/websocket.js';
4
6
  import { WORLD_STORAGE } from './config/storage.js';
5
7
  import { toErrorMessage } from './utils/error-message.js';
6
8
  import { logger } from './utils/logger.js';
@@ -8,7 +10,12 @@ import { createWorldStore } from './world/stores/index.js';
8
10
  import { World } from './world/world.js';
9
11
  async function main() {
10
12
  const world = await World.load(createWorldStore(), WORLD_STORAGE.writeDelayMs);
11
- const handle = serveStdio(() => createServer(world), {
13
+ let wsService = null;
14
+ if (WEBSOCKET_CONFIG.enabled) {
15
+ wsService = startWebSocketService(world, WEBSOCKET_CONFIG);
16
+ logger.info(`WebSocket service listening on ws://${WEBSOCKET_CONFIG.host}:${WEBSOCKET_CONFIG.port}${WEBSOCKET_CONFIG.path}`);
17
+ }
18
+ const handle = serveStdio(() => createServer(world, wsService ? { onToolCall: wsService.broadcast } : {}), {
12
19
  legacy: 'reject'
13
20
  });
14
21
  let shuttingDown = false;
@@ -32,6 +39,15 @@ async function main() {
32
39
  logger.error(`Failed to close the MCP transport: ${toErrorMessage(error)}`);
33
40
  code = 1;
34
41
  }
42
+ if (wsService) {
43
+ try {
44
+ await wsService.close();
45
+ }
46
+ catch (error) {
47
+ logger.error(`Failed to close the WebSocket service: ${toErrorMessage(error)}`);
48
+ code = 1;
49
+ }
50
+ }
35
51
  process.exit(code);
36
52
  };
37
53
  process.once('SIGINT', () => {
@@ -0,0 +1 @@
1
+ export {};
@@ -17,7 +17,7 @@ function dimensionSummary(world, dimension) {
17
17
  uri: `lzimul://minecraft/dimensions/${dimension.type}`
18
18
  };
19
19
  }
20
- function dimensionState(world, dimension) {
20
+ export function dimensionState(world, dimension) {
21
21
  const simulated = world.getDimension(dimension.type);
22
22
  return {
23
23
  key: dimension.key,
@@ -1,4 +1,8 @@
1
1
  import { McpServer } from '@modelcontextprotocol/server';
2
+ import type { ToolCallListener } from './tool-events.js';
2
3
  import type { World } from '../world/world.js';
3
- export declare function createServer(world: World): McpServer;
4
+ export interface ICreateServerOptions {
5
+ readonly onToolCall?: ToolCallListener;
6
+ }
7
+ export declare function createServer(world: World, options?: ICreateServerOptions): McpServer;
4
8
  //# sourceMappingURL=server.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/app/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAMzD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAG/C,wBAAgB,YAAY,CAAC,KAAK,EAAE,KAAK,GAAG,SAAS,CAKpD"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/app/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAOzD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAG/C,MAAM,WAAW,oBAAoB;IAEnC,QAAQ,CAAC,UAAU,CAAC,EAAE,gBAAgB,CAAC;CACxC;AAGD,wBAAgB,YAAY,CAC1B,KAAK,EAAE,KAAK,EACZ,OAAO,CAAC,EAAE,oBAAoB,GAC7B,SAAS,CAaX"}
@@ -0,0 +1,5 @@
1
+ import type { McpServer } from '@modelcontextprotocol/server';
2
+ import type { IToolCallEvent, ToolCallListener } from '../interfaces/websocket.interface.js';
3
+ export type { IToolCallEvent, ToolCallListener };
4
+ export declare function instrumentToolCalls(server: McpServer, listener: ToolCallListener): void;
5
+ //# sourceMappingURL=tool-events.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-events.d.ts","sourceRoot":"","sources":["../../src/app/tool-events.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAK9D,OAAO,KAAK,EACV,cAAc,EACd,gBAAgB,EACjB,MAAM,sCAAsC,CAAC;AAE9C,YAAY,EAAE,cAAc,EAAE,gBAAgB,EAAE,CAAC;AA4DjD,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,SAAS,EACjB,QAAQ,EAAE,gBAAgB,GACzB,IAAI,CAwCN"}
@@ -0,0 +1,10 @@
1
+ import { World } from '../world/world.js';
2
+ import type { IWebSocketConfig } from '../config/websocket.js';
3
+ import type { IToolCallEvent } from '../interfaces/websocket.interface.js';
4
+ export interface IWebSocketService {
5
+ broadcast(event: IToolCallEvent): void;
6
+ clientCount(): number;
7
+ close(): Promise<void>;
8
+ }
9
+ export declare function startWebSocketService(world: World, config: IWebSocketConfig): IWebSocketService;
10
+ //# sourceMappingURL=websocket.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"websocket.d.ts","sourceRoot":"","sources":["../../src/app/websocket.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAE1C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,KAAK,EACV,cAAc,EAIf,MAAM,sCAAsC,CAAC;AAG9C,MAAM,WAAW,iBAAiB;IAEhC,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI,CAAC;IAEvC,WAAW,IAAI,MAAM,CAAC;IAEtB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AA0ED,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,gBAAgB,GACvB,iBAAiB,CAuEnB"}
@@ -0,0 +1,8 @@
1
+ export interface IWebSocketConfig {
2
+ readonly enabled: boolean;
3
+ readonly host: string;
4
+ readonly port: number;
5
+ readonly path: string;
6
+ }
7
+ export declare const WEBSOCKET_CONFIG: IWebSocketConfig;
8
+ //# sourceMappingURL=websocket.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"websocket.d.ts","sourceRoot":"","sources":["../../src/config/websocket.ts"],"names":[],"mappings":"AACA,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AA2DD,eAAO,MAAM,gBAAgB,EAAE,gBAK9B,CAAC"}
package/lib/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
- export {};
2
+ export type { IDimensionSnapshot, ISnapshotPlacedBlock, IToolCallEvent, IWebSocketClientMessage, IWebSocketServerMessage, IWorldSnapshotMessage, ToolCallListener } from './interfaces/websocket.interface.js';
3
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAoGA,YAAY,EACV,kBAAkB,EAClB,oBAAoB,EACpB,cAAc,EACd,uBAAuB,EACvB,uBAAuB,EACvB,qBAAqB,EACrB,gBAAgB,EACjB,MAAM,qCAAqC,CAAC"}
@@ -0,0 +1,47 @@
1
+ import type { EDimensionType } from '../enums/dimension-type.enum.js';
2
+ import type { BlockInfo } from '../schemas/block-schema.js';
3
+ import type { EntityInfo } from '../schemas/entity-schema.js';
4
+ import type { BlockStates } from './block-states.interface.js';
5
+ export interface ISnapshotPlacedBlock {
6
+ readonly x: number;
7
+ readonly y: number;
8
+ readonly z: number;
9
+ readonly block: BlockInfo;
10
+ readonly states: BlockStates;
11
+ }
12
+ export interface IDimensionSnapshot {
13
+ readonly key: string;
14
+ readonly type: EDimensionType;
15
+ readonly displayName: string;
16
+ readonly minY: number;
17
+ readonly maxY: number;
18
+ readonly hasSkylight: boolean;
19
+ readonly modifiedBlockCount: number;
20
+ readonly entityCount: number;
21
+ readonly playerCount: number;
22
+ readonly placedBlocks: readonly ISnapshotPlacedBlock[];
23
+ readonly entities: readonly EntityInfo[];
24
+ }
25
+ export interface IWorldSnapshotMessage {
26
+ readonly type: 'world-snapshot';
27
+ readonly timestamp: number;
28
+ readonly dimensions: readonly IDimensionSnapshot[];
29
+ }
30
+ export interface IToolCallEvent {
31
+ readonly type: 'tool-call';
32
+ readonly tool: string;
33
+ readonly arguments: unknown;
34
+ readonly result: unknown;
35
+ readonly isError: boolean;
36
+ readonly error: string | null;
37
+ readonly startedAt: number;
38
+ readonly durationMs: number;
39
+ }
40
+ export type ToolCallListener = (event: IToolCallEvent) => void;
41
+ export type IWebSocketClientMessage = {
42
+ readonly type: 'world-request';
43
+ } | {
44
+ readonly type: 'world';
45
+ };
46
+ export type IWebSocketServerMessage = IWorldSnapshotMessage | IToolCallEvent;
47
+ //# sourceMappingURL=websocket.interface.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"websocket.interface.d.ts","sourceRoot":"","sources":["../../src/interfaces/websocket.interface.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACtE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAG/D,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;CAC9B;AAGD,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,YAAY,EAAE,SAAS,oBAAoB,EAAE,CAAC;IACvD,QAAQ,CAAC,QAAQ,EAAE,SAAS,UAAU,EAAE,CAAC;CAC1C;AAGD,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC;IAEhC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,SAAS,kBAAkB,EAAE,CAAC;CACpD;AAGD,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAE9B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAE3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAGD,MAAM,MAAM,gBAAgB,GAAG,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;AAG/D,MAAM,MAAM,uBAAuB,GACjC;IAAE,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAA;CAAE,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAGlE,MAAM,MAAM,uBAAuB,GAAG,qBAAqB,GAAG,cAAc,CAAC"}
@@ -1,5 +1,8 @@
1
1
  import type { McpServer } from '@modelcontextprotocol/server';
2
2
  import { World } from '../world/world.js';
3
+ import type { IDimension } from '../interfaces/dimension.interface.js';
4
+ import type { IDimensionSnapshot } from '../interfaces/websocket.interface.js';
5
+ export declare function dimensionState(world: World, dimension: IDimension): IDimensionSnapshot;
3
6
  export declare function registerDimensionIndexResource(server: McpServer, world: World): void;
4
7
  export declare function registerDimensionResources(server: McpServer, world: World): void;
5
8
  //# sourceMappingURL=dimension-resources.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"dimension-resources.d.ts","sourceRoot":"","sources":["../../src/resources/dimension-resources.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAI9D,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAqD1C,wBAAgB,8BAA8B,CAC5C,MAAM,EAAE,SAAS,EACjB,KAAK,EAAE,KAAK,GACX,IAAI,CAgCN;AAGD,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,SAAS,EACjB,KAAK,EAAE,KAAK,GACX,IAAI,CA2BN"}
1
+ {"version":3,"file":"dimension-resources.d.ts","sourceRoot":"","sources":["../../src/resources/dimension-resources.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAI9D,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAG1C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sCAAsC,CAAC;AACvE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sCAAsC,CAAC;AAuB/E,wBAAgB,cAAc,CAC5B,KAAK,EAAE,KAAK,EACZ,SAAS,EAAE,UAAU,GACpB,kBAAkB,CAqBpB;AAGD,wBAAgB,8BAA8B,CAC5C,MAAM,EAAE,SAAS,EACjB,KAAK,EAAE,KAAK,GACX,IAAI,CAgCN;AAGD,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,SAAS,EACjB,KAAK,EAAE,KAAK,GACX,IAAI,CA2BN"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lzimul/minecraft-mcp",
3
3
  "description": "Minecraft Model Context Protocol (MCP) Server",
4
- "version": "1.0.5",
4
+ "version": "1.0.6",
5
5
  "author": {
6
6
  "name": "lZiMUl",
7
7
  "email": "lZiMUl@lzimul.com",
@@ -44,7 +44,8 @@
44
44
  "dev": "npm run build && npm run start",
45
45
  "generate-data": "node ./scripts/generate-data.mjs",
46
46
  "clean": "node ./scripts/clean.mjs",
47
- "test": "node ./scripts/smoke-test.mjs && node ./scripts/persistence-test.mjs && node ./scripts/error-test.mjs && node ./scripts/stress-test.mjs",
47
+ "test": "node ./scripts/smoke-test.mjs && node ./scripts/websocket-test.mjs && node ./scripts/persistence-test.mjs && node ./scripts/error-test.mjs && node ./scripts/stress-test.mjs",
48
+ "test:websocket": "node ./scripts/websocket-test.mjs",
48
49
  "test:stress": "node ./scripts/stress-test.mjs",
49
50
  "prepublishOnly": "npm run build && npm test"
50
51
  },
@@ -72,6 +73,7 @@
72
73
  "@modelcontextprotocol/server": "^2.4.0",
73
74
  "chalk": "^6.0.1",
74
75
  "log4js": "^6.9.1",
76
+ "ws": "^8.22.1",
75
77
  "zod": "^4.6.7"
76
78
  },
77
79
  "devDependencies": {
@@ -81,6 +83,7 @@
81
83
  "@eslint/json": "^2.1.1",
82
84
  "@eslint/markdown": "^8.0.3",
83
85
  "@types/node": "^26.6.5",
86
+ "@types/ws": "^8.18.2",
84
87
  "@typescript-eslint/eslint-plugin": "^8.72.1",
85
88
  "@typescript-eslint/parser": "^8.72.1",
86
89
  "eslint": "^10.13.0",