sbox-mcp-server 1.7.0 → 1.9.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 +5 -4
- package/dist/index.js +2 -0
- package/dist/tools/inspection.d.ts +17 -0
- package/dist/tools/inspection.js +109 -0
- package/dist/tools/networking.js +3 -3
- package/dist/tools/playmode.js +1 -1
- package/dist/tools/visuals.js +17 -17
- package/dist/tools/world.js +58 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# sbox-mcp-server
|
|
2
2
|
|
|
3
|
-
MCP Server for the s&box game engine. Lets Claude Code build s&box games through conversation — **
|
|
3
|
+
MCP Server for the s&box game engine. Lets Claude Code build s&box games through conversation — **170+ tools** for scenes, scripts, GameObjects, components, assets, materials, audio, physics, UI, networking, publishing, world-gen, lighting & atmosphere, characters, scene layout, navmesh & spatial queries, particles, animation, NPC brains, playable-game scaffolds, networking & scene inspection/lint, save & services queries, self-diagnosis, console/C# execution, live docs search, and type discovery.
|
|
4
4
|
|
|
5
5
|
## Fastest install — the Claude Code plugin
|
|
6
6
|
|
|
7
|
-
If you use Claude Code, the easiest install is the companion plugin. It registers this MCP server automatically, ships
|
|
7
|
+
If you use Claude Code, the easiest install is the companion plugin. It registers this MCP server automatically, ships the workflow + recipe skills (`sbox-build-feature`, `sbox-api`, `sbox-cookbook`, `sbox-scaffold-game`, `sbox-setup`), and includes the `sbox-game-dev` specialist agent.
|
|
8
8
|
|
|
9
9
|
```
|
|
10
10
|
/plugin marketplace add LouSputthole/Sbox-Claude
|
|
@@ -54,7 +54,7 @@ Claude Code → (stdio) → sbox-mcp-server → (file IPC) → bridge addon →
|
|
|
54
54
|
|
|
55
55
|
Communication uses file-based IPC through `%TEMP%/sbox-bridge-ipc/`. The MCP server writes request JSON files, the bridge addon (running inside s&box) polls and processes on the main editor thread, then writes response files back. WebSocket is not used — s&box's sandboxed C# environment blocks `System.Net`.
|
|
56
56
|
|
|
57
|
-
## Tools (v1.
|
|
57
|
+
## Tools (v1.9.0)
|
|
58
58
|
|
|
59
59
|
`get_bridge_status` reports the `handlerCount` — that's the C# handlers compiled inside the editor. Six tools run **MCP-server-side** and need no editor handler: `read_log`, `get_compile_errors`, `execute_csharp`, `search_docs`, `get_doc_page`, `list_doc_categories`. They read the log / hotload-eval / fetch docs directly, so they keep working even when the editor has crashed or stalled.
|
|
60
60
|
|
|
@@ -99,6 +99,7 @@ Communication uses file-based IPC through `%TEMP%/sbox-bridge-ipc/`. The MCP ser
|
|
|
99
99
|
| **Console / Exec** *(v1.5.0)* | console_run, execute_csharp *(experimental)* |
|
|
100
100
|
| **Object utilities** *(v1.5.0)* | remove_component, get_tags |
|
|
101
101
|
| **Docs search** *(v1.5.0, MCP-server-side)* | search_docs, get_doc_page, list_doc_categories — official `Facepunch/sbox-docs` |
|
|
102
|
+
| **Inspection & validation** *(v1.9.0)* | inspect_networked_object (per-object `Network.*` + every component's `[Sync]` fields/values), networking_lint (static scan for `[Sync]`/RPC footguns), scene_validate (no-camera / stray root Rigidbody / trigger-vs-trace), save_inspect (list/read/diff `FileSystem.Data` saves), services_query (`Sandbox.Services` stats + leaderboards), simulate_input (drive named input actions in play mode) |
|
|
102
103
|
|
|
103
104
|
## Working with Claude effectively
|
|
104
105
|
|
|
@@ -126,6 +127,6 @@ The companion plugin's `sbox-build-feature` skill encodes this workflow plus the
|
|
|
126
127
|
|
|
127
128
|
## License
|
|
128
129
|
|
|
129
|
-
**
|
|
130
|
+
**AGPL-3.0-or-later** — see [LICENSE](../LICENSE) and [NOTICE](../NOTICE) for details. The code is open under AGPL; the "s&box Claude Bridge" / "sboxskins.gg" name and branding may not be reused to pass a fork off as the original.
|
|
130
131
|
|
|
131
132
|
Copyright (c) 2026 [sboxskins.gg](https://sboxskins.gg)
|
package/dist/index.js
CHANGED
|
@@ -43,6 +43,7 @@ import { registerNavigationTools } from "./tools/navigation.js";
|
|
|
43
43
|
import { registerSelfTestTools } from "./tools/selftest.js";
|
|
44
44
|
import { registerGameplayTools } from "./tools/gameplay.js";
|
|
45
45
|
import { registerNpcTools } from "./tools/npc.js";
|
|
46
|
+
import { registerInspectionTools } from "./tools/inspection.js";
|
|
46
47
|
// ── CLI flags ──────────────────────────────────────────────────────
|
|
47
48
|
const args = process.argv.slice(2);
|
|
48
49
|
/** Read the package version from package.json, or return "unknown" on failure. */
|
|
@@ -189,6 +190,7 @@ registerNavigationTools(server, bridge);
|
|
|
189
190
|
registerSelfTestTools(server, bridge);
|
|
190
191
|
registerGameplayTools(server, bridge);
|
|
191
192
|
registerNpcTools(server, bridge);
|
|
193
|
+
registerInspectionTools(server, bridge);
|
|
192
194
|
/** Start the MCP server on stdio and attempt initial Bridge connection. */
|
|
193
195
|
async function main() {
|
|
194
196
|
const transport = new StdioServerTransport();
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { BridgeClient } from "../transport/bridge-client.js";
|
|
3
|
+
/**
|
|
4
|
+
* Inspection & validation tools — the high-leverage gaps surfaced by mining 27 shipped
|
|
5
|
+
* s&box games. These let an assistant VERIFY the things that are otherwise invisible from
|
|
6
|
+
* a single editor instance: networking authority, save state, input-driven behavior, and
|
|
7
|
+
* scene/networking correctness.
|
|
8
|
+
*
|
|
9
|
+
* inspect_networked_object — per-object Network.Owner/IsProxy/[Sync] values (vs session-only get_network_status)
|
|
10
|
+
* networking_lint — static scan for unguarded [Sync] mutators, money-as-[Sync], List-as-[Sync], etc.
|
|
11
|
+
* scene_validate — missing camera/controller, multiple root Rigidbodies, IsTrigger-vs-trace footguns
|
|
12
|
+
* save_inspect — list/read/diff FileSystem.Data save files (21 of 27 games persist; assistant was blind)
|
|
13
|
+
* services_query — Sandbox.Services Stats / leaderboard reads
|
|
14
|
+
* simulate_input — press/release a named action + AnalogMove/Look during play (drive movement/IPressable/weapons)
|
|
15
|
+
*/
|
|
16
|
+
export declare function registerInspectionTools(server: McpServer, bridge: BridgeClient): void;
|
|
17
|
+
//# sourceMappingURL=inspection.d.ts.map
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Inspection & validation tools — the high-leverage gaps surfaced by mining 27 shipped
|
|
4
|
+
* s&box games. These let an assistant VERIFY the things that are otherwise invisible from
|
|
5
|
+
* a single editor instance: networking authority, save state, input-driven behavior, and
|
|
6
|
+
* scene/networking correctness.
|
|
7
|
+
*
|
|
8
|
+
* inspect_networked_object — per-object Network.Owner/IsProxy/[Sync] values (vs session-only get_network_status)
|
|
9
|
+
* networking_lint — static scan for unguarded [Sync] mutators, money-as-[Sync], List-as-[Sync], etc.
|
|
10
|
+
* scene_validate — missing camera/controller, multiple root Rigidbodies, IsTrigger-vs-trace footguns
|
|
11
|
+
* save_inspect — list/read/diff FileSystem.Data save files (21 of 27 games persist; assistant was blind)
|
|
12
|
+
* services_query — Sandbox.Services Stats / leaderboard reads
|
|
13
|
+
* simulate_input — press/release a named action + AnalogMove/Look during play (drive movement/IPressable/weapons)
|
|
14
|
+
*/
|
|
15
|
+
export function registerInspectionTools(server, bridge) {
|
|
16
|
+
// ── inspect_networked_object ──────────────────────────────────────
|
|
17
|
+
server.tool("inspect_networked_object", "Inspect the live networking contract of a GameObject: Network.Owner/IsProxy/IsOwner/Active, OwnerTransfer + OrphanedMode, and every component's [Sync] property names, SyncFlags, and current values (host vs proxy). Unlike get_network_status (session-only), this is per-object — the way to verify a host-authoritative or ownership change actually replicated. Works in edit or play mode.", {
|
|
18
|
+
id: z.string().describe("GUID of the GameObject to inspect"),
|
|
19
|
+
allProps: z
|
|
20
|
+
.boolean()
|
|
21
|
+
.default(false)
|
|
22
|
+
.describe("Include all component properties, not just [Sync]-marked ones"),
|
|
23
|
+
}, async (params) => {
|
|
24
|
+
const res = await bridge.send("inspect_networked_object", params);
|
|
25
|
+
if (!res.success)
|
|
26
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
27
|
+
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
|
|
28
|
+
});
|
|
29
|
+
// ── networking_lint ───────────────────────────────────────────────
|
|
30
|
+
server.tool("networking_lint", "Static-scan the project's C# for the highest-frequency networking/authority bugs: a mutator that writes a [Sync] field with no IsProxy/Networking.IsHost guard; money/health/score-shaped fields marked plain [Sync] (should be SyncFlags.FromHost); List<>/Dictionary<> marked [Sync] (should be NetList/NetDictionary); [Sync] fields typed Connection/GameObject (sync a Guid instead); [Rpc.Host] methods that mutate without re-checking Rpc.Caller; and component swaps / reflection writes missing Network.Refresh(). Returns findings with file:line + the suggested fix.", {
|
|
31
|
+
path: z
|
|
32
|
+
.string()
|
|
33
|
+
.optional()
|
|
34
|
+
.describe("Optional sub-path under the project (e.g. 'Code/Player') to scope the scan; omit for the whole project"),
|
|
35
|
+
}, async (params) => {
|
|
36
|
+
const res = await bridge.send("networking_lint", params);
|
|
37
|
+
if (!res.success)
|
|
38
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
39
|
+
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
|
|
40
|
+
});
|
|
41
|
+
// ── scene_validate ────────────────────────────────────────────────
|
|
42
|
+
server.tool("scene_validate", "Validate the active scene for the silent setup footguns that break controllers/physics/cameras: no CameraComponent, no player controller, multiple root Rigidbodies, a Rigidbody with MotionEnabled=false fighting a kinematic root, IsTrigger colliders that Scene.Trace will ignore, child Rigidbodies breaking collider binding, and missing required child anchors. Returns each issue with the GameObject and the exact fix.", {}, async () => {
|
|
43
|
+
const res = await bridge.send("scene_validate", {});
|
|
44
|
+
if (!res.success)
|
|
45
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
46
|
+
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
|
|
47
|
+
});
|
|
48
|
+
// ── save_inspect ──────────────────────────────────────────────────
|
|
49
|
+
server.tool("save_inspect", "Inspect the game's FileSystem.Data save files — the assistant is otherwise blind to persisted state. action='list' enumerates files under a folder (with size/mtime); action='read' dumps a save file's JSON; action='diff' compares two save files key-by-key. Use to verify a save actually wrote, debug a load/migration, or confirm a sanitize/clamp ran.", {
|
|
50
|
+
action: z.enum(["list", "read", "diff"]).default("list"),
|
|
51
|
+
path: z
|
|
52
|
+
.string()
|
|
53
|
+
.optional()
|
|
54
|
+
.describe("File or folder path under FileSystem.Data (e.g. 'lumber_corp2_progress' or '<folder>/steam_123.json')"),
|
|
55
|
+
pathB: z
|
|
56
|
+
.string()
|
|
57
|
+
.optional()
|
|
58
|
+
.describe("Second file path for action='diff'"),
|
|
59
|
+
}, async (params) => {
|
|
60
|
+
const res = await bridge.send("save_inspect", params);
|
|
61
|
+
if (!res.success)
|
|
62
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
63
|
+
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
|
|
64
|
+
});
|
|
65
|
+
// ── services_query ────────────────────────────────────────────────
|
|
66
|
+
server.tool("services_query", "Read from Sandbox.Services — the cloud stats/leaderboard layer many games use as their real DB. action='stats' lists/reads the project's stat definitions and the local player's values; action='leaderboard' fetches a leaderboard board's top entries. Read-only. Use to verify a Stats.Increment/SetValue path or a leaderboard wired correctly.", {
|
|
67
|
+
action: z.enum(["stats", "leaderboard"]).default("stats"),
|
|
68
|
+
name: z
|
|
69
|
+
.string()
|
|
70
|
+
.optional()
|
|
71
|
+
.describe("Stat name (action='stats') or leaderboard/board name (action='leaderboard')"),
|
|
72
|
+
limit: z.number().int().default(10).describe("Max leaderboard entries to return"),
|
|
73
|
+
}, async (params) => {
|
|
74
|
+
const res = await bridge.send("services_query", params);
|
|
75
|
+
if (!res.success)
|
|
76
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
77
|
+
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
|
|
78
|
+
});
|
|
79
|
+
// ── simulate_input ────────────────────────────────────────────────
|
|
80
|
+
server.tool("simulate_input", "Synthesize player input during PLAY mode so behavior can be verified without a human at the keyboard: press/hold/release a named input action (e.g. 'jump','attack1','use','reload') and/or set AnalogMove / AnalogLook for a duration. Drives movement, IPressable interactions, weapon fire/cooldown/reload, vehicle controls, and HUD advance-on-click. Requires play mode.", {
|
|
81
|
+
action: z
|
|
82
|
+
.string()
|
|
83
|
+
.optional()
|
|
84
|
+
.describe("Named input action to drive (must exist in Input.config), e.g. 'jump', 'attack1', 'use'"),
|
|
85
|
+
state: z
|
|
86
|
+
.enum(["press", "hold", "release"])
|
|
87
|
+
.default("press")
|
|
88
|
+
.describe("press = one tick (Pressed); hold = held for durationMs; release = clear a held action"),
|
|
89
|
+
analogMove: z
|
|
90
|
+
.object({ x: z.number(), y: z.number(), z: z.number().default(0) })
|
|
91
|
+
.optional()
|
|
92
|
+
.describe("AnalogMove vector to apply (forward/left)"),
|
|
93
|
+
analogLook: z
|
|
94
|
+
.object({ pitch: z.number(), yaw: z.number(), roll: z.number().default(0) })
|
|
95
|
+
.optional()
|
|
96
|
+
.describe("AnalogLook angles delta to apply"),
|
|
97
|
+
durationMs: z
|
|
98
|
+
.number()
|
|
99
|
+
.int()
|
|
100
|
+
.default(100)
|
|
101
|
+
.describe("How long to hold the action / apply the analog values"),
|
|
102
|
+
}, async (params) => {
|
|
103
|
+
const res = await bridge.send("simulate_input", params);
|
|
104
|
+
if (!res.success)
|
|
105
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
106
|
+
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=inspection.js.map
|
package/dist/tools/networking.js
CHANGED
|
@@ -114,7 +114,7 @@ export function registerNetworkingTools(server, bridge) {
|
|
|
114
114
|
syncFlags: z
|
|
115
115
|
.string()
|
|
116
116
|
.optional()
|
|
117
|
-
.describe("
|
|
117
|
+
.describe("Optional SyncFlags to emit as [Sync( SyncFlags.X )] — e.g. 'Interpolate' (smooth interpolation), 'Query', 'FromHost'. Omit for a plain [Sync]"),
|
|
118
118
|
defaultValue: z
|
|
119
119
|
.string()
|
|
120
120
|
.optional()
|
|
@@ -129,7 +129,7 @@ export function registerNetworkingTools(server, bridge) {
|
|
|
129
129
|
};
|
|
130
130
|
});
|
|
131
131
|
// ── add_rpc_method ────────────────────────────────────────────────
|
|
132
|
-
server.tool("add_rpc_method", "Generate an
|
|
132
|
+
server.tool("add_rpc_method", "Generate an RPC method stub in a C# script. Inserts the chosen RPC attribute ([Rpc.Broadcast] all clients, [Rpc.Host] host only, [Rpc.Owner] owner only) above a method with an empty body. Pass methodParams to give it a parameter list (e.g. 'Vector3 pos, int damage'); the body is left as a TODO for you to fill in", {
|
|
133
133
|
path: z
|
|
134
134
|
.string()
|
|
135
135
|
.describe("Relative path to the script file"),
|
|
@@ -141,7 +141,7 @@ export function registerNetworkingTools(server, bridge) {
|
|
|
141
141
|
methodParams: z
|
|
142
142
|
.string()
|
|
143
143
|
.optional()
|
|
144
|
-
.describe("
|
|
144
|
+
.describe("Optional parameter list for the RPC method signature, e.g. 'Vector3 pos, int damage'. Omit for a parameterless method"),
|
|
145
145
|
body: z
|
|
146
146
|
.string()
|
|
147
147
|
.optional()
|
package/dist/tools/playmode.js
CHANGED
|
@@ -82,7 +82,7 @@ export function registerPlayModeTools(server, bridge) {
|
|
|
82
82
|
property: z.string().describe("Property name to set"),
|
|
83
83
|
value: z
|
|
84
84
|
.unknown()
|
|
85
|
-
.describe("New value
|
|
85
|
+
.describe("New value. Primitive: '5', 'true'. Color/Vector3: a comma string ('1,0,0,1' / '0,0,200'), an array ([0,0,200]), or an object ({r,g,b,a} / {x,y,z}). Enum: the member name. Asset ref (Model/Material/...): the asset path e.g. 'models/dev/box.vmdl'. GameObject/Component ref: the target GameObject's GUID. Empty/'null' clears the property"),
|
|
86
86
|
}, async (params) => {
|
|
87
87
|
const res = await bridge.send("set_property", params);
|
|
88
88
|
if (!res.success) {
|
package/dist/tools/visuals.js
CHANGED
|
@@ -61,30 +61,30 @@ export function registerVisualTools(server, bridge) {
|
|
|
61
61
|
};
|
|
62
62
|
});
|
|
63
63
|
// ── set_fog ────────────────────────────────────────────────────────
|
|
64
|
-
server.tool("set_fog", "Add or update
|
|
64
|
+
server.tool("set_fog", "Add or update fog in the active scene. Types: 'gradient' (distance haze — great for mood/horror), 'cubemap' (sky-tinted distance fog), 'volumetric' (a localized fog volume). Re-running on the same target updates it rather than duplicating.", {
|
|
65
65
|
type: z
|
|
66
|
-
.enum(["gradient"])
|
|
66
|
+
.enum(["gradient", "cubemap", "volumetric"])
|
|
67
67
|
.optional()
|
|
68
|
-
.describe("Fog type (default gradient
|
|
68
|
+
.describe("Fog type (default gradient)"),
|
|
69
69
|
name: z.string().optional().describe("GameObject name when creating a new fog object"),
|
|
70
70
|
targetId: z
|
|
71
71
|
.string()
|
|
72
72
|
.optional()
|
|
73
|
-
.describe("GUID of an existing GameObject to host the fog (else a new
|
|
74
|
-
color: ColorSchema.optional().describe("Fog colour"),
|
|
75
|
-
startDistance: z
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
.number()
|
|
73
|
+
.describe("GUID of an existing GameObject to host the fog (else a new fog object is created)"),
|
|
74
|
+
color: ColorSchema.optional().describe("Fog colour (gradient/volumetric Color, cubemap Tint)"),
|
|
75
|
+
startDistance: z.number().optional().describe("gradient/cubemap: distance (units) where fog begins"),
|
|
76
|
+
endDistance: z.number().optional().describe("gradient/cubemap: distance (units) where fog reaches full density"),
|
|
77
|
+
height: z.number().optional().describe("gradient: world height the fog settles around"),
|
|
78
|
+
falloff: z.number().optional().describe("Distance/density falloff exponent (higher = sharper onset)"),
|
|
79
|
+
blur: z.number().optional().describe("cubemap: sky blur amount"),
|
|
80
|
+
heightStart: z.number().optional().describe("cubemap: world height where height-fog starts"),
|
|
81
|
+
heightWidth: z.number().optional().describe("cubemap: height-fog band width"),
|
|
82
|
+
heightExponent: z.number().optional().describe("cubemap: height-fog falloff exponent"),
|
|
83
|
+
strength: z.number().optional().describe("volumetric: fog density/strength"),
|
|
84
|
+
size: z
|
|
85
|
+
.object({ x: z.number(), y: z.number(), z: z.number() })
|
|
86
86
|
.optional()
|
|
87
|
-
.describe("
|
|
87
|
+
.describe("volumetric: bounds size (units) centred on the object"),
|
|
88
88
|
}, async (params) => {
|
|
89
89
|
const res = await bridge.send("set_fog", params);
|
|
90
90
|
if (!res.success) {
|
package/dist/tools/world.js
CHANGED
|
@@ -17,17 +17,21 @@ import { z } from "zod";
|
|
|
17
17
|
*/
|
|
18
18
|
export function registerWorldTools(server, bridge) {
|
|
19
19
|
// ── invoke_button ────────────────────────────────────────────────
|
|
20
|
-
server.tool("invoke_button", "Call a
|
|
20
|
+
server.tool("invoke_button", "Call a public method on a component. Matching is tried in order: (1) a [Button] attribute label, (2) the exact method NAME, (3) case-insensitive name with spaces stripped. Calls ANY public method, not only [Button]-attributed ones (e.g. 'StartGame'). Pass `args` to call methods that take parameters — the arg count must match and each value is coerced to the parameter type (primitives: string/number/bool work; complex types like Vector3 may not coerce). Omit args (or []) for parameterless methods. (list_component_buttons only lists [Button] methods, so a plain method may be invokable yet not appear there.)", {
|
|
21
21
|
component: z
|
|
22
22
|
.string()
|
|
23
23
|
.describe("Component type name (e.g. 'MapBuilder', 'SasquatchedGame')"),
|
|
24
24
|
button: z
|
|
25
25
|
.string()
|
|
26
|
-
.describe("A [Button] label OR a public
|
|
26
|
+
.describe("A [Button] label OR a public method name (e.g. 'Build Terrain', 'StartGame'); case- and space-insensitive"),
|
|
27
27
|
id: z
|
|
28
28
|
.string()
|
|
29
29
|
.optional()
|
|
30
30
|
.describe("Optional GameObject GUID — if omitted, finds first matching component in scene"),
|
|
31
|
+
args: z
|
|
32
|
+
.array(z.unknown())
|
|
33
|
+
.optional()
|
|
34
|
+
.describe("Arguments to pass (must match the method's parameter count); coerced to each parameter type"),
|
|
31
35
|
}, async (params) => {
|
|
32
36
|
const res = await bridge.send("invoke_button", params);
|
|
33
37
|
if (!res.success)
|
|
@@ -49,6 +53,10 @@ export function registerWorldTools(server, bridge) {
|
|
|
49
53
|
x: z.number().describe("World X coordinate"),
|
|
50
54
|
y: z.number().describe("World Y coordinate"),
|
|
51
55
|
id: z.string().optional().describe("Optional GameObject GUID for MapBuilder"),
|
|
56
|
+
component: z
|
|
57
|
+
.string()
|
|
58
|
+
.optional()
|
|
59
|
+
.describe("Override the terrain component type name (default MapBuilder)"),
|
|
52
60
|
}, async (params) => {
|
|
53
61
|
const res = await bridge.send("raycast_terrain", params);
|
|
54
62
|
if (!res.success)
|
|
@@ -63,6 +71,10 @@ export function registerWorldTools(server, bridge) {
|
|
|
63
71
|
height: z.number().default(100).describe("Peak height (negative for depression)"),
|
|
64
72
|
rebuild: z.boolean().default(true).describe("Rebuild terrain after adding (set false to batch)"),
|
|
65
73
|
id: z.string().optional(),
|
|
74
|
+
component: z
|
|
75
|
+
.string()
|
|
76
|
+
.optional()
|
|
77
|
+
.describe("Override the builder component type name — set this if your project's terrain/cave/forest component is named differently than the default (MapBuilder/CaveBuilder/ForestGenerator)"),
|
|
66
78
|
}, async (params) => {
|
|
67
79
|
const res = await bridge.send("add_terrain_hill", params);
|
|
68
80
|
if (!res.success)
|
|
@@ -76,6 +88,10 @@ export function registerWorldTools(server, bridge) {
|
|
|
76
88
|
radius: z.number().default(300),
|
|
77
89
|
rebuild: z.boolean().default(true),
|
|
78
90
|
id: z.string().optional(),
|
|
91
|
+
component: z
|
|
92
|
+
.string()
|
|
93
|
+
.optional()
|
|
94
|
+
.describe("Override the builder component type name — set this if your project's terrain/cave/forest component is named differently than the default (MapBuilder/CaveBuilder/ForestGenerator)"),
|
|
79
95
|
}, async (params) => {
|
|
80
96
|
const res = await bridge.send("add_terrain_clearing", params);
|
|
81
97
|
if (!res.success)
|
|
@@ -88,6 +104,10 @@ export function registerWorldTools(server, bridge) {
|
|
|
88
104
|
to: z.object({ x: z.number(), y: z.number() }),
|
|
89
105
|
rebuild: z.boolean().default(true),
|
|
90
106
|
id: z.string().optional(),
|
|
107
|
+
component: z
|
|
108
|
+
.string()
|
|
109
|
+
.optional()
|
|
110
|
+
.describe("Override the builder component type name — set this if your project's terrain/cave/forest component is named differently than the default (MapBuilder/CaveBuilder/ForestGenerator)"),
|
|
91
111
|
}, async (params) => {
|
|
92
112
|
const res = await bridge.send("add_terrain_trail", params);
|
|
93
113
|
if (!res.success)
|
|
@@ -101,6 +121,10 @@ export function registerWorldTools(server, bridge) {
|
|
|
101
121
|
.default("all"),
|
|
102
122
|
rebuild: z.boolean().default(true),
|
|
103
123
|
id: z.string().optional(),
|
|
124
|
+
component: z
|
|
125
|
+
.string()
|
|
126
|
+
.optional()
|
|
127
|
+
.describe("Override the builder component type name — set this if your project's terrain/cave/forest component is named differently than the default (MapBuilder/CaveBuilder/ForestGenerator)"),
|
|
104
128
|
}, async (params) => {
|
|
105
129
|
const res = await bridge.send("clear_terrain_features", params);
|
|
106
130
|
if (!res.success)
|
|
@@ -119,6 +143,10 @@ export function registerWorldTools(server, bridge) {
|
|
|
119
143
|
.describe("Optional insert position (default: append to end)"),
|
|
120
144
|
rebuild: z.boolean().default(true),
|
|
121
145
|
id: z.string().optional(),
|
|
146
|
+
component: z
|
|
147
|
+
.string()
|
|
148
|
+
.optional()
|
|
149
|
+
.describe("Override the builder component type name — set this if your project's terrain/cave/forest component is named differently than the default (MapBuilder/CaveBuilder/ForestGenerator)"),
|
|
122
150
|
}, async (params) => {
|
|
123
151
|
const res = await bridge.send("add_cave_waypoint", params);
|
|
124
152
|
if (!res.success)
|
|
@@ -128,6 +156,10 @@ export function registerWorldTools(server, bridge) {
|
|
|
128
156
|
// ── clear_cave_path ──────────────────────────────────────────────
|
|
129
157
|
server.tool("clear_cave_path", "Clear all waypoints in CaveBuilder and remove the cave from the scene.", {
|
|
130
158
|
id: z.string().optional(),
|
|
159
|
+
component: z
|
|
160
|
+
.string()
|
|
161
|
+
.optional()
|
|
162
|
+
.describe("Override the builder component type name — set this if your project's terrain/cave/forest component is named differently than the default (MapBuilder/CaveBuilder/ForestGenerator)"),
|
|
131
163
|
}, async (params) => {
|
|
132
164
|
const res = await bridge.send("clear_cave_path", params);
|
|
133
165
|
if (!res.success)
|
|
@@ -149,6 +181,10 @@ export function registerWorldTools(server, bridge) {
|
|
|
149
181
|
.default(false)
|
|
150
182
|
.describe("Forest gen is slow (~1s); default false to batch"),
|
|
151
183
|
id: z.string().optional(),
|
|
184
|
+
component: z
|
|
185
|
+
.string()
|
|
186
|
+
.optional()
|
|
187
|
+
.describe("Override the builder component type name — set this if your project's terrain/cave/forest component is named differently than the default (MapBuilder/CaveBuilder/ForestGenerator)"),
|
|
152
188
|
}, async (params) => {
|
|
153
189
|
const res = await bridge.send("add_forest_poi", params);
|
|
154
190
|
if (!res.success)
|
|
@@ -161,6 +197,10 @@ export function registerWorldTools(server, bridge) {
|
|
|
161
197
|
to_index: z.number().int(),
|
|
162
198
|
rebuild: z.boolean().default(false),
|
|
163
199
|
id: z.string().optional(),
|
|
200
|
+
component: z
|
|
201
|
+
.string()
|
|
202
|
+
.optional()
|
|
203
|
+
.describe("Override the builder component type name — set this if your project's terrain/cave/forest component is named differently than the default (MapBuilder/CaveBuilder/ForestGenerator)"),
|
|
164
204
|
}, async (params) => {
|
|
165
205
|
const res = await bridge.send("add_forest_trail", params);
|
|
166
206
|
if (!res.success)
|
|
@@ -172,6 +212,10 @@ export function registerWorldTools(server, bridge) {
|
|
|
172
212
|
seed: z.number().int().default(77),
|
|
173
213
|
rebuild: z.boolean().default(true),
|
|
174
214
|
id: z.string().optional(),
|
|
215
|
+
component: z
|
|
216
|
+
.string()
|
|
217
|
+
.optional()
|
|
218
|
+
.describe("Override the builder component type name — set this if your project's terrain/cave/forest component is named differently than the default (MapBuilder/CaveBuilder/ForestGenerator)"),
|
|
175
219
|
}, async (params) => {
|
|
176
220
|
const res = await bridge.send("set_forest_seed", params);
|
|
177
221
|
if (!res.success)
|
|
@@ -181,6 +225,10 @@ export function registerWorldTools(server, bridge) {
|
|
|
181
225
|
// ── clear_forest_pois ────────────────────────────────────────────
|
|
182
226
|
server.tool("clear_forest_pois", "Wipe all POIs and trails in ForestGenerator and clear placed forest objects from the scene.", {
|
|
183
227
|
id: z.string().optional(),
|
|
228
|
+
component: z
|
|
229
|
+
.string()
|
|
230
|
+
.optional()
|
|
231
|
+
.describe("Override the builder component type name — set this if your project's terrain/cave/forest component is named differently than the default (MapBuilder/CaveBuilder/ForestGenerator)"),
|
|
184
232
|
}, async (params) => {
|
|
185
233
|
const res = await bridge.send("clear_forest_pois", params);
|
|
186
234
|
if (!res.success)
|
|
@@ -195,6 +243,10 @@ export function registerWorldTools(server, bridge) {
|
|
|
195
243
|
strength: z.number().default(50).describe("Height delta (units) for raise/lower; ignored for flatten/smooth"),
|
|
196
244
|
mode: z.enum(["raise", "lower", "flatten", "smooth"]).default("raise"),
|
|
197
245
|
id: z.string().optional(),
|
|
246
|
+
component: z
|
|
247
|
+
.string()
|
|
248
|
+
.optional()
|
|
249
|
+
.describe("Override the builder component type name — set this if your project's terrain/cave/forest component is named differently than the default (MapBuilder/CaveBuilder/ForestGenerator)"),
|
|
198
250
|
}, async (params) => {
|
|
199
251
|
const res = await bridge.send("sculpt_terrain", params);
|
|
200
252
|
if (!res.success)
|
|
@@ -209,6 +261,10 @@ export function registerWorldTools(server, bridge) {
|
|
|
209
261
|
density: z.number().default(1).describe("Density multiplier (0=clear, 1=normal, 2=dense)"),
|
|
210
262
|
rebuild: z.boolean().default(false),
|
|
211
263
|
id: z.string().optional(),
|
|
264
|
+
component: z
|
|
265
|
+
.string()
|
|
266
|
+
.optional()
|
|
267
|
+
.describe("Override the builder component type name — set this if your project's terrain/cave/forest component is named differently than the default (MapBuilder/CaveBuilder/ForestGenerator)"),
|
|
212
268
|
}, async (params) => {
|
|
213
269
|
const res = await bridge.send("paint_forest_density", params);
|
|
214
270
|
if (!res.success)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sbox-mcp-server",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"description": "MCP Server for s&box game engine — enables Claude to build games through conversation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -51,5 +51,5 @@
|
|
|
51
51
|
"engines": {
|
|
52
52
|
"node": ">=18.0.0"
|
|
53
53
|
},
|
|
54
|
-
"license": "
|
|
54
|
+
"license": "AGPL-3.0-or-later"
|
|
55
55
|
}
|