refactor: make searching guidance entity-agnostic for ExO envs - #432
Merged
Tyler Pina (tylerpina) merged 1 commit intoJul 8, 2026
Merged
Conversation
|
Bito Automatic Review Skipped - Branch Excluded |
Tyler Pina (tylerpina)
added a commit
that referenced
this pull request
Jul 28, 2026
* feat: add ComponentType CRUD MCP tools [AIS-73] (#419) * chore(exo): pin contentful-management to 12.6.0-dev.3 for ExO entities [AIS-73] * feat(exo): support componentType in delete confirmation tokens [AIS-73] * test(exo): add shared componentType mock client [AIS-73] * feat(exo): add get_component_type tool [AIS-73] * feat(exo): add list_component_types tool [AIS-73] Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(exo): add create_component_type tool [AIS-73] * feat(exo): add upsert_component_type tool with read-before-write [AIS-73] * feat(exo): add publish/unpublish_component_type tools with read-before-write [AIS-73] * feat(exo): add delete_component_type tool with two-phase confirmation [AIS-73] * feat(exo): assemble componentType tool collection [AIS-73] * feat(exo): expose getComponentTypeTools on ContentfulMcpTools [AIS-73] * feat(exo): register componentType tools in the MCP server [AIS-73] * style(exo): apply prettier formatting to componentType tools [AIS-73] * fix(exo): preserve dataAssemblies on componentType upsert + add pagePrev test [AIS-73] Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(exo): enforce read-before-write via required version param on componentType upsert/publish/unpublish [AIS-73] Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore: gitignore superpowers planning scaffolding [AIS-73] * feat: schema imported from cma * chore: forgot to add --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(exo): implement Fragment CRUD MCP tools [AIS-76] (#420) * feat(exo): implement Fragment CRUD MCP tools [AIS-76] Add get, list, create, update, delete, publish, and unpublish tools for ExO Fragment entities, following the component-type patterns established in AIS-73. Includes two-phase delete confirmation, read-before-write version enforcement on all mutating ops, and full test coverage (50 tests). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore: nx * fix(test): add fragment tools to register.test.ts expected count Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: add Template CRUD MCP tools [AIS-77] (#421) Implements 7 MCP tools for the ExO Template entity (get, list, create, upsert, delete, publish, unpublish) following the established ComponentType pattern. Includes read-before-write for upsert/publish/unpublish, two-phase confirmation for delete, and full test coverage. Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(exo): implement Experience CRUD MCP tools [AIS-74] Adds 7 MCP tools for ExO Experience management (get, list, create, upsert, delete, publish, unpublish), following the same patterns established by ComponentType, Fragment, and Template tools. All write tools enforce read-before-write with version conflict rejection; delete uses the two-phase confirm/confirmToken flow. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(exo): implement DataAssembly CRUD MCP tools [AIS-75] (#422) * feat(exo): implement DataAssembly CRUD MCP tools [AIS-75] Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(exo): rename sys.id[in] schema key to sysIdIn in data assembly list tools Anthropic API rejects tool schemas with property keys containing brackets — the pattern ^[a-zA-Z0-9_.-]{1,64}$ disallows [ and ]. Rename the input schema key to sysIdIn while preserving the API query mapping to sys.id[in]. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(exo): use z.record for DataAssembly return field to prevent string serialization The return field was typed as z.unknown() which caused the MCP client to serialize object values as JSON strings, resulting in API validation errors. Changed to z.record(z.unknown()) to match parameters/resolvers and ensure correct object serialization. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(exo): update listDataAssemblies test to use sysIdIn param name Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(exo): add getDataAssemblyTools to register.test.ts standard collections Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(exo): pin ExO tool schemas to canonical CMA types (#425) The Zod schemas for the 5 ExO entity groups (component-types, templates, experiences, fragments, data-assemblies) were hand-written and had drifted from contentful-management's actual types: componentTree nodes used a bogus "View" node instead of CMA's "Fragment" arm, componentType was a bare string ID instead of a ResourceLink, design properties were a flat shape instead of CMA's discriminated union, and several create/upsert call sites papered over the mismatch with `as Parameters<...>[1]` casts. contentful-management doesn't re-export the individual ExO Props types by name from its package root, so each schema type is derived structurally off PlainClientAPI's method signatures instead. Derived types are wrapped in a Distribute<T> helper (a distributive mapped type) to keep them nominal for tsup's declaration-emit step — combining two indexed-access types derived from the same CMA entity in one z.object()/extend() call otherwise fails DTS build with TS2742. Data assemblies previously had no real validation (parameters/resolvers/ return were all z.unknown()); new dataAssemblySchemas.ts models CMA's PointerExpressionValue and resolver union (GraphQL | nested data assembly) precisely. Schemas stay zod v3-compatible — the two spots that would have used z.templateLiteral() (a v4-only API) either had no real usage or are replaced with z.custom() plus a regex predicate, so no zod version bump is needed for this change. Removes every `as Parameters<...>[1]` cast in the touched create/upsert tool files; the payloads now typecheck against CMA's real method signatures without one. Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> * fix(exo): use type-literal discriminant for DataAssembly type aliases CanonicalDataAssemblyDataTypeField and LegacyDataAssemblyDataTypeField previously used optional-field filters ({ source?, ref? }) in their Extract/Exclude calls. Because absence satisfies an optional property in TS structural typing, both aliases collapsed to the full union / never. Fix: discriminate by the narrow type literals from DataTypeDefinition (String, Number, Integer, etc.) instead — the canonical arm has those literals, the legacy arm has a wide `type: string`, so the partition is unambiguous. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor: deliver corrected MCP context via get_initial_context (#423) * refactor: split MCP guidance into corrected tiered corpus * feat: add get_guidance tool for on-demand context * refactor: return tiered index from get_initial_context * test: anchor get_initial_context test to legacy-envelope removal * refactor: fold get_guidance sections into get_initial_context Collapse the two-tool tiered design into a single get_initial_context call. Searching and conventions guidance are now returned inline; editing guidance is dropped since its one load-bearing rule (get_entry -> sys.version -> update_entry) already lives in CORE_INVARIANTS. Removes the now-unused get_guidance tool and its tests. * feat: include today's date as a session fact in get_initial_context Restores the current date that the legacy MCP_INSTRUCTIONS envelope exposed via <todaysDate>. Dropping it in the corpus rewrite was an unintended regression — content work (scheduling, "recent", publish timing) needs the model to know the current date. Emitted as a plain session fact (weekday, month, day, year) rather than the old tag format. * fix: add URN examples to ExO resource link schema descriptions Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: gate ExO tool registration behind ENABLE_EXO_TOOLS opt-in (#428) * feat: add EXO_DISPOSITION instruction nudge to mcp-tools * feat: add exoToolsRegistered flag to ContentfulConfig * feat: add detectExoDisposition shared classification helper * feat: append EXO_DISPOSITION to get_initial_context when ExO tools registered * feat: gate ExO tool registration on space detection (local server) Makes registerAllTools async and probes the configured space's ExO disposition once via detectExoDisposition before registering the component-type, data-assembly, experience, template, and fragment tool collections. Fails closed (classic-only) on a classic space or a probe error, and skips the probe entirely when no SPACE_ID is set. Sets exoToolsRegistered on the tools config so getInitialContextTool can surface the EXO_DISPOSITION nudge. * fix: log ExO detection failures instead of silently swallowing * feat: replace ExO space-detection with ENABLE_EXO_TOOLS opt-in Registering ExO tool collections behind a runtime space probe was too heavy for the local server: it made registerAllTools async, added CMA calls at startup, and inferred user intent from Component Type presence. Replace it with an explicit opt-in env var. ENABLE_EXO_TOOLS=true registers the five ExO collections and appends the (reworded) ExO nudge to get_initial_context; anything else registers classic-only with no nudge and byte-for-byte-identical context output. - add ENABLE_EXO_TOOLS to env schema (transforms to boolean; only 'true') - registerAllTools is synchronous again; drop the detection probe - delete detectExoDisposition helper + test and its mcp-tools exports (added only for deferred remote work; nothing consumes them now) - reword EXO_DISPOSITION to drop detection-derived 'empty or already uses ExO' framing - rewrite register.test.ts gating cases as flag-on / flag-off Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat: export shared instruction constants from mcp-tools Re-export CORE_INVARIANTS, SEARCHING_GUIDANCE, CONVENTIONS_GUIDANCE, and EXO_DISPOSITION from the package entrypoint so downstream consumers (the remote MCP server) can import the shared instruction corpus. Completes the export step the ExO local plan deferred. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs: document ENABLE_EXO_TOOLS env var in mcp-server README --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> * refactor: make searching guidance entity-agnostic for ExO envs (#432) * refactor(mcp-tools): make core invariants entity-agnostic, strengthen ExO disposition (#434) * refactor(mcp-tools): make core invariants entity-agnostic, strengthen ExO disposition Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(mcp-tools): drop tool-name assertions from instructions.test after entity-agnostic rewrite Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(exo): rebase ExO type aliases onto dev.4 named Props exports (#435) contentful-management 12.6.0-dev.4 now re-exports the top-level ExO entity Props types (ComponentTypeProps, ExperienceProps, ...) from the package root. Derive the schema type aliases by indexing directly into those named exports instead of reaching through PlainClientAPI method-return signatures. The nested constituent types (ComponentTypeViewport, TreeNode, ...) are still not root-exported, so the Distribute<T> wrapper stays to keep declaration emit portable (TS2742 otherwise surfaces in the tool consumers, not the schema file itself). Comment updated to reflect this. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> * feat: require exoM1 entitlement to register ExO tools [AIS-191] (#437) ExO tool collections now require BOTH gates: the ENABLE_EXO_TOOLS opt-in env var AND the org's exoM1 entitlement. Adds a hasExoM1Entitlement helper to @contentful/mcp-tools that reads the public CMA /organizations/{orgId}/organization_entitlement_set endpoint and fails closed (offline/error/no-org-id → no ExO). registerAllTools is now async; the entitlement call is skipped entirely when the env var is off. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> * chore: trim verbose PR comments to concise one-liners Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore: bump contentful-management to 12.10.0 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: remove as-unknown cast in listComponentTypes query params CursorPaginationParams is a discriminated union with mutually-exclusive pageNext/pagePrev arms. Build the cursor object via a ternary so TypeScript picks the correct union member without a cast. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor: import ExoMetadataProps and ExperienceMetadataProps directly from CMA Both types are exported from common-types.d.ts which is fully re-exported via the CMA barrel, so they don't need Distribute<T> wrappers. Entity-file types (ComponentTypeViewport, TreeNode, etc.) still require Distribute<T> as they're not reachable from the package root. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: import ExO nested types directly from cma.js 12.11.0 Bump contentful-management to 12.11.0, which now barrel-exports the nested ExO entity types. Replace the local Distribute<...> indexed-access derivations with direct imports: - component-type: ComponentTypeViewport, ComponentTypeContentProperty, ComponentTypeDesignProperty, ComponentTypeSlotDefinition, TreeNode, ComponentNode, FragmentNode, SlotNode - experience: ExperienceContentBindings, InlineFragmentNode ExperienceSlotNode is still not barrel-exported, so it remains derived from ExperienceProps via Distribute. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
SEARCHING_GUIDANCEin the shared MCP context corpus from classic-specific wording (list_content_types/get_content_type/search_entries, "referenced entry (the author)") to schema- and entity-agnostic phrasing.contentful/remote-mcp-server#87), keeping the two servers' searching guidance aligned.Why
get_initial_contextreturns this corpus verbatim on every session. In an ExO environment the classic-only framing ("callsearch_entries", "content-type first") points the agent at the wrong primitives. Making it entity-agnostic keeps the guidance correct regardless of whether the environment uses classic content modeling or ExO.Test plan
packages/mcp-toolsinstructions.test.tspasses (3/3) — corpus guard still green; the fiction/load-bearing-fact assertions live inCORE_INVARIANTS, unaffected by the searching-verb changesGenerated with Claude Code