Skip to content

Regression of #27: entry update now fails with "The Slug field is required" #39

Description

@markussorg

Bug Description

The fix for #27 traded one failure for another. updateEntry() now removes the slug from the payload before blueprint validation (unset($data['slug'])). That resolves the "already been taken" false positive, but the blueprint's required rule on the same field can no longer be satisfied by any caller. Entry updates still fail — same tool, same method, opposite error.

#27 now (v2.8.0)
Error slug: This value has already been taken. slug: The Slug field is required.
Cause slug is in the payload, UniqueEntryValue compares it against itself slug is not in the payload, required fires

This affects every collection whose blueprint contains the standard slug field that Statamic generates itself, so in practice every collection.

How to Reproduce

  1. Use a collection whose blueprint has the default Statamic slug field:

    handle: slug
    field:
      type: slug
      localizable: true
      validate:
        - required
        - 'new \Statamic\Rules\UniqueEntryValue({collection}, {id}, {site})'
  2. Call statamic-entries with action: update. Both variants fail:

    Omitting the slug:

    {
      "action": "update",
      "collection": "blog",
      "id": "<entry-id>",
      "data": { "bard": [ { "type": "paragraph", "content": [ { "type": "text", "text": "TEST" } ] } ] }
    }

    Sending the correct, unchanged slug:

    {
      "action": "update",
      "collection": "blog",
      "id": "<entry-id>",
      "data": {
        "title": "My Entry",
        "slug": "my-entry",
        "date": "2025-01-15T00:00:00.000Z",
        "bard": [ { "type": "paragraph", "content": [ { "type": "text", "text": "TEST" } ] } ]
      }
    }
  3. There is no payload that succeeds.

Expected Behavior

The update succeeds. When the caller omits slug, the entry keeps its current slug and the required rule is satisfied from the existing value. When the caller sends a slug, uniqueness is checked with the current entry excluded.

Actual Behavior

Field validation failed: slug: The Slug field is required.

When date is also omitted, both required entry properties are reported:

Field validation failed: slug: The Slug field is required.; date: The Date field is required.

Tool Name

statamic-entries, action update.

Environment

  • cboxdk/statamic-mcp: v2.8.0
  • statamic/cms: v6.28.0 (Pro, licensed)
  • laravel/framework: v12.67.0
  • laravel/mcp: v0.6.7
  • PHP: 8.4
  • Installation: custom, no starter kit
  • Clients: Claude Code and Claude Desktop
  • Servers: both affected — stdio (php artisan mcp:start) and the web endpoint with Bearer token

Additional Context

Root cause, in src/Mcp/Tools/Routers/EntriesRouter.php:

  • L541–561 — the slug is validated separately, applied via $entry->slug(), then unset($data['slug'])
  • L595–600 — an explicit note not to inject the slug into $mergedData
  • L602 — $mergedData = array_merge($entry->data()->all(), $data). $entry->data() does not contain
    the slug either, since it is an entry property, so the key is absent in every case
  • L611–615 — FieldsValidator still validates against the full blueprint, where slug is required

withReplacements() — the fix recommended by the maintainer in #27 — is still not called on either
FieldsValidator invocation.

Suggested fix. Keep the slug in the validated payload and resolve the rule placeholders instead:

(new FieldsValidator)
    ->fields($blueprint->fields()->addValues($mergedData))
    ->withContext($validationContext)
    ->withReplacements([
        'id' => $entry->id(),
        'collection' => $entry->collectionHandle(),
        'site' => $site,
    ])
    ->validate();

Applies to both calls, including the one in the catch (\TypeError $e) fallback.

date shows the pattern that works. L565–569 keeps the normalized value in $data with the
comment "Keep a normalized copy in data so the FieldsValidator sees the required field". The same
treatment would fix slug. Side effect worth noting: date must be resent on every update, or
validation fails with date: The Date field is required. even when the caller does not intend to
change it.

Related, probably from the same refactor. createEntry() reads the slug from
$arguments['slug'] (L353), but slug is not declared in the tool schema. It is therefore never
set, and the slug is always derived from the title. Setting an explicit slug on create is not
possible.

Current workaround, in case it helps others: remove required from the slug field's validate
list in the blueprint. UniqueEntryValue stays in force, and no MCP path can produce an empty slug
(omitting it leaves the entry's slug untouched, and the router's own slug check rejects an empty
string). It does weaken CP-side validation, so it is not a fix.

Checklist

  • I have searched for similar issues
  • I have read the alpha disclaimer
  • I can reproduce this consistently

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

bugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions