MCP Server for Statamic Icon

MCP Server for Statamic

2.10.0

September 10th, 2026

Minor release. New entry, blueprint and extensibility features, two format-spec fixes, and a quadratic write path in the file token store made linear.

One behaviour change to be aware of on upgrade — see the note under reject_unknown_fields.

Added

localize action on statamic-entries (#48) — Creates an entry's localization in another site through Statamic's makeLocalization(), so the origin is set and untranslated fields keep falling back to it. Only the fields actually sent are stored; writing an explicit null for every untouched field would defeat that fallback. Gated as a write action throughout: entries:write, a write-mode resource policy check, and the create {collection} entries permission.

merge_sets on statamic-entries update (#47) — Merges a top-level replicator field into the stored array by item id rather than replacing it, so changing one section of a page builder no longer means resending every other. Opt-in, off by default.

field path on statamic-blueprints get (#51) — Scopes the response to one field or set with a dot path (page_builder.ContentSection.media). A page builder's format spec is proportional to every set it can hold, so on a real blueprint the full response could not be returned at any useful depth.

FieldtypeExtensions registry (#46) — Lets a site or addon describe the wire format of a fieldtype this package does not ship support for, and coerce or reject incoming values for it. Without a registration nothing changes. See Extending fieldtypes.

Icon field format spec (#43) — Icon names live in the set's directory on disk, not in the blueprint, so icon used to report only "this is a string" and a plausible-looking guess rendered nothing. Sets are now listed once per response rather than inlined per field.

Configurable response size limit (#49) — STATAMIC_MCP_MAX_RESPONSE_SIZE, default unchanged at 100000 bytes, 0 disables.

reject_unknown_fields (#45) — Refuses a write carrying a key that is not a field handle inside a replicator set, grid row, bard set or group. processRow() merges the raw row back over the processed one, so such a key is written to the content file as inert data no template reads — and the write reports success.

This changes write behaviour and is on by default. A caller that has been sending a stray key inside a set was silently writing junk and being told it succeeded; it now gets an error naming the valid handles. That is the point, but it surfaces on upgrade rather than when the junk was written. Set STATAMIC_MCP_REJECT_UNKNOWN_FIELDS=false to restore the old behaviour while you fix the caller.

The check does not apply at the top level of a record, where non-blueprint keys such as template, layout and parent are legitimate.

Fixed

Select options are read in every shape Statamic accepts (#50) — selectSpec() dropped the list of ['key' => ..., 'value' => ...] maps that the Control Panel actually writes, so any select, radio, button_group or checkboxes field configured through the CP reported allowed_values: [].

The link fieldtype spec described references that do not resolve (#44) — It advertised statamic://entry/<uuid>, which is Bard link-mark syntax. ResolveRedirect does not understand it and stores the value verbatim as a dead link. The spec now describes what actually resolves: plain URLs, entry::<id>, asset::<container>::<path> and @child.

pruneExpired() and deleteForUser() were quadratic — Both removed tokens from the hash index one at a time, and each removal re-read, re-encoded and rewrote the whole index under an exclusive lock. Pruning n tokens wrote on the order of n² bytes; at 500 tokens that was megabytes of index writes. Batched into a single locked read-modify-write, roughly halving prune time at 500 tokens.

Thanks

Seven of the nine changes in this release came from @JorisOrangeStudio, including the two format-spec fixes and the silent-corruption case behind reject_unknown_fields.

Full changelog: https://github.com/cboxdk/statamic-mcp/compare/v2.9.1...v2.10.0

2.9.1

September 4th, 2026

Patch release fixing asset field handling. No breaking changes.

Fixed

Asset field values are round-trip safe (#41)

statamic-entries get returns an assets field the way Statamic stores it — a container-relative path, icons/heart.svg. Everything downstream of a Control Panel form submission expects the other form: the canonical container::path asset ID, in a list. So sending a value straight back into create or update failed:

The page_builder cards icon field must be a file of type: svg.

MimesRule resolves the value with Asset::find(), and a bare path finds nothing. On a field with no file rules it would have got further and then broken in Assets::process(), which calls Asset::findOrFail(). It also hit fields the caller never touched, because update validates incoming data merged with the entry's stored data — all paths.

Incoming asset paths now resolve to canonical IDs before validation, covering nested replicator, bard, grid and group fields as well as top-level ones, across entries, terms and globals.

content_validate no longer flags every valid assets field

The read-side sweep ran the blueprint's rules against stored values, so it reported up to three false errors for a perfectly valid single-file assets field — the mimes failure above, plus "must be an array" and "must not have more than 1 items" from the fieldtype's own rules, which expect a list rather than the bare string Statamic stores. The rule pass now sees bridged references; the structural pass still sees stored values verbatim, so a missing_asset finding keeps quoting what is actually on disk.

content_validate resolves single-container asset fields — a field omitting container where the site has exactly one was skipped by the missing-asset check; it now resolves the same way the fieldtype does.

Notes

Values that resolve to no asset are passed through untouched, so validation reports the real problem rather than silently dropping content. Canonical container::path IDs continue to work unchanged.

Thanks to @florianbouvot for the report.

Full changelog: https://github.com/cboxdk/statamic-mcp/compare/v2.9.0...v2.9.1

2.9.0

August 27th, 2026

Added

  • content_validate action on statamic-content-facade — Validates content that is already stored against its blueprints. Writes through this addon are validated on the way in; content that arrives another way (git merges, hand-edited YAML, blueprints changed after the content was written) was previously invisible. Each record gets two passes: the blueprint's own validation rules, evaluated the same way a Control Panel save evaluates them, plus structural checks the rule engine cannot express — replicator/bard blocks naming a set that no longer exists, sets and grid rows storing keys the blueprint dropped, select/radio/button_group/checkboxes values outside the declared options, assets fields pointing at missing files, and navigation items linking to deleted entries. All of these pass rule validation silently while breaking at render time. Supports scope, collection/taxonomy filters, severity filtering, offset/limit paging across the combined record stream, and a max_findings cap that keeps summary counts accurate when the list is truncated

  • ValidatesContentRecords concern — The two-pass record validation above, extracted so other routers can reuse it. The rule pass is isolated per record: a malformed stored value that makes a fieldtype's rule builder throw is reported as a rule_engine_error warning rather than aborting the sweep, and the structural pass still runs to name the underlying shape problem

  • MCP resources for blueprints — The server now exposes the resources capability, which it previously left unused. statamic://blueprints lists every readable blueprint with its URI; statamic://blueprints/{namespace}/{handle} returns one blueprint's fields, so a client can look up field structure to shape a write without spending a tool call. Because RequireMcpPermission defers scope checks to the primitive, resources run the same four gates a router read does — tool enablement, token scope (blueprints:read), resource policy, and Statamic permissions — via a new AuthorizesResourceAccess concern. Blueprints the resource policy hides are absent from the index, not merely refused on read

  • #[Title] on every tooltools/list has always carried a title field; without the attribute it fell back to Str::headline(class_basename()), so clients displayed "Entries Router". Tools now declare their own display titles

  • Typed validation modelFinding, RecordRef, and the Severity/FindingType/RecordType enums replace the array<string, mixed> bags the validation sweep threaded through its call chain. Severity is derived from the finding type rather than passed alongside it, so a finding cannot be built with a severity that contradicts what it describes; findings become arrays only at the MCP response boundary

  • Testing/InteractsWithMcp and Testing/FakeTransport — Shipped testing helpers for driving this addon's MCP server, dogfooded by the package's own suite. A tests/Fixtures composition site is included in the PHPStan paths so the traits are analysed where they are actually mixed in — which immediately caught a wrong class-string bound in the trait itself

  • Supply-chain gatebin/check-licenses.php fails the build on any non-permissive dependency (SPDX dual-licensing handled: a package passes if any arm is permissive), and bin/generate-sbom.php emits a deterministic CycloneDX 1.5 sbom.json with sorted components and a content-derived serial number, so it only changes when dependencies do. Wired into CI along with composer audit --no-dev, plus a new composer qa aggregate. CI validates the generated document rather than diffing it against the committed one: composer.lock is gitignored because this is a library, so a freshly resolved lock legitimately differs and a drift check would fail whenever any transitive dependency publishes. The license check found one real case: statamic/cms is proprietary, recorded as a justified exception because a Statamic addon cannot avoid depending on Statamic

  • SECURITY.md — Private vulnerability reporting via GitHub, an explicit split between what this addon secures and what the operator does, and a limitations section stating plainly that the audit log is append-only by convention with no hash chain (neither tamper-proof nor tamper-evident), that confirmation tokens are replayable within their window, and that require_https falls back to off when the published config predates the key

Fixed

  • The MIT license text actually shipscomposer.json has always declared MIT, but the repository never contained a LICENSE file, so the grant existed only as metadata. The standard MIT text is now included
  • Entry updates no longer fail on blueprints with a required slug (#39) — The #27 fix removed the slug from the validated payload entirely, so Statamic's default slug field (validate: [required, UniqueEntryValue…]) could never be satisfied: every update failed with "The Slug field is required", whether the caller omitted the slug or resent the current one. The entry's effective slug is now injected back into the validation payload, and both FieldsValidator invocations (including the TypeError fallback) resolve the UniqueEntryValue({collection}, {id}, {site}) placeholders via withReplacements(), so the rule excludes the entry being updated — the false positive #27 was about — while a slug owned by another entry is still rejected
  • date no longer has to be resent on every update of a dated collection — Like the slug, the date is an entry property absent from the merged data payload, so a blueprint with a required date field failed any update that did not repeat a date the caller never meant to change. The entry's current date now satisfies the rule when the payload omits it
  • Explicit slug on create actually workscreateEntry() read $arguments['slug'], but the tool schema never declared the parameter, so no client could send it and the slug was always derived from the title. The schema now declares slug, and create also accepts it as data.slug — the shape update uses — storing it as an entry property in both cases, never as a data key
  • PHPStan level 9 is no longer partly disabled — The config carried blanket ignoreErrors patterns (#Method .* should return .* but returns mixed#, #Cannot call method .* on .*\|null#, #Parameter .* expects .*, mixed given#, and four more) that suppressed whole error classes across src/, so "level 9 clean" meant considerably less than it sounded. Removing them surfaced 54 real errors — almost all method calls on Blueprint|null, Entry|null, or GlobalSet|null after a lookup, because requireResource() returned an error array without narrowing the variable. Every site now checks for null explicitly (identical messages, identical behaviour, and the analyser can see it), the untyped Statamic/Eloquent return values are narrowed rather than cast, and the four @phpstan-ignore annotations on abort() calls are gone. requireResource() itself is removed, having no callers left
  • CI verifies formatting instead of rewriting it — The Tests workflow ran Pint in fix mode, committed the result, and pushed it back to the branch; it now runs pint --test and fails on violations, matching what the release workflow already does
  • CI exercises both Laravel majorscomposer.json claims Laravel 12 and 13 via orchestra/testbench: ^10.0 || ^11.0, but the matrix only varied PHP, so every job resolved testbench 11 and the Laravel 12 claim was never verified. The matrix now spans both. The suite passes on Laravel 12
  • Package classes are no longer final — Nine classes were sealed, blocking consumers from extending or decorating what the package ships
  • Failed tool calls set the MCP isError flag — Every response was returned via Response::structured(), which never marks an error, so a failed call arrived at the client indistinguishable from a successful one; only a model parsing the JSON body would notice "success": false. Failures are now assembled from Response::error() plus the same structured content, so both the protocol flag and the full envelope (including confirmation tokens) survive
  • Plaintext credentials no longer reach stack tracesTokenService::validateToken()/findByPlainText(), ConfirmationTokenManager's token methods, AuthenticateForMcp::authenticateWithCredentials(), and every ClientConfigGenerator method took secrets as plain parameters. In the stdio server, setupErrorHandling() writes getTraceAsString() to stderr, so a throw anywhere in those call chains logged the bearer token verbatim. All are now marked #[\SensitiveParameter], matching the hardening laravel/mcp applied upstream in v0.9.0
  • Server version no longer hardcodedStatamicMcpServer::$version was the literal '2.8.0' and would have drifted at the next release. It is read from Composer's installed-package metadata, falling back to 0.0.0 only when that is unavailable
  • Release workflow no longer hangs — The release job ran pest --parallel, which is not parallel-safe: Statamic\Testing\AddonTestCase points every Stache store, and PreventsSavingStacheItemsToDisk's dev-null directory, at one shared tests/__fixtures__ path, so ParaTest workers deleted each other's fixtures. This produced ~34 spurious failures or, when workers collided on the file-store flock() calls, a hang that burned the 6h job timeout (v2.6.1 and v2.8.0 both died this way, and both releases had to be published by hand). The release job now runs the same single-process pest that gates pull requests, and every job has an explicit timeout-minutes so a hang fails in minutes instead of hours
  • Release notes are no longer empty — The changelog extraction matched [v2.8.0] against headings written as [2.8.0], so it never selected anything. The tag's v prefix is now stripped, with a fallback message if the section is missing
  • Release workflow verifies formatting instead of rewriting it — The Fix code formatting step ran Pint in fix mode and discarded the result; it now runs pint --test and fails on violations

Changed

  • Docs follow the standard topic layoutintroduction.md became index.md, quickstart.md moved to the docs root, and a generated requirements.md states only what the resolver enforces. docs/superpowers/ and its subfolders gained the _index.md landings and frontmatter they were missing, which had been downgrading the docs site's grading from complete to partial. All relative links repaired and verified
  • Pest constraint widened to ^4.1 || ^5.0 — Pest 5 is stable; the package was a major behind
  • Tests now exercise the real MCP protocol — Every test drove tools through execute() directly, so JSON-RPC argument delivery, response serialization, the isError flag, and outputSchema conformance had no coverage at all; StatamicMcpServerTest even read protected properties by reflection. A new McpProtocolSurfaceTest drives tools through StatamicMcpServer::tool(). This required registering Laravel\Mcp\Server\McpServiceProvider in TestCase::getPackageProviders() — Testbench does not run package auto-discovery, so without it Laravel\Mcp\Request never receives arguments and protocol-level tests pass while asserting nothing
  • composer stan passes --memory-limit=2G — PHPStan crashed its parallel worker at PHP's default 128M; 1G proved marginal once tests/Fixtures joined the analysis paths
  • Removed the composer test:parallel script — It could not work for the reason above; use composer test

2.8.0

July 29th, 2026

Changed

  • laravel/mcp ^0.9 support — Widens the dependency constraint to ^0.6 || ^0.7 || ^0.8 || ^0.9. The 0.9 breaking changes are client-side only (the Laravel\Mcp\Client\Contracts\Transport contract gained setProtocolVersion(), and the MCP client now only negotiates 2025-11-25/2025-06-18); this addon ships a server and does not implement either, so no code changes were needed. Server-side gains come for free: stricter JSON-RPC params/arguments validation (-32602 on non-object payloads), a CursorPaginator fix for negative cursor offsets, and hardened OAuth dynamic client registration

2.7.0

July 5th, 2026

Changed

  • laravel/mcp ^0.8 support — Widens the dependency constraint to ^0.6 || ^0.7 || ^0.8, allowing the latest laravel/mcp release (v0.8.x) with MCP client support, MCP UI Apps, ResourceLink content type, and OAuth improvements. All patterns used by this addon are unchanged across 0.6–0.8

Fixed

  • Blueprint types action null handles — Blueprints without a handle are now skipped during type analysis instead of triggering a type error
  • Output buffer cleanup type safety — Shutdown output-buffer sweep in the MCP server no longer relies on an impossible false comparison flagged by stricter dependency types

Full Changelog: https://github.com/cboxdk/statamic-mcp/compare/v2.6.1...v2.7.0

2.6.1

June 30th, 2026

[2.6.1] - 2026-06-30

Fixed

  • Confirmation token retry loop — Router schemas now expose confirmation_token as an optional top-level argument, giving MCP clients a valid schema slot for the token returned by confirmation-required responses (#34)
  • Confirmation payload drift on retry — Confirmation tokens now preserve the originally confirmed arguments, tolerate associative key reordering, and restore the confirmed payload before executing the gated action while still rejecting changed payloads and reordered lists (#34)

Tests

  • Added regression coverage for schema exposure, two-step confirmation retries, nested associative argument reordering, and confirmed payload restoration

2.6.0

June 2nd, 2026

[2.6.0] - 2026-06-02

Fixed

  • Eloquent user ID compatibility — Normalizes Statamic user IDs before MCP token ownership checks so sites using the Eloquent users driver no longer hit strict type errors in the MCP dashboard or token flows (#31)
  • OAuth authorization user IDs — Applies the same user ID normalization when creating OAuth authorization codes for Eloquent-backed users
  • Fresh install dependency compatibility — Allows laravel/mcp ^0.7 alongside ^0.6 and updates installation docs/generated guidance to match (#31)

Tests

  • Added regression coverage for integer Eloquent user IDs, string user IDs, and missing current users in MCP dashboard user resolution

2.5.0

May 6th, 2026

[2.5.0] - 2026-05-06

Added

  • Revision-aware entry workflows — When a collection has revisions enabled, the MCP server now respects Statamic's editorial workflow instead of bypassing it with direct saves. Updates to published entries create a working copy (published content unchanged), creates use store() for draft + initial revision, and publish/unpublish delegate to Statamic's built-in revision-aware methods (#30)
  • New entry actions: list_revisions, get_revision, restore_revision, publish_working_copy — full revision lifecycle management via the statamic-entries router
  • version parameter on entry get — Retrieve published, working_copy, or latest version of an entry
  • HandlesRevisions trait — Encapsulates revision-aware save, list, get, and restore operations matching the Statamic CP's exact editorial workflow
  • Confirmation gate defaults for revision actionsrestore_revision and publish_working_copy now require confirmation in production by default
  • 31 new tests covering the full revision lifecycle (create → working copy → list revisions → restore → publish)

Fixed

  • Multi-site response in publishWorkingCopyAction — Re-fetches entry with site context to return correct localized data
  • Stale revision metadata after restorerevision_status and restored_as_working_copy now reflect actual state after restoreRevisionAction
  • normalizeTableCell unbounded recursion — Table cell normalization now unwraps one level only, preventing stack overflow on deeply nested structures
  • filterOutputFields denied field stripping — Denied fields are now correctly stripped from list responses and nested entry data, not just top-level get responses
  • generateBlueprint field array shape — Blueprint generation now produces the correct indexed handle/field format
  • revision_message type safetypublishWorkingCopyAction validates that revision message is a string before passing to Statamic
  • PHPStan L8 compliance — Resolved mixed offset access in filterOutputFields recursive field filtering

2.4.0

May 5th, 2026

What's new

Per-field wire-format spec

BlueprintsRouter::get now emits a _format_spec per field describing the exact wire format — shape, allowed node types/marks, set handles, recursive set definitions, and canonical examples. Covers bard (inline + full), replicator, grid, group, markdown, scalar, select/checkbox, relationship, asset, table, and date fields. This eliminates the guessing game that caused agents to produce malformed bard/replicator payloads. (#29)

Configurable confirmation actions

New confirmation.actions config block allows per-domain control over which MCP actions require confirmation tokens. Operators can now gate entries.update, entries.publish, globals.update, etc. without forking the package. Domains not listed fall back to default. * gates every action; [] disables the gate. Shipped defaults preserve existing behaviour. (#26)

Client-safe exception messages

FieldFormatException, ValidationException, FieldtypeNotFoundException, and BlueprintNotFoundException messages now survive production sanitization — agents get actionable error messages instead of a generic placeholder.

Bug fixes

  • Entry slug self-collision on updateupdateEntry() no longer fails with "slug already taken" when updating an entry without changing its slug (#28)
  • Table cell normalizationSanitizesFieldData now correctly normalizes {value: …} objects in table cells to plain strings, preventing [object Object] rendering in the CP
  • Playwright CI stability — Browser tests now use shared auth state (single login per run), preventing Statamic's login throttle from failing tests

Full Changelog: https://github.com/cboxdk/statamic-mcp/compare/v2.3.0...v2.4.0

2.3.0

April 23rd, 2026

What's Changed

  • feat: confirmation tokens and granular resource policy by @sylvesterdamgaard in https://github.com/cboxdk/statamic-mcp/pull/22
  • docs: add Node.js TLS certificate fix for Laravel Herd/Valet by @sylvesterdamgaard in https://github.com/cboxdk/statamic-mcp/pull/24
  • fix(sanitizer): normalize table cells to scalars before persist by @sylvesterdamgaard in https://github.com/cboxdk/statamic-mcp/pull/25

Full Changelog: https://github.com/cboxdk/statamic-mcp/compare/v2.2.4...v2.3.0

2.2.4

April 14th, 2026

Fixed

  • Critical: Update action on entries, terms, and globals no longer crashes with Cannot access offset of type string on string when blueprints include third-party fieldtypes (e.g., SEO Pro) — validation falls back to incoming-only fields on TypeError
  • OAuth CIMD discovery: cimd_enabled config checks use (bool) cast with true default — previously silently disabled by strict comparison, env strings, and missing config keys from shallow merge
  • OAuth path-suffixed discovery: Added /.well-known/oauth-authorization-server/{path} and /.well-known/oauth-protected-resource/{path} routes per RFC 8414 §3.1 — MCP clients (incl. ChatGPT) use path insertion for discovery and previously got 403

Added

  • OAuth 2.1 CIMD support: Client ID Metadata Document resolution — MCP clients can present verified application identity on consent screen
  • CIMD resolver with SSRF protection, JSON-LD validation, and configurable caching
  • 10 update validation tests (deep nested replicator/bard/grid/group, round-trip, crashing fieldtype simulation)
  • 24 discovery endpoint tests (CIMD config edge cases, path-suffixed routes, CP route changes, revocation endpoint, full ChatGPT-style client discovery flow)
  • Comprehensive CIMD test suite (unit, feature, E2E)

See CHANGELOG for full details.

2.2.3

April 14th, 2026

Fixed

  • CIMD still not detected by ChatGPT: Added path-suffixed discovery routes per RFC 8414 §3.1. MCP clients following the 2025-11-25 spec resolve discovery for /mcp/statamic at /.well-known/oauth-authorization-server/mcp/statamic — without these routes the request returned 403, so ChatGPT never saw client_id_metadata_document_supported and disabled CIMD.

2.2.2

April 14th, 2026

Fixed

  • CIMD still not detected: All cimd_enabled config lookups now default to true when the key is missing. mergeConfigFrom() only does a shallow merge — published config files from before v2.2.0 don't have the cimd_enabled key, so it returned null and CIMD stayed disabled. No config republish needed.

2.2.1

April 14th, 2026

Fixed

  • CIMD not detected by clients: cimd_enabled config check used strict === true comparison against an env string — CIMD was never advertised in discovery metadata. Fixed in DiscoveryController, AuthorizeController, and OAuthTokenController.

2.2.0

April 14th, 2026

Fixed

  • Critical: Update action on entries, terms, and globals no longer crashes with Cannot access offset of type string on string when blueprints include third-party fieldtypes (e.g., SEO Pro)

Added

  • OAuth 2.1 CIMD support: Client ID Metadata Document resolution — MCP clients present verified identity on the consent screen
  • 10 new update validation tests covering deeply nested blueprints
  • Comprehensive CIMD test suite

See CHANGELOG for full details.

2.1.0

April 13th, 2026

Highlights

Fieldtype process() pipeline — Data saved via MCP now matches the Statamic CP format. All content routers call $fields->process()->values() after validation, ensuring Terms strip prefixes, Bard normalizes nodes, and Relationships wrap values correctly.

ENG-697 fix — Entry updates with terms field type no longer crash. Relationship fields (terms, entries, users, assets) and checkboxes normalize bare strings to arrays before validation.

Security hardening — OAuth auth code/refresh token double-spend prevented, client_name XSS sanitized, HTTPS enforced on OAuth endpoints, default scopes restricted to read-only.

See CHANGELOG.md for full details.

Upgrading

No breaking changes. composer update cboxdk/statamic-mcp is sufficient.

OAuth default scopes changed from * to read-only. If your OAuth clients need write access, set STATAMIC_MCP_OAUTH_DEFAULT_SCOPES in your .env:

STATAMIC_MCP_OAUTH_DEFAULT_SCOPES#89DDFF;">=#89DDFF;">"content:read,content:write,blueprints:read,entries:read,entries:write#89DDFF;">"

Existing tokens are not affected — only new OAuth clients created after upgrade will use the new defaults.

2.0.4

April 10th, 2026

Fixed

  • Critical: Entry creation no longer crashes with "Cannot access offset of type string on string" when data contains complex nested fields (Bard, Replicator)
  • Date fields now accept any common format (Y-m-d, Y-m-d H:i, ISO 8601, {date, time} objects) — values are normalized to the Zulu format Statamic expects before validation
  • date and published in entry data are now correctly extracted as first-class entry properties instead of failing blueprint validation on dated collections

Added

  • NormalizesDateFields trait for consistent date handling across all routers (Entries, Terms, Globals)
  • 13 new integration tests covering date normalization, published extraction, and error handling

2.0.3

April 9th, 2026

Fixed

  • Critical: Blueprint update action no longer destroys existing fields — fields are now merged by default instead of replaced
  • Blueprint update preserves tab and section organization in multi-tab blueprints

Added

  • replace_fields parameter on blueprint update for explicit full-replacement when needed

2.0.2

March 19th, 2026

Fixed

  • Install command no longer crashes on sites without a database — migrations are now skipped automatically when file-based storage drivers are configured (the default)
  • Config publish prompt: confirming "Overwrite? yes" now actually overwrites the file (previously --force stayed false, so vendor:publish silently skipped it)
  • Migration failures are caught with actionable guidance instead of crashing the installer
  • Completion message now reflects what actually happened during install

Added

  • --skip-migrations flag on mcp:statamic:install as an explicit escape hatch

Full Changelog: https://github.com/cboxdk/statamic-mcp/compare/v2.0.1...v2.0.2

2.0.1

March 18th, 2026

Fixed

  • Token expiry date validation no longer blocks submission — max_token_lifetime_days is now a default suggestion, not a hard server-side rejection
  • Token form error feedback uses Statamic toast notifications and native ui-error-message components with red border highlighting

Added

  • Scope presets (Read Only, Content Editor, Full Access) in token create/edit form, matching documented common combinations
  • Preset-aware badge display in admin token table — shows preset name instead of listing individual scopes
  • Admin token form now uses Statamic-style grouped permission cards with per-group "Check All"

Removed

  • Internal development plans and specs (docs/superpowers/) accidentally included in v2.0.0

2.0.0

March 18th, 2026

v2.0.0 — Storage drivers, OAuth 2.1, audit overhaul, security hardening

Major release: storage driver abstraction, MCP OAuth 2.1 with PKCE, comprehensive audit logging, router-based tool architecture, and security hardening.

Breaking Changes

  • Statamic v5 dropped — requires Statamic v6.6+, Laravel 12/13, PHP 8.3+
  • Laravel MCP v0.6 — new tool attribute pattern
  • Router architecture — 140+ tools consolidated into 11 domain routers
  • Tool names changedstatamic.blueprints.liststatamic-blueprints with action: list
  • Config restructured — re-publish required

Highlights

  • Storage drivers: File (YAML/JSONL, default) and Database (Eloquent)
  • OAuth 2.1: PKCE S256, Dynamic Client Registration, refresh token rotation, revocation
  • 21 scoped API tokens with fine-grained access control
  • CP Dashboard: User + Admin pages with token management and audit log
  • Security: 8 review rounds, 30+ findings fixed
  • Laravel 13 support
  • 772 tests, PHPStan Level 8, full CI matrix

Upgrade Guide

See UPGRADE.md for migration steps from v1.x.

Full Changelog

See CHANGELOG.md for complete details.