sbox-mcp-server 1.20.0 → 2.1.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/dist/index.js +125 -82
- package/dist/tools/aisystems.d.ts +4 -0
- package/dist/tools/aisystems.js +236 -0
- package/dist/tools/assets.js +5 -5
- package/dist/tools/audio.js +12 -19
- package/dist/tools/audit.d.ts +8 -0
- package/dist/tools/audit.js +65 -0
- package/dist/tools/batch.d.ts +8 -0
- package/dist/tools/batch.js +77 -0
- package/dist/tools/characters.js +6 -6
- package/dist/tools/cinematicrecording.d.ts +31 -0
- package/dist/tools/cinematicrecording.js +107 -0
- package/dist/tools/cinematics.js +2 -2
- package/dist/tools/components.js +2 -2
- package/dist/tools/debugdraw.js +4 -4
- package/dist/tools/diagnostics.js +7 -5
- package/dist/tools/dialoguefx.d.ts +4 -0
- package/dist/tools/dialoguefx.js +144 -0
- package/dist/tools/discovery.js +6 -6
- package/dist/tools/economysave.d.ts +27 -0
- package/dist/tools/economysave.js +231 -0
- package/dist/tools/gameobjects.js +7 -7
- package/dist/tools/gameplay.js +4 -4
- package/dist/tools/gameplayrecorder.d.ts +18 -0
- package/dist/tools/gameplayrecorder.js +73 -0
- package/dist/tools/inspection.js +6 -6
- package/dist/tools/leveltools.js +5 -5
- package/dist/tools/materials.js +4 -4
- package/dist/tools/movieauthoring.d.ts +31 -0
- package/dist/tools/movieauthoring.js +84 -0
- package/dist/tools/moviemaker.js +1 -1
- package/dist/tools/multiplayertest.d.ts +23 -0
- package/dist/tools/multiplayertest.js +62 -0
- package/dist/tools/networking.js +22 -22
- package/dist/tools/physics.js +4 -4
- package/dist/tools/playmode.js +5 -5
- package/dist/tools/playtest.js +2 -1
- package/dist/tools/prefabs.js +7 -3
- package/dist/tools/project.js +2 -2
- package/dist/tools/publishing.js +3 -3
- package/dist/tools/roundui.d.ts +22 -0
- package/dist/tools/roundui.js +88 -0
- package/dist/tools/scenes.js +2 -2
- package/dist/tools/scripts.js +3 -3
- package/dist/tools/statsachievements.d.ts +4 -0
- package/dist/tools/statsachievements.js +243 -0
- package/dist/tools/status.js +1 -1
- package/dist/tools/templates.js +7 -7
- package/dist/tools/ui.js +5 -5
- package/dist/tools/uifeedback.js +1 -1
- package/dist/tools/vehicles.d.ts +8 -0
- package/dist/tools/vehicles.js +98 -0
- package/dist/tools/visuals.js +8 -8
- package/dist/tools/workflow.d.ts +8 -0
- package/dist/tools/workflow.js +107 -0
- package/dist/tools/world.js +61 -58
- package/dist/tools/worldrender.d.ts +4 -0
- package/dist/tools/worldrender.js +183 -0
- package/package.json +1 -1
package/dist/tools/templates.js
CHANGED
|
@@ -89,7 +89,7 @@ export function registerTemplateTools(server, bridge) {
|
|
|
89
89
|
};
|
|
90
90
|
});
|
|
91
91
|
// ── create_game_manager ───────────────────────────────────────────
|
|
92
|
-
server.tool("create_game_manager", "Generate a game
|
|
92
|
+
server.tool("create_game_manager", "Generate a minimal game-manager Component: a static Instance singleton, [Property] MaxPlayers / GameState, and a Component.INetworkListener OnActive hook that logs player connects. Writes <name>.cs and returns { created, path, className }. NOTE: the includeScore/includeTimer/includeSpawning params are not currently applied — the same minimal manager is always generated (for richer game-loop scaffolds see create_round_phase_machine / create_objective_system / create_economy_wallet). Follow with trigger_hotload, then get_compile_errors, then place via add_component_to_new_object", {
|
|
93
93
|
name: z
|
|
94
94
|
.string()
|
|
95
95
|
.optional()
|
|
@@ -101,15 +101,15 @@ export function registerTemplateTools(server, bridge) {
|
|
|
101
101
|
includeScore: z
|
|
102
102
|
.boolean()
|
|
103
103
|
.optional()
|
|
104
|
-
.describe("Include score tracking
|
|
104
|
+
.describe("Include score tracking (currently not applied by the handler)"),
|
|
105
105
|
includeTimer: z
|
|
106
106
|
.boolean()
|
|
107
107
|
.optional()
|
|
108
|
-
.describe("Include round timer with countdown
|
|
108
|
+
.describe("Include round timer with countdown (currently not applied by the handler)"),
|
|
109
109
|
includeSpawning: z
|
|
110
110
|
.boolean()
|
|
111
111
|
.optional()
|
|
112
|
-
.describe("Include player spawning from prefab at spawn point
|
|
112
|
+
.describe("Include player spawning from prefab at spawn point (currently not applied by the handler)"),
|
|
113
113
|
}, async (params) => {
|
|
114
114
|
const res = await bridge.send("create_game_manager", params);
|
|
115
115
|
if (!res.success) {
|
|
@@ -120,7 +120,7 @@ export function registerTemplateTools(server, bridge) {
|
|
|
120
120
|
};
|
|
121
121
|
});
|
|
122
122
|
// ── create_trigger_zone ───────────────────────────────────────────
|
|
123
|
-
server.tool("create_trigger_zone", "Generate a trigger
|
|
123
|
+
server.tool("create_trigger_zone", "Generate a trigger-zone Component (Component.ITriggerListener): auto-adds a trigger BoxCollider on start, filters entrants by a TriggerTag [Property] (default 'player'), and logs enter/exit via private OnPlayerEnter/OnPlayerExit extension points you fill in. Writes <name>.cs and returns { created, path, className }. NOTE: the action/filterTag params are not currently applied at generation time — the zone always logs; implement teleport/damage/spawn in the generated methods (edit_script). Follow with trigger_hotload, then get_compile_errors", {
|
|
124
124
|
name: z
|
|
125
125
|
.string()
|
|
126
126
|
.optional()
|
|
@@ -132,11 +132,11 @@ export function registerTemplateTools(server, bridge) {
|
|
|
132
132
|
action: z
|
|
133
133
|
.enum(["log", "teleport", "damage", "spawn"])
|
|
134
134
|
.optional()
|
|
135
|
-
.describe("What happens on trigger
|
|
135
|
+
.describe("What happens on trigger (currently not applied by the handler — the generated zone always logs; implement the effect in OnPlayerEnter yourself)"),
|
|
136
136
|
filterTag: z
|
|
137
137
|
.string()
|
|
138
138
|
.optional()
|
|
139
|
-
.describe("Only trigger for objects with this tag
|
|
139
|
+
.describe("Only trigger for objects with this tag (currently not applied at generation — the generated TriggerTag [Property] defaults to 'player'; change it per-instance with set_property)"),
|
|
140
140
|
}, async (params) => {
|
|
141
141
|
const res = await bridge.send("create_trigger_zone", params);
|
|
142
142
|
if (!res.success) {
|
package/dist/tools/ui.js
CHANGED
|
@@ -5,7 +5,7 @@ import { z } from "zod";
|
|
|
5
5
|
*/
|
|
6
6
|
export function registerUITools(server, bridge) {
|
|
7
7
|
// ── create_razor_ui ───────────────────────────────────────────────
|
|
8
|
-
server.tool("create_razor_ui", "Create a Razor UI component file (.razor)
|
|
8
|
+
server.tool("create_razor_ui", "Create a Razor UI component file (.razor). Returns { created, path, componentName }; errors if the file already exists. NOTE: the current handler generates one fixed basic panel (a root div + label bound to a Title [Property]) from name+directory only — panelType/content/styles/includeStyles are not applied and no .scss is written; for custom markup or a PanelComponent you can host via add_screen_panel, write the files with write_file and check them with razor_lint. Follow with trigger_hotload, then get_compile_errors", {
|
|
9
9
|
name: z
|
|
10
10
|
.string()
|
|
11
11
|
.describe("Component name (e.g. 'GameHud', 'MainMenu')"),
|
|
@@ -43,11 +43,11 @@ export function registerUITools(server, bridge) {
|
|
|
43
43
|
};
|
|
44
44
|
});
|
|
45
45
|
// ── add_screen_panel ──────────────────────────────────────────────
|
|
46
|
-
server.tool("add_screen_panel", "Create a new GameObject with a ScreenPanel component for full-screen UI overlay (HUD, menus, etc.)", {
|
|
46
|
+
server.tool("add_screen_panel", "Create a new GameObject with a ScreenPanel component for full-screen UI overlay (HUD, menus, etc.). Returns { created, gameObject:{ id, name, components, ... } }. NOTE: panelComponent is looked up in the TypeLibrary and SILENTLY skipped if the type isn't loaded (freshly generated panels need trigger_hotload first) — check gameObject.components to confirm it attached", {
|
|
47
47
|
name: z
|
|
48
48
|
.string()
|
|
49
49
|
.optional()
|
|
50
|
-
.describe("Name for the UI GameObject. Defaults to 'Screen
|
|
50
|
+
.describe("Name for the UI GameObject. Defaults to 'Screen Panel'"),
|
|
51
51
|
zIndex: z
|
|
52
52
|
.number()
|
|
53
53
|
.optional()
|
|
@@ -70,11 +70,11 @@ export function registerUITools(server, bridge) {
|
|
|
70
70
|
};
|
|
71
71
|
});
|
|
72
72
|
// ── add_world_panel ───────────────────────────────────────────────
|
|
73
|
-
server.tool("add_world_panel", "Create a new GameObject with a WorldPanel component for in-world 3D UI (health bars, signs, nameplates)", {
|
|
73
|
+
server.tool("add_world_panel", "Create a new GameObject with a WorldPanel component for in-world 3D UI (health bars, signs, nameplates). Returns { created, gameObject:{ id, name, components, ... } }. As with add_screen_panel, panelComponent is SILENTLY skipped if the type isn't in the TypeLibrary (trigger_hotload first, then verify via gameObject.components). For CLICKABLE world UI the scene also needs a Sandbox.WorldInput (see create_worldpanel_ui)", {
|
|
74
74
|
name: z
|
|
75
75
|
.string()
|
|
76
76
|
.optional()
|
|
77
|
-
.describe("Name for the UI GameObject. Defaults to 'World
|
|
77
|
+
.describe("Name for the UI GameObject. Defaults to 'World Panel'"),
|
|
78
78
|
position: z
|
|
79
79
|
.union([
|
|
80
80
|
z.object({ x: z.number(), y: z.number(), z: z.number() }),
|
package/dist/tools/uifeedback.js
CHANGED
|
@@ -34,7 +34,7 @@ export function registerUiFeedbackTools(server, bridge) {
|
|
|
34
34
|
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
|
|
35
35
|
});
|
|
36
36
|
// ── create_proxy_nametag ──────────────────────────────────────────
|
|
37
|
-
server.tool("create_proxy_nametag", "Generate a sealed Component that floats the OWNER'S display name above a networked player. TextRenderer-based (not WorldPanel): a nametag is one short string with a distance fade, so a TextRenderer on a managed child object is far simpler than a WorldPanel + Razor + WorldInput stack — no UI assets, no panel host, per-frame alpha is a one-liner (mirrors create_floating_combat_text). Reads GameObject.Network.Owner.DisplayName (Owner and OwnerConnection are the same Connection on this SDK; falls back to the object name offline). Visibility is the INVERSE of the usual proxy guard: it renders only when GameObject.Network.IsProxy is true — i.e. on OTHER clients' copies of the player — so you never see a tag over your own head (offline / no networking => IsProxy false everywhere => no tags, expected). Spawns a CHILD GameObject for the text so billboarding never rotates the player model; cleaned up on disable. [Property] MaxDistance fades the tag out with distance, HeightOffset floats it above the head, FontSize sizes it. Attach to the ROOT of your networked player object. Pairs with create_networked_player (add it to the generated player prefab so every remote player is labeled).", {
|
|
37
|
+
server.tool("create_proxy_nametag", "Generate a sealed Component that floats the OWNER'S display name above a networked player. TextRenderer-based (not WorldPanel): a nametag is one short string with a distance fade, so a TextRenderer on a managed child object is far simpler than a WorldPanel + Razor + WorldInput stack — no UI assets, no panel host, per-frame alpha is a one-liner (mirrors create_floating_combat_text). Reads GameObject.Network.Owner.DisplayName (Owner and OwnerConnection are the same Connection on this SDK; falls back to the object name offline). Visibility is the INVERSE of the usual proxy guard: it renders only when GameObject.Network.IsProxy is true — i.e. on OTHER clients' copies of the player — so you never see a tag over your own head (offline / no networking => IsProxy false everywhere => no tags, expected). Spawns a CHILD GameObject for the text so billboarding never rotates the player model; cleaned up on disable. [Property] MaxDistance fades the tag out with distance, HeightOffset floats it above the head, FontSize sizes it. Attach to the ROOT of your networked player object. Pairs with create_networked_player (add it to the generated player prefab so every remote player is labeled). Returns { created, path, className, maxDistance, heightOffset, note, nextSteps }. Follow with trigger_hotload, then get_compile_errors, then attach via add_component_with_properties (component=className).", {
|
|
38
38
|
name: z
|
|
39
39
|
.string()
|
|
40
40
|
.optional()
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { BridgeClient } from "../transport/bridge-client.js";
|
|
3
|
+
/**
|
|
4
|
+
* Vehicle tools (Batch 54): create_vehicle_controller, create_seat_system,
|
|
5
|
+
* tune_vehicle, create_physics_grab_tool — the corpus vehicles theme.
|
|
6
|
+
*/
|
|
7
|
+
export declare function registerVehicleTools(server: McpServer, bridge: BridgeClient): void;
|
|
8
|
+
//# sourceMappingURL=vehicles.d.ts.map
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Vehicle tools (Batch 54): create_vehicle_controller, create_seat_system,
|
|
4
|
+
* tune_vehicle, create_physics_grab_tool — the corpus vehicles theme.
|
|
5
|
+
*/
|
|
6
|
+
export function registerVehicleTools(server, bridge) {
|
|
7
|
+
// ── create_vehicle_controller ────────────────────────────────────
|
|
8
|
+
server.tool("create_vehicle_controller", "Generate a drivable raycast-car component: 4-corner spring/damper suspension, mass-scaled engine force, speed-scaled yaw steering, lateral grip (lower = drift), and a BUILT-IN driver seat (press E to enter — the driver is hidden while driving so their controller can't fight the vehicle transform, the host assigns them ownership, and a chase camera follows the car — E to exit). Attach it plus a Rigidbody and collider to any prop and it drives. Returns { created, path, className, nextSteps } — follow with trigger_hotload + compile_status, then batch_add_component the parts. Apply handling presets with tune_vehicle. HONEST LIMIT: compiles + runs is verified; driving FEEL needs a human playtest — tune from the inspector while playing", {
|
|
9
|
+
name: z
|
|
10
|
+
.string()
|
|
11
|
+
.optional()
|
|
12
|
+
.describe("Class/file name (default 'VehicleController' -> Code/VehicleController.cs). Errors if the file exists"),
|
|
13
|
+
directory: z
|
|
14
|
+
.string()
|
|
15
|
+
.optional()
|
|
16
|
+
.describe("Directory for the .cs file. Default 'Code'"),
|
|
17
|
+
engineForce: z
|
|
18
|
+
.number()
|
|
19
|
+
.optional()
|
|
20
|
+
.describe("Engine force (mass-scaled). Default 900"),
|
|
21
|
+
steerStrength: z
|
|
22
|
+
.number()
|
|
23
|
+
.optional()
|
|
24
|
+
.describe("Yaw rate in rad/s at full speed factor. Default 2.0"),
|
|
25
|
+
grip: z
|
|
26
|
+
.number()
|
|
27
|
+
.optional()
|
|
28
|
+
.describe("Lateral grip 0-1 — fraction of sideways velocity killed per tick; lower = drift. Default 0.85"),
|
|
29
|
+
}, async (params) => {
|
|
30
|
+
const res = await bridge.send("create_vehicle_controller", params);
|
|
31
|
+
if (!res.success) {
|
|
32
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
|
|
36
|
+
};
|
|
37
|
+
});
|
|
38
|
+
// ── create_seat_system ───────────────────────────────────────────
|
|
39
|
+
server.tool("create_seat_system", "Generate a standalone networked one-occupant seat component: press E (use) to sit — claims route through the host so seats can't be shared — E to stand; the occupant parents to the seat with their controller input disabled and restored on exit, which tries each ExitOffsets entry and takes the first spot with clearance. Static OnOccupantChanged event for camera/UI. For chairs, benches, turret mounts; vehicles get a seat built into create_vehicle_controller. Returns { created, path, className, nextSteps } — trigger_hotload + compile_status, then attach to any prop", {
|
|
40
|
+
name: z
|
|
41
|
+
.string()
|
|
42
|
+
.optional()
|
|
43
|
+
.describe("Class/file name (default 'Seat' -> Code/Seat.cs). Errors if the file exists"),
|
|
44
|
+
directory: z
|
|
45
|
+
.string()
|
|
46
|
+
.optional()
|
|
47
|
+
.describe("Directory for the .cs file. Default 'Code'"),
|
|
48
|
+
}, async (params) => {
|
|
49
|
+
const res = await bridge.send("create_seat_system", params);
|
|
50
|
+
if (!res.success) {
|
|
51
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
// ── tune_vehicle ─────────────────────────────────────────────────
|
|
58
|
+
server.tool("tune_vehicle", "Apply a handling preset to a vehicle component on a GameObject: arcade (forgiving), drift (low grip, sharp steering), offroad (soft long-travel suspension), or race (fast, planted). Sets EngineForce/MaxSpeed/SteerStrength/GripFactor/SuspensionStrength/SuspensionDamping by name via reflection — targets create_vehicle_controller scaffolds out of the box, partially tunes anything exposing those property names. Returns { tuned, preset, component, applied, missing } — missing lists properties the component lacks. Auto-finds the first component with 'Vehicle' in its type name; pass component to target explicitly. Scene-mutating: refused during play mode (use set_property on runtime objects while playing)", {
|
|
59
|
+
id: z
|
|
60
|
+
.string()
|
|
61
|
+
.describe("GUID of the GameObject carrying the vehicle component (from find_objects)"),
|
|
62
|
+
preset: z
|
|
63
|
+
.enum(["arcade", "drift", "offroad", "race"])
|
|
64
|
+
.describe("Handling preset to apply"),
|
|
65
|
+
component: z
|
|
66
|
+
.string()
|
|
67
|
+
.optional()
|
|
68
|
+
.describe("Component type name to tune. Default: first component with 'Vehicle' in its name"),
|
|
69
|
+
}, async (params) => {
|
|
70
|
+
const res = await bridge.send("tune_vehicle", params);
|
|
71
|
+
if (!res.success) {
|
|
72
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
|
|
76
|
+
};
|
|
77
|
+
});
|
|
78
|
+
// ── create_physics_grab_tool ─────────────────────────────────────
|
|
79
|
+
server.tool("create_physics_grab_tool", "Generate a physgun-lite player component: hold attack2 while looking at a Rigidbody prop within Range to grab it — it spring-follows a point in front of your view with physics LIVE (collides and swings, unlike the parented create_carry_system) — attack1 throws it, release lets go. Grab requests route through the host, which assigns the grabber network ownership. Returns { created, path, className, nextSteps } — trigger_hotload + compile_status, attach to the player object; ensure_input_action if attack1/attack2 aren't bound", {
|
|
80
|
+
name: z
|
|
81
|
+
.string()
|
|
82
|
+
.optional()
|
|
83
|
+
.describe("Class/file name (default 'PhysicsGrabTool' -> Code/PhysicsGrabTool.cs). Errors if the file exists"),
|
|
84
|
+
directory: z
|
|
85
|
+
.string()
|
|
86
|
+
.optional()
|
|
87
|
+
.describe("Directory for the .cs file. Default 'Code'"),
|
|
88
|
+
}, async (params) => {
|
|
89
|
+
const res = await bridge.send("create_physics_grab_tool", params);
|
|
90
|
+
if (!res.success) {
|
|
91
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
|
|
95
|
+
};
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=vehicles.js.map
|
package/dist/tools/visuals.js
CHANGED
|
@@ -72,7 +72,7 @@ export function registerVisualTools(server, bridge) {
|
|
|
72
72
|
};
|
|
73
73
|
});
|
|
74
74
|
// ── set_fog ────────────────────────────────────────────────────────
|
|
75
|
-
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
|
|
75
|
+
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 with the same targetId updates it rather than duplicating; without targetId each call creates a new fog object. Returns { created, type, gameObject } — gameObject.id addresses the fog object for later set_property/delete_gameobject; screenshot to verify the look.", {
|
|
76
76
|
type: z
|
|
77
77
|
.enum(["gradient", "cubemap", "volumetric"])
|
|
78
78
|
.optional()
|
|
@@ -127,7 +127,7 @@ export function registerVisualTools(server, bridge) {
|
|
|
127
127
|
};
|
|
128
128
|
});
|
|
129
129
|
// ── set_skybox ───────────────────────────────────────────────────────
|
|
130
|
-
server.tool("set_skybox", "Set the scene's 2D skybox tint / indirect lighting (re-uses an existing SkyBox2D or creates one). Darken the tint for night/dusk. Optionally point it at a .vmat sky material.", {
|
|
130
|
+
server.tool("set_skybox", "Set the scene's 2D skybox tint / indirect lighting (re-uses an existing SkyBox2D or creates one). Darken the tint for night/dusk. Optionally point it at a .vmat sky material (silently kept as-is if the material fails to load). Returns { created, gameObject } — gameObject.id is the sky object; screenshot to verify the change.", {
|
|
131
131
|
tint: ColorSchema.optional().describe("Sky tint colour"),
|
|
132
132
|
indirectLighting: z
|
|
133
133
|
.boolean()
|
|
@@ -145,7 +145,7 @@ export function registerVisualTools(server, bridge) {
|
|
|
145
145
|
};
|
|
146
146
|
});
|
|
147
147
|
// ── apply_atmosphere (preset) ─────────────────────────────────────────
|
|
148
|
-
server.tool("apply_atmosphere", "One-call scene mood: composes ambient + directional light, gradient fog, and a camera post-fx stack (tonemap + colour grade + vignette) tuned for the chosen mood. Idempotent — re-runs update the same 'Atmosphere *' objects.", {
|
|
148
|
+
server.tool("apply_atmosphere", "One-call scene mood: composes ambient + directional light, gradient fog, and a camera post-fx stack (tonemap + colour grade + vignette) tuned for the chosen mood. Idempotent — re-runs update the same 'Atmosphere *' objects. Returns { applied, mood, components, postFxCamera } — the post-fx stack is only applied when a camera exists (postFxCamera is null otherwise; add a camera and re-run). Screenshot to verify.", {
|
|
149
149
|
mood: z
|
|
150
150
|
.enum(["horror-night", "foggy-dawn", "overcast", "warm-interior"])
|
|
151
151
|
.describe("Atmosphere preset"),
|
|
@@ -159,7 +159,7 @@ export function registerVisualTools(server, bridge) {
|
|
|
159
159
|
};
|
|
160
160
|
});
|
|
161
161
|
// ── apply_post_fx_look (preset) ───────────────────────────────────────
|
|
162
|
-
server.tool("apply_post_fx_look", "Apply just a camera post-processing look (no lights/fog): cinematic (tonemap + bloom + soft vignette), filmic-horror (desaturated, high-contrast, heavy vignette, film grain), or clean (tonemap only).", {
|
|
162
|
+
server.tool("apply_post_fx_look", "Apply just a camera post-processing look (no lights/fog): cinematic (tonemap + bloom + soft vignette), filmic-horror (desaturated, high-contrast, heavy vignette, film grain), or clean (tonemap only). Errors if the scene has no CameraComponent. Returns { applied, look, components, camera } listing the effect components added to the main camera — tune them individually afterwards with add_post_process.", {
|
|
163
163
|
look: z
|
|
164
164
|
.enum(["cinematic", "filmic-horror", "clean"])
|
|
165
165
|
.describe("Post-fx look preset"),
|
|
@@ -173,7 +173,7 @@ export function registerVisualTools(server, bridge) {
|
|
|
173
173
|
};
|
|
174
174
|
});
|
|
175
175
|
// ── add_envmap_probe ─────────────────────────────────────────────────
|
|
176
|
-
server.tool("add_envmap_probe", "Add an environment reflection/ambient probe (EnvmapProbe) at a position with a cubic influence volume — captures local reflections and indirect light for nearby surfaces.", {
|
|
176
|
+
server.tool("add_envmap_probe", "Add an environment reflection/ambient probe (EnvmapProbe) at a position with a cubic influence volume — captures local reflections and indirect light for nearby surfaces. IMPORTANT: a placed probe captures NOTHING until baked — follow with bake_reflections. Returns { created, gameObject } — gameObject.id is the probe object's GUID.", {
|
|
177
177
|
name: z.string().optional().describe("GameObject name"),
|
|
178
178
|
position: Vector3Schema.optional().describe("World position (centre of the probe)"),
|
|
179
179
|
size: z.number().optional().describe("Cubic influence size in units (default 1024)"),
|
|
@@ -192,7 +192,7 @@ export function registerVisualTools(server, bridge) {
|
|
|
192
192
|
};
|
|
193
193
|
});
|
|
194
194
|
// ── spawn_particle (Batch 18: VFX) ───────────────────────────────────
|
|
195
|
-
server.tool("spawn_particle", "
|
|
195
|
+
server.tool("spawn_particle", "EXPERIMENTAL — build an additive runtime ParticleEffect (no texture asset needed): kind = fire, embers, sparks, magic, dust, blood, or snow ('smoke' returns an error). Returns { created, kind, gameObject } — the component graph is created, but this runtime particle path has NOT been verified to render through the bridge; for particles you can actually see, prefer spawn_vpcf.", {
|
|
196
196
|
kind: z
|
|
197
197
|
.enum(["fire", "embers", "sparks", "magic", "dust", "blood", "snow"])
|
|
198
198
|
.describe("Particle preset"),
|
|
@@ -226,7 +226,7 @@ export function registerVisualTools(server, bridge) {
|
|
|
226
226
|
};
|
|
227
227
|
});
|
|
228
228
|
// ── add_beam ─────────────────────────────────────────────────────────
|
|
229
|
-
server.tool("add_beam", "
|
|
229
|
+
server.tool("add_beam", "EXPERIMENTAL — create an energy/laser beam (BeamEffect) from a position to a target point (default: 128u straight up) — additive, tintable. Returns { created, gameObject } — gameObject.id is the beam object's GUID. Like the other runtime particle tools, rendering through the bridge is unverified; use spawn_vpcf when you need a guaranteed-visible effect.", {
|
|
230
230
|
position: Vector3Schema.optional().describe("Beam start (world position of the beam object)"),
|
|
231
231
|
target: Vector3Schema.optional().describe("Beam end point in world space (default: 128u up)"),
|
|
232
232
|
width: z.number().optional().describe("Beam width/scale (default 4)"),
|
|
@@ -286,7 +286,7 @@ export function registerVisualTools(server, bridge) {
|
|
|
286
286
|
};
|
|
287
287
|
});
|
|
288
288
|
// ── bake_reflections ────────────────────────────────────────────────
|
|
289
|
-
server.tool("bake_reflections", "Bake all EnvmapProbe reflection probes in the scene (EnvmapProbe.BakeAll) so they actually capture their surroundings — placing a probe with add_envmap_probe does nothing visible until it's baked. This is a real editor compute step, not a component setter. Runs async; re-screenshot after a moment to see reflections appear
|
|
289
|
+
server.tool("bake_reflections", "Bake all EnvmapProbe reflection probes in the scene (EnvmapProbe.BakeAll) so they actually capture their surroundings — placing a probe with add_envmap_probe does nothing visible until it's baked. This is a real editor compute step, not a component setter. Runs async — returns { baking, count, note, probes } immediately (each probe: id, name, mode, hasBaked), or { baked: false, count: 0 } when the scene has no probes; re-screenshot after a moment to see reflections appear.", {}, async (params) => {
|
|
290
290
|
const res = await bridge.send("bake_reflections", params);
|
|
291
291
|
if (!res.success) {
|
|
292
292
|
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { BridgeClient } from "../transport/bridge-client.js";
|
|
3
|
+
/**
|
|
4
|
+
* Workflow tools (Batch 53): scene checkpoints (the agent-side undo safety net),
|
|
5
|
+
* describe_scene orientation, and the team-assigner / idle-income scaffolds.
|
|
6
|
+
*/
|
|
7
|
+
export declare function registerWorkflowTools(server: McpServer, bridge: BridgeClient): void;
|
|
8
|
+
//# sourceMappingURL=workflow.d.ts.map
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Workflow tools (Batch 53): scene checkpoints (the agent-side undo safety net),
|
|
4
|
+
* describe_scene orientation, and the team-assigner / idle-income scaffolds.
|
|
5
|
+
*/
|
|
6
|
+
export function registerWorkflowTools(server, bridge) {
|
|
7
|
+
// ── checkpoint_scene ─────────────────────────────────────────────
|
|
8
|
+
server.tool("checkpoint_scene", "Snapshot the ENTIRE open scene (every root GameObject, full serialization) to temp storage OUTSIDE the project. The agent-side undo: checkpoint before risky batch edits or experimental changes, roll back with restore_checkpoint if they go wrong. Returns { checkpointed, id, label, scene, rootObjects, sizeBytes } — keep the id. Refused during play mode (runtime state would poison the snapshot). Snapshots survive editor restarts (temp dir, subject to OS cleanup); browse them with list_checkpoints", {
|
|
9
|
+
label: z
|
|
10
|
+
.string()
|
|
11
|
+
.optional()
|
|
12
|
+
.describe("Short label baked into the checkpoint id (e.g. 'before-batch-tint'). Defaults to 'checkpoint'"),
|
|
13
|
+
}, async (params) => {
|
|
14
|
+
const res = await bridge.send("checkpoint_scene", params);
|
|
15
|
+
if (!res.success) {
|
|
16
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
|
|
20
|
+
};
|
|
21
|
+
});
|
|
22
|
+
// ── restore_checkpoint ───────────────────────────────────────────
|
|
23
|
+
server.tool("restore_checkpoint", "REPLACE the open scene's entire contents with a checkpoint_scene snapshot: destroys every current root object, then rebuilds the snapshot's tree (guids preserved, so internal references stay wired). DESTRUCTIVE — requires an explicit id (from checkpoint_scene or list_checkpoints), never guesses. Returns { restored, id, destroyedRoots, restoredRoots }. The scene FILE on disk is untouched until save_scene. Scene-mutating: refused during play mode", {
|
|
24
|
+
id: z
|
|
25
|
+
.string()
|
|
26
|
+
.describe("Checkpoint id to restore (e.g. 'cp_20260709_153000_before-batch-tint')"),
|
|
27
|
+
}, async (params) => {
|
|
28
|
+
const res = await bridge.send("restore_checkpoint", params);
|
|
29
|
+
if (!res.success) {
|
|
30
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
|
|
34
|
+
};
|
|
35
|
+
});
|
|
36
|
+
// ── list_checkpoints ─────────────────────────────────────────────
|
|
37
|
+
server.tool("list_checkpoints", "List this project's scene checkpoints (newest first). Returns { total, checkpoints } — each { id, createdUtc, sizeBytes }. Pass an id to restore_checkpoint to roll the scene back, or create one first with checkpoint_scene. Read-only; no limit (checkpoints are few)", {}, async (params) => {
|
|
38
|
+
const res = await bridge.send("list_checkpoints", 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
|
+
// ── describe_scene ───────────────────────────────────────────────
|
|
47
|
+
server.tool("describe_scene", "One-call orientation for the OPEN scene (works in edit and play mode): total/root object counts, component histogram (top 20 types), every camera with position, light count, tag histogram (top 12), and the aggregate world bounds of renderable content. Returns a structured summary — orient here, then find_objects by component/tag, get_scene_hierarchy for structure, find_broken_references for health, screenshot_orbit to look at something. Complements describe_project (project-level). Read-only", {}, async (params) => {
|
|
48
|
+
const res = await bridge.send("describe_scene", params);
|
|
49
|
+
if (!res.success) {
|
|
50
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
|
|
54
|
+
};
|
|
55
|
+
});
|
|
56
|
+
// ── create_team_assigner ─────────────────────────────────────────
|
|
57
|
+
server.tool("create_team_assigner", "Generate a host-authoritative balanced team assigner component (smallest-bucket draft): AssignSmallest(steamId) drops a joining player into the emptiest team, announces via [Rpc.Broadcast] so every client's roster agrees, and fires static OnTeamAssigned(steamId, index, name); plus Rebalance(), GetTeam, GetMembers. Writes a .cs file and returns { created, path, className, teams, nextSteps } — follow with trigger_hotload + compile_status, attach to your game manager, call AssignSmallest from your join hook (e.g. INetworkListener.OnActive)", {
|
|
58
|
+
name: z
|
|
59
|
+
.string()
|
|
60
|
+
.optional()
|
|
61
|
+
.describe("Class/file name (default 'TeamAssigner' -> Code/TeamAssigner.cs). Errors if the file exists"),
|
|
62
|
+
directory: z
|
|
63
|
+
.string()
|
|
64
|
+
.optional()
|
|
65
|
+
.describe("Directory for the .cs file. Default 'Code'"),
|
|
66
|
+
teams: z
|
|
67
|
+
.array(z.string())
|
|
68
|
+
.optional()
|
|
69
|
+
.describe('Team names in index order. Default ["Red", "Blue"]'),
|
|
70
|
+
}, async (params) => {
|
|
71
|
+
const res = await bridge.send("create_team_assigner", params);
|
|
72
|
+
if (!res.success) {
|
|
73
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
// ── create_idle_income ───────────────────────────────────────────
|
|
80
|
+
server.tool("create_idle_income", "Generate a host-authoritative passive income component: every tickSeconds the host grants incomePerTick × Multiplier, auto-wiring the first sibling component with an AddMoney(int) method (a create_economy_wallet scaffold plugs in with zero code) or an overridable Grant() seam; TotalEarned is [Sync(FromHost)] and static OnIncomeTick fires per grant. The idle-game kit: wallet (create_economy_wallet) + this + create_offline_progress. Writes a .cs file and returns { created, path, className, nextSteps } — follow with trigger_hotload + compile_status", {
|
|
81
|
+
name: z
|
|
82
|
+
.string()
|
|
83
|
+
.optional()
|
|
84
|
+
.describe("Class/file name (default 'IdleIncome' -> Code/IdleIncome.cs). Errors if the file exists"),
|
|
85
|
+
directory: z
|
|
86
|
+
.string()
|
|
87
|
+
.optional()
|
|
88
|
+
.describe("Directory for the .cs file. Default 'Code'"),
|
|
89
|
+
incomePerTick: z
|
|
90
|
+
.number()
|
|
91
|
+
.optional()
|
|
92
|
+
.describe("Amount granted per tick. Default 1"),
|
|
93
|
+
tickSeconds: z
|
|
94
|
+
.number()
|
|
95
|
+
.optional()
|
|
96
|
+
.describe("Seconds between grants. Default 1"),
|
|
97
|
+
}, async (params) => {
|
|
98
|
+
const res = await bridge.send("create_idle_income", params);
|
|
99
|
+
if (!res.success) {
|
|
100
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
|
|
104
|
+
};
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=workflow.js.map
|