fix(exo): allow viewport-free tool payloads [SPA-5371] - #480
Thomas Kellermeier (Chaoste) merged 3 commits into
Conversation
Changelist by BitoThis pull request implements the following key changes.
|
|
| Source | Requirement / Code Area | Status | Notes |
|---|---|---|---|
| SPA-5371, SPA-5269, Viewport-Removal-Client | Allow ExO tool payloads to omit viewport definitions when no viewports are present. | ✅ Met | Tool schemas now make viewport definitions optional, and payload construction omits viewports when no viewport definitions are supplied across ExO component, experience fragment, experience template, and experience tools. This is implemented in packages/mcp-tools/src/tools/exo/components/createComponent.ts, packages/mcp-tools/src/tools/exo/experience-fragments/createExperienceFragment.ts, packages/mcp-tools/src/tools/exo/experience-templates/createExperienceTemplate.ts, and packages/mcp-tools/src/tools/exo/experiences/createExperience.ts. |
| SPA-5371, Viewport-Removal-Client | Preserve the shape of ExO payloads when reading and writing entities: retain the legacy viewport-based representation before the migration phase, and preserve omitted viewports together with flattened design-property values after the API transition. | ✅ Met | The update paths preserve omitted viewports instead of restoring them, while viewport-free Experiences and Experience Fragments accept and preserve flattened design-property values. The compatibility types and payload adapters in packages/mcp-tools/src/types/cmaViewportCompatibility.ts are used by updateExperienceFragmentTool, upsertExperienceTool, upsertComponentTool, and upsertExperienceTemplateTool. |
| SPA-5371, Viewport-Removal-Client | Do not convert an absent viewport definition into an empty viewport array when constructing or updating ExO payloads. | ✅ Met | Payload construction uses conditional spreads so an absent viewport definition is omitted rather than converted to an empty array. Regression tests verify this behavior for create and update/upsert operations, including packages/mcp-tools/src/tools/exo/components/createComponent.test.ts, packages/mcp-tools/src/tools/exo/components/upsertComponent.test.ts, packages/mcp-tools/src/tools/exo/experiences/createExperience.test.ts, and packages/mcp-tools/src/tools/exo/experiences/upsertExperience.test.ts. |
| SPA-5371, Viewport-Removal-Client | Use a published API or SDK contract in which ExO viewport definitions are optional wherever the client relies on that contract for payload validation or typing. | 🟡 Partial | The implementation supports optional viewports and flattened design properties at the SDK boundary through asViewportOptionalCmaPayload and asViewportOptionalCmaPayloadWithFlattenedDesignProperties in packages/mcp-tools/src/types/cmaViewportCompatibility.ts. However, the diff indicates that the consumed contentful-management SDK still declares viewports as required and uses a local compatibility type assertion rather than updating or adopting a published API/SDK contract with optional viewport definitions. |
Impact Analysis by BitoInteraction DiagramsequenceDiagram
participant Agent as Agent Client
participant Server as MCP Server<br/>🔄 Updated | ●●● High
participant Tools as ExO Tools<br/>🔄 Updated | ●●● High
participant Compat as cmaViewportCompatibility<br/>🟩 Added | ●●● High
participant CMA as Contentful Management API
participant Consumer as 🔗 agents-api
Agent->>Server: Connect over stdio MCP transport
Server->>Server: Register ExO tools when entitlement and feature flag pass
Server-->>Agent: Expose create and upsert ExO tool schemas
Agent->>Consumer: Invoke ExO workflow through agent tooling
Consumer->>Server: Call create_component or create_experience
Server->>Tools: Validate arguments and protected environment
Tools->>Compat: Build viewport-optional payload
Compat-->>Tools: Return CMA-compatible transformed payload
Tools->>CMA: POST create request without undefined viewports
CMA-->>Tools: Return created ExO resource
Tools-->>Server: Create success response
Server-->>Consumer: Return structured MCP result
Consumer-->>Agent: Present created resource
alt Existing resource update
Consumer->>Server: Call upsert tool with resource version
Server->>Tools: Read current resource before write
Tools->>CMA: GET current resource and validate version
CMA-->>Tools: Return current state or version conflict
Tools->>Compat: Preserve optional viewports and flatten design properties
Compat-->>Tools: Return update payload without undefined fields
Tools->>CMA: PUT upsert request with validated version
CMA-->>Tools: Return updated resource
end
This MR modifies the ExO component, experience, experience template, and experience fragment tools so viewport-free resources are accepted without reintroducing undefined viewports, while flattened design properties are supported. It adds the cmaViewportCompatibility transformation layer at the Contentful Management API boundary and preserves read-before-write version checks for updates. The MCP Server remains the registration and transport entrypoint, while the cross-repository agents-api consumer can invoke the updated ExO tool contracts. Cross-Repository Impact Analysis
Code Paths AnalyzedImpact: Flow: Direct Changes (Diff Files): Repository Impact: Cross-Repository Dependencies: Database/Caching Impact: API Contract Violations: Infrastructure Dependencies: Additional Insights: Testing RecommendationsFrontend Impact: Service Integration: Data Serialization: Privacy Compliance: Backward Compatibility: OAuth Functionality: Cross-Service Communication: Reliability Testing: Additional Insights: Analysis based on known dependency patterns and edges. Actual impact may vary. |
There was a problem hiding this comment.
Code Review Agent Run #ba199d
Actionable Suggestions - 8
-
packages/mcp-tools/src/tools/exo/components/createComponent.ts - 1
- Viewports optional may break API · Line 32-32
-
packages/mcp-tools/src/tools/exo/experiences/createExperience.ts - 1
- Stale schema description · Line 33-39
-
packages/mcp-tools/src/tools/exo/experience-fragments/createExperienceFragment.ts - 1
- Viewports optional may mismatch API · Line 30-30
-
packages/mcp-tools/src/tools/exo/components/upsertComponent.ts - 1
- Triplicated coalescing expression · Line 97-99
-
packages/mcp-tools/src/tools/exo/experience-templates/upsertExperienceTemplate.ts - 1
- Duplicated fallback expression · Line 104-106
-
packages/mcp-tools/src/tools/exo/experience-templates/createExperienceTemplate.ts - 1
- Viewports optional may break API contract · Line 26-26
-
packages/mcp-tools/src/tools/exo/experience-fragments/updateExperienceFragment.ts - 1
- Redundant conditional spread · Line 107-109
-
packages/mcp-tools/src/tools/exo/experience-fragments/updateExperienceFragment.test.ts - 1
- Untyped body access · Line 63-63
Additional Suggestions - 1
-
packages/mcp-tools/src/tools/exo/experience-fragments/updateExperienceFragment.ts - 1
-
Redundant union arm in record · Line 43-49`DesignPropertyValueSchema` (exoSchemas.ts:322) is a union of object schemas; `DimensionedDesignPropertyValueSchema` (exoSchemas.ts:338) is `z.record(z.string(), DesignPropertyValueSchema)`, which already accepts any such object as a value. The first union arm is subsumed — possibly `DesignPropertyPointerValueSchema` was intended. Resolve the duplication or add the missing arm.
-
Review Details
-
Files reviewed - 16 · Commit Range:
6e92617..6e92617- packages/mcp-tools/src/tools/exo/components/createComponent.test.ts
- packages/mcp-tools/src/tools/exo/components/createComponent.ts
- packages/mcp-tools/src/tools/exo/components/upsertComponent.test.ts
- packages/mcp-tools/src/tools/exo/components/upsertComponent.ts
- packages/mcp-tools/src/tools/exo/experience-fragments/createExperienceFragment.test.ts
- packages/mcp-tools/src/tools/exo/experience-fragments/createExperienceFragment.ts
- packages/mcp-tools/src/tools/exo/experience-fragments/updateExperienceFragment.test.ts
- packages/mcp-tools/src/tools/exo/experience-fragments/updateExperienceFragment.ts
- packages/mcp-tools/src/tools/exo/experience-templates/createExperienceTemplate.test.ts
- packages/mcp-tools/src/tools/exo/experience-templates/createExperienceTemplate.ts
- packages/mcp-tools/src/tools/exo/experience-templates/upsertExperienceTemplate.test.ts
- packages/mcp-tools/src/tools/exo/experience-templates/upsertExperienceTemplate.ts
- packages/mcp-tools/src/tools/exo/experiences/createExperience.test.ts
- packages/mcp-tools/src/tools/exo/experiences/createExperience.ts
- packages/mcp-tools/src/tools/exo/experiences/upsertExperience.test.ts
- packages/mcp-tools/src/tools/exo/experiences/upsertExperience.ts
-
Files skipped - 0
-
Tools
- Whispers (Secret Scanner) - ✔︎ Successful
- Detect-secrets (Secret Scanner) - ✔︎ Successful
- Eslint (Linter) - ✔︎ Successful
Bito Usage Guide
Commands
Type the following command in the pull request comment and save the comment.
-
/review- Manually triggers an incremental AI Review. -
/review full- Manually triggers a full AI Review. -
/pause- Pauses automatic reviews on this pull request. -
/resume- Resumes automatic reviews. -
/resolve- Marks all Bito-posted review comments as resolved. -
/abort- Cancels all in-progress reviews.
Refer to the documentation for additional commands.
Configuration
This repository uses Default Agent You can customize the agent settings here or contact your Bito workspace admin at jared.jolton@contentful.com.
Documentation & Help
✅ Review Settings OverriddenStatus: Guidelines:
Note: Extra guidelines beyond 3 general purpose guidelines and 1 language specific guideline per language are not processed. Guidelines are fetched from the source branch. |
There was a problem hiding this comment.
Code Review Agent Run #609b4f
Actionable Suggestions - 3
-
packages/mcp-tools/src/tools/exo/components/createComponent.ts - 1
- Redundant generic, duplicated conversion · Line 90-92
-
packages/mcp-tools/src/tools/exo/experiences/upsertExperience.ts - 1
- Cast bypasses payload type check · Line 117-124
-
packages/mcp-tools/src/types/cmaViewportCompatibility.ts - 1
- Duplicated viewports member · Line 20-20
Additional Suggestions - 2
-
packages/mcp-tools/src/tools/exo/experiences/createExperience.test.ts - 1
-
undefined key not omitted · Line 43-43`{ ...createArgs, viewports: undefined }` keeps an own `viewports` key with value `undefined`, unlike the removed rest-destructure which omitted the key. Harmless today: `createExperience.ts` gates on `args.viewports !== undefined` and the mock ignores args, so `not.toHaveProperty('viewports')` still passes. But the assertion checks `mock.calls[0][1]` while the key lives on the input object, so the test no longer proves the CMA payload omits `viewports`. Prefer key omission over `undefined` to keep the fixture honest.
-
-
packages/mcp-tools/src/tools/exo/experiences/upsertExperience.ts - 1
-
Duplicated SDK cast pattern · Line 123-123This `satisfies` + `asViewportOptionalCmaPayload*` idiom duplicates `upsertComponent.ts:124` (`asViewportOptionalCmaPayload(componentData)`). Both encode 'current CMA SDK requires viewports/designProperties that the API does not'; when the SDK relaxes those types, one site can be updated while the other silently keeps a stale cast. Consider a shared helper or cross-linking comments between the sites.
-
Review Details
-
Files reviewed - 13 · Commit Range:
6e92617..5020d7d- packages/mcp-tools/src/tools/exo/components/createComponent.test.ts
- packages/mcp-tools/src/tools/exo/components/createComponent.ts
- packages/mcp-tools/src/tools/exo/components/upsertComponent.ts
- packages/mcp-tools/src/tools/exo/experience-fragments/createExperienceFragment.test.ts
- packages/mcp-tools/src/tools/exo/experience-fragments/createExperienceFragment.ts
- packages/mcp-tools/src/tools/exo/experience-fragments/updateExperienceFragment.ts
- packages/mcp-tools/src/tools/exo/experience-templates/createExperienceTemplate.test.ts
- packages/mcp-tools/src/tools/exo/experience-templates/createExperienceTemplate.ts
- packages/mcp-tools/src/tools/exo/experience-templates/upsertExperienceTemplate.ts
- packages/mcp-tools/src/tools/exo/experiences/createExperience.test.ts
- packages/mcp-tools/src/tools/exo/experiences/createExperience.ts
- packages/mcp-tools/src/tools/exo/experiences/upsertExperience.ts
- packages/mcp-tools/src/types/cmaViewportCompatibility.ts
-
Files skipped - 0
-
Tools
- Whispers (Secret Scanner) - ✔︎ Successful
- Detect-secrets (Secret Scanner) - ✔︎ Successful
- Eslint (Linter) - ✔︎ Successful
Bito Usage Guide
Commands
Type the following command in the pull request comment and save the comment.
-
/review- Manually triggers an incremental AI Review. -
/review full- Manually triggers a full AI Review. -
/pause- Pauses automatic reviews on this pull request. -
/resume- Resumes automatic reviews. -
/resolve- Marks all Bito-posted review comments as resolved. -
/abort- Cancels all in-progress reviews.
Refer to the documentation for additional commands.
Configuration
This repository uses Default Agent You can customize the agent settings here or contact your Bito workspace admin at jared.jolton@contentful.com.
Documentation & Help
| componentId: args.componentId, | ||
| }, | ||
| { | ||
| asViewportOptionalCmaPayload< |
There was a problem hiding this comment.
The upsert branch re-wraps the already-validated componentData with an explicit generic that type inference supplies anyway — the same helper is called without type arguments at line 99 and in upsertComponent.ts (line 124), which also passes a sys-bearing payload. Keeping one conversion style avoids divergence between the create and upsert paths.
Code Review Run #609b4f
Should Bito avoid suggestions like this for future reviews? (Manage Rules)
- Yes, avoid them
| } satisfies ViewportOptionalPayloadWithFlattenedDesignProperties< | ||
| Parameters<typeof contentfulClient.experience.upsert>[1] | ||
| >; | ||
|
|
||
| const experience = await contentfulClient.experience.upsert( | ||
| params, | ||
| asViewportOptionalCmaPayloadWithFlattenedDesignProperties(experienceData), | ||
| ); |
There was a problem hiding this comment.
asViewportOptionalCmaPayloadWithFlattenedDesignProperties is a pure as Payload cast (cmaViewportCompatibility.ts:50-51), with no runtime transformation. The satisfies at line 117 validates experienceData against the widened type where flattened designProperties are legal, but contentfulClient.experience.upsert (contentful-management 12.14.0) still types them as the legacy dimensioned shape, so flattened values reach the CMA with no compile-time or runtime check.
Code Review Run #609b4f
Should Bito avoid suggestions like this for future reviews? (Manage Rules)
- Yes, avoid them
| * `satisfies`. | ||
| */ | ||
| export type ViewportOptionalPayload<Payload> = Omit<Payload, 'viewports'> & { | ||
| viewports?: ComponentTypeViewport[]; |
There was a problem hiding this comment.
Lines 20 and 30 re-declare the same viewports?: ComponentTypeViewport[] member in the two sibling compatibility types. Extracting a shared base (e.g. type WithOptionalViewports = { viewports?: ComponentTypeViewport[] }) keeps both SDK-boundary shims in sync as the CMA viewports contract evolves; divergence would silently loosen one call boundary but not the other.
Code Review Run #609b4f
Should Bito avoid suggestions like this for future reviews? (Manage Rules)
- Yes, avoid them
|
Bito Automatic Review Skipped – PR Already Merged |
Summary
Allow ExO MCP tools to create and update payloads without viewport definitions.
Description
Component and Experience Template tool arguments now omit
viewportswhen it is absent. Experience and Experience Fragment tool arguments and read-modify-write paths preserve the flattened design-property shape returned by the API alongside that omission. Focused coverage exercises new create and update payloads as well as legacy payload preservation.Motivation and Context
Implements SPA-5371. The API will stop returning and accepting
viewports; emittingviewports: []would incorrectly retain the legacy value representation.This PR is intentionally blocked on the published CMA type update from contentful-management.js#3149. The current package types still require
viewportsand dimensioned design-property values, sonpm run typecheckfails rather than masking the contract mismatch with assertions.PR Checklist
CONTRIBUTING.mdfileSummary by Bito
This PR updates ExO MCP tools to create and update Components, Experience Templates, Experiences, and Experience Fragments without viewport definitions. It also preserves the flattened design-property representation returned by the API while retaining compatibility with legacy dimensioned values during create and read-modify-write operations.
Detailed Changes