Skip to content

Shader Graph MCP tools corrupt / produce invalid .shadergraph assets (create, disconnect, add_node) #18

Description

@mrooney

Hello there and thanks for this amazing tool, it has been a real "game changer" (haha!)

While trying to work on some shaders, I ran into some issues. I asked Opus to try to disprove them, and write up a bug report with clean repro scenarios for what it couldn't disprove. I am happy to file these as separate bugs if it's helpful, but I wanted to create this first to see what direction was preferable (and if you can repro and agree these are bugs). Below is the bug report generated by Opus 4.8:

Environment

  • com.anklebreaker.unity-mcp v2.32.0 (Git)
  • Unity 6000.3.15f1, com.unity.shadergraph (bundled)
  • Windows 11

Four independent bugs in the unity_shadergraph_* advanced tools. All reproduce on a freshly created graph.


Bug 1 — unity_shadergraph_create ignores the template and writes an empty, invalid graph

Every template (urp_lit, urp_unlit, blank) writes the identical 809-byte file: no UniversalTarget, no SubTarget, no BlockNodes, m_Properties: [], and an m_OutputNode referencing an id (da732cfa…) that does not exist in m_Nodes.

Reproduce

unity_advanced_tool unity_shadergraph_create {"path":"Assets/T/A.shadergraph","template":"urp_lit"}
unity_advanced_tool unity_shadergraph_create {"path":"Assets/T/B.shadergraph","template":"urp_unlit"}
unity_advanced_tool unity_shadergraph_create {"path":"Assets/T/C.shadergraph","template":"blank"}

All three files are byte-identical (809 chars). Compare against a real authored URP Lit graph (17 KB+, with UniversalTarget + UniversalLitSubTarget + 9 surface/vertex BlockNodes).

The full produced file:

{
    "m_SGVersion": 3,
    "m_Type": "UnityEditor.ShaderGraph.GraphData",
    "m_ObjectId": "c5db29678c40486dae73be5271e8cf74",
    "m_Properties": [],
    "m_Keywords": [],
    "m_Dropdowns": [],
    "m_CategoryData": [],
    "m_Nodes": [],
    "m_GroupDatas": [],
    "m_StickyNoteDatas": [],
    "m_Edges": [],
    "m_VertexContext": { "m_Position": { "x": 0.0, "y": 0.0 }, "m_Blocks": [] },
    "m_FragmentContext": { "m_Position": { "x": 200.0, "y": 0.0 }, "m_Blocks": [] },
    "m_PreviewData": { "serializedMesh": { "m_SerializedMesh": "", "m_Guid": "" } },
    "m_Path": "Shader Graphs",
    "m_GraphPrecision": 1,
    "m_PreviewMode": 2,
    "m_OutputNode": { "m_Id": "da732cfa5b4444a59a244468f6f3ad02" }
}
  • Expected: urp_lit produces a graph with a UniversalTarget / UniversalLitSubTarget and the standard surface + vertex block nodes; the templates differ from one another.
  • Actual: all templates yield the same empty graph with a dangling m_OutputNode; unusable as a starting point.

Bug 2 — unity_shadergraph_disconnect deletes the wrong edges and corrupts survivors

Two distinct failures.

2a. Matches by output slot only

Disconnect removes every edge fanning out of the named output node + slot, ignoring the input node/slot.

// nodes: Multiply (M), Add (A); two edges from M.Out(slot 2):
unity_shadergraph_connect    {"path":P,"outputNodeId":M,"outputSlotId":2,"inputNodeId":A,"inputSlotId":0}
unity_shadergraph_connect    {"path":P,"outputNodeId":M,"outputSlotId":2,"inputNodeId":A,"inputSlotId":1}
unity_shadergraph_disconnect {"path":P,"outputNodeId":M,"outputSlotId":2,"inputNodeId":A,"inputSlotId":0}

Returns {"removedEdges":1,"remainingEdges":0}; file m_Edges is now []. The M.Out → A.B edge (input slot 1) was destroyed too. Edges from a different output slot are correctly preserved, confirming the match ignores the input.

2b. Corrupts surviving multi-line edges

When a surviving edge is in standard multi-line format (as the Shader Graph editor and hand/JSON authoring write it), disconnect re-emits it with blanked node ids and zeroed slots:

"m_Edges": [{"m_OutputSlot":{"m_Node":{"m_Id":""},"m_SlotId":0},"m_InputSlot":{"m_Node":{"m_Id":""},"m_SlotId":0}}]

Original ids/slots were M(118b…)/2 → A(37b2…)/0 etc. The reported remainingEdges count is also wrong.

  • Expected: remove exactly the one edge matching all four of {outputNode, outputSlot, inputNode, inputSlot}; leave every other edge byte-faithful.
  • Actual: removes all edges sharing the output slot, and blanks/zeros any surviving multi-line edge.

Note: unity_shadergraph_remove_node handles the same array correctly (removes connected edges, re-emits survivors with full ids and slots), so the regression is specific to disconnect's edge-matching / rewriting path.


Bug 3 — unity_shadergraph_add_node cannot bind a PropertyNode, producing an asset that fails import

Creating a PropertyNode and passing the blackboard property id is ignored; the node is written with "m_Property":{"m_Id":""} and empty m_Slots.

unity_shadergraph_add_node {"path":P,"nodeType":"Property","property":<id>,"propertyId":<id>,"propertyName":"TestColor"}

Result node:

{ "m_Type":"UnityEditor.ShaderGraph.PropertyNode", "m_Slots":[], "m_Property":{"m_Id":""} }

On the next import this throws and fails the whole asset:

NullReferenceException: Object reference not set to an instance of an object
  at UnityEditor.ShaderGraph.PropertyNode.AddOutputSlot () (PropertyNode.cs:86)
  at UnityEditor.ShaderGraph.PropertyNode.OnEnable () (PropertyNode.cs:74)
  at UnityEditor.ShaderGraph.GraphData.OnEnable () (GraphData.cs:2929)
  at UnityEditor.ShaderGraph.ShaderGraphImporter.OnImportAsset (...) (ShaderGraphImporter.cs:240)
  at UnityMCP.Editor.MCPShaderGraphCommands:AddGraphNode (MCPShaderGraphCommands.cs:877)
Asset import failed, "Assets/.../X.shadergraph" > NullReferenceException ...
  • Expected: either bind the PropertyNode to the given property (so the importer creates output slot 0 from the property type), or reject the call. Don't leave an unbindable node that breaks import.
  • Actual: unbound PropertyNode written; every subsequent import throws and the asset fails to compile.

Bug 4 — unity_shadergraph_add_node ignores the x / y position

unity_shadergraph_add_node {"path":P,"nodeType":"Multiply","x":150,"y":250}

Returns "position":{"x":0,"y":0} and writes m_DrawState.m_Position as (0,0). Every scripted node stacks at the origin.

  • Expected: the passed x / y land in m_DrawState.m_Position.
  • Actual: always (0,0); the supplied coordinates are dropped.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions