vitaminmcp 3.0.0 → 3.0.2

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
@@ -64,7 +64,7 @@ agent leaves its host, ports and token where this server reads them.
64
64
 
65
65
  ## What it downloads
66
66
 
67
- On first run, from [the GitHub release](https://github.com/Backas03/VitaminMCP-minecraft/releases)
67
+ On first run, from [the GitHub release](https://github.com/Backas03/VitaminMCP/releases)
68
68
  matching this package's version, into `~/.vitaminmcp/jars/<version>/`:
69
69
 
70
70
  - `mcp-server.jar` (~2 MB) — waited for, since nothing works without it
@@ -83,6 +83,6 @@ Every downloaded file is checked against its pinned SHA-256. A file that does no
83
83
  rather than run.
84
84
 
85
85
  Full documentation, design notes and the plugin itself:
86
- **[github.com/Backas03/VitaminMCP-minecraft](https://github.com/Backas03/VitaminMCP-minecraft)**
86
+ **[github.com/Backas03/VitaminMCP](https://github.com/Backas03/VitaminMCP)**
87
87
 
88
88
  MIT.
package/checksums.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
- "version": "3.0.0",
2
+ "version": "3.0.2",
3
3
  "jars": {
4
- "mcp-server.jar": "576610b6f7deb691fad5633533678ef7a9266574ad731094cc78a01bb24be480"
4
+ "mcp-server.jar": "452ed2928e9e0a6ddcb25dab4586999012b334f9994a0aff8c305148fde0c7ae"
5
5
  },
6
6
  "assets": {
7
- "bot-runner-win-x64.exe": "1a762d0773269ea3205ccb6816e964f33f3ddd15fd1003c6cadd5af4460572ca",
8
- "bot-runner-linux-x64": "a2e975d331461fc699c1998e86a847f8ca161a266caae57c6060bc74bf87569c",
9
- "bot-runner-linux-arm64": "31aad6207a268ddb70298ca1eaa2d443c2f097e1e5b714a2db6b40eea57ec749",
10
- "bot-runner-darwin-x64": "573eb872b1965fe6b8575611f42336fd4dda436e749196e4a1373cffd2939bde",
11
- "bot-runner-darwin-arm64": "853183f7da381f9b831aef9824c69563a732205c61d300fad908cd0f502a73e0",
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",
12
12
  "bot-runner-viewer-win-x64.tgz": "e39a36cdf92b9223aa7c9610f8231afc7a577e8ed007bc3571895bd945baf3e1"
13
13
  }
14
14
  }
package/lib/jars.mjs CHANGED
@@ -9,7 +9,7 @@ import { fileURLToPath } from 'node:url';
9
9
 
10
10
  const HERE = path.dirname(fileURLToPath(import.meta.url));
11
11
 
12
- const RELEASES = 'https://github.com/Backas03/VitaminMCP-minecraft/releases/download';
12
+ const RELEASES = 'https://github.com/Backas03/VitaminMCP/releases/download';
13
13
 
14
14
  /** The jar this package launches, and the one it launches in turn. */
15
15
  export const MCP_SERVER_JAR = 'mcp-server.jar';
@@ -20,7 +20,7 @@ export const VIEWER_ASSET = 'bot-runner-viewer-win-x64.tgz';
20
20
  let checksumsPromise;
21
21
  let manifestPromise;
22
22
 
23
- const REPORT = 'https://github.com/Backas03/VitaminMCP-minecraft/issues';
23
+ const REPORT = 'https://github.com/Backas03/VitaminMCP/issues';
24
24
 
25
25
  /**
26
26
  * The exact bytes this version of the package expects, stamped in at release.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vitaminmcp",
3
- "version": "3.0.0",
3
+ "version": "3.0.2",
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": [
@@ -13,11 +13,11 @@
13
13
  "plugin-testing",
14
14
  "end-to-end-testing"
15
15
  ],
16
- "homepage": "https://github.com/Backas03/VitaminMCP-minecraft#readme",
17
- "bugs": "https://github.com/Backas03/VitaminMCP-minecraft/issues",
16
+ "homepage": "https://github.com/Backas03/VitaminMCP#readme",
17
+ "bugs": "https://github.com/Backas03/VitaminMCP/issues",
18
18
  "repository": {
19
19
  "type": "git",
20
- "url": "git+https://github.com/Backas03/VitaminMCP-minecraft.git",
20
+ "url": "git+https://github.com/Backas03/VitaminMCP.git",
21
21
  "directory": "npm"
22
22
  },
23
23
  "license": "MIT",
@@ -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
+ }