/**
* Take Screenshot Tool
* Captures a screenshot from a running Godot project or creates a temporary screenshot script
*
* ISO/IEC 5055 compliant - Zod validation
* ISO/IEC 25010 compliant - data integrity
*/
import { ToolDefinition, ToolResponse, BaseToolArgs } from '../../server/types.js';
import {
prepareToolArgs,
validateProjectPath,
createSuccessResponse,
} from '../BaseToolHandler.js';
import { createErrorResponse } from '../../utils/ErrorHandler.js';
import { detectGodotPath } from '../../core/PathManager.js';
import { logDebug } from '../../utils/Logger.js';
import { getGodotPool } from '../../core/ProcessPool.js';
import { writeFileSync, existsSync, mkdirSync, unlinkSync } from 'fs';
import { join } from 'path';
import {
TakeScreenshotSchema,
TakeScreenshotInput,
toMcpSchema,
safeValidateInput,
} from '../../core/ZodSchemas.js';
export const takeScreenshotDefinition: ToolDefinition = {
name: 'take_screenshot',
description: 'Take a screenshot of a Godot project by running it briefly',
inputSchema: toMcpSchema(TakeScreenshotSchema),
};
const SCREENSHOT_SCRIPT = `# Temporary screenshot script - auto-generated by godot-mcp
extends Node
var delay_timer := 0.0
var screenshot_taken := false
var target_delay: float
var output_path: String
func _ready():
# Get parameters from command line or use defaults
var args = OS.get_cmdline_args()
target_delay = 1.0
output_path = "res://screenshots/capture.png"
for i in range(args.size()):
if args[i] == "--screenshot-delay" and i + 1 < args.size():
target_delay = float(args[i + 1])
elif args[i] == "--screenshot-output" and i + 1 < args.size():
output_path = args[i + 1]
# Ensure screenshots directory exists
var dir = DirAccess.open("res://")
if dir and not dir.dir_exists("screenshots"):
dir.make_dir("screenshots")
func _process(delta):
if screenshot_taken:
return
delay_timer += delta
if delay_timer >= target_delay:
take_screenshot()
screenshot_taken = true
# Quit after screenshot
await get_tree().create_timer(0.5).timeout
get_tree().quit()
func take_screenshot():
var viewport = get_viewport()
await RenderingServer.frame_post_draw
var img = viewport.get_texture().get_image()
# Ensure the path is absolute for saving
var save_path = output_path
if save_path.begins_with("res://"):
save_path = ProjectSettings.globalize_path(save_path)
var error = img.save_png(save_path)
if error == OK:
print("SCREENSHOT_SUCCESS:" + save_path)
else:
print("SCREENSHOT_ERROR:Failed to save screenshot to " + save_path)
`;
export const handleTakeScreenshot = async (args: BaseToolArgs): Promise<ToolResponse> => {
const preparedArgs = prepareToolArgs(args);
// Zod validation
const validation = safeValidateInput(TakeScreenshotSchema, preparedArgs);
if (!validation.success) {
return createErrorResponse(`Validation failed: ${validation.error}`, [
'Provide a valid path to a Godot project directory',
]);
}
const typedArgs: TakeScreenshotInput = validation.data;
const projectValidationError = validateProjectPath(typedArgs.projectPath);
if (projectValidationError) {
return projectValidationError;
}
try {
const godotPath = await detectGodotPath();
if (!godotPath) {
return createErrorResponse('Could not find a valid Godot executable path', [
'Ensure Godot is installed correctly',
'Set GODOT_PATH environment variable',
]);
}
const delay = typedArgs.delay || 1;
const outputPath = typedArgs.outputPath || 'screenshots/capture.png';
const resOutputPath = `res://${outputPath}`;
logDebug(`Taking screenshot of project: ${typedArgs.projectPath}`);
// Create screenshots directory if needed
const screenshotsDir = join(typedArgs.projectPath, 'screenshots');
if (!existsSync(screenshotsDir)) {
mkdirSync(screenshotsDir, { recursive: true });
}
// Write temporary screenshot script
const tempScriptPath = join(typedArgs.projectPath, '_mcp_screenshot_temp.gd');
writeFileSync(tempScriptPath, SCREENSHOT_SCRIPT, 'utf-8');
// Create a temporary scene that runs the screenshot script
const tempScenePath = join(typedArgs.projectPath, '_mcp_screenshot_temp.tscn');
const tempScene = `[gd_scene load_steps=2 format=3]
[ext_resource type="Script" path="res://_mcp_screenshot_temp.gd" id="1"]
[node name="ScreenshotCapture" type="Node"]
script = ExtResource("1")
`;
writeFileSync(tempScenePath, tempScene, 'utf-8');
try {
// Run the screenshot scene
// If a specific scene is requested, we need a different approach
// For now, we use our temp scene (scenePath support planned for future)
const args = [
'--path',
typedArgs.projectPath,
'res://_mcp_screenshot_temp.tscn',
'--screenshot-delay',
String(delay),
'--screenshot-output',
resOutputPath,
];
logDebug(`Executing via ProcessPool: ${godotPath} ${args.join(' ')}`);
const pool = getGodotPool();
const result = await pool.execute(godotPath, args, {
cwd: typedArgs.projectPath,
timeout: 30000,
});
const output = (result.stdout || '') + (result.stderr || '');
const successMatch = output.match(/SCREENSHOT_SUCCESS:(.+)/);
const errorMatch = output.match(/SCREENSHOT_ERROR:(.+)/);
if (successMatch) {
const fullOutputPath = join(typedArgs.projectPath, outputPath);
return createSuccessResponse(
`Screenshot captured successfully!\n` +
`Output: ${fullOutputPath}\n` +
`Delay: ${delay}s`
);
} else if (errorMatch) {
return createErrorResponse(`Screenshot failed: ${errorMatch[1]}`, [
'Check write permissions for the output directory',
]);
} else {
// Check if file exists anyway
const fullOutputPath = join(typedArgs.projectPath, outputPath);
if (existsSync(fullOutputPath)) {
return createSuccessResponse(
`Screenshot captured successfully!\n` +
`Output: ${fullOutputPath}\n` +
`Delay: ${delay}s`
);
}
return createErrorResponse('Screenshot result unknown - check output directory', [
`Look for file at: ${outputPath}`,
`Raw output: ${output.slice(0, 500)}`,
]);
}
} finally {
// Clean up temporary files
try {
if (existsSync(tempScriptPath)) unlinkSync(tempScriptPath);
if (existsSync(tempScenePath)) unlinkSync(tempScenePath);
} catch {
// Ignore cleanup errors
}
}
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return createErrorResponse(`Failed to take screenshot: ${errorMessage}`, [
'Ensure Godot is installed correctly',
'Check if the project can run without errors',
'Try running the project manually first',
]);
}
};