sbox-mcp-server 1.19.0 → 2.0.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.
Files changed (48) hide show
  1. package/dist/index.js +119 -82
  2. package/dist/tools/assets.js +5 -5
  3. package/dist/tools/audio.js +12 -19
  4. package/dist/tools/audit.d.ts +8 -0
  5. package/dist/tools/audit.js +65 -0
  6. package/dist/tools/batch.d.ts +8 -0
  7. package/dist/tools/batch.js +77 -0
  8. package/dist/tools/characters.js +6 -6
  9. package/dist/tools/cinematics.d.ts +19 -0
  10. package/dist/tools/cinematics.js +73 -0
  11. package/dist/tools/components.js +2 -2
  12. package/dist/tools/debugdraw.js +4 -4
  13. package/dist/tools/diagnostics.js +7 -5
  14. package/dist/tools/discovery.js +6 -6
  15. package/dist/tools/gameobjects.js +7 -7
  16. package/dist/tools/gameplay.js +4 -4
  17. package/dist/tools/inspection.js +6 -6
  18. package/dist/tools/interactionpack.d.ts +16 -0
  19. package/dist/tools/interactionpack.js +107 -0
  20. package/dist/tools/leveltools.js +5 -5
  21. package/dist/tools/looteconomy.d.ts +18 -0
  22. package/dist/tools/looteconomy.js +107 -0
  23. package/dist/tools/materials.js +4 -4
  24. package/dist/tools/moviemaker.d.ts +17 -0
  25. package/dist/tools/moviemaker.js +98 -0
  26. package/dist/tools/netprimitives.d.ts +18 -0
  27. package/dist/tools/netprimitives.js +110 -0
  28. package/dist/tools/networking.js +22 -22
  29. package/dist/tools/physics.js +4 -4
  30. package/dist/tools/playmode.js +5 -5
  31. package/dist/tools/playtest.js +2 -1
  32. package/dist/tools/prefabs.js +7 -3
  33. package/dist/tools/project.js +2 -2
  34. package/dist/tools/publishing.js +3 -3
  35. package/dist/tools/scenes.js +2 -2
  36. package/dist/tools/scripts.js +3 -3
  37. package/dist/tools/status.js +1 -1
  38. package/dist/tools/templates.js +7 -7
  39. package/dist/tools/ui.js +5 -5
  40. package/dist/tools/uifeedback.d.ts +16 -0
  41. package/dist/tools/uifeedback.js +83 -0
  42. package/dist/tools/vehicles.d.ts +8 -0
  43. package/dist/tools/vehicles.js +98 -0
  44. package/dist/tools/visuals.js +8 -8
  45. package/dist/tools/workflow.d.ts +8 -0
  46. package/dist/tools/workflow.js +107 -0
  47. package/dist/tools/world.js +61 -58
  48. package/package.json +55 -55
@@ -8,7 +8,7 @@ import { z } from "zod";
8
8
  */
9
9
  export function registerPlayModeTools(server, bridge) {
10
10
  // ── start_play ───────────────────────────────────────────────────
11
- server.tool("start_play", "Enter play mode — starts running the game in the editor. Scripts execute, physics simulate, everything goes live", {}, async () => {
11
+ server.tool("start_play", "Enter play mode — starts running the game in the editor. Scripts execute, physics simulate, everything goes live. Returns { started, method } (method 'EditorScene.Play', or 'SetPlaying (fallback)' with editorErrorSkipped when the safe path failed). While playing, scene-mutating tools refuse — use get/set_runtime_property, capture_view, and playtest, then stop_play to edit again.", {}, async () => {
12
12
  const res = await bridge.send("start_play");
13
13
  if (!res.success) {
14
14
  return { content: [{ type: "text", text: `Error: ${res.error}` }] };
@@ -41,7 +41,7 @@ export function registerPlayModeTools(server, bridge) {
41
41
  };
42
42
  });
43
43
  // ── take_screenshot ──────────────────────────────────────────────
44
- server.tool("take_screenshot", "Capture the current editor viewport as a PNG screenshot", {
44
+ server.tool("take_screenshot", "Capture a 1920x1080 PNG from the scene's Main Camera (ONE fixed angle — the camera may not face what you changed; use screenshot_from to aim at a target object/point). Returns { taken, note, path }, but the engine saves to its default screenshots folder regardless of the path param (<sbox install>/screenshots/sbox.<timestamp>.png) — list the newest file there and READ the image to verify.", {
45
45
  path: z
46
46
  .string()
47
47
  .optional()
@@ -56,7 +56,7 @@ export function registerPlayModeTools(server, bridge) {
56
56
  };
57
57
  });
58
58
  // ── undo ─────────────────────────────────────────────────────────
59
- server.tool("undo", "Undo the last editor action. Safety net for when a change goes wrong", {}, async () => {
59
+ server.tool("undo", "Undo the last editor action via the editor's UndoSystem. Safety net for when a change goes wrong. Returns { undone: true } — it does not report WHAT was undone, so re-check state with get_scene_hierarchy or get_all_properties. Reverse it with redo. Note: file writes (write_file/create_script) are not editor undo steps.", {}, async () => {
60
60
  const res = await bridge.send("undo");
61
61
  if (!res.success) {
62
62
  return { content: [{ type: "text", text: `Error: ${res.error}` }] };
@@ -66,7 +66,7 @@ export function registerPlayModeTools(server, bridge) {
66
66
  };
67
67
  });
68
68
  // ── redo ─────────────────────────────────────────────────────────
69
- server.tool("redo", "Redo the last undone editor action", {}, async () => {
69
+ server.tool("redo", "Re-apply the editor action most recently reverted by undo (editor UndoSystem redo — the counterpart to the undo tool). Returns { redone: true } without details of what was redone, so verify the resulting state with get_scene_hierarchy or get_all_properties.", {}, async () => {
70
70
  const res = await bridge.send("redo");
71
71
  if (!res.success) {
72
72
  return { content: [{ type: "text", text: `Error: ${res.error}` }] };
@@ -107,7 +107,7 @@ export function registerPlayModeTools(server, bridge) {
107
107
  };
108
108
  });
109
109
  // ── set_runtime_property ─────────────────────────────────────────
110
- server.tool("set_runtime_property", "Set a component property value during play mode — tweak values live while the game runs", {
110
+ server.tool("set_runtime_property", "Set a component property value during play mode — tweak values live while the game runs. Errors if the game is not playing (start_play first). Returns { set, id, component, property, value } like set_property; read it back with get_runtime_property. Runtime changes are DISCARDED when play mode stops — use set_property in edit mode to persist.", {
111
111
  id: z.string().describe("GUID of the GameObject"),
112
112
  component: z.string().describe("Component type name"),
113
113
  property: z.string().describe("Property name to set"),
@@ -34,6 +34,7 @@ export function registerPlaytestTools(server, bridge) {
34
34
  id: z.string().optional().describe("GUID of the player/controller GameObject. Omit to auto-resolve the first PlayerController."),
35
35
  component: z.string().optional().describe("Controller component type to target (e.g. 'PlayerController'). Omit to auto-detect."),
36
36
  }, async (p) => reply(await bridge.send("playtest", p)));
37
- server.tool("playtest_status", "Poll the running/finished playtest. While running: { active:true, step, totalSteps, passed, failed }. When done: { finished:true, verdict:'PASS'|'FAIL', passed, failed, transcript:[...] } — the full per-step pass/fail record.", {}, async () => reply(await bridge.send("playtest_status", {})));
37
+ server.tool("playtest_status", "Poll the playtest started by the playtest tool. While running: { active:true, step, totalSteps, passed, failed }. When done: { finished:true, reason, verdict:'PASS'|'FAIL', passed, failed, stepsRun, totalSteps, controller, controllerResolved, transcript:[...] } — the full per-step pass/fail record, including any capture PNG paths; the finished summary stays readable until the next playtest starts. If none has run yet: { active:false, finished:false }. Stop a stuck/wrong run early with playtest_abort.", {}, async () => reply(await bridge.send("playtest_status", {})));
38
+ server.tool("playtest_abort", "Stop the RUNNING playtest immediately: releases held input actions, restores UseInputControls, and finalizes the partial transcript (reason 'aborted'). Returns { aborted, stepsRun, passed, failed } — or aborted:false if no job is running. The partial transcript stays readable via playtest_status. Use when a step list is clearly stuck or targeting the wrong object", {}, async () => reply(await bridge.send("playtest_abort", {})));
38
39
  }
39
40
  //# sourceMappingURL=playtest.js.map
@@ -5,7 +5,7 @@ import { z } from "zod";
5
5
  */
6
6
  export function registerPrefabTools(server, bridge) {
7
7
  // ── create_prefab ─────────────────────────────────────────────────
8
- server.tool("create_prefab", "Save an existing GameObject as a reusable .prefab file. The prefab can be instantiated later", {
8
+ server.tool("create_prefab", "Save an existing GameObject as a real .prefab file — FULL engine serialization: every component with its property values, and all children, in the same JSON format the editor writes. Returns { created, path, sourceId, components, children } — pass path to instantiate_prefab to spawn copies or get_prefab_info to inspect. Errors if the source GameObject is missing; overwrites an existing file at path.", {
9
9
  id: z.string().describe("GUID of the GameObject to save as prefab"),
10
10
  path: z
11
11
  .string()
@@ -20,10 +20,14 @@ export function registerPrefabTools(server, bridge) {
20
20
  };
21
21
  });
22
22
  // ── instantiate_prefab ────────────────────────────────────────────
23
- server.tool("instantiate_prefab", "Spawn a prefab instance into the active scene at an optional position and rotation", {
23
+ server.tool("instantiate_prefab", "Spawn a FULL prefab instance into the active scene components and children recreated. Uses the engine's GameObject.Clone for registered prefabs, with a guid-remapped deserialize fallback for freshly-written files (repeat instantiations never collide). Returns { instantiated, prefab, method, gameObject, components, childCount } — gameObject.id is the new GUID for set_transform/set_property follow-ups. Optional name/position/rotation override the spawned root.", {
24
24
  path: z
25
25
  .string()
26
26
  .describe("Path to the .prefab file (e.g. 'prefabs/enemies/grunt.prefab')"),
27
+ name: z
28
+ .string()
29
+ .optional()
30
+ .describe("Rename the spawned root (defaults to the prefab's root name)"),
27
31
  position: z
28
32
  .union([
29
33
  z.object({
@@ -80,7 +84,7 @@ export function registerPrefabTools(server, bridge) {
80
84
  };
81
85
  });
82
86
  // ── get_prefab_info ───────────────────────────────────────────────
83
- server.tool("get_prefab_info", "Get detailed information about a prefab file including its JSON contents and metadata", {
87
+ server.tool("get_prefab_info", "Inspect a prefab file as a structured summary: { path, name, size, modified, totalObjects, maxDepth, referencedPrefabs, tree } — tree is the object hierarchy with per-node component type lists (children capped at 8 per node with a truncation count). referencedPrefabs lists other .prefab files this one links to. Use before instantiate_prefab; find prefabs with list_prefabs; raw JSON via read_file if needed.", {
84
88
  path: z
85
89
  .string()
86
90
  .describe("Path to the .prefab file (e.g. 'prefabs/enemies/grunt.prefab')"),
@@ -21,7 +21,7 @@ export function registerProjectTools(server, bridge) {
21
21
  };
22
22
  });
23
23
  // ── list_project_files ───────────────────────────────────────────
24
- server.tool("list_project_files", "Browse the project file tree. Optionally filter by directory path and/or file extension (e.g. '.cs', '.scene')", {
24
+ server.tool("list_project_files", "Browse the project file tree. Optionally filter by directory path and/or file extension (e.g. '.cs', '.scene'). Returns { path, count, files } as project-root-relative paths — CAPPED AT 500 files (count reflects the truncated list, with no marker that more exist), so on large projects narrow with path/extension or use find_in_project. Recursive by default.", {
25
25
  path: z
26
26
  .string()
27
27
  .optional()
@@ -67,7 +67,7 @@ export function registerProjectTools(server, bridge) {
67
67
  };
68
68
  });
69
69
  // ── write_file ───────────────────────────────────────────────────
70
- server.tool("write_file", "Write or overwrite a file in the s&box project. Creates parent directories as needed", {
70
+ server.tool("write_file", "Write or overwrite a file in the s&box project (SILENTLY replaces existing content — read_file first if you need to preserve it). Creates parent directories as needed; paths are confined to the project root (traversal outside it is denied). Returns a confirmation with the path — for C# follow with trigger_hotload so it compiles; for assets (.vmat etc.) follow with recompile_asset.", {
71
71
  path: z
72
72
  .string()
73
73
  .describe("Relative path for the file (e.g. 'code/Components/Health.cs')"),
@@ -22,7 +22,7 @@ export function registerPublishingTools(server, bridge) {
22
22
  };
23
23
  });
24
24
  // ── set_project_config ───────────────────────────────────────────
25
- server.tool("set_project_config", "Update project configuration fields for publishing: title, description, version, type, package ident, summary, visibility. Only provided fields are changed", {
25
+ server.tool("set_project_config", "Update project configuration fields for publishing: title, description, version, type, package ident, summary, visibility. Only provided fields are changed — edits string values in the .sbproj file in place. Returns { updated, path } (the .sbproj path); read the result back with get_project_config to confirm what actually changed.", {
26
26
  title: z.string().optional().describe("Project display title"),
27
27
  description: z
28
28
  .string()
@@ -58,7 +58,7 @@ export function registerPublishingTools(server, bridge) {
58
58
  };
59
59
  });
60
60
  // ── validate_project ─────────────────────────────────────────────
61
- server.tool("validate_project", "Validate that the project is ready for publishing. Checks: compile errors, metadata completeness, scenes, scripts, thumbnail, and project type", {}, async (params) => {
61
+ server.tool("validate_project", "Validate that the project is ready for publishing. Runs four checks: .sbproj exists, at least one scene, project Ident set, project Title set. Returns { valid, issueCount, issues, checks } — issues are human-readable problems and each checks entry has { check, pass, detail }; fix metadata gaps with set_project_config (it does NOT check compile errors — use get_compile_errors for that).", {}, async (params) => {
62
62
  const res = await bridge.send("validate_project", params);
63
63
  if (!res.success) {
64
64
  return { content: [{ type: "text", text: `Error: ${res.error}` }] };
@@ -93,7 +93,7 @@ export function registerPublishingTools(server, bridge) {
93
93
  };
94
94
  });
95
95
  // ── get_package_details ──────────────────────────────────────────
96
- server.tool("get_package_details", "Fetch detailed package information from the s&box asset library (asset.party) including title, author, version, downloads, ratings, and dependencies", {
96
+ server.tool("get_package_details", "Fetch package information from the s&box package backend (Package.FetchAsync) by ident. Returns { fullIdent, title, summary, description, org } — no download/rating/dependency data is included. Use it to confirm a package exists and what it is before install_asset.", {
97
97
  ident: z
98
98
  .string()
99
99
  .describe("Package identifier (e.g. 'facepunch.flatgrass', 'myorg.mygame')"),
@@ -33,7 +33,7 @@ export function registerSceneTools(server, bridge) {
33
33
  };
34
34
  });
35
35
  // ── save_scene ───────────────────────────────────────────────────
36
- server.tool("save_scene", "Save the currently open scene in the s&box editor", {
36
+ server.tool("save_scene", "Save the currently open scene in the s&box editor (EditorScene.SaveSession) to its existing .scene file — a provided path does NOT redirect the save. Returns a plain 'Scene saved' confirmation. Call after a batch of scene edits so work survives an editor restart.", {
37
37
  path: z
38
38
  .string()
39
39
  .optional()
@@ -48,7 +48,7 @@ export function registerSceneTools(server, bridge) {
48
48
  };
49
49
  });
50
50
  // ── create_scene ─────────────────────────────────────────────────
51
- server.tool("create_scene", "Create a new empty scene file. Optionally include basic objects like a camera, directional light, and ground plane", {
51
+ server.tool("create_scene", "Create a new EMPTY .scene file under the project's Assets folder and register it with the AssetSystem so load_scene works immediately. Errors if the scene already exists. NOTE: includeDefaults is not yet honored — the scene is always created empty, so after load_scene add a camera/light/ground yourself (create_gameobject, add_light). Returns a confirmation with the path — pass it to load_scene next.", {
52
52
  path: z
53
53
  .string()
54
54
  .describe("Relative path for the new scene (e.g. 'scenes/level_01.scene')"),
@@ -6,7 +6,7 @@ import { z } from "zod";
6
6
  */
7
7
  export function registerScriptTools(server, bridge) {
8
8
  // ── create_script ────────────────────────────────────────────────
9
- server.tool("create_script", "Create a new C# component script in the project. Generates proper s&box component boilerplate with the specified class name, namespace, and optional properties", {
9
+ server.tool("create_script", "Create a new C# component script in the project a minimal s&box Component class (name is sanitized to a valid identifier), or your exact code when content is provided. Errors if the file already exists. Returns { path, created, className } — the new type is NOT live until a recompile, so call trigger_hotload, then attach it with add_component_with_properties (component=className).", {
10
10
  name: z
11
11
  .string()
12
12
  .describe("Class name for the component (e.g. 'PlayerController'). Will also be the filename"),
@@ -52,7 +52,7 @@ export function registerScriptTools(server, bridge) {
52
52
  };
53
53
  });
54
54
  // ── edit_script ──────────────────────────────────────────────────
55
- server.tool("edit_script", "Edit an existing C# script. Supports find/replace, inserting code at a line number, or appending code to the class body", {
55
+ server.tool("edit_script", "Edit an existing C# script in place via exact-text find/replace or a full-content overwrite. Errors if the file or the find text isn't found (find/replace replaces ALL occurrences). Returns { path, edited, operation } where operation is 'find_replace' or 'overwrite' follow with trigger_hotload so the change compiles, then get_compile_errors if in doubt.", {
56
56
  path: z
57
57
  .string()
58
58
  .describe("Relative path to the script file (e.g. 'code/PlayerController.cs')"),
@@ -98,7 +98,7 @@ export function registerScriptTools(server, bridge) {
98
98
  };
99
99
  });
100
100
  // ── delete_script ────────────────────────────────────────────────
101
- server.tool("delete_script", "Delete a C# script from the project", {
101
+ server.tool("delete_script", "Permanently delete a file from the project by its project-relative path (built for C# scripts, but removes any file; no recycle bin, and editor undo cannot restore it). Errors if the file doesn't exist. Returns a confirmation with the path — follow with trigger_hotload so the removed class actually leaves the compiled assembly.", {
102
102
  path: z
103
103
  .string()
104
104
  .describe("Relative path to the script file to delete"),
@@ -19,7 +19,7 @@ catch {
19
19
  */
20
20
  export function registerStatusTools(server, bridge) {
21
21
  // ── get_bridge_status ────────────────────────────────────────────
22
- server.tool("get_bridge_status", "Check the connection status to the s&box Bridgewhether it's connected, latency, host/port, and editor info. Useful for debugging", {}, async () => {
22
+ server.tool("get_bridge_status", "Check the s&box Bridge connection call this FIRST in a session. Returns a human summary plus JSON: connected, roundTripOk (heartbeat can be fresh while the editor's request loop is stalled trust roundTripOk), bridgeVersion vs mcpServerVersion + versionsAligned (a mismatch means restart Claude Code / republish the addon), handlerCount, heartbeatAgeMs, latencyMs, and ipcDir (transport is file IPC; host/port are legacy fields).", {}, async () => {
23
23
  const connected = bridge.isConnected();
24
24
  const ipcDir = bridge.getIpcDir();
25
25
  const heartbeatAgeMs = bridge.getHeartbeatAgeMs();
@@ -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 manager script with configurable game loop features: score tracking, round timer, player spawning, and game state machine", {
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. Defaults to true"),
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. Defaults to false"),
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. Defaults to false"),
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 zone script that detects when GameObjects enter/exit a collider volume. Supports teleport, damage, spawn, and log actions", {
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: 'log' (print message), 'teleport' (move to destination), 'damage' (apply damage), 'spawn' (create prefab). Defaults to 'log'"),
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. Defaults to 'player'"),
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) with optional SCSS stylesheet. Generates boilerplate for HUD, menu, or basic panel types", {
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 UI'"),
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 UI'"),
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() }),
@@ -0,0 +1,16 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { BridgeClient } from "../transport/bridge-client.js";
3
+ /**
4
+ * UI / Feedback pack — three feedback scaffolds (v1.20.0, Track C):
5
+ *
6
+ * - create_worldpanel_ui diegetic clickable WorldPanel Razor UI (+ scss)
7
+ * - create_proxy_nametag billboarded owner-name tag above a networked player
8
+ * - create_combo_meter combo counter + decay + multiplier (.cs) + Razor HUD
9
+ *
10
+ * All are file/scene-mutating (refused during play mode by the bridge dispatch).
11
+ * The Razor output is razor_lint-safe by construction (BuildHash override, no
12
+ * switch-expressions or non-ASCII in @code, class-selector SCSS roots), modeled
13
+ * on create_leaderboard_panel.
14
+ */
15
+ export declare function registerUiFeedbackTools(server: McpServer, bridge: BridgeClient): void;
16
+ //# sourceMappingURL=uifeedback.d.ts.map
@@ -0,0 +1,83 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * UI / Feedback pack — three feedback scaffolds (v1.20.0, Track C):
4
+ *
5
+ * - create_worldpanel_ui diegetic clickable WorldPanel Razor UI (+ scss)
6
+ * - create_proxy_nametag billboarded owner-name tag above a networked player
7
+ * - create_combo_meter combo counter + decay + multiplier (.cs) + Razor HUD
8
+ *
9
+ * All are file/scene-mutating (refused during play mode by the bridge dispatch).
10
+ * The Razor output is razor_lint-safe by construction (BuildHash override, no
11
+ * switch-expressions or non-ASCII in @code, class-selector SCSS roots), modeled
12
+ * on create_leaderboard_panel.
13
+ */
14
+ export function registerUiFeedbackTools(server, bridge) {
15
+ // ── create_worldpanel_ui ──────────────────────────────────────────
16
+ server.tool("create_worldpanel_ui", "Generate a diegetic, clickable world-space UI: a Razor PanelComponent (+ .razor.scss) meant to sit on a GameObject that ALSO carries a Sandbox.WorldPanel — the WorldPanel is the world-space render surface (PanelSize / RenderScale / InteractionRange), this component is the actual UI it renders (PanelComponent has no world-panel mode of its own). Ships two example buttons wired to @onclick that raise a static event OnButtonPressed(string id), so game code reacts WITHOUT editing the panel (subscribe: <Name>.OnButtonPressed += id => ...). razor_lint-safe by construction (BuildHash override, no switch-expressions / non-ASCII in @code, class-selector SCSS root). SCENE PREREQUISITE FOR CLICKS: a WorldPanel's buttons only fire when a Sandbox.WorldInput exists in the scene (typically on the camera or the player) with its LeftMouseAction set to your click input action (e.g. \"attack1\"); on this SDK WorldInput drives itself from the camera + that action — there is no manual ray to feed, and WorldInput.Hovered (read-only) reflects the panel under the cursor. Without a WorldInput present, @onclick never fires. Setup: add_world_panel to a GameObject, add this component to the same object, add a WorldInput to the scene.", {
17
+ name: z
18
+ .string()
19
+ .optional()
20
+ .describe("Class name for the generated panel. Defaults to 'WorldPanelUi'"),
21
+ directory: z
22
+ .string()
23
+ .optional()
24
+ .describe("Subdirectory for the generated .razor + .razor.scss. Defaults to 'Code/UI'"),
25
+ title: z
26
+ .string()
27
+ .optional()
28
+ .describe("Heading text baked into the panel (editable per-instance via the Title [Property]). Defaults to 'Interact'"),
29
+ }, async (params) => {
30
+ const res = await bridge.send("create_worldpanel_ui", params);
31
+ if (!res.success) {
32
+ return { content: [{ type: "text", text: `Error: ${res.error}` }] };
33
+ }
34
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
35
+ });
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). 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
+ name: z
39
+ .string()
40
+ .optional()
41
+ .describe("Class name for the generated component. Defaults to 'ProxyNametag'"),
42
+ directory: z
43
+ .string()
44
+ .optional()
45
+ .describe("Subdirectory for the generated .cs file. Defaults to 'Code'"),
46
+ maxDistance: z
47
+ .number()
48
+ .optional()
49
+ .describe("Full alpha up close; fades to zero as the camera approaches this distance and is hidden past it, in world units. Defaults to 2000"),
50
+ heightOffset: z
51
+ .number()
52
+ .optional()
53
+ .describe("Height above the object's origin to float the tag, in world units (~72 clears a Citizen's head). Defaults to 72"),
54
+ }, async (params) => {
55
+ const res = await bridge.send("create_proxy_nametag", params);
56
+ if (!res.success) {
57
+ return { content: [{ type: "text", text: `Error: ${res.error}` }] };
58
+ }
59
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
60
+ });
61
+ // ── create_combo_meter ────────────────────────────────────────────
62
+ server.tool("create_combo_meter", "Generate a combo system: a sealed Component (the authoritative, headless state) PLUS a small Razor HUD (PanelComponent + scss). Three files: <Name>.cs + <Name>Hud.razor + <Name>Hud.razor.scss. The component exposes a static Bump() — call it on every hit and the Count rises; an idle window (ComboWindowSeconds, tracked with TimeSince) resets it; the Multiplier steps up through [Property] tier thresholds (Tier2Hits/Tier3Hits/Tier4Hits => 2x/3x/4x); and a static OnComboChanged(int count, float multiplier) event fires so any HUD/audio reacts without a reference (Bump() targets the active instance, so callers never need a handle — attach ONE to a persistent object). The HUD subscribes to OnComboChanged, shows \"<count> HITS x<mult>\", and pulses via a CSS animation on every change (razor_lint-safe: BuildHash folds count/mult/pulse, no switch-expressions / non-ASCII in @code, class-selector SCSS root; host it under a ScreenPanel via add_screen_panel). Pairs with create_health_system / create_floating_combat_text — call Bump() from the damage path and Spawn a popup that reflects the multiplier.", {
63
+ name: z
64
+ .string()
65
+ .optional()
66
+ .describe("Class name for the combo component; the HUD is generated as <Name>Hud. Defaults to 'ComboMeter'"),
67
+ directory: z
68
+ .string()
69
+ .optional()
70
+ .describe("Subdirectory for the generated .cs + .razor + .razor.scss. Defaults to 'Code'"),
71
+ comboWindowSeconds: z
72
+ .number()
73
+ .optional()
74
+ .describe("Idle seconds before the combo resets back to zero (clamped to >= 0.25). Defaults to 3"),
75
+ }, async (params) => {
76
+ const res = await bridge.send("create_combo_meter", params);
77
+ if (!res.success) {
78
+ return { content: [{ type: "text", text: `Error: ${res.error}` }] };
79
+ }
80
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
81
+ });
82
+ }
83
+ //# sourceMappingURL=uifeedback.js.map
@@ -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
@@ -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 on the same target updates it rather than duplicating.", {
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", "Spawn an additive particle effect (no texture asset needed): kind = fire (rising flame), embers (slow drifting glow), or sparks (a one-shot burst). Renders as tinted glowing dotsgreat for campfires, torches, and impacts. (smoke needs a soft sprite; not in v1.)", {
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", "Create an energy/laser beam (BeamEffect) from a position to a target point — additive, tintable. Good for lasers, tracers, magic beams.", {
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 on shiny surfaces.", {}, async (params) => {
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