stonecut-map-builder 0.0.0-stage → 0.1.1
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/LICENSE +21 -0
- package/README.md +86 -2
- package/SECURITY.md +31 -0
- package/config.example.json +18 -0
- package/dist/cli.js +45 -0
- package/dist/config.js +127 -0
- package/dist/content.js +7 -0
- package/dist/context.js +122 -0
- package/dist/doctor.js +202 -0
- package/dist/guide.js +22 -0
- package/dist/index.js +26 -0
- package/dist/mc/blocks.js +197 -0
- package/dist/mc/box.js +71 -0
- package/dist/mc/download.js +75 -0
- package/dist/mc/entities.js +241 -0
- package/dist/mc/java.js +97 -0
- package/dist/mc/properties.js +149 -0
- package/dist/mc/setup.js +167 -0
- package/dist/mc/testserver.js +309 -0
- package/dist/mc/versions.js +108 -0
- package/dist/nbt/nbt.js +282 -0
- package/dist/nbt/snbt.js +36 -0
- package/dist/ops/backup.js +69 -0
- package/dist/ops/files.js +118 -0
- package/dist/ops/readout.js +100 -0
- package/dist/ops/undo.js +45 -0
- package/dist/ops/world.js +370 -0
- package/dist/rcon/rcon.js +216 -0
- package/dist/safety/address.js +44 -0
- package/dist/safety/paths.js +103 -0
- package/dist/schemas.js +57 -0
- package/dist/schematic/sponge.js +264 -0
- package/dist/server.js +16 -0
- package/dist/tools/backup.js +38 -0
- package/dist/tools/blocks.js +140 -0
- package/dist/tools/define.js +37 -0
- package/dist/tools/files.js +51 -0
- package/dist/tools/schematic.js +59 -0
- package/dist/tools/server.js +97 -0
- package/dist/tools/setup.js +187 -0
- package/dist/version.js +2 -0
- package/dist/world/entities.js +56 -0
- package/dist/world/region.js +192 -0
- package/docs/reference.md +231 -0
- package/docs/setup-with-ai.md +83 -0
- package/package.json +54 -4
- package/skills/map-builder/SKILL.md +131 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Stonecut Studio
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,87 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Stonecut Map Builder
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Website: https://stonecut.studio/resources | npm: https://www.npmjs.com/package/stonecut-map-builder | YouTube: https://www.youtube.com/@StonecutStudios (videos coming soon)
|
|
4
|
+
|
|
5
|
+
Let your AI agent build Minecraft maps for you. Stonecut Map Builder gives Claude Code (or any agent
|
|
6
|
+
that supports MCP) its own private Minecraft test server on your computer. The agent sets it up,
|
|
7
|
+
builds on it, checks its own work block by block, and exports what it made as a schematic you can
|
|
8
|
+
paste on any server. Free and open source under the MIT license.
|
|
9
|
+
|
|
10
|
+
<!-- VIDEO: how it works (owner sends the link) -->
|
|
11
|
+
|
|
12
|
+
## Setup
|
|
13
|
+
|
|
14
|
+
1. Install Node.js 20 or newer from https://nodejs.org (the LTS download).
|
|
15
|
+
2. Have Claude Code installed and signed in.
|
|
16
|
+
3. Add the tool to Claude Code. If the `claude` command works in your terminal:
|
|
17
|
+
- macOS and Linux: `claude mcp add --scope user stonecut-map-builder -- npx -y stonecut-map-builder`
|
|
18
|
+
- Windows: `claude mcp add --scope user stonecut-map-builder -- cmd /c npx -y stonecut-map-builder`
|
|
19
|
+
|
|
20
|
+
If you only use the Claude Code extension in VS Code, `claude` is usually not on your PATH
|
|
21
|
+
("not recognized" / "command not found"). Run the copy bundled with the extension instead:
|
|
22
|
+
- Windows (PowerShell):
|
|
23
|
+
|
|
24
|
+
```powershell
|
|
25
|
+
& (Get-ChildItem "$env:USERPROFILE\.vscode\extensions\anthropic.claude-code-*\resources\native-binary\claude.exe" | Sort-Object { [version]($_.FullName -replace '.*claude-code-([\d.]+).*','$1') } | Select-Object -Last 1).FullName mcp add --scope user stonecut-map-builder -- cmd /c npx -y stonecut-map-builder
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
- macOS and Linux:
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
"$(ls -d ~/.vscode/extensions/anthropic.claude-code-*/resources/native-binary/claude | sort -V | tail -n 1)" mcp add --scope user stonecut-map-builder -- npx -y stonecut-map-builder
|
|
32
|
+
```
|
|
33
|
+
4. Start Claude Code (in VS Code: "Developer: Reload Window"), type `/mcp` and check that
|
|
34
|
+
stonecut-map-builder shows as connected.
|
|
35
|
+
5. Paste the setup prompt from [docs/setup-with-ai.md](docs/setup-with-ai.md). The agent installs
|
|
36
|
+
the skill (it asks first), asks your Minecraft version and modpack, shows what it will download,
|
|
37
|
+
and asks you to accept the Minecraft EULA. Nothing is installed without your yes.
|
|
38
|
+
6. It starts the server and gives you an address like `localhost:25565`. Join it from Minecraft
|
|
39
|
+
(same version), ask the agent to make you operator, then ask it to build.
|
|
40
|
+
|
|
41
|
+
## Example requests
|
|
42
|
+
|
|
43
|
+
- "Build a small stone watchtower with a spiral staircase and a lookout on top, next to spawn."
|
|
44
|
+
- "Make a medieval market square with five stalls, a well in the middle and lanterns."
|
|
45
|
+
- "Read back the house you just built and fix anything that looks wrong."
|
|
46
|
+
- "Export the castle as a schematic so I can paste it on my server."
|
|
47
|
+
|
|
48
|
+
## Works with Stonecut Studio
|
|
49
|
+
|
|
50
|
+
Make a map or a piece on [stonecut.studio](https://stonecut.studio), download it as a `.schem`, put
|
|
51
|
+
it in your test server's folder, and ask your agent to import it. Then keep building on it with your
|
|
52
|
+
agent: add interiors, change materials, add quests or mobs, and export the result. Big maps are
|
|
53
|
+
bigger than the tool's import limit by default; download single pieces, or raise `maxImportBlocks`
|
|
54
|
+
(see the [reference](docs/reference.md#settings)).
|
|
55
|
+
|
|
56
|
+
<!-- SHOWCASE: images of builds (owner sends them) -->
|
|
57
|
+
|
|
58
|
+
## What it supports
|
|
59
|
+
|
|
60
|
+
- Minecraft Java Edition 1.20.1 and newer, vanilla and NeoForge. More versions and mod loaders are
|
|
61
|
+
coming.
|
|
62
|
+
- Windows, macOS and Linux. Made for Claude Code; other MCP agents should work.
|
|
63
|
+
- Java for your Minecraft version: Java 17 for 1.20.1 to 1.20.4, Java 21 for 1.20.5 to 1.21.11,
|
|
64
|
+
Java 25 for 26.1 and newer. The agent tells you if it is missing and where to get it.
|
|
65
|
+
|
|
66
|
+
## Safety
|
|
67
|
+
|
|
68
|
+
- The test server only listens on your own computer. Nothing connects to another machine.
|
|
69
|
+
- Server files come only from Mojang and NeoForged, checked against their published hashes.
|
|
70
|
+
- It only works inside the folders you allow. It installs nothing, and clears, replaces or restores nothing, before you say yes.
|
|
71
|
+
- Every change can be undone, and whole-world backups are one request away.
|
|
72
|
+
- Your agent may have other tools, like a shell, that these rules don't cover. Keep your agent's
|
|
73
|
+
own permission prompts on.
|
|
74
|
+
|
|
75
|
+
The full rules and every setting are in the [reference](docs/reference.md#safety).
|
|
76
|
+
|
|
77
|
+
## Troubleshooting
|
|
78
|
+
|
|
79
|
+
Run `npx -y stonecut-map-builder doctor`. It checks Node, your settings, Java, the test server, the
|
|
80
|
+
skill and whether the tool starts, and says how to fix each problem. It changes nothing. See the
|
|
81
|
+
[reference](docs/reference.md#doctor).
|
|
82
|
+
|
|
83
|
+
## More
|
|
84
|
+
|
|
85
|
+
- [Full reference](docs/reference.md): every tool, setting and limit, and manual install.
|
|
86
|
+
- Security reports: [SECURITY.md](SECURITY.md).
|
|
87
|
+
- License: MIT, see [LICENSE](LICENSE).
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Security
|
|
2
|
+
|
|
3
|
+
## Reporting a problem
|
|
4
|
+
|
|
5
|
+
Please report security problems privately through GitHub: open the repository's **Security** tab
|
|
6
|
+
and choose **Report a vulnerability**. Do not open a public issue for a security problem.
|
|
7
|
+
|
|
8
|
+
Include what you did, what happened, what you expected, and the tool version
|
|
9
|
+
(`npx -y stonecut-map-builder --version`). We aim to answer within 7 days and to publish a fix
|
|
10
|
+
and an advisory once it is fixed.
|
|
11
|
+
|
|
12
|
+
## Supported versions
|
|
13
|
+
|
|
14
|
+
Only the latest released version gets security fixes.
|
|
15
|
+
|
|
16
|
+
## What counts
|
|
17
|
+
|
|
18
|
+
Anything that breaks the safety rules in the [reference](docs/reference.md#safety), for example:
|
|
19
|
+
|
|
20
|
+
- a path that reads or writes outside the test server folder, or into the tool's own `.stonecut` folder;
|
|
21
|
+
- RCON or the test server becoming reachable from another computer;
|
|
22
|
+
- a way to run a program, or to add a mod or other code, while `allowModInstall` is off;
|
|
23
|
+
- the RCON password reaching the agent;
|
|
24
|
+
- a download from a source other than Mojang or NeoForged, or one used without a matching hash;
|
|
25
|
+
- a tool that changes something while marked read-only;
|
|
26
|
+
- a way to accept the Minecraft EULA or install something without the person's yes;
|
|
27
|
+
- block names, file contents or schematic data that make the tool send a command nobody asked for.
|
|
28
|
+
Command blocks and functions inside a schematic or datapack are content the game runs, not a
|
|
29
|
+
flaw in the tool (see Limits in the reference).
|
|
30
|
+
|
|
31
|
+
Problems in Minecraft, NeoForge, Java or your agent itself should go to those projects.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_comment": "Stonecut Map Builder settings. Every key is optional; see the Safety section of the README.",
|
|
3
|
+
"serverFolders": [
|
|
4
|
+
"~/stonecut-map-builder/servers"
|
|
5
|
+
],
|
|
6
|
+
"maxFillBlocks": 100000,
|
|
7
|
+
"maxReadBlocks": 32768,
|
|
8
|
+
"maxImportBlocks": 500000,
|
|
9
|
+
"maxFileBytes": 10485760,
|
|
10
|
+
"maxEntities": 256,
|
|
11
|
+
"includeEntities": true,
|
|
12
|
+
"allowFileWrites": true,
|
|
13
|
+
"allowModInstall": false,
|
|
14
|
+
"confirmDestructive": true,
|
|
15
|
+
"autoBackup": true,
|
|
16
|
+
"undoHistory": 10,
|
|
17
|
+
"rconAddress": "127.0.0.1"
|
|
18
|
+
}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { CONFIG_ENV, configFile, exampleConfig } from './config.js';
|
|
5
|
+
import { VERSION } from './version.js';
|
|
6
|
+
export const HELP = `stonecut-map-builder ${VERSION}
|
|
7
|
+
|
|
8
|
+
Without options it runs the MCP server on stdio (an MCP client starts it for you).
|
|
9
|
+
|
|
10
|
+
Commands:
|
|
11
|
+
doctor Check Node, the settings file, Java, the test server, the skill and whether the
|
|
12
|
+
MCP server starts, and say how to fix what fails. Changes nothing.
|
|
13
|
+
|
|
14
|
+
Options:
|
|
15
|
+
--config-path Print where the settings file is read from.
|
|
16
|
+
--init-config Write the settings file with every setting at its default, if it does not exist.
|
|
17
|
+
--skill-path Print the folder of the bundled map-builder skill.
|
|
18
|
+
--version Print the version.
|
|
19
|
+
--help Print this help.
|
|
20
|
+
|
|
21
|
+
Environment:
|
|
22
|
+
${CONFIG_ENV} Use this settings file instead of the standard one.`;
|
|
23
|
+
export function skillPath() {
|
|
24
|
+
return join(dirname(fileURLToPath(import.meta.url)), '..', 'skills', 'map-builder');
|
|
25
|
+
}
|
|
26
|
+
export function initConfig(file = configFile()) {
|
|
27
|
+
if (existsSync(file))
|
|
28
|
+
return `Settings file already exists, left unchanged: ${file}`;
|
|
29
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
30
|
+
writeFileSync(file, `${JSON.stringify(exampleConfig(), null, 2)}\n`);
|
|
31
|
+
return `Wrote ${file}`;
|
|
32
|
+
}
|
|
33
|
+
export function cliAnswer(argv) {
|
|
34
|
+
if (argv.includes('--config-path'))
|
|
35
|
+
return configFile();
|
|
36
|
+
if (argv.includes('--init-config'))
|
|
37
|
+
return initConfig();
|
|
38
|
+
if (argv.includes('--skill-path'))
|
|
39
|
+
return skillPath();
|
|
40
|
+
if (argv.includes('--version'))
|
|
41
|
+
return VERSION;
|
|
42
|
+
if (argv.includes('--help') || argv.includes('-h'))
|
|
43
|
+
return HELP;
|
|
44
|
+
return null;
|
|
45
|
+
}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { dirname, isAbsolute, join, resolve } from 'node:path';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { RCON_HOST } from './safety/address.js';
|
|
6
|
+
// The person's own settings. Nothing here may point at a particular computer: every default comes
|
|
7
|
+
// from the operating system (home folder, standard config folder), and the person can override it.
|
|
8
|
+
export const APP_FOLDER = 'stonecut-map-builder';
|
|
9
|
+
export const CONFIG_ENV = 'STONECUT_MAP_CONFIG';
|
|
10
|
+
/** Standard per-user config folder for this OS. */
|
|
11
|
+
export function configDir(env = process.env, home = homedir(), platform = process.platform) {
|
|
12
|
+
if (platform === 'win32')
|
|
13
|
+
return join(env.APPDATA || join(home, 'AppData', 'Roaming'), APP_FOLDER);
|
|
14
|
+
if (platform === 'darwin')
|
|
15
|
+
return join(home, 'Library', 'Application Support', APP_FOLDER);
|
|
16
|
+
return join(env.XDG_CONFIG_HOME || join(home, '.config'), APP_FOLDER);
|
|
17
|
+
}
|
|
18
|
+
export function defaultServerFolder(home = homedir()) {
|
|
19
|
+
return join(home, APP_FOLDER, 'servers');
|
|
20
|
+
}
|
|
21
|
+
export const DEFAULTS = {
|
|
22
|
+
maxFillBlocks: 100_000,
|
|
23
|
+
maxReadBlocks: 32_768,
|
|
24
|
+
maxImportBlocks: 500_000,
|
|
25
|
+
maxFileBytes: 10 * 1024 * 1024,
|
|
26
|
+
maxEntities: 256,
|
|
27
|
+
includeEntities: true,
|
|
28
|
+
allowFileWrites: true,
|
|
29
|
+
allowModInstall: false,
|
|
30
|
+
confirmDestructive: true,
|
|
31
|
+
autoBackup: true,
|
|
32
|
+
undoHistory: 10,
|
|
33
|
+
};
|
|
34
|
+
const folder = z.string().min(1).describe('Absolute path, or one starting with ~/ for the home folder');
|
|
35
|
+
export const configSchema = z.strictObject({
|
|
36
|
+
serverFolders: z.array(folder).min(1).max(16).optional(),
|
|
37
|
+
maxFillBlocks: z.number().int().min(1).max(10_000_000).default(DEFAULTS.maxFillBlocks),
|
|
38
|
+
maxReadBlocks: z.number().int().min(1).max(1_000_000).default(DEFAULTS.maxReadBlocks),
|
|
39
|
+
maxImportBlocks: z.number().int().min(1).max(20_000_000).default(DEFAULTS.maxImportBlocks),
|
|
40
|
+
maxFileBytes: z.number().int().min(1).max(512 * 1024 * 1024).default(DEFAULTS.maxFileBytes),
|
|
41
|
+
maxEntities: z.number().int().min(0).max(10_000).default(DEFAULTS.maxEntities),
|
|
42
|
+
includeEntities: z.boolean().default(DEFAULTS.includeEntities),
|
|
43
|
+
allowFileWrites: z.boolean().default(DEFAULTS.allowFileWrites),
|
|
44
|
+
allowModInstall: z.boolean().default(DEFAULTS.allowModInstall),
|
|
45
|
+
confirmDestructive: z.boolean().default(DEFAULTS.confirmDestructive),
|
|
46
|
+
autoBackup: z.boolean().default(DEFAULTS.autoBackup),
|
|
47
|
+
undoHistory: z.number().int().min(1).max(100).default(DEFAULTS.undoHistory),
|
|
48
|
+
rconAddress: z
|
|
49
|
+
.literal(RCON_HOST, { error: `rconAddress can only be ${RCON_HOST}: RCON never listens on a network address in this version` })
|
|
50
|
+
.default(RCON_HOST),
|
|
51
|
+
javaPath: z.string().min(1).optional(),
|
|
52
|
+
$schema: z.string().optional(),
|
|
53
|
+
_comment: z.string().optional(),
|
|
54
|
+
});
|
|
55
|
+
export function expandHome(p, home = homedir()) {
|
|
56
|
+
if (p === '~')
|
|
57
|
+
return home;
|
|
58
|
+
if (p.startsWith('~/') || p.startsWith('~\\'))
|
|
59
|
+
return join(home, p.slice(2));
|
|
60
|
+
return p;
|
|
61
|
+
}
|
|
62
|
+
export function configFile(env = process.env, home = homedir()) {
|
|
63
|
+
const override = env[CONFIG_ENV]?.trim();
|
|
64
|
+
return override ? resolve(expandHome(override, home)) : join(configDir(env, home), 'config.json');
|
|
65
|
+
}
|
|
66
|
+
export function loadConfig(env = process.env, home = homedir()) {
|
|
67
|
+
const file = configFile(env, home);
|
|
68
|
+
const fileExists = existsSync(file);
|
|
69
|
+
let raw = {};
|
|
70
|
+
if (fileExists) {
|
|
71
|
+
try {
|
|
72
|
+
raw = JSON.parse(readFileSync(file, 'utf8').replace(/^/, ''));
|
|
73
|
+
}
|
|
74
|
+
catch (e) {
|
|
75
|
+
throw new Error(`Config file ${file} is not valid JSON: ${e instanceof Error ? e.message : String(e)}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const parsed = configSchema.safeParse(raw);
|
|
79
|
+
if (!parsed.success)
|
|
80
|
+
throw new Error(`Config file ${file} has a problem:\n${z.prettifyError(parsed.error)}`);
|
|
81
|
+
const c = parsed.data;
|
|
82
|
+
const serverFolders = (c.serverFolders ?? [defaultServerFolder(home)]).map((f) => {
|
|
83
|
+
const p = expandHome(f, home);
|
|
84
|
+
if (!isAbsolute(p))
|
|
85
|
+
throw new Error(`Config file ${file}: server folder "${f}" must be a full path or start with ~/`);
|
|
86
|
+
return resolve(p);
|
|
87
|
+
});
|
|
88
|
+
const javaPath = c.javaPath ? resolve(expandHome(c.javaPath, home)) : undefined;
|
|
89
|
+
return {
|
|
90
|
+
file,
|
|
91
|
+
fileExists,
|
|
92
|
+
stateDir: env[CONFIG_ENV]?.trim() ? dirname(file) : configDir(env, home),
|
|
93
|
+
serverFolders,
|
|
94
|
+
maxFillBlocks: c.maxFillBlocks,
|
|
95
|
+
maxReadBlocks: c.maxReadBlocks,
|
|
96
|
+
maxImportBlocks: c.maxImportBlocks,
|
|
97
|
+
maxFileBytes: c.maxFileBytes,
|
|
98
|
+
maxEntities: c.maxEntities,
|
|
99
|
+
includeEntities: c.includeEntities,
|
|
100
|
+
allowFileWrites: c.allowFileWrites,
|
|
101
|
+
allowModInstall: c.allowModInstall,
|
|
102
|
+
confirmDestructive: c.confirmDestructive,
|
|
103
|
+
autoBackup: c.autoBackup,
|
|
104
|
+
undoHistory: c.undoHistory,
|
|
105
|
+
rconAddress: c.rconAddress,
|
|
106
|
+
...(javaPath ? { javaPath } : {}),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/** The example config with every setting at its default, as written by --init-config. */
|
|
110
|
+
export function exampleConfig() {
|
|
111
|
+
return {
|
|
112
|
+
_comment: 'Stonecut Map Builder settings. Every key is optional; see the Safety section of the README.',
|
|
113
|
+
serverFolders: [`~/${APP_FOLDER}/servers`],
|
|
114
|
+
maxFillBlocks: DEFAULTS.maxFillBlocks,
|
|
115
|
+
maxReadBlocks: DEFAULTS.maxReadBlocks,
|
|
116
|
+
maxImportBlocks: DEFAULTS.maxImportBlocks,
|
|
117
|
+
maxFileBytes: DEFAULTS.maxFileBytes,
|
|
118
|
+
maxEntities: DEFAULTS.maxEntities,
|
|
119
|
+
includeEntities: DEFAULTS.includeEntities,
|
|
120
|
+
allowFileWrites: DEFAULTS.allowFileWrites,
|
|
121
|
+
allowModInstall: DEFAULTS.allowModInstall,
|
|
122
|
+
confirmDestructive: DEFAULTS.confirmDestructive,
|
|
123
|
+
autoBackup: DEFAULTS.autoBackup,
|
|
124
|
+
undoHistory: DEFAULTS.undoHistory,
|
|
125
|
+
rconAddress: RCON_HOST,
|
|
126
|
+
};
|
|
127
|
+
}
|
package/dist/content.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export function textResult(value) {
|
|
2
|
+
return { content: [{ type: 'text', text: typeof value === 'string' ? value : JSON.stringify(value, null, 2) }] };
|
|
3
|
+
}
|
|
4
|
+
export function errorResult(err) {
|
|
5
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
6
|
+
return { isError: true, content: [{ type: 'text', text: message }] };
|
|
7
|
+
}
|
package/dist/context.js
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { findJava, pickJava } from './mc/java.js';
|
|
4
|
+
import { detectLaunch, metaPath, ServerError, TestServer, writeMeta } from './mc/testserver.js';
|
|
5
|
+
import { readProperties } from './mc/properties.js';
|
|
6
|
+
import { World } from './ops/world.js';
|
|
7
|
+
import { checkAllowedFolder } from './safety/paths.js';
|
|
8
|
+
// What the MCP server remembers between calls: the settings, the one active test server, and
|
|
9
|
+
// setup plans waiting for the person's yes. Calls that touch the server run one at a time.
|
|
10
|
+
export class Context {
|
|
11
|
+
config;
|
|
12
|
+
plans = new Map();
|
|
13
|
+
active = null;
|
|
14
|
+
worldOps = null;
|
|
15
|
+
chain = Promise.resolve();
|
|
16
|
+
constructor(config) {
|
|
17
|
+
this.config = config;
|
|
18
|
+
}
|
|
19
|
+
get stateFile() {
|
|
20
|
+
return join(this.config.stateDir, 'state.json');
|
|
21
|
+
}
|
|
22
|
+
/** Runs fn after every earlier call has finished. */
|
|
23
|
+
serial(fn) {
|
|
24
|
+
const p = this.chain.then(fn, fn);
|
|
25
|
+
this.chain = p.catch(() => undefined);
|
|
26
|
+
return p;
|
|
27
|
+
}
|
|
28
|
+
remembered() {
|
|
29
|
+
if (!existsSync(this.stateFile))
|
|
30
|
+
return null;
|
|
31
|
+
try {
|
|
32
|
+
const s = JSON.parse(readFileSync(this.stateFile, 'utf8'));
|
|
33
|
+
return s.activeServer ?? null;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/** Servers made by this tool inside the allowed folders. */
|
|
40
|
+
knownServers() {
|
|
41
|
+
const out = [];
|
|
42
|
+
for (const root of this.config.serverFolders) {
|
|
43
|
+
if (existsSync(metaPath(root)))
|
|
44
|
+
out.push(root);
|
|
45
|
+
if (!existsSync(root))
|
|
46
|
+
continue;
|
|
47
|
+
for (const d of readdirSync(root, { withFileTypes: true })) {
|
|
48
|
+
if (d.isDirectory() && existsSync(metaPath(join(root, d.name))))
|
|
49
|
+
out.push(join(root, d.name));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
/** The server to use when none is active yet: the remembered one, else the first known one. Only reads. */
|
|
55
|
+
findServer() {
|
|
56
|
+
const candidates = [this.remembered(), ...this.knownServers()].filter((p) => !!p && existsSync(metaPath(p)));
|
|
57
|
+
for (const dir of candidates) {
|
|
58
|
+
try {
|
|
59
|
+
return TestServer.open(checkAllowedFolder(this.config.serverFolders, dir));
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// no longer allowed or unreadable; try the next one
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
/** The active server, or null if there is none yet. */
|
|
68
|
+
current() {
|
|
69
|
+
if (this.active)
|
|
70
|
+
return this.active;
|
|
71
|
+
const found = this.findServer();
|
|
72
|
+
return found ? this.setActive(found.dir) : null;
|
|
73
|
+
}
|
|
74
|
+
server() {
|
|
75
|
+
const s = this.current();
|
|
76
|
+
if (!s)
|
|
77
|
+
throw new ServerError('There is no test server yet. Ask the person which Minecraft version and modpack they use, then call plan_setup.');
|
|
78
|
+
return s;
|
|
79
|
+
}
|
|
80
|
+
world() {
|
|
81
|
+
const s = this.server();
|
|
82
|
+
if (!this.worldOps || this.worldOps.server !== s)
|
|
83
|
+
this.worldOps = new World(s, this.config);
|
|
84
|
+
return this.worldOps;
|
|
85
|
+
}
|
|
86
|
+
setActive(dir) {
|
|
87
|
+
const checked = checkAllowedFolder(this.config.serverFolders, dir);
|
|
88
|
+
if (this.active && this.active.dir !== checked)
|
|
89
|
+
this.active.closeConnections();
|
|
90
|
+
this.active = TestServer.open(checked);
|
|
91
|
+
this.worldOps = null;
|
|
92
|
+
mkdirSync(this.config.stateDir, { recursive: true });
|
|
93
|
+
writeFileSync(this.stateFile, `${JSON.stringify({ activeServer: checked }, null, 2)}\n`);
|
|
94
|
+
return this.active;
|
|
95
|
+
}
|
|
96
|
+
/** Makes an existing server folder (inside an allowed folder) the active one, adopting it if this tool did not make it. */
|
|
97
|
+
async adopt(dir) {
|
|
98
|
+
const checked = checkAllowedFolder(this.config.serverFolders, dir);
|
|
99
|
+
if (existsSync(metaPath(checked)))
|
|
100
|
+
return { server: this.setActive(checked), adopted: false };
|
|
101
|
+
const launch = detectLaunch(checked);
|
|
102
|
+
if (!launch)
|
|
103
|
+
throw new ServerError(`${checked} has no server.jar and no NeoForge install; it does not look like a Minecraft server folder`);
|
|
104
|
+
const javaFound = await findJava(this.config.javaPath ? [this.config.javaPath] : []);
|
|
105
|
+
const java = pickJava(javaFound, 21) ?? pickJava(javaFound, 17);
|
|
106
|
+
if (!java)
|
|
107
|
+
throw new ServerError('No Java 17 or newer found. Run plan_setup to see where to get it.');
|
|
108
|
+
const props = readProperties(checked);
|
|
109
|
+
writeMeta(checked, {
|
|
110
|
+
minecraft: 'unknown',
|
|
111
|
+
loader: launch.type === 'jar' ? 'vanilla' : 'neoforge',
|
|
112
|
+
javaPath: java.path,
|
|
113
|
+
javaMajor: java.major,
|
|
114
|
+
memoryMb: 4096,
|
|
115
|
+
launch,
|
|
116
|
+
rconPort: Number(props.get('rcon.port') ?? 25575),
|
|
117
|
+
createdAt: new Date().toISOString(),
|
|
118
|
+
createdByTool: false,
|
|
119
|
+
});
|
|
120
|
+
return { server: this.setActive(checked), adopted: true };
|
|
121
|
+
}
|
|
122
|
+
}
|