Skip to content

DF0020: Non-JSON Value in JSON-Serializable RPC

Message

RPC function "{name}" declares jsonSerializable: true but the value at "{path}" is a {type}.

Cause

The function is declared jsonSerializable: true, which means its args and return value are encoded with strict JSON.stringify (both on the wire and in build dumps). The strict serializer rejects any value that JSON cannot round-trip losslessly:

  • Map, Set, WeakMap, WeakSet
  • Date (silently coerced to ISO string by JSON)
  • BigInt
  • circular references
  • non-plain class instances
  • undefined leaves
  • Symbol
  • Function

When the strict serializer encounters one of these, it throws synchronously at the offending call rather than producing a corrupt payload.

Example

ts
defineRpcFunction({
  name: 'my-plugin:graph',
  jsonSerializable: true,
  handler: () => ({
    nodes: new Map([['a', 1]]), // ← throws DF0020 with type=Map, path="nodes"
  }),
})

Fix

Either drop jsonSerializable: true so the function uses structured-clone-es (round-trips Map, Set, etc.):

ts
defineRpcFunction({
  name: 'my-plugin:graph',
  // jsonSerializable: false (default) — Map/Set survive the wire and the dump
  handler: () => ({
    nodes: new Map([['a', 1]]),
  }),
})

Or convert the payload to a JSON-safe shape (e.g. an array of entries, an ISO string, a plain object) before returning. Note: removing jsonSerializable: true also disables agent exposure; if you need MCP, you must use a JSON-safe shape.

Source

Released under the MIT License.