vitaminmcp 3.0.2 → 3.1.0

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
@@ -7,11 +7,9 @@ This package is the launcher. It fetches the jars it needs on first run and spea
7
7
  MCP client — it is not the whole product on its own: the agent is a Paper plugin, and it goes on
8
8
  the Minecraft server.
9
9
 
10
- ```bash
11
- claude mcp add vitaminmcp -- npx -y vitaminmcp
12
- ```
13
-
14
- Or in `.mcp.json`, `claude_desktop_config.json`, or whatever your client calls it:
10
+ It speaks plain stdio, so it works in any MCP client — Claude Code, Cursor, Codex, Gemini CLI,
11
+ Windsurf, Claude Desktop, VS Code. Register it wherever your client keeps MCP servers
12
+ (`.mcp.json`, `.cursor/mcp.json`, `~/.gemini/settings.json`, `claude_desktop_config.json`, …):
15
13
 
16
14
  ```json
17
15
  {
@@ -24,8 +22,14 @@ Or in `.mcp.json`, `claude_desktop_config.json`, or whatever your client calls i
24
22
  }
25
23
  ```
26
24
 
27
- Then, in Claude Code, `/mcp__vitaminmcp__setup` walks through the other half — putting
28
- `VitaminMCP.jar` in the server's `plugins/`, restarting it, and connecting. Or just ask:
25
+ Clients with a CLI take the same thing as a command, e.g.
26
+ `claude mcp add vitaminmcp -- npx -y vitaminmcp` or
27
+ `codex mcp add vitaminmcp -- npx -y vitaminmcp`.
28
+
29
+ The server publishes a `setup` MCP prompt that walks through the other half — putting
30
+ `VitaminMCP.jar` in the Minecraft server's `plugins/`, restarting it, and connecting. In Claude
31
+ Code that surfaces as `/mcp__vitaminmcp__setup` (named after whatever the server was registered
32
+ as — `/mcp` lists it); in any client, just ask:
29
33
 
30
34
  > **Prompt:** Set up VitaminMCP on my Minecraft server at ~/servers/test and connect to it.
31
35
 
@@ -39,12 +43,13 @@ agent leaves its host, ports and token where this server reads them.
39
43
  - Open, read, click and assert on inventories and plugin GUIs
40
44
  - Wait for events and conditions instead of sleeping
41
45
  - Read live server state: events, logs, exceptions, permissions
42
- - Paper / Purpur 1.21 through 1.21.8, from one install
46
+ - Paper / Purpur 1.21 through 26.1, from one install
43
47
 
44
48
  ## Requires
45
49
 
46
50
  - **Java 21 or later** on this machine — the jars run on the JVM. Point `JAVA_HOME` at it, or have
47
- `java` on `PATH`
51
+ `java` on `PATH`. (The Paper server itself needs Java 25 from 26.1 on; that is Paper's
52
+ requirement, and the agent loads there unchanged)
48
53
  - **Node 18.17 or later** when using the source runner fallback. If Node is absent, the launcher
49
54
  selects a pinned platform runner asset instead.
50
55
  - **Paper 1.21 or later** on the Minecraft server, with `VitaminMCP.jar` in its `plugins/`
package/checksums.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
- "version": "3.0.2",
2
+ "version": "3.1.0",
3
3
  "jars": {
4
- "mcp-server.jar": "452ed2928e9e0a6ddcb25dab4586999012b334f9994a0aff8c305148fde0c7ae"
4
+ "mcp-server.jar": "f64ee16d5aaa115a9d4e019adccf830b74ebe8683d2ade5e05b7db0cf9692d66"
5
5
  },
6
6
  "assets": {
7
- "bot-runner-win-x64.exe": "d263637058d5e4a894d1765cfa79de528774a36d9d4c5132c0bb5e9c5b1ae0d0",
8
- "bot-runner-linux-x64": "dcac4f6a1e7c9ad9228a95cee4a59925973616eab9214b4bc81ad338ae398de5",
9
- "bot-runner-linux-arm64": "08642a57deec10a1d967bc95e5e4fc35cfb4de5cdcd31c391f90829fdc569ac7",
10
- "bot-runner-darwin-x64": "0f2e229cbb94ef65ff79336b517489a9c2fa715c42a86e5d4da5a2df510f48ba",
11
- "bot-runner-darwin-arm64": "3e570c14a11d0df7eb84bcdce15acb5fbcabbda0f64ceb1c1f389d9ee2a43fef",
7
+ "bot-runner-win-x64.exe": "8af36a3e4a3678db5246777c1c8080bea230b50a742dff173758d0513df74e98",
8
+ "bot-runner-linux-x64": "2f344983a5558d36cf03e4bcf55e0752324d159aebefc18f711c28704cf48264",
9
+ "bot-runner-linux-arm64": "fa6c49b5db5b6cc84b62d295bbef2ace49305400a7480e86baa009e9629d120c",
10
+ "bot-runner-darwin-x64": "1769db980857d36204bf93e14bbe5ba329435032b116c8003a12043ba817168a",
11
+ "bot-runner-darwin-arm64": "b137677b9117af944d006ff0da0e94b524875c86ebfa2596c2b856b9ef63fd2e",
12
12
  "bot-runner-viewer-win-x64.tgz": "e39a36cdf92b9223aa7c9610f8231afc7a577e8ed007bc3571895bd945baf3e1"
13
13
  }
14
14
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vitaminmcp",
3
- "version": "3.0.2",
3
+ "version": "3.1.0",
4
4
  "mcpName": "io.github.Backas03/vitaminmcp",
5
5
  "description": "MCP server for testing Minecraft plugins: drives a real Paper server and real protocol bots from an AI agent.",
6
6
  "keywords": [
package/runner/runner.mjs CHANGED
@@ -16,6 +16,12 @@ import { Dispatch } from './src/dispatch.mjs';
16
16
  import * as protocol from './src/protocol.mjs';
17
17
  import { pingProtocol, versionForProtocol } from './src/version.mjs';
18
18
 
19
+ // stdout is the protocol channel; a dependency's console.log (mineflayer did this on 26.1, with a
20
+ // stack trace) desynchronises every later reply. `write` below is the only path to stdout.
21
+ for (const level of ['log', 'info', 'debug']) {
22
+ console[level] = (...messages) => process.stderr.write(`${messages.map(String).join(' ')}\n`);
23
+ }
24
+
19
25
  async function main() {
20
26
  const argv = process.argv.slice(2);
21
27
  if (argv.length < 2) {
@@ -234,26 +234,45 @@ export function useEntity(bot, name, x, y, z, radius, type) {
234
234
  );
235
235
  }
236
236
 
237
- // Both packets, in this order, because that is what a real client sends and what the Java
238
- // runner reproduces: a plugin listening only for the second sees nothing without the first.
239
- bot._client.write('use_entity', {
240
- target: entityId,
241
- mouse: INTERACT_AT,
242
- x: 0.0,
243
- y: 1.0,
244
- z: 0.0,
245
- hand: MAIN_HAND,
246
- sneaking: false,
247
- });
248
- bot._client.write('use_entity', {
249
- target: entityId,
250
- mouse: INTERACT,
251
- hand: MAIN_HAND,
252
- sneaking: false,
253
- });
237
+ if (interactCarriesMouseButton(bot)) {
238
+ // Both packets, in this order, because that is what a real client sends and what the Java
239
+ // runner reproduces: a plugin listening only for the second sees nothing without the first.
240
+ bot._client.write('use_entity', {
241
+ target: entityId,
242
+ mouse: INTERACT_AT,
243
+ x: 0.0,
244
+ y: 1.0,
245
+ z: 0.0,
246
+ hand: MAIN_HAND,
247
+ sneaking: false,
248
+ });
249
+ bot._client.write('use_entity', {
250
+ target: entityId,
251
+ mouse: INTERACT,
252
+ hand: MAIN_HAND,
253
+ sneaking: false,
254
+ });
255
+ } else {
256
+ // 26.1: one packet with a hand and a hit position; attacks have their own packet.
257
+ bot._client.write('use_entity', {
258
+ target: entityId,
259
+ hand: MAIN_HAND,
260
+ location: { x: 0.0, y: 1.0, z: 0.0 },
261
+ sneaking: false,
262
+ });
263
+ }
254
264
  return entityId;
255
265
  }
256
266
 
267
+ /** Through 1.21.11 `use_entity` has a `mouse` field; from 26.1 it has a `location` instead. */
268
+ function interactCarriesMouseButton(bot) {
269
+ const fields = bot.registry?.protocol?.play?.toServer?.types?.packet_use_entity?.[1];
270
+ if (!Array.isArray(fields)) {
271
+ throw new Error('The protocol definition for use_entity is missing, so the bot cannot interact with entities.');
272
+ }
273
+ return fields.some((field) => field?.name === 'mouse');
274
+ }
275
+
257
276
  /** Left-clicks the nearest tracked entity. */
258
277
  export function attackEntity(bot, name, x, y, z, radius, type) {
259
278
  requireInWorld(bot, name);
@@ -153,6 +153,8 @@ export function connectionOptions(host, port, version, id, clientIp) {
153
153
  auth: 'offline',
154
154
  version,
155
155
  checkTimeoutInterval: LOGIN_TIMEOUT_MILLIS,
156
+ // mineflayer's default console.logs bot errors, and stdout is the protocol channel.
157
+ logErrors: false,
156
158
  };
157
159
  if (clientIp && clientIp.trim()) {
158
160
  options.fakeHost = addressField(host, clientIp.trim(), id);
@@ -30,7 +30,8 @@ export class Dispatch {
30
30
  try {
31
31
  return await this.#run(verb, command);
32
32
  } catch (error) {
33
- return protocol.encode(protocol.ERROR, verb, String(error?.message ?? error));
33
+ // A newline in the message would become a second stdout line, read as the next reply.
34
+ return protocol.encode(protocol.ERROR, verb, protocol.sanitize(error?.message ?? error));
34
35
  }
35
36
  }
36
37