@tool extends "res://addons/godot_mcp/commands/base_command.gd" func get_commands() -> Dictionary: return { "create_shader": _create_shader, "read_shader": _read_shader, "edit_shader": _edit_shader, "assign_shader_material": _assign_shader_material, "set_shader_param": _set_shader_param, "get_shader_params": _get_shader_params, } func _create_shader(params: Dictionary) -> Dictionary: var result := require_string(params, "path") if result[1] != null: return result[1] var path: String = result[0] var content: String = optional_string(params, "content", "") var shader_type: String = optional_string(params, "shader_type", "spatial") var force: bool = optional_bool(params, "force", false) var guard := guard_text_resource_write(path, force) if not guard.is_empty(): return guard if content.is_empty(): match shader_type: "spatial": content = "shader_type spatial;\n\nvoid vertex() {\n\t// Called for every vertex\n}\n\nvoid fragment() {\n\t// Called for every pixel\n\tALBEDO = vec3(1.0);\n}\n" "canvas_item": content = "shader_type canvas_item;\n\nvoid vertex() {\n\t// Called for every vertex\n}\n\nvoid fragment() {\n\t// Called for every pixel\n\tCOLOR = vec4(1.0);\n}\n" "particles": content = "shader_type particles;\n\nvoid start() {\n\t// Called when particle spawns\n}\n\nvoid process() {\n\t// Called every frame per particle\n}\n" "sky": content = "shader_type sky;\n\nvoid sky() {\n\tCOLOR = vec3(0.3, 0.5, 0.8);\n}\n" # Ensure directory exists var dir_path := path.get_base_dir() if not DirAccess.dir_exists_absolute(dir_path): DirAccess.make_dir_recursive_absolute(dir_path) var file := FileAccess.open(path, FileAccess.WRITE) if file == null: return error_internal("Cannot create shader: %s" % error_string(FileAccess.get_open_error())) file.store_string(content) file.close() _refresh_loaded_shader(path, content) return success({"path": path, "shader_type": shader_type, "created": true}) func _read_shader(params: Dictionary) -> Dictionary: var result := require_string(params, "path") if result[1] != null: return result[1] var path: String = result[0] if not FileAccess.file_exists(path): return error_not_found("Shader '%s'" % path) var file := FileAccess.open(path, FileAccess.READ) if file == null: return error_internal("Cannot read shader: %s" % error_string(FileAccess.get_open_error())) var content := file.get_as_text() file.close() return success({"path": path, "content": content, "size": content.length()}) func _refresh_loaded_shader(path: String, content: String) -> void: var normalized := normalize_project_path(path) if normalized.is_empty(): return if ResourceLoader.has_cached(normalized): var shader := Shader.new() shader.code = content shader.take_over_path(normalized) shader.emit_changed() EditorInterface.get_resource_filesystem().update_file(normalized) func _edit_shader(params: Dictionary) -> Dictionary: var result := require_string(params, "path") if result[1] != null: return result[1] var path: String = result[0] if not FileAccess.file_exists(path): return error_not_found("Shader '%s'" % path) var force: bool = optional_bool(params, "force", false) var guard := guard_text_resource_write(path, force) if not guard.is_empty(): return guard var changes_made := 0 var content := "" if params.has("content"): content = str(params["content"]) changes_made = 1 elif params.has("replacements") and params["replacements"] is Array: # Read current var file := FileAccess.open(path, FileAccess.READ) if file == null: return error_internal("Cannot read shader") content = file.get_as_text() file.close() for replacement in params["replacements"]: if replacement is Dictionary: var search: String = replacement.get("search", "") var replace: String = replacement.get("replace", "") if not search.is_empty() and content.contains(search): content = content.replace(search, replace) changes_made += 1 if changes_made > 0: var file := FileAccess.open(path, FileAccess.WRITE) if file == null: return error_internal("Cannot write shader: %s" % error_string(FileAccess.get_open_error())) file.store_string(content) file.close() _refresh_loaded_shader(path, content) return success({"path": path, "changes_made": changes_made}) func _assign_shader_material(params: Dictionary) -> Dictionary: var result := require_string(params, "node_path") if result[1] != null: return result[1] var node_path: String = result[0] var result2 := require_string(params, "shader_path") if result2[1] != null: return result2[1] var shader_path: String = result2[0] var node := find_node_by_path(node_path) if node == null: return error_not_found("Node at '%s'" % node_path) if not ResourceLoader.exists(shader_path): return error_not_found("Shader '%s'" % shader_path) var shader: Shader = load(shader_path) if shader == null: return error_internal("Failed to load shader") var material := ShaderMaterial.new() material.shader = shader if node is CanvasItem: set_property_with_undo(node, "material", material, "MCP: Assign shader material") elif node is MeshInstance3D: set_property_with_undo(node, "material_override", material, "MCP: Assign shader material") else: # Try generic material property if "material" in node: set_property_with_undo(node, "material", material, "MCP: Assign shader material") else: return error_invalid_params("Node '%s' (%s) does not support materials" % [node_path, node.get_class()]) return success({"node_path": node_path, "shader_path": shader_path, "assigned": true}) func _set_shader_param(params: Dictionary) -> Dictionary: var result := require_string(params, "node_path") if result[1] != null: return result[1] var node_path: String = result[0] var result2 := require_string(params, "param") if result2[1] != null: return result2[1] var param_name: String = result2[0] var node := find_node_by_path(node_path) if node == null: return error_not_found("Node at '%s'" % node_path) var material: ShaderMaterial = null if node is CanvasItem and (node as CanvasItem).material is ShaderMaterial: material = (node as CanvasItem).material elif node is MeshInstance3D and (node as MeshInstance3D).material_override is ShaderMaterial: material = (node as MeshInstance3D).material_override if material == null: return error(-32000, "Node has no ShaderMaterial") var value = params.get("value") if value is String: var s: String = value var expr := Expression.new() if expr.parse(s) == OK: var parsed = expr.execute() if parsed != null: value = parsed material.set_shader_parameter(param_name, value) return success({"node_path": node_path, "param": param_name, "value": str(value)}) func _get_shader_params(params: Dictionary) -> Dictionary: var result := require_string(params, "node_path") if result[1] != null: return result[1] var node_path: String = result[0] var node := find_node_by_path(node_path) if node == null: return error_not_found("Node at '%s'" % node_path) var material: ShaderMaterial = null if node is CanvasItem and (node as CanvasItem).material is ShaderMaterial: material = (node as CanvasItem).material elif node is MeshInstance3D and (node as MeshInstance3D).material_override is ShaderMaterial: material = (node as MeshInstance3D).material_override if material == null: return error(-32000, "Node has no ShaderMaterial") var shader_params: Dictionary = {} for prop in material.get_property_list(): var pname: String = prop["name"] if pname.begins_with("shader_parameter/"): var key := pname.substr(17) shader_params[key] = str(material.get(pname)) return success({"node_path": node_path, "params": shader_params})