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
| Name | Required | Description | Default |
|---|---|---|---|
| detail | No | Level of detail: 'summary' (default) for counts and top-level dirs, 'full' for complete file listings |
Implementation Reference
- src/tools/project-info.ts:77-178 (handler)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 }] }; } - src/tools/project-info.ts:71-75 (schema)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"), }, - src/tools/project-info.ts:66-180 (registration)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 }] }; } ); } - src/tools/project-info.ts:14-44 (helper)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; } - src/project-config.ts:4-79 (helper)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; }