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 +2 -2
- package/checksums.json +7 -7
- package/lib/jars.mjs +2 -2
- package/package.json +4 -4
- package/runner/src/bots.mjs +13 -3
- package/runner/src/join.mjs +93 -0
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
|
|
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
|
|
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.
|
|
2
|
+
"version": "3.0.2",
|
|
3
3
|
"jars": {
|
|
4
|
-
"mcp-server.jar": "
|
|
4
|
+
"mcp-server.jar": "452ed2928e9e0a6ddcb25dab4586999012b334f9994a0aff8c305148fde0c7ae"
|
|
5
5
|
},
|
|
6
6
|
"assets": {
|
|
7
|
-
"bot-runner-win-x64.exe": "
|
|
8
|
-
"bot-runner-linux-x64": "
|
|
9
|
-
"bot-runner-linux-arm64": "
|
|
10
|
-
"bot-runner-darwin-x64": "
|
|
11
|
-
"bot-runner-darwin-arm64": "
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
17
|
-
"bugs": "https://github.com/Backas03/VitaminMCP
|
|
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
|
|
20
|
+
"url": "git+https://github.com/Backas03/VitaminMCP.git",
|
|
21
21
|
"directory": "npm"
|
|
22
22
|
},
|
|
23
23
|
"license": "MIT",
|
package/runner/src/bots.mjs
CHANGED
|
@@ -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(
|
|
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
|
+
}
|