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.
Files changed (45) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +90 -0
  3. package/dist/index.d.ts +13 -0
  4. package/dist/index.js +155 -0
  5. package/dist/tools/assets.d.ts +8 -0
  6. package/dist/tools/assets.js +80 -0
  7. package/dist/tools/audio.d.ts +8 -0
  8. package/dist/tools/audio.js +101 -0
  9. package/dist/tools/components.d.ts +11 -0
  10. package/dist/tools/components.js +78 -0
  11. package/dist/tools/console.d.ts +8 -0
  12. package/dist/tools/console.js +59 -0
  13. package/dist/tools/discovery.d.ts +13 -0
  14. package/dist/tools/discovery.js +58 -0
  15. package/dist/tools/gameobjects.d.ts +4 -0
  16. package/dist/tools/gameobjects.js +197 -0
  17. package/dist/tools/materials.d.ts +8 -0
  18. package/dist/tools/materials.js +82 -0
  19. package/dist/tools/networking.d.ts +11 -0
  20. package/dist/tools/networking.js +227 -0
  21. package/dist/tools/physics.d.ts +8 -0
  22. package/dist/tools/physics.js +130 -0
  23. package/dist/tools/playmode.d.ts +11 -0
  24. package/dist/tools/playmode.js +140 -0
  25. package/dist/tools/prefabs.d.ts +8 -0
  26. package/dist/tools/prefabs.js +94 -0
  27. package/dist/tools/project.d.ts +12 -0
  28. package/dist/tools/project.js +90 -0
  29. package/dist/tools/publishing.d.ts +11 -0
  30. package/dist/tools/publishing.js +168 -0
  31. package/dist/tools/scenes.d.ts +8 -0
  32. package/dist/tools/scenes.js +75 -0
  33. package/dist/tools/scripts.d.ts +9 -0
  34. package/dist/tools/scripts.js +132 -0
  35. package/dist/tools/status.d.ts +8 -0
  36. package/dist/tools/status.js +49 -0
  37. package/dist/tools/templates.d.ts +11 -0
  38. package/dist/tools/templates.js +135 -0
  39. package/dist/tools/ui.d.ts +8 -0
  40. package/dist/tools/ui.js +116 -0
  41. package/dist/tools/world.d.ts +20 -0
  42. package/dist/tools/world.js +272 -0
  43. package/dist/transport/bridge-client.d.ts +60 -0
  44. package/dist/transport/bridge-client.js +239 -0
  45. package/package.json +54 -0
@@ -0,0 +1,239 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import * as os from "os";
4
+ /**
5
+ * File-based IPC client that communicates with the s&box Bridge Addon.
6
+ */
7
+ export class BridgeClient {
8
+ requestCounter = 0;
9
+ ipcDir;
10
+ connected = false;
11
+ lastPongTime = 0;
12
+ host;
13
+ port;
14
+ static POLL_INTERVAL_MS = 50; // 50ms polling for responses
15
+ static STATUS_CHECK_INTERVAL_MS = 5000;
16
+ constructor(host = "127.0.0.1", port = 29015) {
17
+ this.host = host;
18
+ this.port = port;
19
+ this.ipcDir = path.join(os.tmpdir(), "sbox-bridge-ipc");
20
+ }
21
+ /**
22
+ * Check if the s&box Bridge is running by looking for the status file.
23
+ */
24
+ async connect() {
25
+ // Ensure IPC directory exists
26
+ if (!fs.existsSync(this.ipcDir)) {
27
+ fs.mkdirSync(this.ipcDir, { recursive: true });
28
+ }
29
+ const statusPath = path.join(this.ipcDir, "status.json");
30
+ if (fs.existsSync(statusPath)) {
31
+ try {
32
+ const status = JSON.parse(fs.readFileSync(statusPath, "utf8"));
33
+ if (status.running) {
34
+ this.connected = true;
35
+ this.lastPongTime = Date.now();
36
+ return;
37
+ }
38
+ }
39
+ catch {
40
+ // Status file exists but is malformed
41
+ }
42
+ }
43
+ throw new Error(`Cannot connect to s&box Bridge. No status file found at ${statusPath}. Is s&box running with the Bridge Addon?`);
44
+ }
45
+ /**
46
+ * Send a command to the s&box Bridge and wait for its response.
47
+ */
48
+ async send(command, params = {}, timeoutMs = 30000) {
49
+ // Try to connect if not connected
50
+ if (!this.connected) {
51
+ try {
52
+ await this.connect();
53
+ }
54
+ catch {
55
+ return {
56
+ id: "",
57
+ success: false,
58
+ error: "Not connected to s&box Bridge. Make sure s&box is running with the Bridge Addon installed.",
59
+ };
60
+ }
61
+ }
62
+ const id = `${++this.requestCounter}_${Date.now()}`;
63
+ const request = { id, command, params };
64
+ // Ensure IPC directory exists
65
+ if (!fs.existsSync(this.ipcDir)) {
66
+ fs.mkdirSync(this.ipcDir, { recursive: true });
67
+ }
68
+ // Write request file
69
+ const reqPath = path.join(this.ipcDir, `req_${id}.json`);
70
+ const resPath = path.join(this.ipcDir, `res_${id}.json`);
71
+ try {
72
+ fs.writeFileSync(reqPath, JSON.stringify(request), "utf8");
73
+ }
74
+ catch (err) {
75
+ return {
76
+ id,
77
+ success: false,
78
+ error: `Failed to write request file: ${err}`,
79
+ };
80
+ }
81
+ // Poll for response file
82
+ const startTime = Date.now();
83
+ return new Promise((resolve) => {
84
+ const poll = setInterval(() => {
85
+ // Check timeout
86
+ if (Date.now() - startTime > timeoutMs) {
87
+ clearInterval(poll);
88
+ // Clean up request file if still there
89
+ try {
90
+ if (fs.existsSync(reqPath))
91
+ fs.unlinkSync(reqPath);
92
+ }
93
+ catch { }
94
+ resolve({
95
+ id,
96
+ success: false,
97
+ error: `Request timed out after ${timeoutMs}ms`,
98
+ });
99
+ return;
100
+ }
101
+ // Check for response file
102
+ if (fs.existsSync(resPath)) {
103
+ try {
104
+ // Strip UTF-8 BOM that C#'s File.WriteAllText prepends
105
+ const responseJson = fs.readFileSync(resPath, "utf8").replace(/^\uFEFF/, "");
106
+ const response = JSON.parse(responseJson);
107
+ // Clean up response file
108
+ try {
109
+ fs.unlinkSync(resPath);
110
+ }
111
+ catch { }
112
+ clearInterval(poll);
113
+ this.lastPongTime = Date.now();
114
+ resolve(response);
115
+ }
116
+ catch {
117
+ // Response file might be partially written, try again next poll
118
+ }
119
+ }
120
+ }, BridgeClient.POLL_INTERVAL_MS);
121
+ });
122
+ }
123
+ /**
124
+ * Send multiple commands as a batch.
125
+ */
126
+ async sendBatch(commands, timeoutMs = 30000) {
127
+ if (!this.connected) {
128
+ try {
129
+ await this.connect();
130
+ }
131
+ catch {
132
+ return {
133
+ id: "",
134
+ success: false,
135
+ error: "Not connected to s&box Bridge.",
136
+ };
137
+ }
138
+ }
139
+ const id = `batch_${++this.requestCounter}_${Date.now()}`;
140
+ const request = { id, commands };
141
+ if (!fs.existsSync(this.ipcDir)) {
142
+ fs.mkdirSync(this.ipcDir, { recursive: true });
143
+ }
144
+ const reqPath = path.join(this.ipcDir, `req_${id}.json`);
145
+ try {
146
+ fs.writeFileSync(reqPath, JSON.stringify(request), "utf8");
147
+ }
148
+ catch (err) {
149
+ return {
150
+ id,
151
+ success: false,
152
+ error: `Failed to write request file: ${err}`,
153
+ };
154
+ }
155
+ const resPath = path.join(this.ipcDir, `res_${id}.json`);
156
+ const startTime = Date.now();
157
+ return new Promise((resolve) => {
158
+ const poll = setInterval(() => {
159
+ if (Date.now() - startTime > timeoutMs) {
160
+ clearInterval(poll);
161
+ try {
162
+ if (fs.existsSync(reqPath))
163
+ fs.unlinkSync(reqPath);
164
+ }
165
+ catch { }
166
+ resolve({
167
+ id,
168
+ success: false,
169
+ error: `Batch request timed out after ${timeoutMs}ms`,
170
+ });
171
+ return;
172
+ }
173
+ if (fs.existsSync(resPath)) {
174
+ try {
175
+ // Strip UTF-8 BOM that C#'s File.WriteAllText prepends
176
+ const responseJson = fs.readFileSync(resPath, "utf8").replace(/^\uFEFF/, "");
177
+ const response = JSON.parse(responseJson);
178
+ try {
179
+ fs.unlinkSync(resPath);
180
+ }
181
+ catch { }
182
+ clearInterval(poll);
183
+ this.lastPongTime = Date.now();
184
+ resolve(response);
185
+ }
186
+ catch { }
187
+ }
188
+ }, BridgeClient.POLL_INTERVAL_MS);
189
+ });
190
+ }
191
+ /**
192
+ * Check if bridge is alive by looking for status file.
193
+ */
194
+ async ping() {
195
+ const statusPath = path.join(this.ipcDir, "status.json");
196
+ const start = Date.now();
197
+ try {
198
+ if (fs.existsSync(statusPath)) {
199
+ const status = JSON.parse(fs.readFileSync(statusPath, "utf8"));
200
+ if (status.running) {
201
+ this.lastPongTime = Date.now();
202
+ return Date.now() - start;
203
+ }
204
+ }
205
+ }
206
+ catch { }
207
+ return -1;
208
+ }
209
+ isConnected() {
210
+ // Re-check status file
211
+ const statusPath = path.join(this.ipcDir, "status.json");
212
+ try {
213
+ if (fs.existsSync(statusPath)) {
214
+ const status = JSON.parse(fs.readFileSync(statusPath, "utf8"));
215
+ this.connected = !!status.running;
216
+ }
217
+ else {
218
+ this.connected = false;
219
+ }
220
+ }
221
+ catch {
222
+ this.connected = false;
223
+ }
224
+ return this.connected;
225
+ }
226
+ getHost() {
227
+ return this.host;
228
+ }
229
+ getPort() {
230
+ return this.port;
231
+ }
232
+ getLastPongTime() {
233
+ return this.lastPongTime;
234
+ }
235
+ disconnect() {
236
+ this.connected = false;
237
+ }
238
+ }
239
+ //# sourceMappingURL=bridge-client.js.map
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "sbox-mcp-server",
3
+ "version": "1.2.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
+ "start": "node dist/index.js",
20
+ "dev": "tsc --watch",
21
+ "prepublishOnly": "npm run build"
22
+ },
23
+ "keywords": [
24
+ "mcp",
25
+ "sbox",
26
+ "s&box",
27
+ "game-engine",
28
+ "claude",
29
+ "ai",
30
+ "source2",
31
+ "model-context-protocol"
32
+ ],
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "git+https://github.com/LouSputthole/Sbox-Claude.git"
36
+ },
37
+ "author": "sboxskins.gg (https://sboxskins.gg)",
38
+ "homepage": "https://sboxskins.gg",
39
+ "bugs": {
40
+ "url": "https://github.com/LouSputthole/Sbox-Claude/issues"
41
+ },
42
+ "dependencies": {
43
+ "@modelcontextprotocol/sdk": "^1.12.1",
44
+ "zod": "^3.24.0"
45
+ },
46
+ "devDependencies": {
47
+ "@types/node": "^22.0.0",
48
+ "typescript": "^5.7.0"
49
+ },
50
+ "engines": {
51
+ "node": ">=18.0.0"
52
+ },
53
+ "license": "GPL-3.0-or-later"
54
+ }