Skip to main content
Glama

godot_get_project_info

Retrieve Godot project structure details including name, version, scenes, scripts, and directory tree with optional full file listings.

Instructions

Return project structure overview: project name, Godot version, scenes, scripts, autoloads, addons, and directory tree. Uses progressive disclosure — summary by default, full details on request.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
detailNoLevel of detail: 'summary' (default) for counts and top-level dirs, 'full' for complete file listings

Implementation Reference

  • The main handler function that executes the godot_get_project_info tool logic. It retrieves project configuration, collects scenes/scripts/resources files, checks for addons, and returns either summary or full detail output based on the 'detail' parameter.
    async (args) => {
      if (!ctx.projectDir) {
        return { content: [{ type: "text", text: formatError(projectNotFound()) }] };
      }
    
      const config = parseProjectGodot(ctx.projectDir);
      if (!config) {
        return {
          content: [
            {
              type: "text",
              text: formatError({
                message: "No project.godot found.",
                suggestion: "Ensure the MCP server is configured with the correct project path.",
              }),
            },
          ],
        };
      }
    
      const scenes = collectFiles(ctx.projectDir, ctx.projectDir, new Set([".tscn"]), 10);
      const scripts = collectFiles(ctx.projectDir, ctx.projectDir, new Set([".gd"]), 10);
      const resources = collectFiles(ctx.projectDir, ctx.projectDir, new Set([".tres"]), 10);
    
      // Check for addons
      const addons: string[] = [];
      try {
        const addonsDir = resolve(ctx.projectDir, "addons");
        const items = readdirSync(addonsDir, { withFileTypes: true });
        for (const item of items) {
          if (item.isDirectory()) addons.push(item.name);
        }
      } catch {
        // no addons directory
      }
    
      const detail = args.detail ?? "summary";
    
      if (detail === "summary") {
        let text = `## ${config.name}\n\n`;
        if (config.godotVersion) text += `**Godot version:** ${config.godotVersion}\n`;
        text += `**Scenes:** ${scenes.length}\n`;
        text += `**Scripts:** ${scripts.length}\n`;
        text += `**Resources:** ${resources.length}\n`;
        text += `**Autoloads:** ${config.autoloads.length}\n`;
        text += `**Addons:** ${addons.length > 0 ? addons.join(", ") : "none"}\n`;
    
        if (config.autoloads.length > 0) {
          text += `\n### Autoloads\n`;
          for (const a of config.autoloads) {
            text += `- **${a.name}**: ${a.path}\n`;
          }
        }
    
        text += `\n### Directory Structure\n\`\`\`\n`;
        const tree = getDirectoryTree(ctx.projectDir, ctx.projectDir, 3);
        text += tree.join("\n");
        text += `\n\`\`\`\n`;
    
        return { content: [{ type: "text", text }] };
      }
    
      // Full detail
      let text = `## ${config.name}\n\n`;
      if (config.godotVersion) text += `**Godot version:** ${config.godotVersion}\n\n`;
    
      if (config.autoloads.length > 0) {
        text += `### Autoloads\n`;
        for (const a of config.autoloads) {
          text += `- **${a.name}**: ${a.path}\n`;
        }
        text += "\n";
      }
    
      if (config.inputActions.length > 0) {
        text += `### Input Actions\n`;
        for (const a of config.inputActions) {
          text += `- ${a.name}\n`;
        }
        text += "\n";
      }
    
      text += `### Scenes (${scenes.length})\n`;
      for (const s of scenes) {
        text += `- ${s.path} (${s.size} bytes)\n`;
      }
    
      text += `\n### Scripts (${scripts.length})\n`;
      for (const s of scripts) {
        text += `- ${s.path} (${s.size} bytes)\n`;
      }
    
      text += `\n### Resources (${resources.length})\n`;
      for (const r of resources) {
        text += `- ${r.path} (${r.size} bytes)\n`;
      }
    
      text += `\n### Addons\n`;
      text += addons.length > 0 ? addons.map((a) => `- ${a}`).join("\n") : "None";
    
      return { content: [{ type: "text", text }] };
    }
  • Input schema definition using Zod. Defines the optional 'detail' parameter with enum values 'summary' or 'full' to control the level of detail in the output.
      detail: z
        .enum(["summary", "full"])
        .optional()
        .describe("Level of detail: 'summary' (default) for counts and top-level dirs, 'full' for complete file listings"),
    },
  • Registration function that registers the godot_get_project_info tool with the MCP server. Defines the tool name, description, input schema, hints (readOnlyHint, idempotentHint, openWorldHint), and the handler function.
    export function registerProjectInfo(server: McpServer, ctx: ServerContext): void {
      server.tool(
        "godot_get_project_info",
        "Return project structure overview: project name, Godot version, scenes, scripts, autoloads, addons, and directory tree. Uses progressive disclosure — summary by default, full details on request.",
        {
          detail: z
            .enum(["summary", "full"])
            .optional()
            .describe("Level of detail: 'summary' (default) for counts and top-level dirs, 'full' for complete file listings"),
        },
        { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
        async (args) => {
          if (!ctx.projectDir) {
            return { content: [{ type: "text", text: formatError(projectNotFound()) }] };
          }
    
          const config = parseProjectGodot(ctx.projectDir);
          if (!config) {
            return {
              content: [
                {
                  type: "text",
                  text: formatError({
                    message: "No project.godot found.",
                    suggestion: "Ensure the MCP server is configured with the correct project path.",
                  }),
                },
              ],
            };
          }
    
          const scenes = collectFiles(ctx.projectDir, ctx.projectDir, new Set([".tscn"]), 10);
          const scripts = collectFiles(ctx.projectDir, ctx.projectDir, new Set([".gd"]), 10);
          const resources = collectFiles(ctx.projectDir, ctx.projectDir, new Set([".tres"]), 10);
    
          // Check for addons
          const addons: string[] = [];
          try {
            const addonsDir = resolve(ctx.projectDir, "addons");
            const items = readdirSync(addonsDir, { withFileTypes: true });
            for (const item of items) {
              if (item.isDirectory()) addons.push(item.name);
            }
          } catch {
            // no addons directory
          }
    
          const detail = args.detail ?? "summary";
    
          if (detail === "summary") {
            let text = `## ${config.name}\n\n`;
            if (config.godotVersion) text += `**Godot version:** ${config.godotVersion}\n`;
            text += `**Scenes:** ${scenes.length}\n`;
            text += `**Scripts:** ${scripts.length}\n`;
            text += `**Resources:** ${resources.length}\n`;
            text += `**Autoloads:** ${config.autoloads.length}\n`;
            text += `**Addons:** ${addons.length > 0 ? addons.join(", ") : "none"}\n`;
    
            if (config.autoloads.length > 0) {
              text += `\n### Autoloads\n`;
              for (const a of config.autoloads) {
                text += `- **${a.name}**: ${a.path}\n`;
              }
            }
    
            text += `\n### Directory Structure\n\`\`\`\n`;
            const tree = getDirectoryTree(ctx.projectDir, ctx.projectDir, 3);
            text += tree.join("\n");
            text += `\n\`\`\`\n`;
    
            return { content: [{ type: "text", text }] };
          }
    
          // Full detail
          let text = `## ${config.name}\n\n`;
          if (config.godotVersion) text += `**Godot version:** ${config.godotVersion}\n\n`;
    
          if (config.autoloads.length > 0) {
            text += `### Autoloads\n`;
            for (const a of config.autoloads) {
              text += `- **${a.name}**: ${a.path}\n`;
            }
            text += "\n";
          }
    
          if (config.inputActions.length > 0) {
            text += `### Input Actions\n`;
            for (const a of config.inputActions) {
              text += `- ${a.name}\n`;
            }
            text += "\n";
          }
    
          text += `### Scenes (${scenes.length})\n`;
          for (const s of scenes) {
            text += `- ${s.path} (${s.size} bytes)\n`;
          }
    
          text += `\n### Scripts (${scripts.length})\n`;
          for (const s of scripts) {
            text += `- ${s.path} (${s.size} bytes)\n`;
          }
    
          text += `\n### Resources (${resources.length})\n`;
          for (const r of resources) {
            text += `- ${r.path} (${r.size} bytes)\n`;
          }
    
          text += `\n### Addons\n`;
          text += addons.length > 0 ? addons.map((a) => `- ${a}`).join("\n") : "None";
    
          return { content: [{ type: "text", text }] };
        }
      );
    }
  • Helper function 'collectFiles' that recursively collects files with specific extensions from a directory up to a maximum depth. Used to gather scenes (.tscn), scripts (.gd), and resources (.tres) from the project.
    function collectFiles(
      dir: string,
      base: string,
      extensions: Set<string>,
      maxDepth: number,
      currentDepth = 0
    ): FileEntry[] {
      if (currentDepth > maxDepth) return [];
    
      const entries: FileEntry[] = [];
      try {
        const items = readdirSync(dir, { withFileTypes: true });
        for (const item of items) {
          const fullPath = resolve(dir, item.name);
          if (item.isDirectory()) {
            // Skip .godot, .git, addons (unless specifically requested)
            if (item.name.startsWith(".") && item.name !== ".godot") continue;
            entries.push(...collectFiles(fullPath, base, extensions, maxDepth, currentDepth + 1));
          } else if (extensions.has(extname(item.name))) {
            const stat = statSync(fullPath);
            entries.push({
              path: "res://" + relative(base, fullPath),
              size: stat.size,
            });
          }
        }
      } catch {
        // Permission error or similar
      }
      return entries;
    }
  • Helper function 'parseProjectGodot' and its schema 'ProjectConfig' that parses the project.godot file to extract project name, Godot version, autoloads, and input actions. This utility is used by the handler to get project configuration.
    export interface ProjectConfig {
      name: string;
      godotVersion: string | null;
      autoloads: Array<{ name: string; path: string }>;
      inputActions: Array<{ name: string; events: string[] }>;
    }
    
    /**
     * Parse project.godot for key configuration.
     * The file is an INI-like format with [sections].
     */
    export function parseProjectGodot(projectDir: string): ProjectConfig | null {
      const configPath = resolve(projectDir, "project.godot");
      if (!existsSync(configPath)) return null;
    
      const content = readFileSync(configPath, "utf-8");
      const lines = content.split("\n");
    
      const config: ProjectConfig = {
        name: "Unknown",
        godotVersion: null,
        autoloads: [],
        inputActions: [],
      };
    
      let currentSection = "";
    
      for (const line of lines) {
        const trimmed = line.trim();
    
        // Section header
        const sectionMatch = trimmed.match(/^\[(.+)\]$/);
        if (sectionMatch) {
          currentSection = sectionMatch[1];
          continue;
        }
    
        // Key=value pairs
        const kvMatch = trimmed.match(/^([^=]+)=(.+)$/);
        if (!kvMatch) continue;
    
        const key = kvMatch[1].trim();
        const value = kvMatch[2].trim();
    
        if (currentSection === "application") {
          if (key === "config/name") {
            config.name = value.replace(/^"(.*)"$/, "$1");
          }
        }
    
        if (currentSection === "autoload") {
          // Format: AutoloadName="*res://path/to/script.gd"
          const pathStr = value.replace(/^"?\*?(.*?)"?$/, "$1");
          config.autoloads.push({ name: key, path: pathStr });
        }
    
        if (currentSection === "input") {
          // Input actions are complex, just capture the action name
          const actionMatch = key.match(/^(.+?)$/);
          if (actionMatch && !config.inputActions.find((a) => a.name === actionMatch[1])) {
            config.inputActions.push({ name: actionMatch[1], events: [] });
          }
        }
      }
    
      // Try to extract Godot version from config/features
      const featuresMatch = content.match(/config\/features=PackedStringArray\(([^)]+)\)/);
      if (featuresMatch) {
        const versionMatch = featuresMatch[1].match(/"(\d+\.\d+)"/);
        if (versionMatch) {
          config.godotVersion = versionMatch[1];
        }
      }
    
      return config;
    }

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