Skip to content

04 — Schedule and Unschedule a Draft

Scheduling is the only way the SEO Navigator MCP server lets a post go live without manual intervention. There’s no publish_post tool — but schedule_draft works because WordPress flips the post status to future and only publishes when the clock catches up. The worker enforces a hard 60-second minimum lead time so accidental “schedule for now” calls turn into errors instead of an instant publish.

You

Schedule post 47 on viper-template for tomorrow at 9 AM Vietnam time.

Claude
schedule_draft

Scheduled. Here’s the result:

  • Post ID: 47
  • Status: scheduled (WordPress shows this as future internally)
  • Goes live at: 2026-05-27T02:00:00Z (09:00 ICT)
  • Edit in wp-admin: https://viper-template.on-forge.com/wp-admin/post.php?post=47&action=edit

You’ll see a “Scheduled” badge instead of “Draft” in the WordPress post list. If you want to cancel the schedule, just say the word — I’ll flip it back to draft with unschedule.

WordPress treats “schedule for the past” or “schedule for the next few seconds” as publish immediately. The worker refuses any scheduled_at that’s less than 60 seconds in the future:

You

Actually schedule it for 30 seconds from now to test.

Claude
schedule_draft

I can’t — the MCP server returned ForbiddenOperationError:

scheduled_at must be at least 60s in the future (got 30000ms)

This is a hard guardrail. The reason is WordPress’s future status path is effectively a delayed publish — if the time is too close, WP just publishes immediately, which would bypass the server’s no-publish-now rule. Pick a time at least one minute out, or schedule the slot you actually want.

Reverting a scheduled post to draft uses the unschedule tool. The post’s content is preserved; only the status changes.

You

Unschedule post 47 — I want to revise the copy.

Claude
unschedule

Done. Post 47 is back to draft. The scheduled time has been cleared.

Open https://viper-template.on-forge.com/wp-admin/post.php?post=47&action=edit to edit. When you’re ready, ask me to re-schedule it.

  • create_draft → always lands as status=draft regardless of caller-supplied status
  • schedule_draft → requires scheduled_at ≥ now + 60s, throws ForbiddenOperationError otherwise
  • unschedule → flips status back to draft. Never touches published posts.
  • No publish_now tool exists — see tokens & scoping for why this is enforced at the registry level, not just at runtime

Both calls show in the audit log:

Audit log with schedule_draft and unschedule entries

If schedule_draft returned error with the upstream message E_UPSTREAM (wordpress 401), the site’s WP app password is wrong or expired — see diagnose_site to confirm which check fails.

Demonstrate the schedule_draft and unschedule tools, including the guardrail that a scheduled post never becomes published until the WP cron fires.

  • Scenario 03 completed; a draft post exists on the WordPress site acme-blog.
  • You know that draft’s post id (Claude reported it in scenario 03; if lost, ask Claude > list my drafts on acme-blog).
  • The user’s timezone is correctly set on the WP site (check wp-admin → Settings → General).
  • Claude Desktop on a new conversation (or continuing scenario 03’s thread is fine).
  • A WP admin tab open on the post editor for the scenario-03 draft.
  1. Action: In the composer, type:

    Schedule my latest draft on acme-blog for tomorrow at 09:00 in my local timezone.

    Press Enter. Expected: Claude calls list_drafts to find the post (or reuses the known id), then calls schedule_draft with scheduled_date set to tomorrow 09:00 in ISO 8601. Card resolves green. Recorder: open the tool card to show the ISO timestamp — viewers often miss that it must include a timezone offset.

  2. Action: Switch to the WP admin tab and refresh the post editor. Expected: The status pill now reads Scheduled, and the Publish button is replaced with Schedule showing the new date/time. Recorder: zoom on the status pill, then on the Scheduled date in the right sidebar.

  3. Action: Back in Claude Desktop, type:

    Actually, unschedule that post — I want to keep editing it.

    Expected: Claude calls unschedule with the post id. Card resolves green. Claude confirms the post is back to draft. Recorder: capture the tool card; the input is small ({ post_id, site_id }) so it fits on screen.

  4. Action: Refresh the WP admin tab again. Expected: Status pill returns to Draft. The Schedule button has reverted to Publish. Recorder: highlight the pill change with a side-by-side before/after frame if possible.

  • Admin dashboard Audit log shows two rows in order: tools/call schedule_draft then tools/call unschedule, both ok.
  • GET /wp-json/wp/v2/posts/<id>?context=edit returns status: "draft" and date_gmt reset to current time.
  • Trash the draft in WP admin if you want a clean slate, or leave it for scenario 06.
  • Scheduled date in the past: server returns E_INVALID_DATE. Show Claude correcting the time after reading the error.
  • Already published post: unschedule refuses with ForbiddenOperationError because the guardrail only allows scheduled → draft, never published → draft. Show the red card and explain.
  • Trying to schedule a Duda page: schedule_page exists for WP only. Show Claude apologising and suggesting Duda’s publish_on_next_site_publish flag instead.