Skip to content

Re-encode JSON Schema type IDs with firewall-safe delimiters #80

Description

@IvanMurzak

Summary

Re-encode the JSON Schema type identifiers ReflectorNet emits for MCP tool inputs so they use delimiters that are simultaneously illegal in C# identifiers, legal unescaped in a URI fragment, and absent from every LLM provider's content-firewall blocklist. This supersedes PR #77, which fixed OpenAI but broke Google Gemini.

Context / Motivation

ReflectorNet builds JSON Schema for MCP tool inputs. Non-primitive C# types become $defs keys and $ref values via TypeUtils.GetSchemaTypeId / GetTypeId. Generic, array, and nested C# type-ids contain < > [ ] +, which OpenAI's content firewall rejects raw.

PR #77 (merged) percent-encoded those characters in the $ref values (%5B, %3C, %2B, ...). This fixed OpenAI but broke Google Gemini, whose firewall rejects the % character, so Gemini-CLI can no longer call the tools (see Unity-MCP issue #780). The two providers have disjoint blocklists. Percent-encoding also risks OpenAI strict-structured-output's documented intolerance of escaped $ref pointers, and PR #77 left raw < > [ ] + in the $defs keys — it only encoded the $ref values, leaving the schema asymmetric.

The fix is to drop escaping entirely and re-derive the schema type-id with delimiters that are (a) illegal in C# identifiers (collision-proof — no data-model name can ever clash), (b) legal in a URI fragment unescaped (valid JSON Schema 2020-12), and (c) absent from both firewalls' blocklists.

Delimiter mapping:

  • generic open/close < > -> ( )
  • nested-class + -> -
  • array [] rank-1 -> -1; multi-dim rank N -> -N; jagged repeats suffixes (int[][] -> ...-1-1)
  • keep namespace . and generic-arg separator , (both already pass both firewalls)

Disambiguation: after -, a digit denotes array rank and a letter/underscore denotes a nested class. Since C# identifiers cannot start with a digit, this is unambiguous in every nesting order. The $defs key equals the $ref value (symmetric — no encode/decode asymmetry).

Verified against the current source: GetSchemaTypeId / GetTypeId live in ReflectorNet/src/Utils/TypeUtils.Name.cs, GetSchemaRef / EncodeForJsonSchemaRef in ReflectorNet/src/Utils/Json/JsonSchema.cs, and DecodeSchemaRefChars / TryResolveArrayType / TryResolveCSharpGenericType / TryResolveClassicGenericType in ReflectorNet/src/Utils/TypeUtils.GetType.cs.

Proposed approach

  • ReflectorNet/src/Utils/TypeUtils.Name.cs -> GetTypeId: change the four structural format strings (nested +, generic <>, array [] / [,]) to emit -, ( ), and -<rank>.
  • ReflectorNet/src/Utils/Json/JsonSchema.cs -> GetSchemaRef: drop EncodeForJsonSchemaRef; emit [Ref] = RefValue + typeId raw so the key equals the ref.
  • ReflectorNet/src/Utils/TypeUtils.GetType.cs: replace DecodeSchemaRefChars (percent-decode) with a safe-form -> C#-canonical converter ((-><, )->>, -<digits>->[+commas+], -<ident>->+<ident>) run at the top of each GetType overload so the existing TryResolveArrayType / TryResolveCSharpGenericType / TryResolveClassicGenericType parsers keep working. Mind the assembly-qualified-name edge (a hyphen can appear in assembly names).
  • Tests: update TestSchemaTypeId to the new readable forms; replace TestSchemaRefEncoding's % assertions with assertions that $defs keys AND $ref values contain none of < > [ ] + %; keep TestGetTypeDecodeSchemaRef's round-trip intent (raw id and safe-form resolve to the same Type) for arrays, generics, nested classes, and the combo IList<Outer+Nested[]>.
  • Close PR fix: sanitize JSON Schema $defs keys to produce valid $ref URI references #77 as superseded.

Acceptance criteria

  • $defs keys and $ref values never contain any of < > [ ] + %.
  • Encode/decode round-trips for arbitrary type names with zero collisions (covering arrays, generics, nested classes, and the combo IList<Outer+Nested[]>).
  • All ReflectorNet tests are green on net8.0 and net9.0.
  • Consumer verifiers MCP-Plugin-dotnet and Unity-MCP compile against the change.
  • Generated schema validates against the latest Anthropic, Google (Gemini), and OpenAI (strict structured outputs) models.
  • PR fix: sanitize JSON Schema $defs keys to produce valid $ref URI references #77 is closed as superseded.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions