sbox-mcp-server 1.17.1 → 1.18.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 CHANGED
@@ -1,6 +1,6 @@
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 — **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.
3
+ MCP Server for the s&box game engine. Lets Claude Code build s&box games through conversation — **200+ tools** for scenes, scripts, GameObjects, components, assets, materials, audio, physics, UI, networking, publishing, world-gen, lighting & atmosphere, characters, lipsync, scene layout, navmesh & spatial queries, particles, animation, NPC brains, playable-game scaffolds, gameplay playtesting, 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
 
@@ -54,9 +54,9 @@ 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.9.0)
57
+ ## Tools
58
58
 
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.
59
+ `get_bridge_status` reports the `handlerCount` — that's the C# handlers compiled inside the editor. A handful of 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`, `run_self_test`, `screenshot_orbit`). They read the log / hotload-eval / fetch docs / orchestrate other tools directly, so most keep working even when the editor has crashed or stalled.
60
60
 
61
61
  | Category | Tools |
62
62
  |----------|-------|
@@ -100,6 +100,16 @@ Communication uses file-based IPC through `%TEMP%/sbox-bridge-ipc/`. The MCP ser
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
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) |
103
+ | **Editor & self-test** *(v1.5.1–v1.7)* | restart_editor (self-restart + reconnect — closes the C#-edit loop), list_libraries, recompile_asset, run_self_test (8-check end-to-end health gate), capture_view (**renders the RUNNING play scene** from any camera — the play-mode eyes) |
104
+ | **Animation & verification** *(v1.6.0)* | list_animations, play_animation, set_animgraph_param, get_bounds, screenshot_orbit (multi-angle orbit shots of any object) |
105
+ | **Call & input** *(v1.10.0)* | invoke_method (call a component method **with args**), ensure_input_action (add a `.sbproj` input action), drive_player / drive_player_status (play-mode input driver) |
106
+ | **Gameplay scaffolds** *(v1.7–v1.13)* | set_component_reference, add_component_to_new_object, create_objective_system, create_health_system, create_pickup, create_trigger_zone, create_economy_wallet, create_round_phase_machine, create_day_night_clock, create_interactable, create_weighted_loot_table, create_save_system, create_leaderboard_panel, create_inventory, create_stat_modifier_system, create_placement_mode |
107
+ | **NPC brains** *(v1.7.0)* | create_npc_brain (FSM: Idle/Patrol/Wander/Chase/Search/Flee/Ambush + FOV/LOS/hearing), place_patrol_route, assign_patrol_route, create_npc_spawner, simulate_npc_perception |
108
+ | **Lints & asset utils** *(v1.12.0)* | sandbox_lint (whitelist pre-compile scan), razor_lint (Razor/SCSS transpiler footguns), copy_asset_with_dependencies (asset + full dependency closure) |
109
+ | **Meta & debug-draw** *(v1.14–v1.15)* | set_time_scale (pause/slow-mo/fast-forward), get_profiler_stats (FPS/frame/GPU/allocations), debug_draw_line/ray/box/sphere, debug_clear |
110
+ | **Playtest harness** *(v1.17)* | playtest (scripted gameplay loop in PLAY MODE with **in-frame assertions** — move/look/action/jump/set/wait/capture/assert), playtest_status |
111
+ | **Lipsync** *(v1.18.0)* | add_lipsync — wires `Sandbox.LipSync` (new in the 2026-07-01 engine update) to a citizen/model: `SkinnedModelRenderer` + a `SoundPointComponent` bound to a `.sound` path, facial morphs animating while the sound plays |
112
+ | **Gameplay scaffolds** *(v1.18.0)* | create_round_state_machine (multi-state round manager + abstract `RoundState` lifecycle base, host-authoritative with late-joiner reconcile), add_interaction_station (one-occupant `IPressable` station, host-routed claims, reservation grace window, level gate), create_event_director (weighted-interval pacing/AI director, dedupe, `MaxActive` cap, timed self-destruct), create_save_slots (multi-slot save manager, versioned, optional GUID scene reconciliation — companion to create_save_system) |
103
113
 
104
114
  ## Working with Claude effectively
105
115
 
package/dist/index.js CHANGED
@@ -48,6 +48,10 @@ import { registerInputTools } from "./tools/inputs.js";
48
48
  import { registerDebugVizTools } from "./tools/debugviz.js";
49
49
  import { registerDebugDrawTools } from "./tools/debugdraw.js";
50
50
  import { registerPlaytestTools } from "./tools/playtest.js";
51
+ import { registerRoundStateTools } from "./tools/roundstate.js";
52
+ import { registerStationTools } from "./tools/stations.js";
53
+ import { registerDirectorTools } from "./tools/director.js";
54
+ import { registerSaveSlotsTools } from "./tools/saveslots.js";
51
55
  // ── CLI flags ──────────────────────────────────────────────────────
52
56
  const args = process.argv.slice(2);
53
57
  /** Read the package version from package.json, or return "unknown" on failure. */
@@ -199,6 +203,10 @@ registerInputTools(server, bridge);
199
203
  registerDebugVizTools(server, bridge);
200
204
  registerDebugDrawTools(server, bridge);
201
205
  registerPlaytestTools(server, bridge);
206
+ registerRoundStateTools(server, bridge);
207
+ registerStationTools(server, bridge);
208
+ registerDirectorTools(server, bridge);
209
+ registerSaveSlotsTools(server, bridge);
202
210
  /** Start the MCP server on stdio and attempt initial Bridge connection. */
203
211
  async function main() {
204
212
  const transport = new StdioServerTransport();
@@ -268,5 +268,29 @@ export function registerCharacterTools(server, bridge) {
268
268
  content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
269
269
  };
270
270
  });
271
+ // ── add_lipsync ────────────────────────────────────────────────────
272
+ server.tool("add_lipsync", "Add + wire s&box's LipSync component (new in the 2026-07 engine update): drives a SkinnedModelRenderer's facial morphs (visemes) from a playing sound. Wires the renderer on the target GameObject (or rendererId) and, if soundEvent is given, a SoundPointComponent bound to that .sound asset (see list_sounds / create_sound_event). Morphs animate at RUNTIME while the sound plays — verify in play mode with capture_view, not the static editor pose.", {
273
+ id: z.string().describe("GUID of the GameObject to add LipSync to (e.g. a spawn_citizen)"),
274
+ rendererId: z
275
+ .string()
276
+ .optional()
277
+ .describe("GUID of a different GameObject holding the SkinnedModelRenderer (default: same as id)"),
278
+ soundEvent: z
279
+ .string()
280
+ .optional()
281
+ .describe("Path to a .sound event to speak, e.g. 'sounds/dialogue/hello.sound' — creates/reuses a SoundPointComponent. Omit to wire an existing sound component on the GameObject (or wire audio later)."),
282
+ playOnStart: z.boolean().optional().describe("Play the sound on scene start (default true; only with soundEvent)"),
283
+ volume: z.number().optional().describe("SoundPointComponent volume (only with soundEvent)"),
284
+ morphScale: z.number().optional().describe("LipSync.MorphScale — exaggerate/dampen mouth movement (engine default when omitted)"),
285
+ morphSmoothTime: z.number().optional().describe("LipSync.MorphSmoothTime — morph smoothing seconds (engine default when omitted)"),
286
+ }, async (params) => {
287
+ const res = await bridge.send("add_lipsync", params);
288
+ if (!res.success) {
289
+ return { content: [{ type: "text", text: `Error: ${res.error}` }] };
290
+ }
291
+ return {
292
+ content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
293
+ };
294
+ });
271
295
  }
272
296
  //# sourceMappingURL=characters.js.map
@@ -0,0 +1,14 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { BridgeClient } from "../transport/bridge-client.js";
3
+ /**
4
+ * AI-director scaffold — generalized L4D-style event pacing.
5
+ *
6
+ * - create_event_director generate a host-authoritative "AI director" component that
7
+ * rolls on an interval, weighted-picks an event prefab,
8
+ * deduplicates against the active set, spawns it, and gives
9
+ * each spawned event a timed self-destruct.
10
+ *
11
+ * Code-gen + scene-mutating; refused during play mode by the bridge dispatch.
12
+ */
13
+ export declare function registerDirectorTools(server: McpServer, bridge: BridgeClient): void;
14
+ //# sourceMappingURL=director.d.ts.map
@@ -0,0 +1,48 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * AI-director scaffold — generalized L4D-style event pacing.
4
+ *
5
+ * - create_event_director generate a host-authoritative "AI director" component that
6
+ * rolls on an interval, weighted-picks an event prefab,
7
+ * deduplicates against the active set, spawns it, and gives
8
+ * each spawned event a timed self-destruct.
9
+ *
10
+ * Code-gen + scene-mutating; refused during play mode by the bridge dispatch.
11
+ */
12
+ export function registerDirectorTools(server, bridge) {
13
+ // ── create_event_director ─────────────────────────────────────────
14
+ server.tool("create_event_director", "Generate a generalized L4D-style AI/pacing director component (host-authoritative). On a configurable interval the host rolls a weighted pick over a [Property] List<GameObject> EventPrefabs (with a parallel List<float> Weights), skips any event already active (dedupe) and anything past a MaxActive concurrency cap, clones the chosen prefab, NetworkSpawns it, and attaches a generated {name}TimedEvent companion so each spawned event self-destructs after EventLifetime seconds. Great for ambient events, waves, and world events. Single-player safe (IsProxy guard; NetworkSpawn falls back to a local clone). Fill EventPrefabs/Weights in the inspector or via the bridge after a hotload; edit the RollInterval() stub to make pacing adaptive (player-count/inactivity/time-pressure factors) per the ai-director cookbook. Optionally attached to an existing GameObject by GUID (after a hotload). NOTE: emits ONE .cs file containing two classes ({name} + {name}TimedEvent); the type only resolves after trigger_hotload.", {
15
+ name: z
16
+ .string()
17
+ .optional()
18
+ .describe("Class name for the director (a {name}TimedEvent companion is generated alongside it). Defaults to 'EventDirector'"),
19
+ path: z
20
+ .string()
21
+ .optional()
22
+ .describe("Subdirectory for the generated .cs file. Defaults to 'Code'"),
23
+ intervalSeconds: z
24
+ .number()
25
+ .optional()
26
+ .describe("Base seconds between director rolls. Defaults to 30 (clamped to >= 0.1)"),
27
+ maxActive: z
28
+ .number()
29
+ .int()
30
+ .optional()
31
+ .describe("Maximum number of concurrently-live events. Defaults to 3 (clamped to >= 1)"),
32
+ eventLifetime: z
33
+ .number()
34
+ .optional()
35
+ .describe("Seconds before each spawned event self-destructs. Defaults to 60 (clamped to >= 0.1)"),
36
+ targetId: z
37
+ .string()
38
+ .optional()
39
+ .describe("GUID of an existing GameObject to attach the director to (only attaches if the type is already loaded — hotload first)"),
40
+ }, async (params) => {
41
+ const res = await bridge.send("create_event_director", params);
42
+ if (!res.success) {
43
+ return { content: [{ type: "text", text: `Error: ${res.error}` }] };
44
+ }
45
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
46
+ });
47
+ }
48
+ //# sourceMappingURL=director.js.map
@@ -0,0 +1,28 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { BridgeClient } from "../transport/bridge-client.js";
3
+ /**
4
+ * create_round_state_machine — the COMPLEX, multi-state round machine scaffold.
5
+ *
6
+ * The sibling of create_round_phase_machine. Both are host-authoritative round
7
+ * flow, but they scaffold different shapes:
8
+ *
9
+ * create_round_phase_machine → ONE sealed component with a [Sync] enum cycled
10
+ * on a shared per-phase timer. Reach for it for 3–5 LIGHT phases whose
11
+ * only per-phase logic is "how long" (round/day-night/match phases).
12
+ *
13
+ * create_round_state_machine → a RoundManager singleton + an abstract
14
+ * RoundState base (Begin/Tick/OnTimeUp/Finish lifecycle + per-state
15
+ * [Sync] TimeUntil timer) + one sealed stub class PER state. Reach for it
16
+ * when each phase deserves its OWN file/behaviour: entry side-effects,
17
+ * per-frame Tick logic, a CanEnter() skip condition, copy-data-out-on-exit.
18
+ * This is the "state-as-component" pattern from the round-match cookbook.
19
+ *
20
+ * The generated manager auto-attaches the state components on start (so a
21
+ * non-coder only places the manager), index-wraps when advancing, skips states
22
+ * whose CanEnter() returns false, and announces transitions via a static
23
+ * OnStateChanged event mirrored by an [Rpc.Broadcast] so proxies converge
24
+ * instantly (the [Sync] index is the durable late-joiner reconcile path).
25
+ * Single-player safe. Scene/file-mutating; refused during play mode.
26
+ */
27
+ export declare function registerRoundStateTools(server: McpServer, bridge: BridgeClient): void;
28
+ //# sourceMappingURL=roundstate.d.ts.map
@@ -0,0 +1,67 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * create_round_state_machine — the COMPLEX, multi-state round machine scaffold.
4
+ *
5
+ * The sibling of create_round_phase_machine. Both are host-authoritative round
6
+ * flow, but they scaffold different shapes:
7
+ *
8
+ * create_round_phase_machine → ONE sealed component with a [Sync] enum cycled
9
+ * on a shared per-phase timer. Reach for it for 3–5 LIGHT phases whose
10
+ * only per-phase logic is "how long" (round/day-night/match phases).
11
+ *
12
+ * create_round_state_machine → a RoundManager singleton + an abstract
13
+ * RoundState base (Begin/Tick/OnTimeUp/Finish lifecycle + per-state
14
+ * [Sync] TimeUntil timer) + one sealed stub class PER state. Reach for it
15
+ * when each phase deserves its OWN file/behaviour: entry side-effects,
16
+ * per-frame Tick logic, a CanEnter() skip condition, copy-data-out-on-exit.
17
+ * This is the "state-as-component" pattern from the round-match cookbook.
18
+ *
19
+ * The generated manager auto-attaches the state components on start (so a
20
+ * non-coder only places the manager), index-wraps when advancing, skips states
21
+ * whose CanEnter() returns false, and announces transitions via a static
22
+ * OnStateChanged event mirrored by an [Rpc.Broadcast] so proxies converge
23
+ * instantly (the [Sync] index is the durable late-joiner reconcile path).
24
+ * Single-player safe. Scene/file-mutating; refused during play mode.
25
+ */
26
+ export function registerRoundStateTools(server, bridge) {
27
+ server.tool("create_round_state_machine", "Generate a host-authoritative MULTI-STATE round machine (the complex variant of create_round_phase_machine). Produces one .cs file: a RoundManager singleton component + an abstract RoundState base (Begin/Tick/OnTimeUp/Finish lifecycle with a per-state [Sync(SyncFlags.FromHost)] TimeUntil timer) + one sealed stub class per named state. The manager auto-attaches the state components on start (you only place the manager), ticks ONLY the active state on the host, Advance()s on timeout with index-wrap, SKIPS any state whose CanEnter() returns false, and announces every transition via a static OnStateChanged event plus an [Rpc.Broadcast] mirror so the host fires immediately and proxies converge without waiting a snapshot (the [Sync] index reconciles late joiners). Single-player safe. USE THIS (not create_round_phase_machine) when each phase needs its OWN behaviour — entry side-effects, per-frame Tick logic, a skip condition, or copy-data-out-on-exit; use the phase machine for 3–5 light phases that differ only in duration. Optionally attached to an existing GameObject by GUID (after a hotload).", {
28
+ name: z
29
+ .string()
30
+ .optional()
31
+ .describe("Manager class name. Defaults to 'RoundManager'. The abstract base is derived from it (RoundManager → RoundState)"),
32
+ directory: z
33
+ .string()
34
+ .optional()
35
+ .describe("Subdirectory for the .cs file (path override). Defaults to 'Code'"),
36
+ states: z
37
+ .array(z.string())
38
+ .optional()
39
+ .describe('Ordered state names — each becomes a sealed {Name}State stub class. Defaults to ["Waiting","Active","PostRound"]'),
40
+ duration: z
41
+ .number()
42
+ .optional()
43
+ .describe("Default seconds each state lasts (each state also gets its own tunable [Property] Duration). 0 = no auto-advance for a state. Defaults to 30"),
44
+ durations: z
45
+ .union([
46
+ z.array(z.number()),
47
+ z.record(z.number()),
48
+ ])
49
+ .optional()
50
+ .describe('Optional per-state duration override: an array aligned to `states` ([10,120,8]) OR an object keyed by state name ({"Waiting":10,"Active":120}). Any state not covered falls back to `duration`'),
51
+ loop: z
52
+ .boolean()
53
+ .optional()
54
+ .describe("Loop back to the first state after the last (true) or hold on the last state (false). Defaults to true"),
55
+ targetId: z
56
+ .string()
57
+ .optional()
58
+ .describe("GUID of an existing GameObject to attach the manager to (only if the type is already loaded — trigger_hotload first)"),
59
+ }, async (params) => {
60
+ const res = await bridge.send("create_round_state_machine", params);
61
+ if (!res.success) {
62
+ return { content: [{ type: "text", text: `Error: ${res.error}` }] };
63
+ }
64
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
65
+ });
66
+ }
67
+ //# sourceMappingURL=roundstate.js.map
@@ -0,0 +1,15 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { BridgeClient } from "../transport/bridge-client.js";
3
+ /**
4
+ * Multi-slot save manager scaffold — the slot-picker sibling of create_save_system.
5
+ *
6
+ * - create_save_slots generate a SaveSlotManager component: list / create /
7
+ * load / save / delete N save slots, each with picker
8
+ * metadata (name + timestamp + playtime), versioned
9
+ * payloads with delete-on-version-mismatch, and an
10
+ * optional GUID scene-object reconciliation on load.
11
+ *
12
+ * File/scene-mutating; refused during play mode by the bridge dispatch.
13
+ */
14
+ export declare function registerSaveSlotsTools(server: McpServer, bridge: BridgeClient): void;
15
+ //# sourceMappingURL=saveslots.d.ts.map
@@ -0,0 +1,45 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Multi-slot save manager scaffold — the slot-picker sibling of create_save_system.
4
+ *
5
+ * - create_save_slots generate a SaveSlotManager component: list / create /
6
+ * load / save / delete N save slots, each with picker
7
+ * metadata (name + timestamp + playtime), versioned
8
+ * payloads with delete-on-version-mismatch, and an
9
+ * optional GUID scene-object reconciliation on load.
10
+ *
11
+ * File/scene-mutating; refused during play mode by the bridge dispatch.
12
+ */
13
+ export function registerSaveSlotsTools(server, bridge) {
14
+ // ── create_save_slots ─────────────────────────────────────────────
15
+ server.tool("create_save_slots", "Generate a multi-slot save MANAGER component (the slot-picker sibling of create_save_system). Use this when the game needs SEVERAL named save slots the player chooses between (New Game / Load Game menu, per-character or per-run saves) — not one silent autosave. Use create_save_system instead when a single implicit save file is enough. Emits one sealed Component that lists / creates / loads / saves / deletes N slots: a lightweight manifest file (saveslots.json) holds per-slot metadata for the picker (Used flag + Name + SavedAtUnix timestamp + PlaytimeSeconds) so listing never loads a heavy payload, and each slot's game state lives in its own saveslot_<i>.json. Versioned SlotData POCO with clamp-on-load Sanitize() and delete-on-version-mismatch; runs only on the owning machine (IsProxy guard). Static OnSlotLoaded / OnSlotSaved / OnSlotDeleted hooks for HUD. Storage stays within the verified FileSystem.Data.ReadJsonOrDefault / WriteJson / DeleteFile surface (index-file pattern, no directory enumeration). Set sceneReconciliation:true to also reconcile scene objects by GameObject.Id on load — records the save marks destroyed are destroyed, survivors repositioned, missing skipped (good for a placeable-world tycoon). Optionally attached to an existing GameObject by GUID (after a hotload).", {
16
+ name: z
17
+ .string()
18
+ .optional()
19
+ .describe("Class name for the generated component. Defaults to 'SaveSlotManager'"),
20
+ directory: z
21
+ .string()
22
+ .optional()
23
+ .describe("Subdirectory for the generated .cs file. Defaults to 'Code'"),
24
+ maxSlots: z
25
+ .number()
26
+ .int()
27
+ .optional()
28
+ .describe("How many save slots the manager manages (manifest is normalized to exactly this many, indexed 0..N-1). Clamped to 1..100. Defaults to 3"),
29
+ sceneReconciliation: z
30
+ .boolean()
31
+ .optional()
32
+ .describe("If true, saved records carry each object's GameObject.Id GUID and load reconciles the live scene against them (destroy the save's destroyed records via Scene.Directory.FindByGuid, reposition survivors, skip missing) — call RecordObject(go) to track a placeable. If false (default), the slot save is a plain payload with no scene reconciliation. Defaults to false"),
33
+ targetId: z
34
+ .string()
35
+ .optional()
36
+ .describe("GUID of an existing GameObject to attach the manager to (only attaches if the type is already loaded — hotload first)"),
37
+ }, async (params) => {
38
+ const res = await bridge.send("create_save_slots", params);
39
+ if (!res.success) {
40
+ return { content: [{ type: "text", text: `Error: ${res.error}` }] };
41
+ }
42
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
43
+ });
44
+ }
45
+ //# sourceMappingURL=saveslots.js.map
@@ -0,0 +1,15 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { BridgeClient } from "../transport/bridge-client.js";
3
+ /**
4
+ * Interaction-station scaffold — a Tier-1 backlog item (mined from 2 games).
5
+ *
6
+ * - add_interaction_station an IPressable "station" prop (crafting bench /
7
+ * shop till / arcade cabinet) with host-authoritative
8
+ * single-occupant occupancy, a reservation grace
9
+ * window, an unlock-level gate, and an overlay-open hook.
10
+ *
11
+ * Generates a clean, self-contained .cs and optionally attaches it to a live
12
+ * GameObject. Scene/file-mutating; refused during play mode by the bridge dispatch.
13
+ */
14
+ export declare function registerStationTools(server: McpServer, bridge: BridgeClient): void;
15
+ //# sourceMappingURL=stations.d.ts.map
@@ -0,0 +1,47 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Interaction-station scaffold — a Tier-1 backlog item (mined from 2 games).
4
+ *
5
+ * - add_interaction_station an IPressable "station" prop (crafting bench /
6
+ * shop till / arcade cabinet) with host-authoritative
7
+ * single-occupant occupancy, a reservation grace
8
+ * window, an unlock-level gate, and an overlay-open hook.
9
+ *
10
+ * Generates a clean, self-contained .cs and optionally attaches it to a live
11
+ * GameObject. Scene/file-mutating; refused during play mode by the bridge dispatch.
12
+ */
13
+ export function registerStationTools(server, bridge) {
14
+ // ── add_interaction_station ───────────────────────────────────────
15
+ server.tool("add_interaction_station", "Generate a Component.IPressable 'station' prop (crafting bench / shop till / arcade cabinet) that ONE user occupies at a time. Occupancy is host-authoritative: the occupant is a [Sync(SyncFlags.FromHost)] Guid (GameObject/Connection aren't [Sync]-able) and Press() routes the claim to the host via an [Rpc.Host] Occupy(). Includes a reservation grace window (the station stays reserved for its last user for graceSeconds after they leave, so a brief walk-away can't jump the queue), an optional unlock-level gate (users below requiredLevel can't use it — wire the static ResolveUserLevel hook to your progression system to activate it), and an overlay-open hook (a static OnStationOpened(GameObject) event to open your UI, plus an opt-in [Rpc.Broadcast] mirror). Single-player safe. Optionally attached to an existing GameObject by GUID (only after a trigger_hotload). Give the prop a Collider so the player's use key can raycast it. Mined from interaction-station patterns across shipped s&box games.", {
16
+ name: z
17
+ .string()
18
+ .optional()
19
+ .describe("Class name for the generated component. Defaults to 'InteractionStation'"),
20
+ directory: z
21
+ .string()
22
+ .optional()
23
+ .describe("Subdirectory for the .cs file (path override). Defaults to 'Code'"),
24
+ graceSeconds: z
25
+ .number()
26
+ .optional()
27
+ .describe("Seconds the station stays reserved for its last user after they leave, before anyone else can claim it. 0 = no grace window. Defaults to 5"),
28
+ requiredLevel: z
29
+ .number()
30
+ .int()
31
+ .optional()
32
+ .describe("Unlock-level gate: users below this level can't use the station. 0 = no gate. The gate only bites once you wire the static ResolveUserLevel hook to your progression system. Defaults to 0"),
33
+ targetId: z
34
+ .string()
35
+ .optional()
36
+ .describe("GUID of an existing GameObject to attach the station component to (only attaches if the type is already loaded — generate, trigger_hotload, then it places; otherwise add it after the hotload)"),
37
+ }, async (params) => {
38
+ const res = await bridge.send("add_interaction_station", params);
39
+ if (!res.success) {
40
+ return { content: [{ type: "text", text: `Error: ${res.error}` }] };
41
+ }
42
+ return {
43
+ content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
44
+ };
45
+ });
46
+ }
47
+ //# sourceMappingURL=stations.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sbox-mcp-server",
3
- "version": "1.17.1",
3
+ "version": "1.18.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",