vitaminmcp 3.0.1 → 3.0.3

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,7 +43,7 @@ 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 1.21.11, from one install
43
47
 
44
48
  ## Requires
45
49
 
package/checksums.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
- "version": "3.0.1",
2
+ "version": "3.0.3",
3
3
  "jars": {
4
- "mcp-server.jar": "d2873f9e4d64a8aad41e2529dc337697343b377d4a5d892d7b618d3476309426"
4
+ "mcp-server.jar": "e077b0ab2ea776f4d57560c260818f0b9bb7ed7e76418e513fcdcc8a110a3801"
5
5
  },
6
6
  "assets": {
7
- "bot-runner-win-x64.exe": "e4ed541a46bdb338d566e1b036acc786864d6b3cefcdc6b4272952ab1e265a66",
8
- "bot-runner-linux-x64": "9b2a202455a88870d6a4d1afe0b23b8e287e620ca213b36c2fbe6d2f4699d10e",
9
- "bot-runner-linux-arm64": "077c8b45b14ba2419700ddf0607e92d35ffe1f134e89db42ccc6d927f002355e",
10
- "bot-runner-darwin-x64": "7e30f2c345ddcd7fc16c7d94849ef3dbd0543c184d84606562744d0c3d98e990",
11
- "bot-runner-darwin-arm64": "9f581bcbcba6f995500c3d93d45d0d05bc9aad582f0824d57f4f9beca208a9b1",
7
+ "bot-runner-win-x64.exe": "19cec3a83b03611c29808aad3bc3f395d8ace7202ead621a6bd0eb9dd5dc03bf",
8
+ "bot-runner-linux-x64": "63443c1b796d1ef8132bdb7212860462db88b020948d53ca940e468f7fa26c9a",
9
+ "bot-runner-linux-arm64": "871b9ce95e5f0411451935bcb2cf11b1151ca82d17448dda314eae8f4c6d3e41",
10
+ "bot-runner-darwin-x64": "f69a7b78c936fd183eeb1bfb2734a7d54c6d34d36d61532ea0738c818bc195d9",
11
+ "bot-runner-darwin-arm64": "81e30798ad2d06aced01b2fea076ed5c6a334debe96ae0dd12213aa961c696b9",
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.1",
3
+ "version": "3.0.3",
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": [
@@ -5,6 +5,7 @@ import { stopAllViews, stopView as stopBotView, view as startView } from './view
5
5
 
6
6
  import { collect, forget } from './clientview.mjs';
7
7
  import { addressField, identity } from './identity.mjs';
8
+ import { answerResourcePacks, describeProgress, traceProgress } from './join.mjs';
8
9
 
9
10
  /** How long a bot has to get from a socket to standing in the world. */
10
11
  const LOGIN_TIMEOUT_MILLIS = 30_000;
@@ -55,13 +56,19 @@ export class BotRegistry {
55
56
  );
56
57
  loadPathfinder(bot);
57
58
 
59
+ // Both before anything is awaited. A server can push a resource pack the moment login
60
+ // succeeds, and a request that arrives before its listener does is a connection that
61
+ // hangs in configuration until the timeout below gives up on it.
62
+ answerResourcePacks(bot);
63
+ const progress = traceProgress(bot);
64
+
58
65
  // Before waiting to join, not after: messages are events, and a plugin that greets or refuses
59
66
  // on join says so within a tick of the bot arriving. Attaching afterwards loses exactly the
60
67
  // messages most worth having.
61
68
  collect(bot, name);
62
69
 
63
70
  try {
64
- await joined(bot, name);
71
+ await joined(bot, name, progress);
65
72
  } catch (failure) {
66
73
  quietly(() => bot.end());
67
74
  throw failure;
@@ -223,7 +230,7 @@ async function worldKnown(bot, name) {
223
230
  }
224
231
 
225
232
  /** Resolves when the bot is in the world; rejects on a kick, an error, or the timeout. */
226
- function joined(bot, name) {
233
+ function joined(bot, name, progress) {
227
234
  return new Promise((resolve, reject) => {
228
235
  const finish = (settleFn, value) => {
229
236
  clearTimeout(timer);
@@ -238,7 +245,10 @@ function joined(bot, name) {
238
245
  const onError = (error) => finish(reject, error);
239
246
 
240
247
  const timer = setTimeout(
241
- () => finish(reject, new Error(`Bot ${name} did not join within ${LOGIN_TIMEOUT_MILLIS}ms`)),
248
+ () => finish(reject, new Error(
249
+ `Bot ${name} did not join within ${LOGIN_TIMEOUT_MILLIS}ms `
250
+ + `(${describeProgress(bot, progress)})`,
251
+ )),
242
252
  LOGIN_TIMEOUT_MILLIS,
243
253
  );
244
254
 
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Getting a bot from an open socket to standing in the world.
3
+ *
4
+ * Since 1.20.2 that path runs through the configuration phase, where the server may hold the
5
+ * connection open waiting for answers a headless client has no reason to send on its own.
6
+ * minecraft-protocol answers the vanilla ones (client settings, known packs, finish), and what is
7
+ * left over is here.
8
+ */
9
+
10
+ /**
11
+ * The client's answers to a resource pack request — vanilla's `ResourcePack.Action` ordinals.
12
+ *
13
+ * Only the two used below are named. `DECLINED` is deliberately not among them: a plugin that
14
+ * forces a pack kicks on a decline, and a bot that cannot join is no better than one that hangs.
15
+ */
16
+ const ACCEPTED = 3;
17
+ const SUCCESSFULLY_LOADED = 0;
18
+
19
+ /**
20
+ * Answers every resource pack the server sends, without downloading it.
21
+ *
22
+ * A plugin that pushes a pack from `AsyncPlayerConnectionConfigureEvent` blocks that connection
23
+ * until the client reports what became of the pack, and until then no `PlayerJoinEvent` happens
24
+ * and no play state is entered. mineflayer only *emits* `resourcePack` and leaves the answer to
25
+ * the bot author, so a bot that never answers waits out the whole login timeout on a server that
26
+ * would have let it in. That is what happens on any server hosting its own pack — CraftEngine,
27
+ * Oraxen, ItemsAdder — which is most of them.
28
+ *
29
+ * Nothing is fetched: the URL is usually only reachable from inside the network the server is on,
30
+ * and a test bot has no use for textures. `SUCCESSFULLY_LOADED` is what a client that applied the
31
+ * pack reports, and it is the only answer that satisfies both a required pack and an optional one.
32
+ *
33
+ * `bot.acceptResourcePack()` is not used for this. It answers with mineflayer's own tracked
34
+ * `latestUUID`, which is a `uuid-1345` object where the protocol writer expects the string form —
35
+ * it serialises to the nil UUID, naming a pack the server has never heard of, and the connection
36
+ * goes on waiting. The uuid is echoed straight back off the request here instead.
37
+ */
38
+ export function answerResourcePacks(bot) {
39
+ const client = bot._client;
40
+
41
+ const answer = (request) => {
42
+ // ACCEPTED first, then the terminal result, in the order a real client reports them: a server
43
+ // that tracks the intermediate state sees the same sequence it would see from a player.
44
+ send(client, request, ACCEPTED);
45
+ send(client, request, SUCCESSFULLY_LOADED);
46
+ };
47
+
48
+ // 1.20.3 renamed the request and started identifying packs by uuid; both names are registered
49
+ // because only one of them exists in any given version's protocol.
50
+ client.on('add_resource_pack', answer);
51
+ client.on('resource_pack_send', answer);
52
+ }
53
+
54
+ /** One response, built from whatever identified the request, since that differs by version. */
55
+ function send(client, request, result) {
56
+ const response = { result };
57
+ if (request?.uuid !== undefined) {
58
+ response.uuid = request.uuid;
59
+ }
60
+ if (request?.hash !== undefined) {
61
+ response.hash = request.hash;
62
+ }
63
+
64
+ try {
65
+ client.write('resource_pack_receive', response);
66
+ } catch (error) {
67
+ // A bot that cannot answer is about to time out with a message that says where it stopped.
68
+ // Throwing from a packet handler would instead take the whole runner, and every other bot in
69
+ // it, down with an unhandled error.
70
+ process.stderr.write(`could not answer a resource pack request: ${error?.message ?? error}\n`);
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Records how far a connection got, so a failure can say so.
76
+ *
77
+ * `did not join within 30000ms` is the same sentence whether the server refused the handshake,
78
+ * dropped the login, or is sitting in configuration waiting for something. The protocol state and
79
+ * the last packet that arrived separate those three at a glance, and cost one listener.
80
+ */
81
+ export function traceProgress(bot) {
82
+ const trace = { packet: null };
83
+ bot._client.on('packet', (_data, meta) => {
84
+ trace.packet = meta?.name ?? null;
85
+ });
86
+ return trace;
87
+ }
88
+
89
+ /** Where the connection had got to, in the words the protocol uses for it. */
90
+ export function describeProgress(bot, trace) {
91
+ return `last state: ${bot._client?.state ?? 'unknown'}, `
92
+ + `last packet received: ${trace?.packet ?? 'none'}`;
93
+ }