name: seed-content description: "Scrape a strapi.io page, extract content for specific components, transform to match local Strapi schemas, and seed into local Strapi via MCP. Schema-driven and resilient to evolving schemas. Triggers: seed content, scrape strapi, import content, seed page, seed component."
Seed Content from strapi.io
Scrape a strapi.io page, extract content, map it to local Strapi schemas, and seed it into the local Strapi instance via MCP.
This workflow is intentionally adaptive. Local schemas evolve, so the skill must discover schema shape at runtime and avoid hardcoded assumptions.
Prerequisites
- Strapi MCP server is configured. Verify with
strapi_list_servers(). - If MCP is not configured, tell the user to run
/setup-strapi-mcpfirst and stop. - Strapi is running locally at
http://localhost:1337.
Execution Modes
Autonomous mode (caller_authorized: true)
When invoked by another skill (e.g. /copy-component) with caller_authorized: true, this skill runs fully autonomously:
- All required inputs are provided by the caller — do not prompt for any of them.
- Skip all approval prompts (Step 6 preview approval, Step 9 page-exists choice).
- Default to
appendwhen a page already exists (do not ask replace/append/cancel). - Never use
AskUserQuestion— the calling skill has already obtained user intent. - If something fails, log the error and continue. Report failures in Step 10.
Interactive mode (default, caller_authorized: false)
When invoked directly by the user, prompt for missing inputs and request approval before writes.
Inputs
| Input | Required | Default | Description |
|---|---|---|---|
source_url |
yes | — | strapi.io page to scrape (e.g. https://strapi.io/pricing) |
target_component |
no | all recognized | specific component UID to seed (e.g. sections.newsletter-banner) |
target_page_path |
no | derived from URL | local page full path (e.g. /pricing) |
locale |
yes | en |
locale to seed into |
parent_page_fullPath |
no | derived from path | explicit parent for nested pages |
unknown_schema_policy |
no | best-effort fill |
how to handle unmapped fields |
preExtractedContent |
no | — | { desktop, mobile } structure data from /copy-component mega-extract. When provided, skip Step 3. |
caller_authorized |
no | false |
when true, run in autonomous mode (see above) |
In interactive mode, ask the user for any missing required inputs. In autonomous mode, derive all missing values automatically — never prompt.
Safety Defaults
- Schema files are the source of truth, not heuristics.
- Use best-effort mapping only when type-compatible.
- Never send payloads that violate required fields or enum constraints.
- If data cannot be mapped safely, skip that fragment and report it.
- In interactive mode, show a preview and wait for explicit user approval before writing.
- In autonomous mode, log the preview but proceed without prompting.
Dynamic zone merge rule (CRITICAL)
Strapi PUT requests replace the entire field value. Sending "content": [{ new }] will delete all existing components on that page.
Always GET the page with full content populate first, then append new components to the existing array before PUT.
// WRONG — wipes existing content
PUT { "data": { "content": [{ "__component": "sections.new", ... }] } }
// RIGHT — preserves existing
GET → content = [{ existing1 }, { existing2 }]
PUT { "data": { "content": [{ existing1 }, { existing2 }, { "__component": "sections.new", ... }] } }
Schema registration rule (CRITICAL)
Never write a __component UID to Strapi that the running server hasn't registered. After creating new schema files, verify registration automatically:
- Check Strapi is alive: run
lsof -ti:1337. If no process is listening, Strapi has crashed — ask the user to restart manually. - Run
touch apps/strapi/src/index.tsto guarantee the file watcher triggers a restart. - Wait 5 seconds for the restart cycle to begin.
- Poll
strapi_get_componentsvia MCP every 5s, up to 6 attempts (30s total). - On each poll failure, re-check
lsof -ti:1337. If process died, stop polling and ask user to restart. - Check if the target UID appears in the component list.
- Found → proceed with writes.
- Timeout (30s) with process alive → fall back to asking the user to restart Strapi manually. Wait for confirmation before any MCP write operations.
Steps
Step 1: Verify MCP setup
Call strapi_list_servers():
- If call fails or Strapi MCP tools are unavailable, tell user to run
/setup-strapi-mcpand stop. - If call succeeds, continue.
Step 2: Verify local Strapi availability
Check if port 1337 is in use (lsof -ti:1337), then verify Strapi responds:
- Prefer
GET /api/healthand expect200. - If unavailable, fall back to checking
http://localhost:1337/admin. - If Strapi is not running, ask the user to start it in a separate terminal and wait for confirmation. Never launch dev servers in the background.
Step 3: Fetch or receive source page content
If preExtractedContent was provided in the inputs, use its desktop tree directly — skip fetching. The tree is a merged structure+styles tree ({ tag, styles, text?, attrs?, children? }) already extracted by the copy-component mega-extract and contains headings, body text, links, images, lists, and section hierarchy.
Otherwise, fetch rendered page content using the best available tool in this environment:
- Primary: Web fetch of HTML.
- Fallback: browser automation snapshot for JavaScript-rendered layouts.
Extract:
- Headings, body text, labels, and descriptions.
- Links (label + href + new tab hints).
- Image URLs and alt text.
- List/card/table structures.
- Section boundaries and hierarchy.
Step 4: Read local schemas (recursive)
Read schema definitions before mapping:
- Page schema:
apps/strapi/src/api/page/content-types/page/schema.json - Header schema:
apps/strapi/src/api/header/content-types/header/schema.json - Footer schema:
apps/strapi/src/api/footer/content-types/footer/schema.json - Component schemas:
apps/strapi/src/components/{category}/{name}.json - Nested component schemas: recursively follow
componentreferences. - Optional context:
apps/strapi/src/populateDynamicZone/**/*.ts
Build a runtime schema map that includes:
- Allowed dynamic zone component UIDs for each content type (page, header, footer).
- Field types (
string,text,richtext,boolean,integer,enumeration,media,relation,component). - Required flags.
- Enum allowed values.
- Relation targets.
Step 5: Build schema-first mapping plan
Match source sections to local component UIDs by reading docs/component-registry.md. Use pattern matching as initial candidates only — schema validation decides final mapping.
For each extracted source section:
- Match to a candidate local component UID.
- Validate that UID is allowed by page
contentdynamic zone.- If no local component matches, skip the section and describe what was found. Suggest
/create-content-componentwhen a new local component is needed.
- If no local component matches, skip the section and describe what was found. Suggest
- Map values recursively by schema:
- Scalars: coerce conservatively and only when type-compatible.
- Enumerations: assign only allowed values; otherwise skip and report.
- Components: recurse into nested schema.
- Relations: build unresolved placeholders for Step 8.
- Media: build unresolved placeholders for Step 7.
- Unknown or new schema fields: try best-effort mapping only when type-compatible. If not safely mappable, skip and report. Never fail the whole run for one unmapped field unless user requests strict mode.
- Enforce required-field validity:
- If a required field cannot be mapped, mark the component fragment as invalid, skip it, and report under
invalid. Never fail the whole run for one invalid fragment.
- If a required field cannot be mapped, mark the component fragment as invalid, skip it, and report under
- Link rule:
- For
utilities.linkwith unresolved page relation, convert to:type: "external"href: absolute source URL
- For
Step 6: Preview and approval
Build a preview of planned operations:
- Component UIDs planned for create/update.
- Page target and locale.
- Mapping summary:
mapped,best_effort_mapped,skipped,invalid,requires_manual_followup. - Media URLs to upload.
- Relations to resolve/create.
Autonomous mode: Log the preview summary and proceed immediately to Step 7. Do NOT use AskUserQuestion.
Interactive mode: Present the preview and wait for explicit user approval before continuing.
Step 7: Resolve media
Never use Playwright screenshots (browser_take_screenshot) to capture assets. Screenshots produce lossy raster captures that lose transparency, resolution, and SVG scalability. Always extract the original source URL from the page DOM (e.g. img[src], source[srcset], inline SVG <svg> elements, CSS background-image URLs) and download directly.
If a direct source URL cannot be determined (e.g. inline SVG without a file reference, canvas-rendered graphics), skip the asset entirely and report it in manual_steps_needed. Never fall back to a screenshot.
For each mapped media placeholder:
- Check for existing media first (reuse by name):
- Derive a filename from the source URL (e.g.
https://strapi.io/assets/icons/rocket.svg→rocket.svg,rocket). - Query Strapi media library:
GET /api/upload/files?filters[name][$containsi]=<filename>. - If a match exists, reuse its media ID — do not re-upload.
- Derive a filename from the source URL (e.g.
- Download and upload only when no match exists:
- Download source asset with
curl -Lusing the original source URL. - Upload via
strapi_upload_media()with a descriptivenameandalternativeTextfrom the sourcealtattribute.
- Download source asset with
- Replace placeholder with local media ID (reused or newly uploaded).
Validation rules:
- If media upload fails and field is optional, omit that field and continue.
- If media upload fails and field is required, do not write
null.- Skip the containing component fragment.
- Add item to
invalidandrequires_manual_followup.
Step 8: Resolve relations with deterministic order and dedupe
Resolve dependencies bottom-up with find-before-create:
api::plan-feature.plan-featureapi::plan.plan- Components that reference plans/features
- Page entry
Dedupe keys (default guidance):
- Plan feature:
name + category (+ locale when localized) - Plan:
name + type + locale - Page:
fullPath + locale
Rules:
- Query existing records first via
strapi_rest()GET. - Reuse exact matches — always run find-before-create for page and relation entities to avoid duplicate plans/features/pages.
- Create only when no exact match exists.
Step 9: Create or update content entry (locale + hierarchy safe)
Determine the target content type based on mapped components:
| Component category | Content type | API endpoint | Type |
|---|---|---|---|
sections, forms, plans |
Page | api/pages |
Collection type |
navigation |
Header | api/headers |
Single type |
footer |
Footer | api/footers |
Single type |
For pages (collection type)
Do not treat fullPath as authored data by default.
- Find existing page by
fullPathandlocale:
GET /api/pages?locale=<locale>&filters[fullPath][$eq]=<targetFullPath>
If page exists:
- Autonomous mode: default to
append— add new components to existing content without prompting. - Interactive mode: ask user to choose:
replace,append, orcancel.
- Autonomous mode: default to
If page does not exist:
- Derive
slugfrom target path. - Resolve parent page from
parentFullPathor derived parent path (same locale). - Create using
slug, optionalparentrelation,content, andstatus: "published". - Avoid writing
fullPathdirectly unless user explicitly requests it and confirms hierarchy automation is disabled.
- Derive
After write:
- Re-fetch by
documentId+locale. - Verify expected
fullPath. - If
fullPathis missing/stale because internal jobs are pending, report manual follow-up:- In Strapi admin, run
Recalculate all fullpaths. - Run
Create all redirectsonly when redirects are desired.
- In Strapi admin, run
- Re-fetch by
For header/footer (single types)
- GET the existing entry with full
contentpopulate and locale. - Merge new components into the existing
contentarray (dynamic zone merge rule applies). - PUT the merged content back.
- Single types always exist — never create, only update.
Step 10: Report results
Report: actions taken, items created/updated/reused, items skipped/invalid, media upload results, relation reuse/create results, and any manual follow-up needed. Include page title, locale, fullPath, and documentId when available.