Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/nnaridz/RbxGenie/llms.txt

Use this file to discover all available pages before exploring further.

The Node.js daemon is the central coordinator of RbxGenie, providing an HTTP API for the MCP server and managing the command queue bridge to the Studio plugin.

Components

The daemon consists of two main TypeScript modules:

server.ts

Express HTTP server with REST endpoints

bridge.ts

Event-driven command queue with promise-based resolution

Server Architecture

Express Application

Location: src/server.ts:8-20
const app = express();
app.use(express.json({ limit: "10mb" }));

// Error handler for malformed JSON
app.use((err: any, _req: express.Request, res: express.Response, next: express.NextFunction) => {
  if (err.type === "entity.parse.failed") {
    res.status(400).json({
      ok: false,
      error: `Invalid JSON body: ${err.message}`,
    });
    return;
  }
  next(err);
});
The server accepts up to 10MB JSON payloads to support large script sources and complex data structures.

Configuration

Location: src/server.ts:22-23
const PORT = process.env.PORT ? parseInt(process.env.PORT) : 7766;
const LONG_POLL_TIMEOUT_MS = 15_000;
7766 — Standard port for RbxGenie daemon

HTTP Endpoints

POST /tool/:name

Execute a tool by enqueueing it in the bridge. Location: src/server.ts:25-43
app.post("/tool/:name", async (req, res) => {
  const tool = req.params.name;
  const args = req.body ?? {};
  const id = uuidv4();

  try {
    const result = await enqueue(id, tool, args);
    res.json({ ok: true, id, result });
  } catch (err: any) {
    const isTimeout = err && typeof err === "object" && err.timeout;
    res.status(500).json({
      ok: false,
      id,
      error: isTimeout ? err.error : String(err),
      timeout: isTimeout ? true : undefined,
      timeoutMs: isTimeout ? err.timeoutMs : undefined,
    });
  }
});
Request:
{
  "path": "Workspace.Part",
  "property": "BrickColor",
  "value": "Bright red"
}
Success Response:
{
  "ok": true,
  "id": "a3f2c1b5-...",
  "result": "Property set"
}
Error Response:
{
  "ok": false,
  "id": "a3f2c1b5-...",
  "error": "Path not found at segment [2] 'Part' in 'Workspace.Part'",
  "timeout": false
}
Timeout Response:
{
  "ok": false,
  "id": "a3f2c1b5-...",
  "error": "Command a3f2c1b5-... timed out after 120000ms",
  "timeout": true,
  "timeoutMs": 120000
}

GET /poll

Long-polling endpoint for the Studio plugin to retrieve pending commands. Location: src/server.ts:45-80
app.get("/poll", (req, res) => {
  const cmd = dequeue();
  if (cmd) {
    res.json({ hasCommand: true, ...cmd });
    return;
  }

  const deadline = Date.now() + LONG_POLL_TIMEOUT_MS;
  let resolved = false;

  const tryDequeue = () => {
    if (resolved) return;
    const cmd = dequeue();
    if (cmd) {
      resolved = true;
      queueEvents.removeListener("enqueued", tryDequeue);
      res.json({ hasCommand: true, ...cmd });
    }
  };

  queueEvents.on("enqueued", tryDequeue);

  const timer = setTimeout(() => {
    if (!resolved) {
      resolved = true;
      queueEvents.removeListener("enqueued", tryDequeue);
      res.json({ hasCommand: false });
    }
  }, LONG_POLL_TIMEOUT_MS);

  req.on("close", () => {
    resolved = true;
    clearTimeout(timer);
    queueEvents.removeListener("enqueued", tryDequeue);
  });
});
1

Immediate Return

If a command is already queued, dequeue and return it immediately
2

Event Listener

Subscribe to "enqueued" event to wake up when a new command arrives
3

Timeout

After 15 seconds, return { hasCommand: false } if no command was queued
4

Cleanup

Remove event listener on timeout, response, or client disconnect
Empty Response (15s timeout):
{ "hasCommand": false }
Command Response:
{
  "hasCommand": true,
  "id": "a3f2c1b5-...",
  "tool": "set_property",
  "args": {
    "path": "Workspace.Part",
    "property": "BrickColor",
    "value": "Bright red"
  }
}

POST /result

Receive command result or error from the Studio plugin. Location: src/server.ts:82-101
app.post("/result", (req, res) => {
  const { id, result, error } = req.body as {
    id: string;
    result?: unknown;
    error?: string;
  };

  if (!id) {
    res.status(400).json({ ok: false, error: "Missing id" });
    return;
  }

  if (error) {
    rejectCommand(id, error);
  } else {
    resolveCommand(id, result);
  }

  res.json({ ok: true });
});
Success Result:
{
  "id": "a3f2c1b5-...",
  "result": { "name": "Part", "className": "Part" }
}
Error Result:
{
  "id": "a3f2c1b5-...",
  "error": "Tool error [set_property]: Workspace.Part.InvalidProperty is not a valid member of Part"
}

GET /health

Health check endpoint for connection verification. Location: src/server.ts:103-105
app.get("/health", (_req, res) => {
  res.json({ ok: true, service: "RbxGenie", port: PORT });
});

Bridge Implementation

Location: src/bridge.ts The bridge manages the command queue and promise-based async resolution.

Data Structures

// bridge.ts:4-5
const queue: PendingCommand[] = [];
const COMMAND_TIMEOUT_MS = 120_000;

// bridge.ts:7
export const queueEvents = new EventEmitter();
The PendingCommand interface (from types.ts:13-21):
interface PendingCommand {
  id: string;                      // UUID generated by server
  tool: string;                    // Tool name (e.g., "get_file_tree")
  args: Record<string, unknown>;   // Tool arguments
  resolve: (value: unknown) => void;  // Promise resolver
  reject: (reason: any) => void;      // Promise rejector
  timer: NodeJS.Timeout;           // 120s timeout timer
  _dispatched?: boolean;           // Marked true after dequeue
}

enqueue()

Add a command to the queue and return a promise. Location: src/bridge.ts:9-28
export function enqueue(
  id: string,
  tool: string,
  args: Record<string, unknown>
): Promise<unknown> {
  return new Promise((resolve, reject) => {
    const timer = setTimeout(() => {
      const idx = queue.findIndex((c) => c.id === id);
      if (idx !== -1) queue.splice(idx, 1);
      reject({
        error: `Command ${id} timed out after ${COMMAND_TIMEOUT_MS}ms`,
        timeout: true,
        timeoutMs: COMMAND_TIMEOUT_MS,
      });
    }, COMMAND_TIMEOUT_MS);

    queue.push({ id, tool, args, resolve, reject, timer });
    queueEvents.emit("enqueued");
  });
}
The "enqueued" event immediately wakes up any long-polling /poll requests, avoiding the 15-second delay.

dequeue()

Retrieve the next undispatched command from the queue. Location: src/bridge.ts:30-35
export function dequeue(): PollResponse | null {
  const cmd = queue.find((c) => !c._dispatched);
  if (!cmd) return null;
  (cmd as PendingCommand & { _dispatched: boolean })._dispatched = true;
  return { id: cmd.id, tool: cmd.tool, args: cmd.args };
}
Commands remain in the queue after being dequeued (marked _dispatched: true) until they are resolved/rejected or timeout. This prevents duplicate dispatch but keeps the promise alive.

resolveCommand()

Resolve a command’s promise with a result. Location: src/bridge.ts:37-44
export function resolveCommand(id: string, result: unknown): boolean {
  const idx = queue.findIndex((c) => c.id === id);
  if (idx === -1) return false;
  const cmd = queue.splice(idx, 1)[0];
  clearTimeout(cmd.timer);
  cmd.resolve(result);
  return true;
}

rejectCommand()

Reject a command’s promise with an error. Location: src/bridge.ts:46-53
export function rejectCommand(id: string, error: string): boolean {
  const idx = queue.findIndex((c) => c.id === id);
  if (idx === -1) return false;
  const cmd = queue.splice(idx, 1)[0];
  clearTimeout(cmd.timer);
  cmd.reject(error);
  return true;
}

Startup Flow

Location: src/server.ts:107-164 The daemon starts with an interactive menu:
function showMenu(): void {
  // ...
  console.log("=== RbxGenie Daemon ===");
  console.log("  1) Start Server");
  console.log("  2) Create SKILLS.md");
  console.log("  3) Exit");
}
1

Menu Display

Show interactive menu with three options
2

Option 1: Start Server

Launch Express server on port 7766, begin accepting requests
3

Option 2: Create SKILLS.md

Download the RbxGenie skill documentation for AI assistants
4

Option 3: Exit

Exit the daemon process

Server Listener

function startServer(): void {
  app.listen(PORT, "127.0.0.1", () => {
    console.log(`[RbxGenie] Daemon listening on http://127.0.0.1:${PORT}`);
  });
}
The server binds to 127.0.0.1 (localhost only) for security. It cannot be accessed from the network.

Error Handling

JSON Parse Errors

Location: src/server.ts:11-20
app.use((err: any, _req: express.Request, res: express.Response, next: express.NextFunction) => {
  if (err.type === "entity.parse.failed") {
    res.status(400).json({
      ok: false,
      error: `Invalid JSON body: ${err.message}`,
    });
    return;
  }
  next(err);
});

Command Timeouts

Commands automatically timeout after 120 seconds:
reject({
  error: `Command ${id} timed out after ${COMMAND_TIMEOUT_MS}ms`,
  timeout: true,
  timeoutMs: COMMAND_TIMEOUT_MS,
});

Tool Execution Errors

Errors from the plugin are passed through:
if (error) {
  rejectCommand(id, error);
} else {
  resolveCommand(id, result);
}

Debugging

The daemon logs key events to console:
console.log(`[RbxGenie] Daemon listening on http://127.0.0.1:${PORT}`);
console.log(`[RbxGenie] Created: ${dest}`);
console.error(`[RbxGenie] Failed to create SKILLS.md: ${err.message}`);
Use a tool like curl to test endpoints directly:
curl http://127.0.0.1:7766/health
curl -X POST http://127.0.0.1:7766/tool/get_place_info -H "Content-Type: application/json" -d '{}'

Next Steps

Studio Plugin

Learn how the Lua plugin polls and executes commands

MCP Server

Understand how AI assistants connect via MCP