Banners
MCP Banner Fields

MCP Banner Fields

The MCP banner tools use the same schema-facing field names as the CMS create/update mutations. The field contract is defined in packages/shared/src/util/BannerFieldRegistry.ts.

For MCP authentication and token generation, see MCP Authentication.

VISIBLE_BANNER_FIELDS_BY_TYPENAME is the source of truth for two consumers:

  • The Keystone Admin UI reads each field name to decide which fields are visible for the selected typename.
  • The MCP information endpoint exposes the full field descriptor so AI callers can build correct payload objects.
💡
When adding or changing a banner field, update the field descriptor first. The CMS visibility and MCP guidance should stay together.

Call cms.information.get before cms.banners.create or cms.banners.update when the host has not already injected the MCP capability document into the model context.

Information payload

The MCP information response exposes banner metadata under structures.bannerTypeSystem.

bannerTypeSystem: {
  allowedTypenames: Array<BannerTypename>;
  defaultVisibleFieldNames: Array<string>;
  relationshipWrappers: Array<BannerRelationMutationContract>;
  byTypename: Array<{
    typename: BannerTypename;
    visibleFieldNames: Array<string>;
    requiredFieldNames: Array<string>;
    fields: Array<BannerVisibleFieldDefinition>;
  }>;
}

Use byTypename[*].fields as the practical payload guide for cms.banners.create and cms.banners.update.

Field descriptor

Each visible banner field is described as a BannerVisibleFieldDefinition.

type BannerVisibleFieldDefinition = {
  name: string;
  type: BannerFieldType;
  requiredForCreate: boolean;
  autoManaged: boolean;
  enumValues?: Array<string>;
  relationshipWrapper?: 'oneToOne' | 'oneToMany';
  hints: Array<string>;
};

Fields

name
string

The exact schema field name accepted by cms.banners.create and cms.banners.update.

Use the original CMS field name, not the hydrated frontend property name. For example, GridWrapper uses gridFullWidth in create/update payloads even though the rendered banner exposes fullWidth.

type
BannerFieldType

The simplified payload type used by MCP clients.

Common values:

  • string
  • string_array
  • boolean
  • integer
  • enum
  • enum_array
  • json
  • json_array
  • query_params
  • richtext
  • relation_one
  • relation_many
  • order_entries
requiredForCreate
boolean

Marks fields that should be provided for a valid create flow.

This powers the CMS required-field check and tells MCP callers what to include. It can represent hard validation or practical rendering requirements. For example, a PricingBanner without name or price can save in the CMS, but the backend filters it out during hydration.

autoManaged
boolean

Marks fields that are usually maintained by CMS/admin ordering behavior.

Examples:

  • heroSlidesOrder
  • stepsItemsOrder
  • gridLeftBannersOrder
  • gridRightBannersOrder
  • upsellFunnelBannersOrder
  • productComparisonCarsOrder

AI callers may still need to set these fields when creating or updating through MCP. This is especially important for nested rendered content, because the backend often resolves rendered children from the order field rather than from the raw relationship field.

enumValues
Array<string>

Lists exact accepted values for enum-like fields.

Always copy the schema values, not translated labels. For example, gridColumnsWidth uses values like FIFTY_FIFTY, SIXTY_FORTY, and FORTY_SIXTY.

relationshipWrapper
'oneToOne'|
'oneToMany'

Marks relation fields that need Keystone nested mutation wrappers.

The MCP tools do not automatically wrap relation values. Callers should send the explicit shape shown by bannerTypeSystem.relationshipWrappers.

// oneToOne connect
imageBlock: { connect: { id: 'image-id' } }
 
// oneToOne create
ctaLink: { create: { label: 'Open', absoluteUrl: 'https://example.com' } }
 
// oneToMany connect
images: { connect: [{ id: 'image-id-1' }, { id: 'image-id-2' }] }
 
// oneToMany create
ctaLinks: {
  create: [
    { label: 'Offers', absoluteUrl: 'https://example.com/offers' },
    { label: 'Contact', absoluteUrl: 'https://example.com/contact' },
  ],
}
hints
Array<string>

Use hints for behavior that is important but not obvious from the schema type.

Good hint candidates:

  • A field saves successfully but the backend filters the banner as invalid when it is empty.
  • A relation update appends instead of replacing.
  • A field expects a specific document shape, such as Lexical rich text.
  • A field has a paired order field that must be kept in sync.

Some fields are intentionally "stringly typed" because the CMS GraphQL schema expects text even for numeric-looking values. For example, imageBlockMaxHeight should be treated as a string field. Prefer values like "520" or "520px", even if the semantic meaning is a height.

Relation update behavior

Relationship fields follow Keystone nested mutation semantics.

  • create adds new related records.
  • connect attaches existing related records.
  • disconnect removes specific existing relations.
  • For many-relations, create and connect append to the relation set unless a field explicitly supports replacement semantics.

This matters for fields such as ctaLinks. Updating a LinkRowBanner with ctaLinks: { create: [...] } adds links to the existing row. To remove old links, include disconnect for those relation ids or use the replacement behavior supported by the underlying schema.

Order fields

Some banners render nested records from an order field.

For GridWrapper, setting only gridLeftBanners and gridRightBanners is not enough for hydrated rendering. The backend resolves leftColumn and rightColumn from:

  • gridLeftBannersOrder
  • gridRightBannersOrder

Use typed order entries for nested banners:

gridLeftBanners: {
  connect: [{ id: 'textblock-a' }, { id: 'textblock-b' }],
},
gridLeftBannersOrder: [
  { id: 'textblock-a', typename: 'Textblock' },
  { id: 'textblock-b', typename: 'Textblock' },
],

The same pattern applies to other order_entries fields. Keep the relationship field and the order field in sync when writing through MCP.

Rich text fields

richtext fields such as markdownBlocks should use Lexical-style document nodes or JSON-stringified document arrays.

markdownBlocks: [
  {
    type: 'paragraph',
    children: [{ text: 'Visible banner text.' }],
  },
]

Plain Markdown strings are not converted into rich text content. Send real document nodes or JSON-stringified document arrays; unsupported plain-string markdown payloads should be treated as invalid input.

Archive query fields

query_params fields accept archive filter pairs and archive sorting presets.

Canonical shape:

query: [
  { key: 'BRAND', value: 'BMW' },
  { key: 'PRICE_MIN', value: '1000' },
  { key: 'sort', value: 'CREATED_AT' },
  { key: 'order', value: 'DESC' },
]

Use the serialized enum values from SortOption and OrderOption, for example CREATED_AT, OPRICE, NAME, ASC, and DESC.

The MCP payload normalizer also accepts shorthand strings like ['BRAND:BMW'] for archive banners, but new examples should prefer the canonical shape.

Add a new field

  1. Add the base definition to FIELD_DEFINITIONS_BY_NAME.
  2. Add the field descriptor to the banner entry in VISIBLE_BANNER_FIELDS_BY_TYPENAME.
  3. Set requiredForCreate when the field is necessary for creation or hydration.
  4. Set autoManaged for order fields that are usually maintained by the Admin UI.
  5. Set relationshipWrapper for relation fields.
  6. Add enumValues for enum and enum-array fields.
  7. Add hints for hidden behavior that an MCP caller needs to know.

The result should be enough for an AI caller to create, update, and validate the banner without reading the backend mapper first.