Skip to content

binding-mcp-openapi: nest overlay inside catalog entry instead of peer placement #2319

Description

@jfallows

Describe the desired outcome from the user's perspective

As an operator configuring mcp-openapi, I want an overlay to be written where the schema entry it annotates is, so the relationship between a spec's base document and its overlay is structural rather than a same-name convention across two separate config blocks.

Current shape

"specs": {
  "patternProperties": {
    "^[a-zA-Z]+[a-zA-Z0-9\\._\\-]*$": {
      "properties": {
        "server": { "type": "string" },
        "catalog": {
          "patternProperties": {
            "^[a-zA-Z]+[a-zA-Z0-9\\._\\-]*$": {
              "properties": { "subject": {...}, "version": {...} },
              "required": ["subject"]
            }
          }
        },
        "overlay": {
          "patternProperties": {
            "^[a-zA-Z]+[a-zA-Z0-9\\._\\-]*$": {
              "properties": { "subject": {...}, "version": {...} },
              "required": ["subject"]
            }
          }
        }
      }
    }
  }
}
specs:
  petstore:
    server: https://api.example.com
    catalog:
      apicurio0:
        subject: petstore-spec
        version: latest
    overlay:
      apicurio0:
        subject: petstore-mcp-overlay
        version: latest

catalog and overlay are independent top-level patternProperties blocks. Each already uses the correct internal shape — a map keyed by catalog name, containing subject/version — and that internal shape is not what's changing here. The problem is only that overlay sits as a second, separate top-level block, correlated with catalog solely because both happen to use the same catalog name (apicurio0) as their key. Nothing in the schema requires that, and nothing would catch it if an operator added a new catalog entry and forgot the matching overlay entry, or added it under a mistyped name.

Proposed shape

"catalog": {
  "patternProperties": {
    "^[a-zA-Z]+[a-zA-Z0-9\\._\\-]*$": {
      "properties": {
        "subject": {...},
        "version": {...},
        "overlay": {
          "type": "object",
          "patternProperties": {
            "^[a-zA-Z]+[a-zA-Z0-9\\._\\-]*$": {
              "properties": { "subject": {...}, "version": {...} },
              "required": ["subject"]
            }
          },
          "additionalProperties": false,
          "maxProperties": 1
        }
      },
      "required": ["subject"]
    }
  }
}
specs:
  petstore:
    server: https://api.example.com
    catalog:
      apicurio0:
        subject: petstore-spec
        version: latest
        overlay:
          apicurio0:
            subject: petstore-mcp-overlay
            version: latest

Only the placement changes. overlay moves inside the catalog entry it annotates — a sibling of that entry's own subject/version — but keeps exactly the same internal shape it already has today: a map keyed by catalog name, containing subject/version. maxProperties: 1, since one overlay per base document is the intended cardinality. The catalog name under overlay does not have to match the enclosing entry's name (apicurio0 here happens to match, but a different named catalog is equally valid) — there is nothing to default or override, because the map key already says which catalog, exactly as it does everywhere else catalog: is used in this schema.

An earlier version of this issue proposed collapsing overlay to {subject, version} directly, with an optional flat catalog: string to name a different registry. That was wrong to introduce — it's a different, one-off shape found nowhere else in this schema, where every other catalog reference is a name-keyed map. Reverted; the fix here is placement only.

Why now rather than leave it

This schema was only added 2026-07-05 and has been actively reshaped since — #2128 closed options.* abstraction leaks and aligned terminology, #2269 unified route condition naming, #2272 switched to hyphenated type names. It's still in the phase where getting the shape right costs a schema patch, not a migration. Leaving the current placement in place only gets more expensive to fix the longer it ships.

Scope check — only this module

binding-mcp-schema-registry and binding-mcp-kafka-connect were checked and are unaffected: both explicitly set "catalog": false in their schema patch and have no overlay property anywhere in options (only server). Their overlays (karapace-schema-registry.overlay.json, kafka-connect.overlay.json) are bundled resource files applied internally by their composite generators — never exposed as zilla.yaml config, so there's no peer-vs-nested question for either of them.

Consistency with the schema-privacy overlay design

#2318 (schema overlay for PII field tagging, a different repo area entirely) independently arrived at the same nested-and-name-keyed shape — overlay: as a sibling of subject/version inside a catalog entry, itself a map keyed by catalog name. Fixing this now means both consumers of common-json's JsonOverlay mechanism share one placement and one internal-shape convention instead of diverging in either.

Acceptance criteria

  • Update mcp_openapi.schema.patch.json: move overlay from a peer of catalog to nested inside each catalog.<name> entry, keeping its existing catalog-name-keyed internal shape unchanged
  • Update the config class(es) and adapter(s) that currently read specs.<name>.overlay.<name> as a peer lookup, to instead read specs.<name>.catalog.<name>.overlay.<name>
  • Update McpOpenapiCompositeGenerator (and its test) wherever it currently resolves overlay by cross-referencing the catalog name between two separate maps
  • Update existing k3po scripts and IT expectations in specs/binding-mcp-openapi.spec that construct or validate the current peer-placement config shape
  • Config validation: an overlay catalog name (the map key) that doesn't correspond to any configured catalog produces a clear startup error, per this repo's existing test-first discipline for config validation

Additional context

This is a pre-existing binding-mcp-openapi config shape, not part of the PII epic (zilla-platform#383). No production behavior changes; this only reshapes where the same information is written in zilla.yaml.

Revised: overlay keeps its existing catalog-name-keyed internal shape; only its placement (nested vs. peer) changes. An earlier revision of this issue incorrectly flattened the internal shape too — reverted.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions