vitaminmcp 1.5.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 +75 -0
- package/bin/vitaminmcp.mjs +134 -0
- package/checksums.json +7 -0
- package/lib/jars.mjs +148 -0
- package/lib/java.mjs +67 -0
- package/package.json +42 -0
package/README.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# vitaminmcp
|
|
2
|
+
|
|
3
|
+
**MCP server for testing Minecraft plugins.** Drives a real Paper/Purpur server and real protocol
|
|
4
|
+
bots from an AI agent, so a plugin can be tested end to end without opening the game.
|
|
5
|
+
|
|
6
|
+
This package is the launcher. It fetches the jars it needs on first run and speaks stdio to your
|
|
7
|
+
MCP client — it is not the whole product on its own: the agent is a Paper plugin, and it goes on
|
|
8
|
+
the Minecraft server.
|
|
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:
|
|
15
|
+
|
|
16
|
+
```json
|
|
17
|
+
{
|
|
18
|
+
"mcpServers": {
|
|
19
|
+
"vitaminmcp": {
|
|
20
|
+
"command": "npx",
|
|
21
|
+
"args": ["-y", "vitaminmcp"]
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
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:
|
|
29
|
+
|
|
30
|
+
> **Prompt:** Set up VitaminMCP on my Minecraft server at ~/servers/test and connect to it.
|
|
31
|
+
|
|
32
|
+
Once the plugin is running, `session_start` needs no arguments for a server on this machine: the
|
|
33
|
+
agent leaves its host, ports and token where this server reads them.
|
|
34
|
+
|
|
35
|
+
## What you get
|
|
36
|
+
|
|
37
|
+
- Spawn and control test players — real protocol clients, not mock `Player` objects
|
|
38
|
+
- Execute commands as the console or as a player
|
|
39
|
+
- Open, read, click and assert on inventories and plugin GUIs
|
|
40
|
+
- Wait for events and conditions instead of sleeping
|
|
41
|
+
- Read live server state: events, logs, exceptions, permissions
|
|
42
|
+
- Paper / Purpur 1.21 through 1.21.8, from one install
|
|
43
|
+
|
|
44
|
+
## Requires
|
|
45
|
+
|
|
46
|
+
- **Java 21 or later** on this machine — the jars run on the JVM. Point `JAVA_HOME` at it, or have
|
|
47
|
+
`java` on `PATH`
|
|
48
|
+
- **Paper 1.21 or later** on the Minecraft server, with `VitaminMCP.jar` in its `plugins/`
|
|
49
|
+
|
|
50
|
+
## Environment
|
|
51
|
+
|
|
52
|
+
| | |
|
|
53
|
+
|---|---|
|
|
54
|
+
| `JAVA_HOME` | the JDK to run the jars with |
|
|
55
|
+
| `VITAMINMCP_HOME` | where jars and agent handshakes are kept. Default `~/.vitaminmcp` |
|
|
56
|
+
| `VITAMINMCP_TOKEN` | an agent token, for a server that leaves no local handshake |
|
|
57
|
+
| `VITAMINMCP_SERVER_JAR` | run this `mcp-server.jar` instead of a downloaded one |
|
|
58
|
+
| `VITAMINMCP_RUNNER_JAR` | use this bot runner instead of a downloaded one |
|
|
59
|
+
|
|
60
|
+
## What it downloads
|
|
61
|
+
|
|
62
|
+
On first run, from [the GitHub release](https://github.com/Backas03/VitaminMCP-minecraft/releases)
|
|
63
|
+
matching this package's version, into `~/.vitaminmcp/jars/<version>/`:
|
|
64
|
+
|
|
65
|
+
- `mcp-server.jar` (~2 MB) — waited for, since nothing works without it
|
|
66
|
+
- `bot-runner.jar` (~93 MB) — fetched in the background, because only bots need it. A client that
|
|
67
|
+
never spawns one never waits for it
|
|
68
|
+
|
|
69
|
+
Both are checked against a SHA-256 pinned into this package at publish time. A file that does not
|
|
70
|
+
match is deleted rather than run.
|
|
71
|
+
|
|
72
|
+
Full documentation, design notes and the plugin itself:
|
|
73
|
+
**[github.com/Backas03/VitaminMCP-minecraft](https://github.com/Backas03/VitaminMCP-minecraft)**
|
|
74
|
+
|
|
75
|
+
MIT.
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { spawn } from 'node:child_process';
|
|
4
|
+
import fs from 'node:fs/promises';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
|
|
8
|
+
import { BOT_RUNNER_JAR, MCP_SERVER_JAR, ensureJar, jarPath } from '../lib/jars.mjs';
|
|
9
|
+
import { checkJava, findJava } from '../lib/java.mjs';
|
|
10
|
+
|
|
11
|
+
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* stdout is the MCP channel and carries nothing but JSON-RPC. Everything this launcher has to say
|
|
15
|
+
* goes to stderr, where a client shows it as server output rather than trying to parse it.
|
|
16
|
+
*/
|
|
17
|
+
function say(message) {
|
|
18
|
+
process.stderr.write(`[vitaminmcp] ${message}\n`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function die(message) {
|
|
22
|
+
process.stderr.write(`[vitaminmcp] ${message}\n`);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function version() {
|
|
27
|
+
const manifest = await fs.readFile(path.join(HERE, '..', 'package.json'), 'utf8');
|
|
28
|
+
return JSON.parse(manifest).version;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const HELP = `vitaminmcp — MCP server for testing Minecraft plugins
|
|
32
|
+
|
|
33
|
+
Launched by an MCP client, not usually by hand. To connect it to Claude Code:
|
|
34
|
+
|
|
35
|
+
claude mcp add vitaminmcp -- npx -y vitaminmcp
|
|
36
|
+
|
|
37
|
+
The agent plugin still has to be installed on the Minecraft server itself. Once this server is
|
|
38
|
+
connected, the /mcp__vitaminmcp__setup command walks through it.
|
|
39
|
+
|
|
40
|
+
Options
|
|
41
|
+
--help this text
|
|
42
|
+
--version the version this launcher will run
|
|
43
|
+
--jars download the jars and print where they are, without starting anything
|
|
44
|
+
|
|
45
|
+
Environment
|
|
46
|
+
JAVA_HOME the JDK to run the jars with; Java 21 or later
|
|
47
|
+
VITAMINMCP_HOME where jars and agent handshakes are kept (default ~/.vitaminmcp)
|
|
48
|
+
VITAMINMCP_TOKEN an agent token, for a server that leaves no local handshake
|
|
49
|
+
VITAMINMCP_SERVER_JAR run this mcp-server jar instead of a downloaded one
|
|
50
|
+
VITAMINMCP_RUNNER_JAR use this bot runner instead of a downloaded one
|
|
51
|
+
`;
|
|
52
|
+
|
|
53
|
+
async function main() {
|
|
54
|
+
const argv = process.argv.slice(2);
|
|
55
|
+
const release = await version();
|
|
56
|
+
|
|
57
|
+
if (argv.includes('--help') || argv.includes('-h')) {
|
|
58
|
+
process.stderr.write(HELP);
|
|
59
|
+
return 0;
|
|
60
|
+
}
|
|
61
|
+
if (argv.includes('--version') || argv.includes('-v')) {
|
|
62
|
+
process.stderr.write(`${release}\n`);
|
|
63
|
+
return 0;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const java = findJava();
|
|
67
|
+
const usable = checkJava(java);
|
|
68
|
+
if (!usable.ok) {
|
|
69
|
+
die(usable.message);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// The server jar is small and nothing works without it, so it is waited for. The runner is
|
|
73
|
+
// ninety megabytes and only bots need it, so it is fetched alongside: a client that never
|
|
74
|
+
// spawns a bot never waits for it, and one that does waits inside a tool call rather than
|
|
75
|
+
// inside a startup timeout.
|
|
76
|
+
let server = process.env.VITAMINMCP_SERVER_JAR;
|
|
77
|
+
if (!server) {
|
|
78
|
+
try {
|
|
79
|
+
server = await ensureJar(release, MCP_SERVER_JAR, { log: say });
|
|
80
|
+
} catch (error) {
|
|
81
|
+
die(String(error.message ?? error));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const runner = process.env.VITAMINMCP_RUNNER_JAR ?? jarPath(release, BOT_RUNNER_JAR);
|
|
86
|
+
const runnerReady = process.env.VITAMINMCP_RUNNER_JAR
|
|
87
|
+
? Promise.resolve(runner)
|
|
88
|
+
: ensureJar(release, BOT_RUNNER_JAR, { log: say }).catch((error) => {
|
|
89
|
+
say(`the bot runner could not be downloaded: ${error.message ?? error}`);
|
|
90
|
+
say('Everything except bots still works. Retry by restarting this server.');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
if (argv.includes('--jars')) {
|
|
94
|
+
await runnerReady;
|
|
95
|
+
process.stderr.write(`${server}\n${runner}\n`);
|
|
96
|
+
return 0;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return await run(java, server, runner);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Runs the server jar, and lives exactly as long as it does.
|
|
104
|
+
*
|
|
105
|
+
* A download still in flight is abandoned when the server exits rather than held on to: the
|
|
106
|
+
* `.part` file it leaves is claimed again by the next start, and a client waiting on a process
|
|
107
|
+
* that no longer serves anything is worse than a jar fetched twice.
|
|
108
|
+
*/
|
|
109
|
+
function run(java, server, runner) {
|
|
110
|
+
const child = spawn(java, ['-jar', server], {
|
|
111
|
+
stdio: 'inherit',
|
|
112
|
+
env: { ...process.env, VITAMINMCP_RUNNER_JAR: runner },
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
return new Promise((resolve) => {
|
|
116
|
+
for (const signal of ['SIGINT', 'SIGTERM']) {
|
|
117
|
+
process.on(signal, () => child.kill(signal));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
child.on('error', (error) => {
|
|
121
|
+
say(`could not start java: ${error.message}`);
|
|
122
|
+
resolve(1);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
child.on('exit', (code, signal) => {
|
|
126
|
+
resolve(signal ? 1 : (code ?? 0));
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
main().then(
|
|
132
|
+
(code) => process.exit(code),
|
|
133
|
+
(error) => die(String(error?.stack ?? error)),
|
|
134
|
+
);
|
package/checksums.json
ADDED
package/lib/jars.mjs
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { createWriteStream } from 'node:fs';
|
|
3
|
+
import fs from 'node:fs/promises';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { Readable } from 'node:stream';
|
|
7
|
+
import { pipeline } from 'node:stream/promises';
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
9
|
+
|
|
10
|
+
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
|
|
12
|
+
const RELEASES = 'https://github.com/Backas03/VitaminMCP-minecraft/releases/download';
|
|
13
|
+
|
|
14
|
+
/** The jar this package launches, and the one it launches in turn. */
|
|
15
|
+
export const MCP_SERVER_JAR = 'mcp-server.jar';
|
|
16
|
+
export const BOT_RUNNER_JAR = 'bot-runner.jar';
|
|
17
|
+
|
|
18
|
+
let checksumsPromise;
|
|
19
|
+
|
|
20
|
+
const REPORT = 'https://github.com/Backas03/VitaminMCP-minecraft/issues';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The exact bytes this version of the package expects, stamped in at release.
|
|
24
|
+
*
|
|
25
|
+
* Pinning them here rather than trusting whatever the release currently serves is the point: a
|
|
26
|
+
* release asset can be replaced after the fact, and a published npm version cannot.
|
|
27
|
+
*
|
|
28
|
+
* The file names the version it was stamped for, and that is checked. Otherwise the one mistake
|
|
29
|
+
* this design invites — publishing a version bump without re-stamping — would ship a package that
|
|
30
|
+
* downloads one release and checks it against another, and every install would fail on a hash
|
|
31
|
+
* mismatch that reads exactly like a compromised download.
|
|
32
|
+
*/
|
|
33
|
+
async function checksums(version) {
|
|
34
|
+
checksumsPromise ??= fs
|
|
35
|
+
.readFile(path.join(HERE, '..', 'checksums.json'), 'utf8')
|
|
36
|
+
.then(JSON.parse)
|
|
37
|
+
.catch((cause) => {
|
|
38
|
+
throw new Error(
|
|
39
|
+
'This package was published without checksums.json, so it cannot verify what it ' +
|
|
40
|
+
`downloads and will not download anything. Please report it at ${REPORT}.`,
|
|
41
|
+
{ cause },
|
|
42
|
+
);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const stamped = await checksumsPromise;
|
|
46
|
+
if (stamped.version !== version) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
`This package is version ${version} but its checksums were stamped for ` +
|
|
49
|
+
`${stamped.version}, so it cannot tell a bad download from a mismatched one. Nothing ` +
|
|
50
|
+
`was installed. Please report it at ${REPORT}.`,
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
return stamped.jars ?? {};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Where downloaded jars live: one directory per version, so switching versions never mixes them.
|
|
58
|
+
*
|
|
59
|
+
* The same root the agent writes its handshake into, so one variable moves everything VitaminMCP
|
|
60
|
+
* keeps outside the project.
|
|
61
|
+
*/
|
|
62
|
+
export function cacheDirectory(version) {
|
|
63
|
+
const root = process.env.VITAMINMCP_HOME || path.join(os.homedir(), '.vitaminmcp');
|
|
64
|
+
return path.join(root, 'jars', version);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** A jar already on this machine, or null. Never downloads. */
|
|
68
|
+
export async function cachedJar(version, name) {
|
|
69
|
+
const file = path.join(cacheDirectory(version), name);
|
|
70
|
+
try {
|
|
71
|
+
await fs.access(file);
|
|
72
|
+
return file;
|
|
73
|
+
} catch {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Makes sure one jar is on this machine, downloading it if not, and returns its path.
|
|
80
|
+
*
|
|
81
|
+
* Downloads land on a `.part` file and are renamed once the hash matches, so an interrupted
|
|
82
|
+
* download is never mistaken for a finished one — and the server, which waits for exactly that
|
|
83
|
+
* rename, never opens a half-written jar.
|
|
84
|
+
*/
|
|
85
|
+
export async function ensureJar(version, name, { log = () => {} } = {}) {
|
|
86
|
+
const directory = cacheDirectory(version);
|
|
87
|
+
const file = path.join(directory, name);
|
|
88
|
+
|
|
89
|
+
const existing = await cachedJar(version, name);
|
|
90
|
+
if (existing) {
|
|
91
|
+
return existing;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const expected = (await checksums(version))[name];
|
|
95
|
+
if (!expected) {
|
|
96
|
+
throw new Error(`checksums.json does not cover ${name}, so it will not be downloaded.`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
await fs.mkdir(directory, { recursive: true });
|
|
100
|
+
const partial = `${file}.part`;
|
|
101
|
+
const url = `${RELEASES}/${version}/${name}`;
|
|
102
|
+
|
|
103
|
+
// Claimed before the request goes out, not after it answers. The server waits on this file to
|
|
104
|
+
// decide whether a jar is coming or simply absent, and between spawning it and the first byte
|
|
105
|
+
// arriving there is easily enough time for it to ask.
|
|
106
|
+
await fs.writeFile(partial, '');
|
|
107
|
+
|
|
108
|
+
log(`downloading ${name} (${version})`);
|
|
109
|
+
|
|
110
|
+
const response = await fetch(url, { redirect: 'follow' });
|
|
111
|
+
if (!response.ok || !response.body) {
|
|
112
|
+
throw new Error(
|
|
113
|
+
`Could not download ${name}: ${response.status} ${response.statusText}\n ${url}`,
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const hash = createHash('sha256');
|
|
118
|
+
const source = Readable.fromWeb(response.body);
|
|
119
|
+
source.on('data', (chunk) => hash.update(chunk));
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
await pipeline(source, createWriteStream(partial));
|
|
123
|
+
|
|
124
|
+
const actual = hash.digest('hex');
|
|
125
|
+
if (actual !== expected) {
|
|
126
|
+
throw new Error(
|
|
127
|
+
`${name} does not match the checksum this package was published with.\n` +
|
|
128
|
+
` expected ${expected}\n` +
|
|
129
|
+
` received ${actual}\n` +
|
|
130
|
+
` from ${url}\n` +
|
|
131
|
+
'Nothing was installed. Please report this.',
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
await fs.rename(partial, file);
|
|
136
|
+
} catch (error) {
|
|
137
|
+
await fs.rm(partial, { force: true });
|
|
138
|
+
throw error;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
log(`${name} ready`);
|
|
142
|
+
return file;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Where a jar will be once it has downloaded, whether or not it has. */
|
|
146
|
+
export function jarPath(version, name) {
|
|
147
|
+
return path.join(cacheDirectory(version), name);
|
|
148
|
+
}
|
package/lib/java.mjs
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
/** The floor the jars are compiled against. Below it they will not load at all. */
|
|
6
|
+
export const REQUIRED_JAVA = 21;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The java to run the jars with: JAVA_HOME if it points at one, otherwise whatever is on PATH.
|
|
10
|
+
*
|
|
11
|
+
* JAVA_HOME wins because a machine with several JDKs usually means one of them was chosen
|
|
12
|
+
* deliberately, and PATH is the one nobody remembers setting.
|
|
13
|
+
*/
|
|
14
|
+
export function findJava() {
|
|
15
|
+
const home = process.env.JAVA_HOME;
|
|
16
|
+
if (home) {
|
|
17
|
+
const candidate = path.join(home, 'bin', process.platform === 'win32' ? 'java.exe' : 'java');
|
|
18
|
+
if (existsSync(candidate)) {
|
|
19
|
+
return candidate;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return process.platform === 'win32' ? 'java.exe' : 'java';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Checks the java that will actually be used, and says what is wrong in terms of a fix.
|
|
27
|
+
*
|
|
28
|
+
* Worth doing before spawning: java's own failure for a too-old runtime is
|
|
29
|
+
* `UnsupportedClassVersionError` naming class file version 65, which is a puzzle rather than a
|
|
30
|
+
* message, and an MCP client shows it — if it shows anything — as a server that died at startup.
|
|
31
|
+
*/
|
|
32
|
+
export function checkJava(java) {
|
|
33
|
+
const probe = spawnSync(java, ['-version'], { encoding: 'utf8' });
|
|
34
|
+
|
|
35
|
+
if (probe.error) {
|
|
36
|
+
return {
|
|
37
|
+
ok: false,
|
|
38
|
+
message:
|
|
39
|
+
`No Java found. VitaminMCP runs on the JVM, so it needs Java ${REQUIRED_JAVA} or later ` +
|
|
40
|
+
`on this machine.\n` +
|
|
41
|
+
` Tried: ${java}\n` +
|
|
42
|
+
` Install a JDK (https://adoptium.net/), or point JAVA_HOME at one you already have.`,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Every JVM prints its version to stderr, and has since before anyone thought to standardise it.
|
|
47
|
+
const output = `${probe.stderr || ''}${probe.stdout || ''}`;
|
|
48
|
+
const match = output.match(/version "(\d+)(?:\.(\d+))?/);
|
|
49
|
+
if (!match) {
|
|
50
|
+
// An unrecognised banner is not a reason to refuse; the jars themselves will say so if it
|
|
51
|
+
// really is too old.
|
|
52
|
+
return { ok: true, version: null };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const major = match[1] === '1' ? Number(match[2]) : Number(match[1]);
|
|
56
|
+
if (major < REQUIRED_JAVA) {
|
|
57
|
+
return {
|
|
58
|
+
ok: false,
|
|
59
|
+
version: major,
|
|
60
|
+
message:
|
|
61
|
+
`Java ${major} is too old — VitaminMCP needs ${REQUIRED_JAVA} or later.\n` +
|
|
62
|
+
` Using: ${java}\n` +
|
|
63
|
+
` Install a newer JDK (https://adoptium.net/), or point JAVA_HOME at one you already have.`,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
return { ok: true, version: major };
|
|
67
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vitaminmcp",
|
|
3
|
+
"version": "1.5.0",
|
|
4
|
+
"mcpName": "io.github.backas03/vitaminmcp",
|
|
5
|
+
"description": "MCP server for testing Minecraft plugins: drives a real Paper server and real protocol bots from an AI agent.",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"mcp",
|
|
8
|
+
"modelcontextprotocol",
|
|
9
|
+
"minecraft",
|
|
10
|
+
"paper",
|
|
11
|
+
"purpur",
|
|
12
|
+
"bukkit",
|
|
13
|
+
"plugin-testing",
|
|
14
|
+
"end-to-end-testing"
|
|
15
|
+
],
|
|
16
|
+
"homepage": "https://github.com/Backas03/VitaminMCP-minecraft#readme",
|
|
17
|
+
"bugs": "https://github.com/Backas03/VitaminMCP-minecraft/issues",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/Backas03/VitaminMCP-minecraft.git",
|
|
21
|
+
"directory": "npm"
|
|
22
|
+
},
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"author": "Backas03",
|
|
25
|
+
"type": "module",
|
|
26
|
+
"bin": {
|
|
27
|
+
"vitaminmcp": "bin/vitaminmcp.mjs"
|
|
28
|
+
},
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=18.17"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"bin",
|
|
34
|
+
"lib",
|
|
35
|
+
"checksums.json",
|
|
36
|
+
"README.md"
|
|
37
|
+
],
|
|
38
|
+
"scripts": {
|
|
39
|
+
"stamp": "node scripts/stamp-checksums.mjs",
|
|
40
|
+
"prepublishOnly": "node scripts/stamp-checksums.mjs --verify"
|
|
41
|
+
}
|
|
42
|
+
}
|