sbox-mcp-server 1.3.2 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +131 -113
- package/dist/index.js +34 -1
- package/dist/tools/characters.d.ts +4 -0
- package/dist/tools/characters.js +209 -0
- package/dist/tools/diagnostics.d.ts +4 -0
- package/dist/tools/diagnostics.js +325 -0
- package/dist/tools/docs.d.ts +4 -0
- package/dist/tools/docs.js +334 -0
- package/dist/tools/leveltools.d.ts +4 -0
- package/dist/tools/leveltools.js +148 -0
- package/dist/tools/navigation.d.ts +4 -0
- package/dist/tools/navigation.js +51 -0
- package/dist/tools/networking.js +10 -8
- package/dist/tools/objecttools.d.ts +4 -0
- package/dist/tools/objecttools.js +106 -0
- package/dist/tools/physics.js +22 -0
- package/dist/tools/project.js +12 -2
- package/dist/tools/visuals.d.ts +4 -0
- package/dist/tools/visuals.js +289 -0
- package/dist/transport/bridge-client.js +27 -4
- package/package.json +1 -1
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Scene & level-building tools (Batch 21): snap-to-ground, align, distribute,
|
|
4
|
+
* grid-duplicate, and measure. Transform-level operations for arranging a
|
|
5
|
+
* scene — all verifiable via the editor (screenshot or hierarchy/state).
|
|
6
|
+
*/
|
|
7
|
+
const Vector3Schema = z
|
|
8
|
+
.object({ x: z.number(), y: z.number(), z: z.number() })
|
|
9
|
+
.describe("Vector {x,y,z}");
|
|
10
|
+
export function registerLevelTools(server, bridge) {
|
|
11
|
+
// ── snap_to_ground ─────────────────────────────────────────────────
|
|
12
|
+
server.tool("snap_to_ground", "Drop a GameObject straight down onto the surface below it (physics raycast). Works best on collider-less props (an object with its own collider may self-hit). Optional offset lifts it off the surface.", {
|
|
13
|
+
id: z.string().describe("GUID of the GameObject to snap"),
|
|
14
|
+
offset: z.number().optional().describe("Height above the surface to place it (default 0)"),
|
|
15
|
+
startHeight: z.number().optional().describe("How far above the object to start the trace (default 2000)"),
|
|
16
|
+
maxDistance: z.number().optional().describe("Max trace distance downward (default 20000)"),
|
|
17
|
+
}, async (params) => {
|
|
18
|
+
const res = await bridge.send("snap_to_ground", 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
|
+
// ── align_objects ──────────────────────────────────────────────────
|
|
27
|
+
server.tool("align_objects", "Align several GameObjects on one axis so they share a coordinate. mode = first (match the first object), min, max, or average.", {
|
|
28
|
+
ids: z.array(z.string()).describe("GUIDs of the GameObjects to align (>= 2)"),
|
|
29
|
+
axis: z.enum(["x", "y", "z"]).describe("Axis to align on"),
|
|
30
|
+
mode: z
|
|
31
|
+
.enum(["first", "min", "max", "average"])
|
|
32
|
+
.optional()
|
|
33
|
+
.describe("Target coordinate to align to (default first)"),
|
|
34
|
+
}, async (params) => {
|
|
35
|
+
const res = await bridge.send("align_objects", params);
|
|
36
|
+
if (!res.success) {
|
|
37
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
|
|
41
|
+
};
|
|
42
|
+
});
|
|
43
|
+
// ── distribute_objects ─────────────────────────────────────────────
|
|
44
|
+
server.tool("distribute_objects", "Evenly space GameObjects along an axis between the lowest and highest (keeps the two ends fixed, spreads the rest evenly).", {
|
|
45
|
+
ids: z.array(z.string()).describe("GUIDs of the GameObjects to distribute (>= 3)"),
|
|
46
|
+
axis: z.enum(["x", "y", "z"]).describe("Axis to distribute along"),
|
|
47
|
+
}, async (params) => {
|
|
48
|
+
const res = await bridge.send("distribute_objects", 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
|
+
// ── grid_duplicate ─────────────────────────────────────────────────
|
|
57
|
+
server.tool("grid_duplicate", "Clone a GameObject into an X/Y/Z grid with fixed spacing (the original stays in place). Each count is clamped to 50 and total clones to 500. Great for fences, crates, pillars, foliage rows.", {
|
|
58
|
+
id: z.string().describe("GUID of the GameObject to clone"),
|
|
59
|
+
countX: z.number().int().optional().describe("Copies along X (default 1)"),
|
|
60
|
+
countY: z.number().int().optional().describe("Copies along Y (default 1)"),
|
|
61
|
+
countZ: z.number().int().optional().describe("Copies along Z (default 1)"),
|
|
62
|
+
spacing: Vector3Schema.optional().describe("Spacing between copies per axis (default 100,100,100)"),
|
|
63
|
+
}, async (params) => {
|
|
64
|
+
const res = await bridge.send("grid_duplicate", params);
|
|
65
|
+
if (!res.success) {
|
|
66
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
|
|
70
|
+
};
|
|
71
|
+
});
|
|
72
|
+
// ── measure_distance ───────────────────────────────────────────────
|
|
73
|
+
server.tool("measure_distance", "Measure the distance between two points or two GameObjects. Provide a/b as {x,y,z} or idA/idB as GUIDs. Returns straight-line distance, horizontal (ground) distance, and the delta vector. Read-only (works during play).", {
|
|
74
|
+
a: Vector3Schema.optional().describe("First point {x,y,z}"),
|
|
75
|
+
b: Vector3Schema.optional().describe("Second point {x,y,z}"),
|
|
76
|
+
idA: z.string().optional().describe("First GameObject GUID (overrides a)"),
|
|
77
|
+
idB: z.string().optional().describe("Second GameObject GUID (overrides b)"),
|
|
78
|
+
}, async (params) => {
|
|
79
|
+
const res = await bridge.send("measure_distance", params);
|
|
80
|
+
if (!res.success) {
|
|
81
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
|
|
85
|
+
};
|
|
86
|
+
});
|
|
87
|
+
// ── scatter_props ──────────────────────────────────────────────────
|
|
88
|
+
server.tool("scatter_props", "Scatter N copies of a model randomly within a radius around a center point — instant foliage, rocks, debris. Each copy gets a random yaw and (by default) is snapped to the ground. Seeded for reproducibility; copies are grouped under one parent by default. Count capped at 300.", {
|
|
89
|
+
model: z.string().describe("Model path to scatter, e.g. 'models/dev/box.vmdl'"),
|
|
90
|
+
center: Vector3Schema.optional().describe("Centre of the scatter area (default origin)"),
|
|
91
|
+
radius: z.number().optional().describe("Scatter radius in units (default 256)"),
|
|
92
|
+
count: z.number().int().optional().describe("How many to place (default 10, max 300)"),
|
|
93
|
+
randomYaw: z.boolean().optional().describe("Randomly rotate each around Z (default true)"),
|
|
94
|
+
snapToGround: z.boolean().optional().describe("Raycast each onto the surface below (default true)"),
|
|
95
|
+
scaleMin: z.number().optional().describe("Min uniform scale (default 1)"),
|
|
96
|
+
scaleMax: z.number().optional().describe("Max uniform scale (default 1; set >min for size variation)"),
|
|
97
|
+
tint: z
|
|
98
|
+
.object({
|
|
99
|
+
r: z.number().min(0),
|
|
100
|
+
g: z.number().min(0),
|
|
101
|
+
b: z.number().min(0),
|
|
102
|
+
a: z.number().min(0).max(1).optional(),
|
|
103
|
+
})
|
|
104
|
+
.optional()
|
|
105
|
+
.describe("Tint applied to every copy"),
|
|
106
|
+
seed: z.number().int().optional().describe("PRNG seed for a reproducible layout (default 1)"),
|
|
107
|
+
group: z.boolean().optional().describe("Parent all copies under one group object (default true)"),
|
|
108
|
+
name: z.string().optional().describe("Base name for the props/group (default 'Prop')"),
|
|
109
|
+
}, async (params) => {
|
|
110
|
+
const res = await bridge.send("scatter_props", params);
|
|
111
|
+
if (!res.success) {
|
|
112
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
|
|
116
|
+
};
|
|
117
|
+
});
|
|
118
|
+
// ── randomize_transforms ───────────────────────────────────────────
|
|
119
|
+
server.tool("randomize_transforms", "Add natural variation to existing objects: random yaw and/or random uniform scale within a range. Great for breaking up repetition in placed foliage/rocks/crates. Seeded.", {
|
|
120
|
+
ids: z.array(z.string()).describe("GUIDs of the GameObjects to randomize"),
|
|
121
|
+
randomYaw: z.boolean().optional().describe("Randomize Z rotation (default true)"),
|
|
122
|
+
scaleMin: z.number().optional().describe("Min uniform scale (default 1)"),
|
|
123
|
+
scaleMax: z.number().optional().describe("Max uniform scale (default 1; set >min to vary)"),
|
|
124
|
+
seed: z.number().int().optional().describe("PRNG seed (default 1)"),
|
|
125
|
+
}, async (params) => {
|
|
126
|
+
const res = await bridge.send("randomize_transforms", params);
|
|
127
|
+
if (!res.success) {
|
|
128
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
129
|
+
}
|
|
130
|
+
return {
|
|
131
|
+
content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
|
|
132
|
+
};
|
|
133
|
+
});
|
|
134
|
+
// ── group_objects ──────────────────────────────────────────────────
|
|
135
|
+
server.tool("group_objects", "Parent a set of GameObjects under a new empty group object (placed at their centroid) — tidies the hierarchy and lets you move/rotate them together.", {
|
|
136
|
+
ids: z.array(z.string()).describe("GUIDs of the GameObjects to group"),
|
|
137
|
+
name: z.string().optional().describe("Name for the group object (default 'Group')"),
|
|
138
|
+
}, async (params) => {
|
|
139
|
+
const res = await bridge.send("group_objects", params);
|
|
140
|
+
if (!res.success) {
|
|
141
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
142
|
+
}
|
|
143
|
+
return {
|
|
144
|
+
content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
|
|
145
|
+
};
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=leveltools.js.map
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Navigation tools (Batch 27) — REAL editor operations, not component wrappers.
|
|
4
|
+
*
|
|
5
|
+
* bake_navmesh runs the editor's static NavMesh bake (NavMesh.BakeNavMesh) so
|
|
6
|
+
* NavMeshAgents can pathfind; get_navmesh_path queries the baked mesh for a
|
|
7
|
+
* route (NavMesh.GetSimplePath). Neither is reachable via add_component — they
|
|
8
|
+
* operate on the scene's navmesh itself, which is why they earn dedicated tools.
|
|
9
|
+
*/
|
|
10
|
+
const Vec3 = z
|
|
11
|
+
.object({
|
|
12
|
+
x: z.number().describe("X"),
|
|
13
|
+
y: z.number().describe("Y"),
|
|
14
|
+
z: z.number().describe("Z"),
|
|
15
|
+
})
|
|
16
|
+
.describe("A world-space Vector3");
|
|
17
|
+
export function registerNavigationTools(server, bridge) {
|
|
18
|
+
// ── bake_navmesh ────────────────────────────────────────────────────
|
|
19
|
+
server.tool("bake_navmesh", "Enable + bake the active scene's navigation mesh so NavMeshAgents can pathfind. This is an editor operation (NavMesh.BakeNavMesh), not a component. The bake runs ASYNC — it returns immediately with baking:true; the editor shows a progress bar and isGenerating flips false when done (give it a moment before querying paths). Optional agent params let you size the mesh to your characters.", {
|
|
20
|
+
agentRadius: z.number().optional().describe("Agent radius (default scene setting)"),
|
|
21
|
+
agentHeight: z.number().optional().describe("Agent height"),
|
|
22
|
+
agentStepSize: z.number().optional().describe("Max step-up height"),
|
|
23
|
+
agentMaxSlope: z.number().optional().describe("Max walkable slope, degrees"),
|
|
24
|
+
includeStaticBodies: z
|
|
25
|
+
.boolean()
|
|
26
|
+
.optional()
|
|
27
|
+
.describe("Include static physics bodies in the bake"),
|
|
28
|
+
}, async (params) => {
|
|
29
|
+
const res = await bridge.send("bake_navmesh", params);
|
|
30
|
+
if (!res.success) {
|
|
31
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
|
|
35
|
+
};
|
|
36
|
+
});
|
|
37
|
+
// ── get_navmesh_path ────────────────────────────────────────────────
|
|
38
|
+
server.tool("get_navmesh_path", "Query the baked navmesh for a walkable path between two world points (NavMesh.GetSimplePath). Returns the ordered path points, or reachable:false if no route exists. Requires bake_navmesh to have run first. Read-only — useful for validating connectivity, AI patrol routes, and spawn reachability.", {
|
|
39
|
+
from: Vec3.describe("Start point (world space)"),
|
|
40
|
+
to: Vec3.describe("Destination point (world space)"),
|
|
41
|
+
}, async (params) => {
|
|
42
|
+
const res = await bridge.send("get_navmesh_path", params);
|
|
43
|
+
if (!res.success) {
|
|
44
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
|
|
48
|
+
};
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=navigation.js.map
|
package/dist/tools/networking.js
CHANGED
|
@@ -100,23 +100,25 @@ export function registerNetworkingTools(server, bridge) {
|
|
|
100
100
|
};
|
|
101
101
|
});
|
|
102
102
|
// ── add_sync_property ─────────────────────────────────────────────
|
|
103
|
-
server.tool("add_sync_property", "
|
|
103
|
+
server.tool("add_sync_property", "Annotate an EXISTING public property in a C# script with the [Sync] attribute so s&box replicates it across the network. This does NOT create a new property — the property named by `propertyName` must already be declared in the file; the tool only inserts the [Sync] attribute above it", {
|
|
104
104
|
path: z
|
|
105
105
|
.string()
|
|
106
106
|
.describe("Relative path to the script file (e.g. 'code/Player.cs')"),
|
|
107
|
-
propertyName: z
|
|
107
|
+
propertyName: z
|
|
108
|
+
.string()
|
|
109
|
+
.describe("Name of the existing public property to annotate with [Sync]"),
|
|
108
110
|
propertyType: z
|
|
109
111
|
.string()
|
|
110
112
|
.optional()
|
|
111
|
-
.describe("
|
|
113
|
+
.describe("Currently ignored — not yet implemented. The addon only annotates an existing property; it does not declare a new one, so the type comes from the existing declaration"),
|
|
112
114
|
syncFlags: z
|
|
113
115
|
.string()
|
|
114
116
|
.optional()
|
|
115
|
-
.describe("
|
|
117
|
+
.describe("Currently ignored — not yet implemented. The addon emits a plain [Sync] with no SyncFlags"),
|
|
116
118
|
defaultValue: z
|
|
117
119
|
.string()
|
|
118
120
|
.optional()
|
|
119
|
-
.describe("
|
|
121
|
+
.describe("Currently ignored — not yet implemented. The addon does not create or initialize a property, so no default is written"),
|
|
120
122
|
}, async (params) => {
|
|
121
123
|
const res = await bridge.send("add_sync_property", params);
|
|
122
124
|
if (!res.success) {
|
|
@@ -127,7 +129,7 @@ export function registerNetworkingTools(server, bridge) {
|
|
|
127
129
|
};
|
|
128
130
|
});
|
|
129
131
|
// ── add_rpc_method ────────────────────────────────────────────────
|
|
130
|
-
server.tool("add_rpc_method", "
|
|
132
|
+
server.tool("add_rpc_method", "Generate an EMPTY, no-argument RPC method stub in a C# script for you to fill in. The addon inserts the chosen RPC attribute ([Rpc.Broadcast] all clients, [Rpc.Host] host only, [Rpc.Owner] owner only) above a parameterless method with an empty body — it does NOT add parameters or generate body code", {
|
|
131
133
|
path: z
|
|
132
134
|
.string()
|
|
133
135
|
.describe("Relative path to the script file"),
|
|
@@ -139,11 +141,11 @@ export function registerNetworkingTools(server, bridge) {
|
|
|
139
141
|
methodParams: z
|
|
140
142
|
.string()
|
|
141
143
|
.optional()
|
|
142
|
-
.describe("
|
|
144
|
+
.describe("Currently ignored — not yet implemented. The addon emits a no-argument method; add parameters yourself afterward"),
|
|
143
145
|
body: z
|
|
144
146
|
.string()
|
|
145
147
|
.optional()
|
|
146
|
-
.describe("
|
|
148
|
+
.describe("Currently ignored — not yet implemented. The addon emits an empty method body; fill it in yourself afterward"),
|
|
147
149
|
}, async (params) => {
|
|
148
150
|
const res = await bridge.send("add_rpc_method", params);
|
|
149
151
|
if (!res.success) {
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Object utility & query tools (Batch 23): find objects in the scene, and
|
|
4
|
+
* bulk-edit tint / model / tags across one or many objects. find_objects is
|
|
5
|
+
* the composable workhorse — query GUIDs, then feed them to align/distribute/
|
|
6
|
+
* set_tint/group/etc.
|
|
7
|
+
*/
|
|
8
|
+
const ColorSchema = z
|
|
9
|
+
.object({
|
|
10
|
+
r: z.number().min(0).describe("Red, 0-1"),
|
|
11
|
+
g: z.number().min(0).describe("Green, 0-1"),
|
|
12
|
+
b: z.number().min(0).describe("Blue, 0-1"),
|
|
13
|
+
a: z.number().min(0).max(1).optional().describe("Alpha, 0-1 (default 1)"),
|
|
14
|
+
})
|
|
15
|
+
.describe("RGBA colour as 0-1 floats");
|
|
16
|
+
export function registerObjectTools(server, bridge) {
|
|
17
|
+
// ── find_objects ───────────────────────────────────────────────────
|
|
18
|
+
server.tool("find_objects", "Query the scene for GameObjects by name (case-insensitive substring), component type name, and/or tag — combine filters (AND). Returns {id,name} for matches (limit default 50, max 500). Read-only; works during play. Use it to get GUIDs to feed into align/distribute/set_tint/group/delete/etc.", {
|
|
19
|
+
name: z.string().optional().describe("Name substring (case-insensitive)"),
|
|
20
|
+
component: z
|
|
21
|
+
.string()
|
|
22
|
+
.optional()
|
|
23
|
+
.describe("Component type name, e.g. 'PointLight', 'SkinnedModelRenderer'"),
|
|
24
|
+
tag: z.string().optional().describe("Tag the object must have"),
|
|
25
|
+
limit: z.number().int().optional().describe("Max results (default 50, max 500)"),
|
|
26
|
+
}, async (params) => {
|
|
27
|
+
const res = await bridge.send("find_objects", params);
|
|
28
|
+
if (!res.success) {
|
|
29
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
|
|
33
|
+
};
|
|
34
|
+
});
|
|
35
|
+
// ── set_tint ───────────────────────────────────────────────────────
|
|
36
|
+
server.tool("set_tint", "Set the renderer tint colour on one object (id) or many (ids) at once. Works on any ModelRenderer/SkinnedModelRenderer.", {
|
|
37
|
+
id: z.string().optional().describe("Single GameObject GUID"),
|
|
38
|
+
ids: z.array(z.string()).optional().describe("Multiple GameObject GUIDs"),
|
|
39
|
+
tint: ColorSchema.describe("Tint colour to apply"),
|
|
40
|
+
}, async (params) => {
|
|
41
|
+
const res = await bridge.send("set_tint", params);
|
|
42
|
+
if (!res.success) {
|
|
43
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
|
|
47
|
+
};
|
|
48
|
+
});
|
|
49
|
+
// ── replace_model ──────────────────────────────────────────────────
|
|
50
|
+
server.tool("replace_model", "Swap the model on one object (id) or many (ids) — e.g. retheme a row of props in one call.", {
|
|
51
|
+
id: z.string().optional().describe("Single GameObject GUID"),
|
|
52
|
+
ids: z.array(z.string()).optional().describe("Multiple GameObject GUIDs"),
|
|
53
|
+
model: z.string().describe("New model path, e.g. 'models/dev/sphere.vmdl'"),
|
|
54
|
+
}, async (params) => {
|
|
55
|
+
const res = await bridge.send("replace_model", params);
|
|
56
|
+
if (!res.success) {
|
|
57
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
|
|
61
|
+
};
|
|
62
|
+
});
|
|
63
|
+
// ── set_tags ───────────────────────────────────────────────────────
|
|
64
|
+
server.tool("set_tags", "Add, remove, and/or clear gameplay tags on one object (id) or many (ids). Tags drive collision groups, queries, and triggers.", {
|
|
65
|
+
id: z.string().optional().describe("Single GameObject GUID"),
|
|
66
|
+
ids: z.array(z.string()).optional().describe("Multiple GameObject GUIDs"),
|
|
67
|
+
add: z.array(z.string()).optional().describe("Tags to add"),
|
|
68
|
+
remove: z.array(z.string()).optional().describe("Tags to remove"),
|
|
69
|
+
clear: z.boolean().optional().describe("Remove all existing tags first"),
|
|
70
|
+
}, async (params) => {
|
|
71
|
+
const res = await bridge.send("set_tags", 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
|
+
// ── remove_component ───────────────────────────────────────────────
|
|
80
|
+
server.tool("remove_component", "Remove a component from a GameObject by type name (e.g. 'PointLight', 'ModelRenderer'). Removes the first match; pass all:true to remove every matching one. The counterpart to add_component_with_properties.", {
|
|
81
|
+
id: z.string().describe("GUID of the GameObject"),
|
|
82
|
+
component: z.string().describe("Component type name to remove"),
|
|
83
|
+
all: z.boolean().optional().describe("Remove all matching components, not just the first"),
|
|
84
|
+
}, async (params) => {
|
|
85
|
+
const res = await bridge.send("remove_component", params);
|
|
86
|
+
if (!res.success) {
|
|
87
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
|
|
91
|
+
};
|
|
92
|
+
});
|
|
93
|
+
// ── get_tags ───────────────────────────────────────────────────────
|
|
94
|
+
server.tool("get_tags", "Read the tags currently on a GameObject. (Pair with set_tags to add/remove/clear, and find_objects to query by tag.)", {
|
|
95
|
+
id: z.string().describe("GUID of the GameObject"),
|
|
96
|
+
}, async (params) => {
|
|
97
|
+
const res = await bridge.send("get_tags", params);
|
|
98
|
+
if (!res.success) {
|
|
99
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
|
|
103
|
+
};
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=objecttools.js.map
|
package/dist/tools/physics.js
CHANGED
|
@@ -126,5 +126,27 @@ export function registerPhysicsTools(server, bridge) {
|
|
|
126
126
|
content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
|
|
127
127
|
};
|
|
128
128
|
});
|
|
129
|
+
// ── physics_overlap ───────────────────────────────────────────────
|
|
130
|
+
server.tool("physics_overlap", "Spatial volume query: return the GameObjects whose colliders intersect a SPHERE (center + radius) or a BOX (center + size) — the volume counterpart to raycast's ray. Use it for 'what's near this point' / 'what's inside this trigger volume' checks (proximity, blast radius, spawn-clearance). Read-only.", {
|
|
131
|
+
center: z
|
|
132
|
+
.object({ x: z.number(), y: z.number(), z: z.number() })
|
|
133
|
+
.describe("Center of the query volume (world space)"),
|
|
134
|
+
radius: z
|
|
135
|
+
.number()
|
|
136
|
+
.optional()
|
|
137
|
+
.describe("Sphere radius. Provide this OR size (box), not both"),
|
|
138
|
+
size: z
|
|
139
|
+
.object({ x: z.number(), y: z.number(), z: z.number() })
|
|
140
|
+
.optional()
|
|
141
|
+
.describe("Full box size (not half-extents). Provide this OR radius"),
|
|
142
|
+
}, async (params) => {
|
|
143
|
+
const res = await bridge.send("physics_overlap", params);
|
|
144
|
+
if (!res.success) {
|
|
145
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
146
|
+
}
|
|
147
|
+
return {
|
|
148
|
+
content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
|
|
149
|
+
};
|
|
150
|
+
});
|
|
129
151
|
}
|
|
130
152
|
//# sourceMappingURL=physics.js.map
|
package/dist/tools/project.js
CHANGED
|
@@ -74,8 +74,18 @@ export function registerProjectTools(server, bridge) {
|
|
|
74
74
|
content: z.string().describe("The full file content to write"),
|
|
75
75
|
}, async (params) => {
|
|
76
76
|
const res = await bridge.send("write_file", params);
|
|
77
|
-
|
|
78
|
-
|
|
77
|
+
// The handler can report a logical failure inside res.data even when the
|
|
78
|
+
// transport call "succeeded" (res.success === true). Only claim success
|
|
79
|
+
// when there is no error field anywhere; otherwise surface the real error
|
|
80
|
+
// instead of a misleading "written successfully".
|
|
81
|
+
const dataError = res.data && typeof res.data === "object"
|
|
82
|
+
? res.data.error
|
|
83
|
+
: undefined;
|
|
84
|
+
if (!res.success || dataError) {
|
|
85
|
+
const message = res.error ?? (dataError ? String(dataError) : undefined);
|
|
86
|
+
return {
|
|
87
|
+
content: [{ type: "text", text: `Error: ${message ?? "write_file failed"}` }],
|
|
88
|
+
};
|
|
79
89
|
}
|
|
80
90
|
return {
|
|
81
91
|
content: [
|