sbox-mcp-server 1.2.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/LICENSE +21 -0
- package/README.md +90 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +155 -0
- package/dist/tools/assets.d.ts +8 -0
- package/dist/tools/assets.js +80 -0
- package/dist/tools/audio.d.ts +8 -0
- package/dist/tools/audio.js +101 -0
- package/dist/tools/components.d.ts +11 -0
- package/dist/tools/components.js +78 -0
- package/dist/tools/console.d.ts +8 -0
- package/dist/tools/console.js +59 -0
- package/dist/tools/discovery.d.ts +13 -0
- package/dist/tools/discovery.js +58 -0
- package/dist/tools/gameobjects.d.ts +4 -0
- package/dist/tools/gameobjects.js +197 -0
- package/dist/tools/materials.d.ts +8 -0
- package/dist/tools/materials.js +82 -0
- package/dist/tools/networking.d.ts +11 -0
- package/dist/tools/networking.js +227 -0
- package/dist/tools/physics.d.ts +8 -0
- package/dist/tools/physics.js +130 -0
- package/dist/tools/playmode.d.ts +11 -0
- package/dist/tools/playmode.js +140 -0
- package/dist/tools/prefabs.d.ts +8 -0
- package/dist/tools/prefabs.js +94 -0
- package/dist/tools/project.d.ts +12 -0
- package/dist/tools/project.js +90 -0
- package/dist/tools/publishing.d.ts +11 -0
- package/dist/tools/publishing.js +168 -0
- package/dist/tools/scenes.d.ts +8 -0
- package/dist/tools/scenes.js +75 -0
- package/dist/tools/scripts.d.ts +9 -0
- package/dist/tools/scripts.js +132 -0
- package/dist/tools/status.d.ts +8 -0
- package/dist/tools/status.js +49 -0
- package/dist/tools/templates.d.ts +11 -0
- package/dist/tools/templates.js +135 -0
- package/dist/tools/ui.d.ts +8 -0
- package/dist/tools/ui.js +116 -0
- package/dist/tools/world.d.ts +20 -0
- package/dist/tools/world.js +272 -0
- package/dist/transport/bridge-client.d.ts +60 -0
- package/dist/transport/bridge-client.js +239 -0
- package/package.json +54 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Type discovery and code-search tools.
|
|
4
|
+
*
|
|
5
|
+
* These help Claude reference real s&box APIs instead of guessing. Use
|
|
6
|
+
* describe_type before writing code that touches an unfamiliar type — the
|
|
7
|
+
* Game.TypeLibrary reflection returns properties, methods, events, and
|
|
8
|
+
* attributes for any loaded type. Use search_types to find types matching
|
|
9
|
+
* a name pattern. find_in_project greps the user's project for usage examples.
|
|
10
|
+
*/
|
|
11
|
+
export function registerDiscoveryTools(server, bridge) {
|
|
12
|
+
// ── describe_type ────────────────────────────────────────────────
|
|
13
|
+
server.tool("describe_type", "Inspect a type's full surface — properties, methods, events, attributes — via reflection on Game.TypeLibrary and loaded assemblies. Use this before writing code touching an unfamiliar component or s&box API. Examples: 'MeshComponent', 'PlayerController', 'NetworkHelper', 'Vector3'.", {
|
|
14
|
+
name: z.string().describe("Type name (short or fully-qualified)"),
|
|
15
|
+
}, async (params) => {
|
|
16
|
+
const res = await bridge.send("describe_type", params);
|
|
17
|
+
if (!res.success)
|
|
18
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
19
|
+
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
|
|
20
|
+
});
|
|
21
|
+
// ── search_types ─────────────────────────────────────────────────
|
|
22
|
+
server.tool("search_types", "Find types matching a name pattern. Pass components_only=true to filter to Component subclasses only. Useful for discovering 'is there a built-in X for this?'", {
|
|
23
|
+
pattern: z.string().describe("Substring to match against type name (case-insensitive)"),
|
|
24
|
+
namespace: z
|
|
25
|
+
.string()
|
|
26
|
+
.optional()
|
|
27
|
+
.describe("Optional namespace filter (case-insensitive substring)"),
|
|
28
|
+
components_only: z.boolean().default(false),
|
|
29
|
+
limit: z.number().int().default(50),
|
|
30
|
+
}, async (params) => {
|
|
31
|
+
const res = await bridge.send("search_types", params);
|
|
32
|
+
if (!res.success)
|
|
33
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
34
|
+
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
|
|
35
|
+
});
|
|
36
|
+
// ── get_method_signature ─────────────────────────────────────────
|
|
37
|
+
server.tool("get_method_signature", "Get the formal signature(s) of a method on a type — parameter names, types, defaults, return type, all overloads. Use before invoking an API you're unsure of.", {
|
|
38
|
+
type: z.string().describe("Type name (e.g. 'Scene', 'GameObject')"),
|
|
39
|
+
method: z.string().describe("Method name (case-sensitive)"),
|
|
40
|
+
}, async (params) => {
|
|
41
|
+
const res = await bridge.send("get_method_signature", params);
|
|
42
|
+
if (!res.success)
|
|
43
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
44
|
+
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
|
|
45
|
+
});
|
|
46
|
+
// ── find_in_project ──────────────────────────────────────────────
|
|
47
|
+
server.tool("find_in_project", "Grep the user's s&box project for a symbol. Returns file paths and line numbers. Useful for finding usage examples of an API or seeing how the project already does something.", {
|
|
48
|
+
symbol: z.string().describe("Substring or symbol to search for"),
|
|
49
|
+
extension: z.string().default(".cs").describe("File extension filter"),
|
|
50
|
+
max_results: z.number().int().default(25),
|
|
51
|
+
}, async (params) => {
|
|
52
|
+
const res = await bridge.send("find_in_project", params);
|
|
53
|
+
if (!res.success)
|
|
54
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
55
|
+
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=discovery.js.map
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { BridgeClient } from "../transport/bridge-client.js";
|
|
3
|
+
export declare function registerGameObjectTools(server: McpServer, bridge: BridgeClient): void;
|
|
4
|
+
//# sourceMappingURL=gameobjects.d.ts.map
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* GameObject lifecycle, hierarchy, and selection tools.
|
|
4
|
+
*
|
|
5
|
+
* Registers: create/delete/duplicate/rename_gameobject, set_parent,
|
|
6
|
+
* set_enabled, set_transform, get_scene_hierarchy, get_selected_objects,
|
|
7
|
+
* select_object, focus_object.
|
|
8
|
+
*
|
|
9
|
+
* Uses shared Zod schemas (Vector3Schema, RotationSchema) for consistent
|
|
10
|
+
* Vector3 and Rotation parameter validation across multiple tools.
|
|
11
|
+
*/
|
|
12
|
+
const Vector3Schema = z
|
|
13
|
+
.object({
|
|
14
|
+
x: z.number().describe("X coordinate"),
|
|
15
|
+
y: z.number().describe("Y coordinate"),
|
|
16
|
+
z: z.number().describe("Z coordinate"),
|
|
17
|
+
})
|
|
18
|
+
.describe("3D vector with x, y, z components");
|
|
19
|
+
const RotationSchema = z
|
|
20
|
+
.object({
|
|
21
|
+
pitch: z.number().describe("Pitch angle in degrees"),
|
|
22
|
+
yaw: z.number().describe("Yaw angle in degrees"),
|
|
23
|
+
roll: z.number().describe("Roll angle in degrees"),
|
|
24
|
+
})
|
|
25
|
+
.describe("Euler rotation with pitch, yaw, roll in degrees");
|
|
26
|
+
export function registerGameObjectTools(server, bridge) {
|
|
27
|
+
// ── create_gameobject ────────────────────────────────────────────
|
|
28
|
+
server.tool("create_gameobject", "Create a new GameObject in the active scene. Returns its GUID for future reference", {
|
|
29
|
+
name: z
|
|
30
|
+
.string()
|
|
31
|
+
.optional()
|
|
32
|
+
.describe("Display name (e.g. 'Player', 'Enemy Spawn Point'). Defaults to 'New Object'"),
|
|
33
|
+
position: Vector3Schema.optional().describe("World position"),
|
|
34
|
+
rotation: RotationSchema.optional().describe("World rotation"),
|
|
35
|
+
scale: z
|
|
36
|
+
.union([z.number(), Vector3Schema])
|
|
37
|
+
.optional()
|
|
38
|
+
.describe("Uniform scale (number) or per-axis scale (Vector3)"),
|
|
39
|
+
parent: z
|
|
40
|
+
.string()
|
|
41
|
+
.optional()
|
|
42
|
+
.describe("GUID of parent GameObject. Omit for scene root"),
|
|
43
|
+
}, async (params) => {
|
|
44
|
+
const res = await bridge.send("create_gameobject", params);
|
|
45
|
+
if (!res.success) {
|
|
46
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
// ── delete_gameobject ────────────────────────────────────────────
|
|
53
|
+
server.tool("delete_gameobject", "Delete a GameObject from the active scene by its GUID", {
|
|
54
|
+
id: z.string().describe("GUID of the GameObject to delete"),
|
|
55
|
+
}, async (params) => {
|
|
56
|
+
const res = await bridge.send("delete_gameobject", params);
|
|
57
|
+
if (!res.success) {
|
|
58
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
|
|
62
|
+
};
|
|
63
|
+
});
|
|
64
|
+
// ── duplicate_gameobject ─────────────────────────────────────────
|
|
65
|
+
server.tool("duplicate_gameobject", "Clone a GameObject with all its components. Optionally offset position or rename", {
|
|
66
|
+
id: z.string().describe("GUID of the GameObject to duplicate"),
|
|
67
|
+
name: z.string().optional().describe("New name for the clone"),
|
|
68
|
+
offset: Vector3Schema.optional().describe("Position offset from original so the clone doesn't overlap"),
|
|
69
|
+
}, async (params) => {
|
|
70
|
+
const res = await bridge.send("duplicate_gameobject", 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
|
+
// ── rename_gameobject ────────────────────────────────────────────
|
|
79
|
+
server.tool("rename_gameobject", "Change the display name of a GameObject", {
|
|
80
|
+
id: z.string().describe("GUID of the GameObject"),
|
|
81
|
+
name: z.string().describe("New display name"),
|
|
82
|
+
}, async (params) => {
|
|
83
|
+
const res = await bridge.send("rename_gameobject", params);
|
|
84
|
+
if (!res.success) {
|
|
85
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
|
|
89
|
+
};
|
|
90
|
+
});
|
|
91
|
+
// ── set_parent ───────────────────────────────────────────────────
|
|
92
|
+
server.tool("set_parent", "Reparent a GameObject. Set parentId to null or omit to move to scene root", {
|
|
93
|
+
id: z.string().describe("GUID of the GameObject to reparent"),
|
|
94
|
+
parentId: z
|
|
95
|
+
.string()
|
|
96
|
+
.nullable()
|
|
97
|
+
.optional()
|
|
98
|
+
.describe("GUID of the new parent. Null or omitted = scene root"),
|
|
99
|
+
}, async (params) => {
|
|
100
|
+
const res = await bridge.send("set_parent", params);
|
|
101
|
+
if (!res.success) {
|
|
102
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
|
|
106
|
+
};
|
|
107
|
+
});
|
|
108
|
+
// ── set_enabled ──────────────────────────────────────────────────
|
|
109
|
+
server.tool("set_enabled", "Enable or disable a GameObject (disabled objects are invisible and inactive)", {
|
|
110
|
+
id: z.string().describe("GUID of the GameObject"),
|
|
111
|
+
enabled: z.boolean().describe("true to enable, false to disable"),
|
|
112
|
+
}, async (params) => {
|
|
113
|
+
const res = await bridge.send("set_enabled", params);
|
|
114
|
+
if (!res.success) {
|
|
115
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
|
|
119
|
+
};
|
|
120
|
+
});
|
|
121
|
+
// ── set_transform ────────────────────────────────────────────────
|
|
122
|
+
server.tool("set_transform", "Set position, rotation, and/or scale on a GameObject. Only provided values are changed", {
|
|
123
|
+
id: z.string().describe("GUID of the GameObject"),
|
|
124
|
+
position: Vector3Schema.optional().describe("New position"),
|
|
125
|
+
rotation: RotationSchema.optional().describe("New rotation"),
|
|
126
|
+
scale: z
|
|
127
|
+
.union([z.number(), Vector3Schema])
|
|
128
|
+
.optional()
|
|
129
|
+
.describe("New scale — uniform number or per-axis Vector3"),
|
|
130
|
+
local: z
|
|
131
|
+
.boolean()
|
|
132
|
+
.optional()
|
|
133
|
+
.describe("If true, values are in local space. Default is world space"),
|
|
134
|
+
}, async (params) => {
|
|
135
|
+
const res = await bridge.send("set_transform", params);
|
|
136
|
+
if (!res.success) {
|
|
137
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
|
|
141
|
+
};
|
|
142
|
+
});
|
|
143
|
+
// ── get_scene_hierarchy ──────────────────────────────────────────
|
|
144
|
+
server.tool("get_scene_hierarchy", "Get the full scene tree — all GameObjects with their names, GUIDs, components, positions, and parent/child relationships. This is how you 'see' the scene", {
|
|
145
|
+
maxDepth: z
|
|
146
|
+
.number()
|
|
147
|
+
.optional()
|
|
148
|
+
.describe("Maximum depth of the tree to return. Defaults to 10"),
|
|
149
|
+
}, async (params) => {
|
|
150
|
+
const res = await bridge.send("get_scene_hierarchy", params);
|
|
151
|
+
if (!res.success) {
|
|
152
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
|
|
156
|
+
};
|
|
157
|
+
});
|
|
158
|
+
// ── get_selected_objects ─────────────────────────────────────────
|
|
159
|
+
server.tool("get_selected_objects", "Get the GameObjects currently selected by the user in the s&box editor", {}, async () => {
|
|
160
|
+
const res = await bridge.send("get_selected_objects");
|
|
161
|
+
if (!res.success) {
|
|
162
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
163
|
+
}
|
|
164
|
+
return {
|
|
165
|
+
content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
|
|
166
|
+
};
|
|
167
|
+
});
|
|
168
|
+
// ── select_object ────────────────────────────────────────────────
|
|
169
|
+
server.tool("select_object", "Select a GameObject in the editor (highlights it in the hierarchy and scene view)", {
|
|
170
|
+
id: z.string().describe("GUID of the GameObject to select"),
|
|
171
|
+
addToSelection: z
|
|
172
|
+
.boolean()
|
|
173
|
+
.optional()
|
|
174
|
+
.describe("If true, adds to current selection instead of replacing it"),
|
|
175
|
+
}, async (params) => {
|
|
176
|
+
const res = await bridge.send("select_object", params);
|
|
177
|
+
if (!res.success) {
|
|
178
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
|
|
182
|
+
};
|
|
183
|
+
});
|
|
184
|
+
// ── focus_object ─────────────────────────────────────────────────
|
|
185
|
+
server.tool("focus_object", "Move the editor camera to focus on a specific GameObject (like double-clicking in the hierarchy)", {
|
|
186
|
+
id: z.string().describe("GUID of the GameObject to focus"),
|
|
187
|
+
}, async (params) => {
|
|
188
|
+
const res = await bridge.send("focus_object", params);
|
|
189
|
+
if (!res.success) {
|
|
190
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
191
|
+
}
|
|
192
|
+
return {
|
|
193
|
+
content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
|
|
194
|
+
};
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
//# sourceMappingURL=gameobjects.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
|
+
* Material and model tools: assign_model, create_material, assign_material, set_material_property.
|
|
5
|
+
* Handles .vmat creation, ModelRenderer management, and runtime material property changes.
|
|
6
|
+
*/
|
|
7
|
+
export declare function registerMaterialTools(server: McpServer, bridge: BridgeClient): void;
|
|
8
|
+
//# sourceMappingURL=materials.d.ts.map
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Material and model tools: assign_model, create_material, assign_material, set_material_property.
|
|
4
|
+
* Handles .vmat creation, ModelRenderer management, and runtime material property changes.
|
|
5
|
+
*/
|
|
6
|
+
export function registerMaterialTools(server, bridge) {
|
|
7
|
+
// ── assign_model ─────────────────────────────────────────────────
|
|
8
|
+
server.tool("assign_model", "Set a 3D model on a GameObject's ModelRenderer. Creates the renderer component if it doesn't exist", {
|
|
9
|
+
id: z.string().describe("GUID of the GameObject"),
|
|
10
|
+
model: z
|
|
11
|
+
.string()
|
|
12
|
+
.describe("Model path (e.g. 'models/citizen/citizen.vmdl', 'models/dev/box.vmdl')"),
|
|
13
|
+
}, async (params) => {
|
|
14
|
+
const res = await bridge.send("assign_model", 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
|
+
// ── create_material ──────────────────────────────────────────────
|
|
23
|
+
server.tool("create_material", "Create a new material file (.vmat) with a shader and properties like color, roughness, metallic, texture", {
|
|
24
|
+
path: z
|
|
25
|
+
.string()
|
|
26
|
+
.describe("Relative path for the material (e.g. 'materials/walls/brick.vmat')"),
|
|
27
|
+
shader: z
|
|
28
|
+
.string()
|
|
29
|
+
.optional()
|
|
30
|
+
.describe("Shader to use. Defaults to 'shaders/complex.shader' (PBR)"),
|
|
31
|
+
properties: z
|
|
32
|
+
.record(z.unknown())
|
|
33
|
+
.optional()
|
|
34
|
+
.describe("Material properties as key-value pairs (e.g. { \"Color\": \"#ff0000\", \"Roughness\": 0.8 })"),
|
|
35
|
+
}, async (params) => {
|
|
36
|
+
const res = await bridge.send("create_material", 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
|
+
// ── assign_material ──────────────────────────────────────────────
|
|
45
|
+
server.tool("assign_material", "Apply a material to a ModelRenderer on a GameObject. Optionally target a specific material slot", {
|
|
46
|
+
id: z.string().describe("GUID of the GameObject"),
|
|
47
|
+
material: z
|
|
48
|
+
.string()
|
|
49
|
+
.describe("Material path (e.g. 'materials/walls/brick.vmat')"),
|
|
50
|
+
slot: z
|
|
51
|
+
.number()
|
|
52
|
+
.optional()
|
|
53
|
+
.describe("Material slot index. Defaults to 0 (first slot)"),
|
|
54
|
+
}, async (params) => {
|
|
55
|
+
const res = await bridge.send("assign_material", 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_material_property ────────────────────────────────────────
|
|
64
|
+
server.tool("set_material_property", "Change a property on the material assigned to a GameObject — color, roughness, metallic, texture, etc.", {
|
|
65
|
+
id: z.string().describe("GUID of the GameObject"),
|
|
66
|
+
property: z
|
|
67
|
+
.string()
|
|
68
|
+
.describe("Material property name (e.g. 'Color', 'Roughness', 'Metalness', 'Normal')"),
|
|
69
|
+
value: z
|
|
70
|
+
.unknown()
|
|
71
|
+
.describe("Property value — number for floats, string for texture paths/colors, {r,g,b,a} for colors"),
|
|
72
|
+
}, async (params) => {
|
|
73
|
+
const res = await bridge.send("set_material_property", params);
|
|
74
|
+
if (!res.success) {
|
|
75
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
|
|
79
|
+
};
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=materials.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { BridgeClient } from "../transport/bridge-client.js";
|
|
3
|
+
/**
|
|
4
|
+
* Networking tools: add_network_helper, configure_network, get_network_status,
|
|
5
|
+
* network_spawn, set_ownership, add_sync_property, add_rpc_method,
|
|
6
|
+
* create_networked_player, create_lobby_manager, create_network_events.
|
|
7
|
+
*
|
|
8
|
+
* Manages s&box multiplayer: lobby creation, networked objects, RPCs, sync properties.
|
|
9
|
+
*/
|
|
10
|
+
export declare function registerNetworkingTools(server: McpServer, bridge: BridgeClient): void;
|
|
11
|
+
//# sourceMappingURL=networking.d.ts.map
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Networking tools: add_network_helper, configure_network, get_network_status,
|
|
4
|
+
* network_spawn, set_ownership, add_sync_property, add_rpc_method,
|
|
5
|
+
* create_networked_player, create_lobby_manager, create_network_events.
|
|
6
|
+
*
|
|
7
|
+
* Manages s&box multiplayer: lobby creation, networked objects, RPCs, sync properties.
|
|
8
|
+
*/
|
|
9
|
+
export function registerNetworkingTools(server, bridge) {
|
|
10
|
+
// ── add_network_helper ────────────────────────────────────────────
|
|
11
|
+
server.tool("add_network_helper", "Add a NetworkHelper component to the scene for quick multiplayer setup. Handles lobby creation and player prefab spawning", {
|
|
12
|
+
id: z
|
|
13
|
+
.string()
|
|
14
|
+
.optional()
|
|
15
|
+
.describe("GUID of existing GameObject. Creates new if omitted"),
|
|
16
|
+
name: z
|
|
17
|
+
.string()
|
|
18
|
+
.optional()
|
|
19
|
+
.describe("Name for the network manager object. Defaults to 'Network Manager'"),
|
|
20
|
+
maxPlayers: z
|
|
21
|
+
.number()
|
|
22
|
+
.optional()
|
|
23
|
+
.describe("Maximum number of players in the lobby"),
|
|
24
|
+
playerPrefab: z
|
|
25
|
+
.string()
|
|
26
|
+
.optional()
|
|
27
|
+
.describe("Path to the player prefab to spawn for each connection"),
|
|
28
|
+
}, async (params) => {
|
|
29
|
+
const res = await bridge.send("add_network_helper", 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
|
+
// ── configure_network ─────────────────────────────────────────────
|
|
38
|
+
server.tool("configure_network", "Configure networking settings on the existing NetworkHelper: max players, lobby name, player prefab, start server", {
|
|
39
|
+
maxPlayers: z
|
|
40
|
+
.number()
|
|
41
|
+
.optional()
|
|
42
|
+
.describe("Maximum number of players"),
|
|
43
|
+
lobbyName: z
|
|
44
|
+
.string()
|
|
45
|
+
.optional()
|
|
46
|
+
.describe("Display name for the lobby"),
|
|
47
|
+
playerPrefab: z
|
|
48
|
+
.string()
|
|
49
|
+
.optional()
|
|
50
|
+
.describe("Path to the player prefab"),
|
|
51
|
+
startServer: z
|
|
52
|
+
.boolean()
|
|
53
|
+
.optional()
|
|
54
|
+
.describe("Start the server/lobby immediately"),
|
|
55
|
+
}, async (params) => {
|
|
56
|
+
const res = await bridge.send("configure_network", params);
|
|
57
|
+
if (!res.success) {
|
|
58
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
|
|
62
|
+
};
|
|
63
|
+
});
|
|
64
|
+
// ── get_network_status ────────────────────────────────────────────
|
|
65
|
+
server.tool("get_network_status", "Check the current multiplayer status: connection state, player count, lobby info, networked objects", {}, async (params) => {
|
|
66
|
+
const res = await bridge.send("get_network_status", params);
|
|
67
|
+
if (!res.success) {
|
|
68
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
|
|
72
|
+
};
|
|
73
|
+
});
|
|
74
|
+
// ── network_spawn ─────────────────────────────────────────────────
|
|
75
|
+
server.tool("network_spawn", "Network-enable a GameObject so it is synchronized across all connected clients. Calls NetworkSpawn()", {
|
|
76
|
+
id: z.string().describe("GUID of the GameObject to network"),
|
|
77
|
+
}, async (params) => {
|
|
78
|
+
const res = await bridge.send("network_spawn", params);
|
|
79
|
+
if (!res.success) {
|
|
80
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
|
|
84
|
+
};
|
|
85
|
+
});
|
|
86
|
+
// ── set_ownership ─────────────────────────────────────────────────
|
|
87
|
+
server.tool("set_ownership", "Transfer network ownership of a GameObject to a different connection, or take/drop ownership", {
|
|
88
|
+
id: z.string().describe("GUID of the networked GameObject"),
|
|
89
|
+
connectionId: z
|
|
90
|
+
.string()
|
|
91
|
+
.optional()
|
|
92
|
+
.describe("GUID of the target connection. Empty string = drop ownership. Omit = take ownership"),
|
|
93
|
+
}, async (params) => {
|
|
94
|
+
const res = await bridge.send("set_ownership", params);
|
|
95
|
+
if (!res.success) {
|
|
96
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
|
|
100
|
+
};
|
|
101
|
+
});
|
|
102
|
+
// ── add_sync_property ─────────────────────────────────────────────
|
|
103
|
+
server.tool("add_sync_property", "Add a [Sync] networked property to a C# script. The property auto-replicates across the network", {
|
|
104
|
+
path: z
|
|
105
|
+
.string()
|
|
106
|
+
.describe("Relative path to the script file (e.g. 'code/Player.cs')"),
|
|
107
|
+
propertyName: z.string().describe("Name for the new property"),
|
|
108
|
+
propertyType: z
|
|
109
|
+
.string()
|
|
110
|
+
.optional()
|
|
111
|
+
.describe("C# type (float, int, string, Vector3, bool, etc.). Defaults to 'float'"),
|
|
112
|
+
syncFlags: z
|
|
113
|
+
.string()
|
|
114
|
+
.optional()
|
|
115
|
+
.describe("Sync flags: 'FromHost' (host→clients only). Omit for bidirectional"),
|
|
116
|
+
defaultValue: z
|
|
117
|
+
.string()
|
|
118
|
+
.optional()
|
|
119
|
+
.describe("Default value expression (e.g. '100f', 'true', 'Vector3.Zero')"),
|
|
120
|
+
}, async (params) => {
|
|
121
|
+
const res = await bridge.send("add_sync_property", params);
|
|
122
|
+
if (!res.success) {
|
|
123
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
|
|
127
|
+
};
|
|
128
|
+
});
|
|
129
|
+
// ── add_rpc_method ────────────────────────────────────────────────
|
|
130
|
+
server.tool("add_rpc_method", "Add an RPC method to a C# script. Supports [Rpc.Broadcast] (all clients), [Rpc.Host] (host only), [Rpc.Owner] (owner only)", {
|
|
131
|
+
path: z
|
|
132
|
+
.string()
|
|
133
|
+
.describe("Relative path to the script file"),
|
|
134
|
+
methodName: z.string().describe("Name for the RPC method"),
|
|
135
|
+
rpcType: z
|
|
136
|
+
.enum(["Broadcast", "Host", "Owner"])
|
|
137
|
+
.optional()
|
|
138
|
+
.describe("RPC type: Broadcast (all), Host (server), Owner (owning client). Defaults to Broadcast"),
|
|
139
|
+
methodParams: z
|
|
140
|
+
.string()
|
|
141
|
+
.optional()
|
|
142
|
+
.describe("Method parameters as C# signature (e.g. 'float damage, Vector3 hitPos')"),
|
|
143
|
+
body: z
|
|
144
|
+
.string()
|
|
145
|
+
.optional()
|
|
146
|
+
.describe("Method body code (without braces). Defaults to a log statement"),
|
|
147
|
+
}, async (params) => {
|
|
148
|
+
const res = await bridge.send("add_rpc_method", params);
|
|
149
|
+
if (!res.success) {
|
|
150
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
151
|
+
}
|
|
152
|
+
return {
|
|
153
|
+
content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
|
|
154
|
+
};
|
|
155
|
+
});
|
|
156
|
+
// ── create_networked_player ───────────────────────────────────────
|
|
157
|
+
server.tool("create_networked_player", "Generate a network-aware player controller with [Sync] properties, owner-only input, and [Rpc.Broadcast] actions", {
|
|
158
|
+
name: z
|
|
159
|
+
.string()
|
|
160
|
+
.optional()
|
|
161
|
+
.describe("Class name. Defaults to 'NetworkedPlayer'"),
|
|
162
|
+
directory: z
|
|
163
|
+
.string()
|
|
164
|
+
.optional()
|
|
165
|
+
.describe("Subdirectory under code/"),
|
|
166
|
+
moveSpeed: z.number().optional().describe("Movement speed. Defaults to 300"),
|
|
167
|
+
includeHealth: z
|
|
168
|
+
.boolean()
|
|
169
|
+
.optional()
|
|
170
|
+
.describe("Include health/damage system with host-authoritative TakeDamage. Defaults to true"),
|
|
171
|
+
}, async (params) => {
|
|
172
|
+
const res = await bridge.send("create_networked_player", params);
|
|
173
|
+
if (!res.success) {
|
|
174
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
175
|
+
}
|
|
176
|
+
return {
|
|
177
|
+
content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
|
|
178
|
+
};
|
|
179
|
+
});
|
|
180
|
+
// ── create_lobby_manager ──────────────────────────────────────────
|
|
181
|
+
server.tool("create_lobby_manager", "Generate a lobby manager script with create/join/leave lobby, player spawning, and connection cleanup", {
|
|
182
|
+
name: z
|
|
183
|
+
.string()
|
|
184
|
+
.optional()
|
|
185
|
+
.describe("Class name. Defaults to 'LobbyManager'"),
|
|
186
|
+
directory: z
|
|
187
|
+
.string()
|
|
188
|
+
.optional()
|
|
189
|
+
.describe("Subdirectory under code/"),
|
|
190
|
+
maxPlayers: z
|
|
191
|
+
.number()
|
|
192
|
+
.optional()
|
|
193
|
+
.describe("Default max players. Defaults to 8"),
|
|
194
|
+
}, async (params) => {
|
|
195
|
+
const res = await bridge.send("create_lobby_manager", params);
|
|
196
|
+
if (!res.success) {
|
|
197
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
198
|
+
}
|
|
199
|
+
return {
|
|
200
|
+
content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
|
|
201
|
+
};
|
|
202
|
+
});
|
|
203
|
+
// ── create_network_events ─────────────────────────────────────────
|
|
204
|
+
server.tool("create_network_events", "Generate a network event handler script implementing INetworkListener for connect/disconnect/chat events", {
|
|
205
|
+
name: z
|
|
206
|
+
.string()
|
|
207
|
+
.optional()
|
|
208
|
+
.describe("Class name. Defaults to 'NetworkEvents'"),
|
|
209
|
+
directory: z
|
|
210
|
+
.string()
|
|
211
|
+
.optional()
|
|
212
|
+
.describe("Subdirectory under code/"),
|
|
213
|
+
includeChat: z
|
|
214
|
+
.boolean()
|
|
215
|
+
.optional()
|
|
216
|
+
.describe("Include a chat message broadcast system. Defaults to false"),
|
|
217
|
+
}, async (params) => {
|
|
218
|
+
const res = await bridge.send("create_network_events", params);
|
|
219
|
+
if (!res.success) {
|
|
220
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
221
|
+
}
|
|
222
|
+
return {
|
|
223
|
+
content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
|
|
224
|
+
};
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
//# sourceMappingURL=networking.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
|
+
* Physics tools: add_physics, add_collider, add_joint, raycast.
|
|
5
|
+
* Manages rigidbodies, colliders, physics constraints, and ray tracing.
|
|
6
|
+
*/
|
|
7
|
+
export declare function registerPhysicsTools(server: McpServer, bridge: BridgeClient): void;
|
|
8
|
+
//# sourceMappingURL=physics.d.ts.map
|