Skip to main content
Glama

godot_run_project

Launch, stop, or monitor debug output for Godot projects with timestamped logs to manage development workflows.

Instructions

Launch, stop, or get debug output from a running Godot project. Captures stdout/stderr with timestamps.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
actionYesAction to perform
sceneNoScene to launch directly (e.g., res://scenes/main.tscn)

Implementation Reference

  • The main handler function for 'godot_run_project' tool. Implements three actions: 'start' (launches Godot project with optional scene), 'stop' (terminates running process), and 'get_output' (retrieves timestamped stdout/stderr). Manages a ChildProcess instance and output buffer.
    async (args) => {
      if (!ctx.projectDir) {
        return { content: [{ type: "text", text: formatError(projectNotFound()) }] };
      }
    
      if (args.action === "start") {
        if (!ctx.godotBinary) {
          return { content: [{ type: "text", text: formatError(godotNotFound()) }] };
        }
    
        if (runningProcess) {
          return {
            content: [
              {
                type: "text",
                text: "A Godot project is already running. Stop it first with action: 'stop'.",
              },
            ],
          };
        }
    
        const godotArgs = ["--path", ctx.projectDir];
        if (args.scene) {
          godotArgs.push(args.scene);
        }
    
        outputBuffer = [];
        lastReadIndex = 0;
    
        runningProcess = spawnGodotManaged(ctx.godotBinary, godotArgs, {
          cwd: ctx.projectDir,
        });
    
        runningProcess.stdout?.on("data", (chunk: Buffer) => {
          const lines = chunk.toString().split("\n").filter((l) => l.length > 0);
          for (const text of lines) {
            outputBuffer.push({ timestamp: Date.now(), stream: "stdout", text });
          }
          if (outputBuffer.length > MAX_BUFFER_LINES) {
            outputBuffer = outputBuffer.slice(-MAX_BUFFER_LINES);
          }
        });
    
        runningProcess.stderr?.on("data", (chunk: Buffer) => {
          const lines = chunk.toString().split("\n").filter((l) => l.length > 0);
          for (const text of lines) {
            outputBuffer.push({ timestamp: Date.now(), stream: "stderr", text });
          }
          if (outputBuffer.length > MAX_BUFFER_LINES) {
            outputBuffer = outputBuffer.slice(-MAX_BUFFER_LINES);
          }
        });
    
        runningProcess.on("close", () => {
          runningProcess = null;
        });
    
        return {
          content: [
            {
              type: "text",
              text: `Godot project launched (PID: ${runningProcess.pid}). Use action: "get_output" to read debug output, or action: "stop" to terminate.`,
            },
          ],
        };
      }
    
      if (args.action === "stop") {
        if (!runningProcess) {
          return {
            content: [{ type: "text", text: "No Godot project is currently running." }],
          };
        }
    
        runningProcess.kill("SIGTERM");
        runningProcess = null;
    
        return {
          content: [{ type: "text", text: "Godot project stopped." }],
        };
      }
    
      if (args.action === "get_output") {
        const newLines = outputBuffer.slice(lastReadIndex);
        lastReadIndex = outputBuffer.length;
    
        if (newLines.length === 0) {
          return {
            content: [
              {
                type: "text",
                text: runningProcess
                  ? "No new output since last read."
                  : "No output available. Project is not running.",
              },
            ],
          };
        }
    
        const formatted = newLines
          .map(
            (l) =>
              `[${new Date(l.timestamp).toISOString()}] [${l.stream}] ${l.text}`
          )
          .join("\n");
    
        return { content: [{ type: "text", text: formatted }] };
      }
    
      return { content: [{ type: "text", text: "Unknown action." }] };
    }
  • Zod schema definition for tool inputs. Defines 'action' as enum of 'start', 'stop', 'get_output', and optional 'scene' string parameter for specifying which scene to launch.
    {
      action: z
        .enum(["start", "stop", "get_output"])
        .describe("Action to perform"),
      scene: z
        .string()
        .optional()
        .describe("Scene to launch directly (e.g., res://scenes/main.tscn)"),
    },
  • registerProjectRunner function that registers the 'godot_run_project' tool with the MCP server using server.tool() method. Includes tool name, description, schema, hints, and handler.
    export function registerProjectRunner(server: McpServer, ctx: ServerContext): void {
      server.tool(
        "godot_run_project",
        "Launch, stop, or get debug output from a running Godot project. Captures stdout/stderr with timestamps.",
        {
          action: z
            .enum(["start", "stop", "get_output"])
            .describe("Action to perform"),
          scene: z
            .string()
            .optional()
            .describe("Scene to launch directly (e.g., res://scenes/main.tscn)"),
        },
        { readOnlyHint: false, idempotentHint: false, openWorldHint: false },
        async (args) => {
          if (!ctx.projectDir) {
            return { content: [{ type: "text", text: formatError(projectNotFound()) }] };
          }
    
          if (args.action === "start") {
            if (!ctx.godotBinary) {
              return { content: [{ type: "text", text: formatError(godotNotFound()) }] };
            }
    
            if (runningProcess) {
              return {
                content: [
                  {
                    type: "text",
                    text: "A Godot project is already running. Stop it first with action: 'stop'.",
                  },
                ],
              };
            }
    
            const godotArgs = ["--path", ctx.projectDir];
            if (args.scene) {
              godotArgs.push(args.scene);
            }
    
            outputBuffer = [];
            lastReadIndex = 0;
    
            runningProcess = spawnGodotManaged(ctx.godotBinary, godotArgs, {
              cwd: ctx.projectDir,
            });
    
            runningProcess.stdout?.on("data", (chunk: Buffer) => {
              const lines = chunk.toString().split("\n").filter((l) => l.length > 0);
              for (const text of lines) {
                outputBuffer.push({ timestamp: Date.now(), stream: "stdout", text });
              }
              if (outputBuffer.length > MAX_BUFFER_LINES) {
                outputBuffer = outputBuffer.slice(-MAX_BUFFER_LINES);
              }
            });
    
            runningProcess.stderr?.on("data", (chunk: Buffer) => {
              const lines = chunk.toString().split("\n").filter((l) => l.length > 0);
              for (const text of lines) {
                outputBuffer.push({ timestamp: Date.now(), stream: "stderr", text });
              }
              if (outputBuffer.length > MAX_BUFFER_LINES) {
                outputBuffer = outputBuffer.slice(-MAX_BUFFER_LINES);
              }
            });
    
            runningProcess.on("close", () => {
              runningProcess = null;
            });
    
            return {
              content: [
                {
                  type: "text",
                  text: `Godot project launched (PID: ${runningProcess.pid}). Use action: "get_output" to read debug output, or action: "stop" to terminate.`,
                },
              ],
            };
          }
    
          if (args.action === "stop") {
            if (!runningProcess) {
              return {
                content: [{ type: "text", text: "No Godot project is currently running." }],
              };
            }
    
            runningProcess.kill("SIGTERM");
            runningProcess = null;
    
            return {
              content: [{ type: "text", text: "Godot project stopped." }],
            };
          }
    
          if (args.action === "get_output") {
            const newLines = outputBuffer.slice(lastReadIndex);
            lastReadIndex = outputBuffer.length;
    
            if (newLines.length === 0) {
              return {
                content: [
                  {
                    type: "text",
                    text: runningProcess
                      ? "No new output since last read."
                      : "No output available. Project is not running.",
                  },
                ],
              };
            }
    
            const formatted = newLines
              .map(
                (l) =>
                  `[${new Date(l.timestamp).toISOString()}] [${l.stream}] ${l.text}`
              )
              .join("\n");
    
            return { content: [{ type: "text", text: formatted }] };
          }
    
          return { content: [{ type: "text", text: "Unknown action." }] };
        }
      );
    }
  • Helper types and state management for the project runner. Defines OutputLine interface for capturing timestamped output, MAX_BUFFER_LINES constant, and module-level state for runningProcess, outputBuffer, and lastReadIndex.
    interface OutputLine {
      timestamp: number;
      stream: "stdout" | "stderr";
      text: string;
    }
    
    const MAX_BUFFER_LINES = 5000;
    
    let runningProcess: ChildProcess | null = null;
    let outputBuffer: OutputLine[] = [];
    let lastReadIndex = 0;

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/gregario/godot-forge'

If you have feedback or need assistance with the MCP directory API, please join our Discord server