feat(blueprints): emit per-field wire-format spec to guide MCP agents#29
Merged
Merged
Conversation
Fixes ENG-804. Adds a FieldFormatSpec service that derives a wire-format guidance object from each Statamic Field — bard inline vs full, replicator/grid/group item shape, allowed set types, recursive set definitions, markdown vs ProseMirror distinction, relationship/asset/date input shapes — and returns it via BlueprintsRouter::get on the new _format_spec key for every field. Agents reading a blueprint now see the exact payload contract instead of having to interpret raw fieldtype config. Backs the format spec with a dedicated FieldFormatException class. The SanitizesFieldData trait throws this specific subclass for malformed bard/replicator/grid/table input, and BaseStatamicTool::execute() allow-lists it (alongside Laravel's ValidationException and Statamic's FieldtypeNotFoundException / BlueprintNotFoundException) so the field-path error message reaches clients in production rather than being replaced with the generic "An error occurred" placeholder. Other Throwables continue to flow through the production sanitizer to avoid leaking internals. Schema descriptions on BlueprintsRouter were also corrected to reflect the actual list-vs-get scoping of include_fields, include_config, include_format_spec, and max_format_depth.
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.
Fixes ENG-804.
Why
Agents using the Statamic MCP keep failing on bard/replicator writes because the blueprint output exposes raw fieldtype config but never explains the wire format — inline-vs-full bard, ProseMirror vs markdown nesting, the
id/type/enabledshape replicator items need, which set handles are allowed where, and so on. ENG-804 was the visible symptom (silent generic error on bard/replicator updates), but the underlying issue is information architecture: agents are guessing because the server doesn't tell them.This PR shifts that — the server now derives and emits a structured wire-format spec from the fieldtype config, and surfaces validation errors with field paths through the production sanitization layer.
What changed
FieldFormatSpecservice (src/Mcp/Support/FieldFormatSpec.php) — projects any Statamic Field into a normalized spec: shape, allowed node types/marks/heading levels, allowed set types, recursive set definitions, common mistakes, canonical example. Covers bard (inline + full), replicator, grid, group, markdown, scalar, select/checkbox, relationship, asset, table, date.BlueprintsRouter::getreturns_format_specper field by default. New flagsinclude_format_spec(defaulttrue) andmax_format_depth(default2, max 5) for callers that want to opt out or change recursion depth.FieldFormatExceptionclass — curated subclass ofInvalidArgumentException.SanitizesFieldDatathrows this specific class for malformed bard/replicator/grid/table input, replacing bareInvalidArgumentException.BaseStatamicTool::isClientSafeException()— allow-list of exception classes whose messages survive production sanitization (FieldFormatException,ValidationException,FieldtypeNotFoundException,BlueprintNotFoundException). OtherThrowabletypes continue to be genericised in production.BlueprintsRouterforinclude_fields,include_config,include_format_spec,max_format_depth— descriptions now reflect actual list-vs-get scoping.Why this fixes ENG-804
callout.contentis markdown or ProseMirror.SanitizesFieldDatathrowsFieldFormatExceptionwith a precise field path, and that message now reaches the client in production rather than being replaced with the generic placeholder.Test plan
composer pint:test— cleancomposer stan— PHPStan Level 8 zero errorscomposer test— 1009 passed, 5234 assertionstests/Unit/FieldFormatSpecTest.php)BlueprintsRouter::getreturns_format_specand that malformed entry writes surface field-path errors (tests/Integration/BlueprintFormatSpecTest.php)FieldFormatExceptionmessages survive while unrelatedRuntimeException/TypeErrorget genericised — including assertions that vendor paths do not appear in the response (tests/Integration/ClientSafeExceptionTest.php)_format_specpayload size stays within the 100KB response cap at default depthNotes for reviewers
_format_specdefaults totrueonget. Verify this is acceptable for your largest blueprints; passinclude_format_spec=falseto disable, or lowermax_format_depthto trim the response.Throwablewidening that an earlier draft of this PR introduced was reverted — the create/update catches are back at\Exception, with non-ExceptionThrowabletypes flowing throughBaseStatamicToolfor centralized logging and environment-aware sanitization.