Follow-up from the docs pass for #1923 (documentation#605). Writing the fragment-key reference table required documenting the same key twice, once per surface, because the two mappers diverge in three places. Each divergence is defensible in isolation; together they mean an author cannot reason about static properties without knowing which consumer is reading.
#1921 already extracted JSON_SCHEMA_SCALAR_TYPES into resources/jsonSchemaTypes.ts so the two type mappers could not drift on recognized scalars. These are the cases that shared set doesn't cover.
1. Unrecognized type names diverge silently
A type name in neither vocabulary — not a DATA_TYPES key, not a JSON Schema scalar ('Text', 'Object', 'Array') — resolves differently:
- MCP:
components/mcp/tools/schemas/derive.ts:77 default: → { type: 'string' }
- OpenAPI:
resources/openApi.ts:187 → new Type(DATA_TYPES['Text'] /* undefined */, 'Text') → {}
One coerces to a wrong-but-confident type, the other emits untyped. Neither warns. A typo in a static properties fragment produces two different wrong answers with no signal to the author.
Suggested: share one resolution helper, and log a warn-once at registration naming the property and the unrecognized type — the author almost certainly meant one of the two vocabularies.
2. enum / format / const / description are dropped by OpenAPI below the top level
openApi.ts:190-198 attaches these only to the top-level props[name]. Nested objects route through attributeToFragment, which deliberately omits enum/format/const (jsonSchemaTypes.ts:121-124), and primitive array items route through new Type(...), which carries only type — dropping description too. MCP's attributeToProperty (derive.ts:113-115) recurses and keeps all of them.
So the same fragment yields a documented, constrained nested property for an LLM and a bare typed one for Swagger UI. description in particular is a plain loss for the OpenAPI consumer.
3. Nullability has three different representations
An author writes nullable: true or type: ['string','null']; both fold to the same internal flag (jsonSchemaTypes.ts:159-165). Emission:
- MCP re-expands to
{ "type": ["string","null"] } (derive.ts:103-109) — never emits a nullable keyword.
- OpenAPI drops it entirely for a top-level property —
openApi.ts:148 reads nullable only to compute required, and never writes it onto the property schema.
- OpenAPI does emit
nullable: true for a property inside a nested object, via attributeToFragment (jsonSchemaTypes.ts:120).
The top-level OpenAPI drop is the actual bug: OpenAPI 3.0.3 has a nullable keyword and we have the information, so a nullable property is currently advertised as non-nullable. The nested/top-level inconsistency within OpenAPI itself is the clearest evidence this wasn't deliberate.
Suggested approach
Route both emit paths through one projector that takes a target dialect (json-schema-2020 for MCP, openapi-3.0.3 for OpenAPI) and handles the two legitimate dialect differences — union-with-null vs nullable: true — in one place, so everything else stays identical by construction. That is a bigger change than #1921 made; splitting item 3 (the top-level nullable drop) out as a standalone fix is reasonable if the unification slips.
Each item is separately observable, so unit coverage should assert MCP and OpenAPI output for the same fragment side by side — the convergence test added in #1921 is the natural home.
Once fixed, the per-surface caveats in the fragment-key table in reference/resources/resource-api.md collapse back to single rows.
Issue generated by kAIle (Claude Opus 4.8).
Follow-up from the docs pass for #1923 (documentation#605). Writing the fragment-key reference table required documenting the same key twice, once per surface, because the two mappers diverge in three places. Each divergence is defensible in isolation; together they mean an author cannot reason about
static propertieswithout knowing which consumer is reading.#1921 already extracted
JSON_SCHEMA_SCALAR_TYPESintoresources/jsonSchemaTypes.tsso the two type mappers could not drift on recognized scalars. These are the cases that shared set doesn't cover.1. Unrecognized type names diverge silently
A type name in neither vocabulary — not a
DATA_TYPESkey, not a JSON Schema scalar ('Text','Object','Array') — resolves differently:components/mcp/tools/schemas/derive.ts:77default:→{ type: 'string' }resources/openApi.ts:187→new Type(DATA_TYPES['Text'] /* undefined */, 'Text')→{}One coerces to a wrong-but-confident type, the other emits untyped. Neither warns. A typo in a
static propertiesfragment produces two different wrong answers with no signal to the author.Suggested: share one resolution helper, and log a warn-once at registration naming the property and the unrecognized type — the author almost certainly meant one of the two vocabularies.
2.
enum/format/const/descriptionare dropped by OpenAPI below the top levelopenApi.ts:190-198attaches these only to the top-levelprops[name]. Nested objects route throughattributeToFragment, which deliberately omitsenum/format/const(jsonSchemaTypes.ts:121-124), and primitive array items route throughnew Type(...), which carries onlytype— droppingdescriptiontoo. MCP'sattributeToProperty(derive.ts:113-115) recurses and keeps all of them.So the same fragment yields a documented, constrained nested property for an LLM and a bare typed one for Swagger UI.
descriptionin particular is a plain loss for the OpenAPI consumer.3. Nullability has three different representations
An author writes
nullable: trueortype: ['string','null']; both fold to the same internal flag (jsonSchemaTypes.ts:159-165). Emission:{ "type": ["string","null"] }(derive.ts:103-109) — never emits anullablekeyword.openApi.ts:148readsnullableonly to computerequired, and never writes it onto the property schema.nullable: truefor a property inside a nested object, viaattributeToFragment(jsonSchemaTypes.ts:120).The top-level OpenAPI drop is the actual bug: OpenAPI 3.0.3 has a
nullablekeyword and we have the information, so a nullable property is currently advertised as non-nullable. The nested/top-level inconsistency within OpenAPI itself is the clearest evidence this wasn't deliberate.Suggested approach
Route both emit paths through one projector that takes a target dialect (
json-schema-2020for MCP,openapi-3.0.3for OpenAPI) and handles the two legitimate dialect differences — union-with-nullvsnullable: true— in one place, so everything else stays identical by construction. That is a bigger change than #1921 made; splitting item 3 (the top-levelnullabledrop) out as a standalone fix is reasonable if the unification slips.Each item is separately observable, so unit coverage should assert MCP and OpenAPI output for the same fragment side by side — the convergence test added in #1921 is the natural home.
Once fixed, the per-surface caveats in the fragment-key table in
reference/resources/resource-api.mdcollapse back to single rows.Issue generated by kAIle (Claude Opus 4.8).