GDShader — 18 Operations for AI Agents
GDShader files define how Godot games look — materials, vertex and fragment logic, visual effects. act101 navigates shader functions and uniforms structurally, so agents adjust rendering code with the precision GPUs require.
This page is the canonical reference an AI coding agent uses to refactor, query, and analyze GDShader code through the act MCP server. 18 operations available: 0 refactor, 18 query, 0 analysis. Each operation is callable from Claude Code, Cursor, Codex, OpenCode, or any MCP-compatible agent host. Click any operation for a stable anchor link suitable for citation.
Worked GDShader examples
act101 reads a GDShader file's uniform, varying, const, group_uniforms, and local variable declarations as variable declarations, its struct definitions as struct, and its functions — both built-in entry points like vertex()/fragment() and ordinary helper functions — as function. symbols covers the identical set plus two things the skeleton skips: a struct's own fields (as field) and a function's parameters (as parameter); it also gives const declarations their own constant kind instead of the skeleton's flat variable. The unit of structure in this grammar is the declaration wherever it appears: a local variable declared inside fragment()'s body is read the same way a top-level uniform is, so both queries report it alongside the shader's entry points. Each example below is the verbatim output of the command shown, run against the file shown. Query outputs are pretty-printed with the timing block omitted.
Read the ember shader's uniforms, struct, and functions
ember_glow.gdshader is a Godot canvas-item shader for a campfire ember glow effect: a glow uniform group holds glow_color and pulse_speed, an ember_intensity varying carries flicker from vertex() to fragment(), and an EmberSample struct is declared alongside them.
$ act query skeleton ember_glow.gdshader
Before
shader_type canvas_item;
group_uniforms glow;
uniform vec4 glow_color : source_color = vec4(1.0, 0.4, 0.0, 1.0);
uniform float pulse_speed : hint_range(0.0, 5.0) = 1.5;
varying float ember_intensity;
const float EMBER_THRESHOLD = 0.6;
struct EmberSample {
float heat;
float flicker;
};
float compute_flicker(float time) {
return sin(time * pulse_speed) * 0.5 + 0.5;
}
void vertex() {
ember_intensity = compute_flicker(TIME);
}
void fragment() {
float heat = texture(TEXTURE, UV).r;
if (heat > EMBER_THRESHOLD) {
COLOR = glow_color * ember_intensity;
} else {
COLOR = texture(TEXTURE, UV);
}
}
Output
{
"type": "Skeleton",
"declarations": [
{
"kind": "variable",
"name": "glow",
"range": {
"start": {
"file": "ember_glow.gdshader",
"line": 3,
"column": 1,
"byte_offset": 26
},
"end": {
"file": "ember_glow.gdshader",
"line": 3,
"column": 21,
"byte_offset": 46
}
},
"name_range": {
"start": {
"file": "ember_glow.gdshader",
"line": 3,
"column": 16,
"byte_offset": 41
},
"end": {
"file": "ember_glow.gdshader",
"line": 3,
"column": 20,
"byte_offset": 45
}
}
},
{
"kind": "variable",
"name": "glow_color",
"range": {
"start": {
"file": "ember_glow.gdshader",
"line": 4,
"column": 1,
"byte_offset": 47
},
"end": {
"file": "ember_glow.gdshader",
"line": 4,
"column": 67,
"byte_offset": 113
}
},
"name_range": {
"start": {
"file": "ember_glow.gdshader",
"line": 4,
"column": 14,
"byte_offset": 60
},
"end": {
"file": "ember_glow.gdshader",
"line": 4,
"column": 24,
"byte_offset": 70
}
}
},
{
"kind": "variable",
"name": "pulse_speed",
"range": {
"start": {
"file": "ember_glow.gdshader",
"line": 5,
"column": 1,
"byte_offset": 114
},
"end": {
"file": "ember_glow.gdshader",
"line": 5,
"column": 56,
"byte_offset": 169
}
},
"name_range": {
"start": {
"file": "ember_glow.gdshader",
"line": 5,
"column": 15,
"byte_offset": 128
},
"end": {
"file": "ember_glow.gdshader",
"line": 5,
"column": 26,
"byte_offset": 139
}
}
},
{
"kind": "variable",
"name": "ember_intensity",
"range": {
"start": {
"file": "ember_glow.gdshader",
"line": 7,
"column": 1,
"byte_offset": 171
},
"end": {
"file": "ember_glow.gdshader",
"line": 7,
"column": 31,
"byte_offset": 201
}
},
"name_range": {
"start": {
"file": "ember_glow.gdshader",
"line": 7,
"column": 15,
"byte_offset": 185
},
"end": {
"file": "ember_glow.gdshader",
"line": 7,
"column": 30,
"byte_offset": 200
}
}
},
{
"kind": "variable",
"name": "EMBER_THRESHOLD",
"range": {
"start": {
"file": "ember_glow.gdshader",
"line": 9,
"column": 1,
"byte_offset": 203
},
"end": {
"file": "ember_glow.gdshader",
"line": 9,
"column": 35,
"byte_offset": 237
}
},
"name_range": {
"start": {
"file": "ember_glow.gdshader",
"line": 9,
"column": 13,
"byte_offset": 215
},
"end": {
"file": "ember_glow.gdshader",
"line": 9,
"column": 28,
"byte_offset": 230
}
}
},
{
"kind": "struct",
"name": "EmberSample",
"range": {
"start": {
"file": "ember_glow.gdshader",
"line": 11,
"column": 1,
"byte_offset": 239
},
"end": {
"file": "ember_glow.gdshader",
"line": 14,
"column": 3,
"byte_offset": 297
}
},
"name_range": {
"start": {
"file": "ember_glow.gdshader",
"line": 11,
"column": 8,
"byte_offset": 246
},
"end": {
"file": "ember_glow.gdshader",
"line": 11,
"column": 19,
"byte_offset": 257
}
}
},
{
"kind": "function",
"name": "compute_flicker",
"range": {
"start": {
"file": "ember_glow.gdshader",
"line": 16,
"column": 1,
"byte_offset": 299
},
"end": {
"file": "ember_glow.gdshader",
"line": 18,
"column": 2,
"byte_offset": 384
}
},
"name_range": {
"start": {
"file": "ember_glow.gdshader",
"line": 16,
"column": 7,
"byte_offset": 305
},
"end": {
"file": "ember_glow.gdshader",
"line": 16,
"column": 22,
"byte_offset": 320
}
}
},
{
"kind": "function",
"name": "vertex",
"range": {
"start": {
"file": "ember_glow.gdshader",
"line": 20,
"column": 1,
"byte_offset": 386
},
"end": {
"file": "ember_glow.gdshader",
"line": 22,
"column": 2,
"byte_offset": 448
}
},
"name_range": {
"start": {
"file": "ember_glow.gdshader",
"line": 20,
"column": 6,
"byte_offset": 391
},
"end": {
"file": "ember_glow.gdshader",
"line": 20,
"column": 12,
"byte_offset": 397
}
}
},
{
"kind": "function",
"name": "fragment",
"range": {
"start": {
"file": "ember_glow.gdshader",
"line": 24,
"column": 1,
"byte_offset": 450
},
"end": {
"file": "ember_glow.gdshader",
"line": 31,
"column": 2,
"byte_offset": 647
}
},
"name_range": {
"start": {
"file": "ember_glow.gdshader",
"line": 24,
"column": 6,
"byte_offset": 455
},
"end": {
"file": "ember_glow.gdshader",
"line": 24,
"column": 14,
"byte_offset": 463
}
}
},
{
"kind": "variable",
"name": "heat",
"range": {
"start": {
"file": "ember_glow.gdshader",
"line": 25,
"column": 5,
"byte_offset": 472
},
"end": {
"file": "ember_glow.gdshader",
"line": 25,
"column": 41,
"byte_offset": 508
}
},
"name_range": {
"start": {
"file": "ember_glow.gdshader",
"line": 25,
"column": 11,
"byte_offset": 478
},
"end": {
"file": "ember_glow.gdshader",
"line": 25,
"column": 15,
"byte_offset": 482
}
}
}
]
}
The skeleton reports ten declarations: glow, glow_color, pulse_speed, ember_intensity, and the const EMBER_THRESHOLD all as variable, EmberSample as struct, compute_flicker/vertex/fragment as function, and the local heat declared inside fragment()'s body, also as variable — a function's own body is read the same as top-level code.
List the same declarations plus fields and parameters
EmberSample carries two fields, heat and flicker, and compute_flicker takes one parameter, time.
$ act query symbols ember_glow.gdshader
Before
shader_type canvas_item;
group_uniforms glow;
uniform vec4 glow_color : source_color = vec4(1.0, 0.4, 0.0, 1.0);
uniform float pulse_speed : hint_range(0.0, 5.0) = 1.5;
varying float ember_intensity;
const float EMBER_THRESHOLD = 0.6;
struct EmberSample {
float heat;
float flicker;
};
float compute_flicker(float time) {
return sin(time * pulse_speed) * 0.5 + 0.5;
}
void vertex() {
ember_intensity = compute_flicker(TIME);
}
void fragment() {
float heat = texture(TEXTURE, UV).r;
if (heat > EMBER_THRESHOLD) {
COLOR = glow_color * ember_intensity;
} else {
COLOR = texture(TEXTURE, UV);
}
}
Output
{
"type": "Symbols",
"symbols": [
{
"name": "glow",
"kind": "variable",
"range": {
"start": {
"file": "ember_glow.gdshader",
"line": 3,
"column": 16,
"byte_offset": 41
},
"end": {
"file": "ember_glow.gdshader",
"line": 3,
"column": 20,
"byte_offset": 45
}
},
"visibility": "unknown"
},
{
"name": "glow_color",
"kind": "variable",
"range": {
"start": {
"file": "ember_glow.gdshader",
"line": 4,
"column": 14,
"byte_offset": 60
},
"end": {
"file": "ember_glow.gdshader",
"line": 4,
"column": 24,
"byte_offset": 70
}
},
"visibility": "unknown"
},
{
"name": "pulse_speed",
"kind": "variable",
"range": {
"start": {
"file": "ember_glow.gdshader",
"line": 5,
"column": 15,
"byte_offset": 128
},
"end": {
"file": "ember_glow.gdshader",
"line": 5,
"column": 26,
"byte_offset": 139
}
},
"visibility": "unknown"
},
{
"name": "ember_intensity",
"kind": "variable",
"range": {
"start": {
"file": "ember_glow.gdshader",
"line": 7,
"column": 15,
"byte_offset": 185
},
"end": {
"file": "ember_glow.gdshader",
"line": 7,
"column": 30,
"byte_offset": 200
}
},
"visibility": "unknown"
},
{
"name": "EMBER_THRESHOLD",
"kind": "constant",
"range": {
"start": {
"file": "ember_glow.gdshader",
"line": 9,
"column": 13,
"byte_offset": 215
},
"end": {
"file": "ember_glow.gdshader",
"line": 9,
"column": 28,
"byte_offset": 230
}
},
"visibility": "unknown"
},
{
"name": "EmberSample",
"kind": "struct",
"range": {
"start": {
"file": "ember_glow.gdshader",
"line": 11,
"column": 8,
"byte_offset": 246
},
"end": {
"file": "ember_glow.gdshader",
"line": 11,
"column": 19,
"byte_offset": 257
}
},
"visibility": "unknown"
},
{
"name": "heat",
"kind": "field",
"range": {
"start": {
"file": "ember_glow.gdshader",
"line": 12,
"column": 11,
"byte_offset": 270
},
"end": {
"file": "ember_glow.gdshader",
"line": 12,
"column": 15,
"byte_offset": 274
}
},
"visibility": "unknown"
},
{
"name": "flicker",
"kind": "field",
"range": {
"start": {
"file": "ember_glow.gdshader",
"line": 13,
"column": 11,
"byte_offset": 286
},
"end": {
"file": "ember_glow.gdshader",
"line": 13,
"column": 18,
"byte_offset": 293
}
},
"visibility": "unknown"
},
{
"name": "compute_flicker",
"kind": "function",
"range": {
"start": {
"file": "ember_glow.gdshader",
"line": 16,
"column": 7,
"byte_offset": 305
},
"end": {
"file": "ember_glow.gdshader",
"line": 16,
"column": 22,
"byte_offset": 320
}
},
"visibility": "unknown"
},
{
"name": "time",
"kind": "parameter",
"range": {
"start": {
"file": "ember_glow.gdshader",
"line": 16,
"column": 29,
"byte_offset": 327
},
"end": {
"file": "ember_glow.gdshader",
"line": 16,
"column": 33,
"byte_offset": 331
}
},
"visibility": "unknown"
},
{
"name": "vertex",
"kind": "function",
"range": {
"start": {
"file": "ember_glow.gdshader",
"line": 20,
"column": 6,
"byte_offset": 391
},
"end": {
"file": "ember_glow.gdshader",
"line": 20,
"column": 12,
"byte_offset": 397
}
},
"visibility": "unknown"
},
{
"name": "fragment",
"kind": "function",
"range": {
"start": {
"file": "ember_glow.gdshader",
"line": 24,
"column": 6,
"byte_offset": 455
},
"end": {
"file": "ember_glow.gdshader",
"line": 24,
"column": 14,
"byte_offset": 463
}
},
"visibility": "unknown"
},
{
"name": "heat",
"kind": "variable",
"range": {
"start": {
"file": "ember_glow.gdshader",
"line": 25,
"column": 11,
"byte_offset": 478
},
"end": {
"file": "ember_glow.gdshader",
"line": 25,
"column": 15,
"byte_offset": 482
}
},
"visibility": "unknown"
}
]
}
symbols reports thirteen entries: the same set as the skeleton, but EMBER_THRESHOLD now comes back kind constant instead of variable, plus EmberSample's two fields (heat, flicker) and compute_flicker's time parameter; the struct field heat and the local variable heat inside fragment() both appear, one as field and one as variable, at different ranges.
Query
18 query tools, the same on every supported language. Descriptions live in the shared reference: /docs/query-tools.
callers control_flow data_flow definition diagnostics effect_closure effect_summary fix_auto get_type graph import_organize interface mutations references repo_outline skeleton symbols symbols_batch