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 +13 -3
- package/dist/index.js +8 -0
- package/dist/tools/characters.js +24 -0
- package/dist/tools/director.d.ts +14 -0
- package/dist/tools/director.js +48 -0
- package/dist/tools/roundstate.d.ts +28 -0
- package/dist/tools/roundstate.js +67 -0
- package/dist/tools/saveslots.d.ts +15 -0
- package/dist/tools/saveslots.js +45 -0
- package/dist/tools/stations.d.ts +15 -0
- package/dist/tools/stations.js +47 -0
- package/package.json +1 -1
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 — **
|
|
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
|
|
57
|
+
## Tools
|
|
58
58
|
|
|
59
|
-
`get_bridge_status` reports the `handlerCount` — that's the C# handlers compiled inside the editor.
|
|
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();
|
package/dist/tools/characters.js
CHANGED
|
@@ -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
|