Summary
SerializedMember serialization can emit "name": null, which violates the tool's own advertised output JSON Schema and gets rejected by strict MCP clients (e.g. Copilot):
MCP error -32602: Structured content does not match the tool's output schema: data/result/name must be string
Reproduced via assets-get-data on a Material (root SerializedMember.name was null). Every tool that returns a SerializedMember tree is affected (assets-get-data, object-get-data, gameobject-component-get, gameobject-find, scene-get-data, all *-modify, etc.) because they share the same $defs entry.
Root cause
- The hard-coded schema in
src/Converter/Json/SerializedMemberConverter.cs (≈ lines 33-40) declares name as {"type":"string"} (no null), and name is correctly NOT in required (only typeName is, ≈ line 75).
- But
SerializedMember.name is string? (src/Model/SerializedMember.cs) and is legitimately null for root/unnamed/value-only members and SerializedMember.Null(type) placeholders.
SerializedMemberConverter.Write (≈ line 156) always writes the name key (writer.WriteString("name", value.name)), emitting JSON null when value.name == null. null is not a string → genuine schema violation. The client is correct to reject.
Fix (targeted, minimal, low-risk)
In SerializedMemberConverter.Write, omit the name key when it is null (write it only when non-null). Because name is not in required, an absent key is schema-valid; a present key is always a string. This makes it impossible to emit data that violates our own schema for name.
Do NOT widen the schema to ["string","null"] — JsonSchema.Internal.cs PostprocessFields actively strips "null" out of type unions, so that path would fight itself. Omit-when-null is the clean fix.
Acceptance criteria
Out of scope (separate follow-up)
The broader systemic nullability blind spot in JsonSchema.Internal.cs (generated schemas not honoring NRT/Nullable<T>; PostprocessFields stripping "null") — tracked separately; this issue is the targeted contract fix for SerializedMember.name.
Reported from a live assets-get-data call on a Material via Copilot.
Summary
SerializedMemberserialization can emit"name": null, which violates the tool's own advertised output JSON Schema and gets rejected by strict MCP clients (e.g. Copilot):Reproduced via
assets-get-dataon a Material (rootSerializedMember.namewas null). Every tool that returns aSerializedMembertree is affected (assets-get-data,object-get-data,gameobject-component-get,gameobject-find,scene-get-data, all*-modify, etc.) because they share the same$defsentry.Root cause
src/Converter/Json/SerializedMemberConverter.cs(≈ lines 33-40) declaresnameas{"type":"string"}(nonull), andnameis correctly NOT inrequired(onlytypeNameis, ≈ line 75).SerializedMember.nameisstring?(src/Model/SerializedMember.cs) and is legitimately null for root/unnamed/value-only members andSerializedMember.Null(type)placeholders.SerializedMemberConverter.Write(≈ line 156) always writes thenamekey (writer.WriteString("name", value.name)), emitting JSONnullwhenvalue.name == null.nullis not astring→ genuine schema violation. The client is correct to reject.Fix (targeted, minimal, low-risk)
In
SerializedMemberConverter.Write, omit thenamekey when it is null (write it only when non-null). Becausenameis not inrequired, an absent key is schema-valid; a present key is always astring. This makes it impossible to emit data that violates our own schema forname.Do NOT widen the schema to
["string","null"]—JsonSchema.Internal.csPostprocessFieldsactively strips"null"out of type unions, so that path would fight itself. Omit-when-null is the clean fix.Acceptance criteria
SerializedMemberConverter.Writeomits thenameproperty whenvalue.name == null; still writes it when non-null.SerializedMemberwith nullnameproduces JSON with NOnamekey (and validates againstSerializedMemberConverter.Schema); with non-nullnamethe key is present and a string.Out of scope (separate follow-up)
The broader systemic nullability blind spot in
JsonSchema.Internal.cs(generated schemas not honoring NRT/Nullable<T>;PostprocessFieldsstripping"null") — tracked separately; this issue is the targeted contract fix forSerializedMember.name.Reported from a live
assets-get-datacall on a Material via Copilot.