# ADR 0185: A served schema speaks the dialect it is stamped with

> Source: https://github.com/max-listov/stitchkit/blob/master/docs/decisions/0185-a-served-schema-speaks-the-dialect-it-is-stamped-with.md

---

# ADR 0185 — A served schema speaks the dialect it is stamped with

## Context

A consumer tried to build a CLI whose commands come from a running server — the
composition the CLI surface exists to support — and `mountConnections` threw
before returning a single definition:

```
Error: Reference not found: #/definitions/input/definitions/__schema0
```

Both sides of that sentence were ours: the MCP mount wrote the schema and the
MCP client refused to read it. On one real surface of 205 tools, five carried
such a reference. The mount is all-or-nothing, so the whole connection failed.

Two separate defects sat underneath, and only one of them is the one the error
names.

**The dialect.** `buildToolPresentationSchema` emits draft-07, whose reusable
subschemas live under `definitions`. `presentationMetadata` strips `$schema`
because "the SDK supplies its own dialect" — and the SDK's own dialect is
2020-12, whose keyword is `$defs`. The served document therefore declared
2020-12 and used draft-07's keyword. A reader registers reusable subschemas from
the keyword its declared dialect names, so it registered `$defs` (empty) and then
could not resolve `#/definitions/x` at all. Measured: the identical document
converts cleanly when `$schema` says draft-07 and fails when it says 2020-12 or
says nothing. Nesting was never the cause; it was the thing the error message
happened to point at.

**The nesting.** `namespaceLocalReferences` kept params' and input's local
references apart by wrapping each schema in a per-namespace `definitions` entry,
producing `definitions.input.definitions.__schema0`. That is a valid JSON Pointer
target and an unreadable one: a reader that registers top-level definitions —
which is what a dialect describes — never looks one level deeper.

## Decision

1. **The document matches its stamp.** `presentationMetadata` moves the
   top-level `definitions` to `$defs` and carries its pointers with it, because
   2020-12 is what the SDK stamps on what it is handed. Only the top level
   moves: a `definitions` deeper in a document may be a property literally called
   "definitions", and guessing which is worse than leaving it alone.
2. **Definitions are hoisted to the document root.** A namespace becomes a
   *prefix on the name* (`input__schema0`) instead of a wrapper, so every pointer
   resolves against the root in one step. An `allOf` merge lifts both branches'
   definitions up with it for the same reason.
3. **The client does not believe a contradicted stamp.** A discovered document
   that declares 2020-12 (or declares nothing) while carrying `definitions` and
   no `$defs` is reconciled before conversion. This is not politeness toward
   foreign servers: it is how a consumer reaches every stitchkit server published
   before this release, none of which it can upgrade.
4. **One unconvertible tool does not take the connection down.** The mount builds
   per tool, skips what it cannot build and names it (`onSkippedTool`, or a
   stderr line). A surface of 205 tools is not lost to one schema.

## Consequences

The served bytes change: `definitions` becomes `$defs` and nested blocks become
prefixed top-level ones. Both documents describe the same schema, and the new one
is resolvable by readers that only register what the dialect names — which is
most of them, ours included.

Nothing in the repository asserted that our MCP output and our MCP client agree;
that is how a defect this mechanical survived. `packages/core/tests/mcp-schema-round-trip.test.ts`
mounts a contract, reads the served schema and converts it back, and pins the
dialect against the keyword. It fails if either half changes its layout without
the other.

The wider lesson has nowhere else to live, so it goes here: **a document that
names one dialect and uses another's keyword is not a style question.** It is a
document that says one thing and does another, and every reader that believes it
is wrong in the same way at the same place.
