sbox-mcp-server 1.12.0 → 1.14.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 CHANGED
@@ -45,6 +45,7 @@ import { registerGameplayTools } from "./tools/gameplay.js";
45
45
  import { registerNpcTools } from "./tools/npc.js";
46
46
  import { registerInspectionTools } from "./tools/inspection.js";
47
47
  import { registerInputTools } from "./tools/inputs.js";
48
+ import { registerDebugVizTools } from "./tools/debugviz.js";
48
49
  // ── CLI flags ──────────────────────────────────────────────────────
49
50
  const args = process.argv.slice(2);
50
51
  /** Read the package version from package.json, or return "unknown" on failure. */
@@ -193,6 +194,7 @@ registerGameplayTools(server, bridge);
193
194
  registerNpcTools(server, bridge);
194
195
  registerInspectionTools(server, bridge);
195
196
  registerInputTools(server, bridge);
197
+ registerDebugVizTools(server, bridge);
196
198
  /** Start the MCP server on stdio and attempt initial Bridge connection. */
197
199
  async function main() {
198
200
  const transport = new StdioServerTransport();
@@ -24,9 +24,8 @@ export function registerComponentTools(server, bridge) {
24
24
  };
25
25
  });
26
26
  // ── get_all_properties ───────────────────────────────────────────
27
- server.tool("get_all_properties", "Dump all public properties of a component as JSON names, types, and current values", {
27
+ server.tool("get_all_properties", "Dump all public properties of every component on a GameObject as JSON -- names, types, and current values", {
28
28
  id: z.string().describe("GUID of the GameObject"),
29
- component: z.string().describe("Component type name"),
30
29
  }, async (params) => {
31
30
  const res = await bridge.send("get_all_properties", params);
32
31
  if (!res.success) {
@@ -0,0 +1,12 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { BridgeClient } from "../transport/bridge-client.js";
3
+ /**
4
+ * Debug-visualization & playtest meta-tools ported from the Unity bridge's
5
+ * engine/workflow layer (see docs/plans/2026-06-17-unity-carryover-meta-tools.md).
6
+ *
7
+ * Currently: set_time_scale + get_profiler_stats (Unity carry-over wave, 2 of 4).
8
+ * Planned next: debug_draw_* + debug_clear (Gizmo.Draw edit-mode + DebugOverlay
9
+ * play-mode) and run_tests (dotnet test spike).
10
+ */
11
+ export declare function registerDebugVizTools(server: McpServer, bridge: BridgeClient): void;
12
+ //# sourceMappingURL=debugviz.d.ts.map
@@ -0,0 +1,45 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Debug-visualization & playtest meta-tools ported from the Unity bridge's
4
+ * engine/workflow layer (see docs/plans/2026-06-17-unity-carryover-meta-tools.md).
5
+ *
6
+ * Currently: set_time_scale + get_profiler_stats (Unity carry-over wave, 2 of 4).
7
+ * Planned next: debug_draw_* + debug_clear (Gizmo.Draw edit-mode + DebugOverlay
8
+ * play-mode) and run_tests (dotnet test spike).
9
+ */
10
+ export function registerDebugVizTools(server, bridge) {
11
+ // ── set_time_scale ───────────────────────────────────────────────
12
+ server.tool("set_time_scale", "Set the running game's time scale DURING PLAY MODE (ported from Unity's playtest_set_time_scale). 0 = pause, 1 = normal, 0.1 = slow-mo to watch a fast interaction frame-by-frame, 2+ = fast-forward idle/economy ticks. Requires start_play first — the edit scene doesn't tick, so this no-ops outside play mode and returns an error. Sets Game.ActiveScene.TimeScale (clamped to 0–100). Returns the applied and previous values.", {
13
+ scale: z
14
+ .number()
15
+ .min(0)
16
+ .describe("Time multiplier: 0 = pause, 1 = normal speed, 0.1 = slow-mo, 2 = double speed. Clamped to 0–100"),
17
+ }, async (params) => {
18
+ const res = await bridge.send("set_time_scale", params);
19
+ if (!res.success) {
20
+ return { content: [{ type: "text", text: `Error: ${res.error}` }] };
21
+ }
22
+ return {
23
+ content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
24
+ };
25
+ });
26
+ // ── get_profiler_stats ───────────────────────────────────────────
27
+ server.tool("get_profiler_stats", "Read live engine performance counters (ported from Unity's get_profiler_stats): FPS, frame ms, GPU ms, bytes allocated, process memory, exception count, and per-category timings (update/physics/ui/render/network/gcPause) averaged over `frames`. Reads Sandbox.Diagnostics.PerformanceStats. Most meaningful during play (call start_play first) but populated in the editor too. Read-only.", {
28
+ frames: z
29
+ .number()
30
+ .int()
31
+ .min(1)
32
+ .max(1000)
33
+ .optional()
34
+ .describe("Averaging window (frames) for the per-category timings. Default 60"),
35
+ }, async (params) => {
36
+ const res = await bridge.send("get_profiler_stats", params);
37
+ if (!res.success) {
38
+ return { content: [{ type: "text", text: `Error: ${res.error}` }] };
39
+ }
40
+ return {
41
+ content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
42
+ };
43
+ });
44
+ }
45
+ //# sourceMappingURL=debugviz.js.map
@@ -378,5 +378,59 @@ export function registerGameplayTools(server, bridge) {
378
378
  }
379
379
  return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
380
380
  });
381
+ // -- create_leaderboard_panel ------------------------------------------
382
+ server.tool("create_leaderboard_panel", "Generate a Razor PanelComponent that fetches and displays a Sandbox.Services leaderboard derived from a stat name. Produces TWO files: {name}.razor and {name}.razor.scss. The panel auto-refreshes every 30 s, shows rank/displayName/value rows, handles loading state, and includes a BuildHash() override (razor-lint clean). Must be hosted under a ScreenPanel or WorldPanel. Stats must be configured for the project ident on sbox.game. Uses Leaderboards.Get(statName) + board.Refresh() -- the exact API from ServicesQueryHandler.", {
383
+ name: z.string().optional().describe("Class name for the panel component. Defaults to 'LeaderboardPanel'"),
384
+ directory: z.string().optional().describe("Subdirectory for the generated files. Defaults to 'Code/UI'"),
385
+ statName: z.string().optional().describe("Sandbox.Services stat name the leaderboard is derived from. Defaults to 'score'"),
386
+ title: z.string().optional().describe("Display title shown at the top of the panel. Defaults to 'Leaderboard'"),
387
+ maxRows: z.number().int().optional().describe("Maximum leaderboard rows to fetch and display. Defaults to 10"),
388
+ }, async (params) => {
389
+ const res = await bridge.send("create_leaderboard_panel", params);
390
+ if (!res.success) {
391
+ return { content: [{ type: "text", text: `Error: ${res.error}` }] };
392
+ }
393
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
394
+ });
395
+ // -- create_inventory --------------------------------------------------
396
+ server.tool("create_inventory", "Generate a slot-based inventory component using parallel List<string> ItemIds / List<int> Counts (serialization-safe, inspector-editable). Includes TryAdd (stack-first, partial-add rejected), TryRemove, CountOf, Move (swap or merge same-id slots), and Clear. Static OnChanged event fires after every successful mutation. Host-authoritative usage note: mutate on the host in multiplayer, replicate via your own [Sync]/RPC. Pairs with create_pickup.", {
397
+ name: z.string().optional().describe("Class name. Defaults to 'Inventory'"),
398
+ directory: z.string().optional().describe("Subdirectory for the .cs file. Defaults to 'Code'"),
399
+ capacity: z.number().int().optional().describe("Total slot count. Defaults to 24"),
400
+ maxStack: z.number().int().optional().describe("Maximum items per slot (stack cap). Defaults to 99"),
401
+ targetId: z.string().optional().describe("GUID of an existing GameObject to attach to (hotload first)"),
402
+ }, async (params) => {
403
+ const res = await bridge.send("create_inventory", params);
404
+ if (!res.success) {
405
+ return { content: [{ type: "text", text: `Error: ${res.error}` }] };
406
+ }
407
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
408
+ });
409
+ // -- create_stat_modifier_system ---------------------------------------
410
+ server.tool("create_stat_modifier_system", "Generate an enum-keyed stat modifier system with three modifier layers: SET (highest-priority-wins hard override), ADD (summed bonuses), MULT (multiplied factors applied last). Modifier storage uses parallel private Lists of primitive types (serialization-safe). RemoveModifiersFrom(source) cleans up all mods from a buff/debuff source by reference. Static OnStatChanged(stat, value) event fires after every add/remove. Mined from RPG/buff/debuff patterns across shipped s&box games.", {
411
+ name: z.string().optional().describe("Class name prefix -- generates {name}Stat enum + {name} Component. Defaults to 'StatSystem'"),
412
+ directory: z.string().optional().describe("Subdirectory for the .cs file. Defaults to 'Code'"),
413
+ stats: z.union([z.array(z.string()), z.string()]).optional().describe("Stat names as a JSON array or comma-separated string. Defaults to 'Health,Speed,Damage'"),
414
+ targetId: z.string().optional().describe("GUID of an existing GameObject to attach to (hotload first)"),
415
+ }, async (params) => {
416
+ const res = await bridge.send("create_stat_modifier_system", params);
417
+ if (!res.success) {
418
+ return { content: [{ type: "text", text: `Error: ${res.error}` }] };
419
+ }
420
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
421
+ });
422
+ // -- create_placement_mode ---------------------------------------------
423
+ server.tool("create_placement_mode", "Generate a ghost-preview + commit placement component (single class). StartPlacing() clones GhostPrefab as a NetworkMode.Never preview with colliders disabled and ModelRenderers tinted semi-transparent. Each frame while placing: ray from Scene.Camera.GetMouseRay(), IgnoreGameObjectHierarchy(ghost), snap hit position to GridSize (0 = freeform), move ghost. On Input.Pressed('attack1') TryPlace() re-validates distance and commits a real clone. StopPlacing() destroys the ghost. Static OnPlaced(GameObject, Vector3) event. Includes a multiplayer RPC note. API grounded in building-placement cookbook (enifun.shop_manager pattern).", {
424
+ name: z.string().optional().describe("Class name. Defaults to 'PlacementMode'"),
425
+ directory: z.string().optional().describe("Subdirectory for the .cs file. Defaults to 'Code'"),
426
+ gridSize: z.number().optional().describe("Snap grid size in world units (0 = freeform placement). Defaults to 0"),
427
+ targetId: z.string().optional().describe("GUID of an existing GameObject to attach to (hotload first)"),
428
+ }, async (params) => {
429
+ const res = await bridge.send("create_placement_mode", params);
430
+ if (!res.success) {
431
+ return { content: [{ type: "text", text: `Error: ${res.error}` }] };
432
+ }
433
+ return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
434
+ });
381
435
  }
382
436
  //# sourceMappingURL=gameplay.js.map
package/package.json CHANGED
@@ -1,55 +1,55 @@
1
- {
2
- "name": "sbox-mcp-server",
3
- "version": "1.12.0",
4
- "description": "MCP Server for s&box game engine — enables Claude to build games through conversation",
5
- "type": "module",
6
- "main": "dist/index.js",
7
- "bin": {
8
- "sbox-mcp-server": "dist/index.js",
9
- "sbox-mcp": "dist/index.js"
10
- },
11
- "files": [
12
- "dist/**/*.js",
13
- "dist/**/*.d.ts",
14
- "README.md",
15
- "LICENSE"
16
- ],
17
- "scripts": {
18
- "build": "tsc",
19
- "test": "npm run build && node --test",
20
- "start": "node dist/index.js",
21
- "dev": "tsc --watch",
22
- "prepublishOnly": "npm run build"
23
- },
24
- "keywords": [
25
- "mcp",
26
- "sbox",
27
- "s&box",
28
- "game-engine",
29
- "claude",
30
- "ai",
31
- "source2",
32
- "model-context-protocol"
33
- ],
34
- "repository": {
35
- "type": "git",
36
- "url": "git+https://github.com/LouSputthole/Sbox-Claude.git"
37
- },
38
- "author": "sboxskins.gg (https://sboxskins.gg)",
39
- "homepage": "https://sboxskins.gg",
40
- "bugs": {
41
- "url": "https://github.com/LouSputthole/Sbox-Claude/issues"
42
- },
43
- "dependencies": {
44
- "@modelcontextprotocol/sdk": "^1.12.1",
45
- "zod": "^3.24.0"
46
- },
47
- "devDependencies": {
48
- "@types/node": "^22.0.0",
49
- "typescript": "^5.7.0"
50
- },
51
- "engines": {
52
- "node": ">=18.0.0"
53
- },
54
- "license": "AGPL-3.0-or-later"
55
- }
1
+ {
2
+ "name": "sbox-mcp-server",
3
+ "version": "1.14.0",
4
+ "description": "MCP Server for s&box game engine — enables Claude to build games through conversation",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "sbox-mcp-server": "dist/index.js",
9
+ "sbox-mcp": "dist/index.js"
10
+ },
11
+ "files": [
12
+ "dist/**/*.js",
13
+ "dist/**/*.d.ts",
14
+ "README.md",
15
+ "LICENSE"
16
+ ],
17
+ "scripts": {
18
+ "build": "tsc",
19
+ "test": "npm run build && node --test",
20
+ "start": "node dist/index.js",
21
+ "dev": "tsc --watch",
22
+ "prepublishOnly": "npm run build"
23
+ },
24
+ "keywords": [
25
+ "mcp",
26
+ "sbox",
27
+ "s&box",
28
+ "game-engine",
29
+ "claude",
30
+ "ai",
31
+ "source2",
32
+ "model-context-protocol"
33
+ ],
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "git+https://github.com/LouSputthole/Sbox-Claude.git"
37
+ },
38
+ "author": "sboxskins.gg (https://sboxskins.gg)",
39
+ "homepage": "https://sboxskins.gg",
40
+ "bugs": {
41
+ "url": "https://github.com/LouSputthole/Sbox-Claude/issues"
42
+ },
43
+ "dependencies": {
44
+ "@modelcontextprotocol/sdk": "^1.12.1",
45
+ "zod": "^3.24.0"
46
+ },
47
+ "devDependencies": {
48
+ "@types/node": "^22.0.0",
49
+ "typescript": "^5.7.0"
50
+ },
51
+ "engines": {
52
+ "node": ">=18.0.0"
53
+ },
54
+ "license": "AGPL-3.0-or-later"
55
+ }