Skip to content

SerializedMember serialization emits name:null, violating the tool output schema (omit name when null) #86

Description

@IvanMurzak

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

  • SerializedMemberConverter.Write omits the name property when value.name == null; still writes it when non-null.
  • Unit test: serializing a SerializedMember with null name produces JSON with NO name key (and validates against SerializedMemberConverter.Schema); with non-null name the key is present and a string.
  • Existing tests green (net8.0 + net9.0). No version bump / no release in this PR.

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions