# Validation checklist

Run these checks after writing the bundle. Universal checks apply to every mode; mode-specific checks live in the per-mode references and must also be run for the chosen mode.

If a check fails, fix and re-run before reporting success to the user.

## Two lifecycle passes

A bundle gets validated at two distinct points, with different criteria:

- **Scaffolder-output pass** — run immediately after the builder generates the bundle. The description and homepage are *expected* to be `TODO:` placeholders; the body is the unmodified template. Goal: confirm scaffolding produced the right shape, not a publishable artifact.
- **Pre-publish pass** — run before shipping the bundle to ClawHub. The `TODO:` markers must be gone; the description must be durable purpose framing per [`conventions.md` § "Skill-vs-MCP boundary"](conventions.md#skill-vs-mcp-boundary); the homepage must point at real upstream MCP docs.

Both passes share the universal and mode-specific checks below — pick the right column based on which pass you're running. Auditing a published, iterated skill is the pre-publish pass, *not* the scaffolder-output pass; running the scaffolder-output checklist against a published skill flags refined-past-template content as drift (false positive).

## Universal checks

### File presence

- [ ] `<out>/<slug>/SKILL.md` exists.
- [ ] `<out>/<slug>/mcporter.json` exists.
- [ ] No anti-pattern files: no `README.md`, `CHANGELOG.md`, `INSTALLATION_GUIDE.md`, or `QUICK_REFERENCE.md` inside the bundle. No symlinks anywhere. (Skill-creator anti-patterns.)

### Naming consistency

Load-bearing — drift across these four sites breaks invocation silently. All four must be identical to `<slug>`:

- [ ] **Directory name** = `<slug>`.
- [ ] **`name` field** in SKILL.md frontmatter = `<slug>`.
- [ ] **mcporter server key** in `mcporter.json` (`mcpServers.<slug>`) = `<slug>`.
- [ ] **Invocation prefix** in any concrete SKILL.md examples = `<slug>`. (Generic `<tool>` placeholders are fine; what's load-bearing is that whenever an example uses a real-looking invocation, it uses `<slug>`.)

### `requires.bins` discipline

- [ ] `metadata.openclaw.requires.bins` includes `mcporter`.
- [ ] For OAuth mode: `requires.bins` is exactly `["mcporter", "jq", "flock", "shasum"]`, matching the runtime wrapper and seeder tools.
- [ ] For other HTTP modes (bearer, headers, noauth): `requires.bins` is exactly `["mcporter"]` and nothing else.
- [ ] For stdio mode: `requires.bins` includes `mcporter` plus the spawn binary (`command` value, or `npx`/`uvx` for package runners). Nothing more.

### SKILL.md content

- [ ] **Body ≤ 500 lines** (skill-creator hard cap).
- [ ] **No tool name enumeration in prose.** The body should reference `mcporter list <slug> --schema` for discovery, not list specific tools the upstream MCP exposes. Tool names drift; that drift is the bug we're avoiding.
- [ ] **Description shape — lifecycle-aware.**
  - *Scaffolder-output pass*: description starts with `TODO:` (the quoted-string form chosen because it parses as valid YAML and renders as plain text on ClawHub; the previous `<PLACEHOLDER>` form was fragile under strict YAML parsers and could be misinterpreted as HTML in CommonMark renderers). If `TODO:` is missing the scaffolder skipped the placeholder — re-emit.
  - *Pre-publish pass*: description does NOT start with `TODO:`, contains no `TODO:` markers anywhere, AND is durable purpose framing per [`conventions.md` § "Skill-vs-MCP boundary"](conventions.md#skill-vs-mcp-boundary) — names the integration's purpose, not the tool surface; includes a "use when …" trigger cue; doesn't enumerate domain entities (these will drift).
- [ ] **Homepage shape — lifecycle-aware.**
  - *Scaffolder-output pass*: `homepage` starts with `"TODO:"`.
  - *Pre-publish pass*: `homepage` is a real URL pointing at the upstream MCP's documentation (e.g. `https://linear.app/docs/mcp`, `https://docs.sentry.io/product/seer/mcp/`). Not a placeholder, not the vendor's marketing homepage.
- [ ] **`{baseDir}` used for in-bundle paths.** Examples invoking scripts or the config use `{baseDir}/...`, not absolute paths.
- [ ] **No internal references.** No mentions of internal projects, employees, customers, live tickets, or private repos. The bundle ships to public ClawHub.
- [ ] **Authentication block is structural-verbatim, with an optional addendum.** The canonical paragraphs from the per-mode template's Authentication block are copied verbatim after required expansion (placeholder substitution and bullet expansion); a separate vendor-specific addendum *may* be appended below the canonical block when, and only when, the upstream MCP documents auth behavior the canonical block doesn't cover. The bar is high — default to no addendum. Manual audit: diff the canonical paragraphs against the per-mode reference (must match byte-for-byte after expansion); review any addendum against the criteria in [`conventions.md` § "Editability — Discovery flow and Authentication block"](conventions.md#editability--discovery-flow-and-authentication-block) (verified, public-safe, doesn't contradict the canonical block, doesn't duplicate troubleshooting guesses or list tool behavior).

### `mcporter.json`

- [ ] Top-level `imports` is `[]`. Do not pull in editor imports (`cursor`, `claude-code`, etc.) — the per-skill config should be self-contained.
- [ ] `mcpServers` has exactly one entry, keyed by `<slug>`.
- [ ] No additional servers in `mcpServers`. The skill is one-server-per-skill (mcporter's own guidance).

### Bundle ⇄ template consistency

- [ ] `bash {baseDir}/scripts/verify-bundle.sh <out>/<slug>` exits 0. The verifier reads `mcporter.json` to classify the mode, then enforces three checks: (1) OAuth bundles must have all templates present in `scripts/` and byte-identical to the source; (2) non-OAuth bundles must NOT have stray template files in `scripts/`; (3) all modes must have no unreplaced `<UPPER_SNAKE>` placeholders in `SKILL.md` or `mcporter.json` (lowercase tokens like `<tool>`, `<arg>` are legitimate invocation syntax and ignored). Failures it surfaces include byte-level drift, partial copies (some templates present, others missing), zero-templates-copied for OAuth bundles, mode mismatch (mcporter.json declares non-OAuth but stray template files exist), leaked placeholders (e.g. `<SLUG>` or `<BASE_URL>` survived scaffolding), and malformed bundles missing `mcporter.json`. Resolution depends on the failure — re-`cp` from `{baseDir}/scripts/templates/` for shell-template drift, fix `mcporter.json` for mode mismatches, substitute the missed placeholder per the per-mode reference's "Substitutions to make" section for leaked placeholders; never edit the per-skill copy of a shell template.

## Mode-specific checks

After the universal checks pass, run the mode-specific section from the reference file you used:

- HTTP + OAuth: [`http-oauth.md`](http-oauth.md) § "Mode-specific validation"
- HTTP + bearer: [`http-bearer.md`](http-bearer.md) § "Mode-specific validation"
- HTTP + custom headers: [`http-headers.md`](http-headers.md) § "Mode-specific validation"
- HTTP + no auth: [`http-noauth.md`](http-noauth.md) § "Mode-specific validation"
- stdio: [`stdio.md`](stdio.md) § "Mode-specific validation"

These check transport-specific config fields, env-var consistency between `requires.env` and the actual config references, and the absence of cross-mode contamination (e.g. an OAuth `auth` field showing up in a bearer template, indicating someone copy-pasted from the wrong reference).

## Optional: live MCP smoke test

If the user has credentials handy and wants to verify the bundle works:

- [ ] **OAuth mode**: with the required env vars set, run `bash <out>/<slug>/scripts/invoke.sh list <slug> --schema`. Should exit 0, seed the OAuth vault if needed, and print the live tool catalog.
- [ ] **Bearer/headers mode**: with the env vars set, `mcporter --config <out>/<slug>/mcporter.json list <slug> --schema` should print the live tool catalog.
- [ ] **No-auth mode**: `mcporter --config <out>/<slug>/mcporter.json list <slug> --schema` should succeed without prompts.
- [ ] **stdio mode**: `mcporter --config <out>/<slug>/mcporter.json list <slug> --schema` should spawn the subprocess, list tools, and tear down. May pay a cold-start cost on first call for `npx`/`uvx` modes.

If the live smoke test isn't possible right now (no credentials, no network, missing binary), tell the user that explicitly rather than declaring success.

## References

The check criteria above are derived from these authoritative sources:

- [OpenClaw skill-creator playbook](https://github.com/openclaw/openclaw/blob/main/skills/skill-creator/SKILL.md) — body ≤500 lines; anti-pattern files (no README/CHANGELOG/INSTALLATION_GUIDE inside bundles); no symlinks; description-as-trigger; lowercase + hyphens + ≤64 chars naming spec
- [OpenClaw skills reference](https://github.com/openclaw/openclaw/blob/main/docs/tools/skills.md) — `requires.bins`/`requires.env` semantics, `{baseDir}` placeholder, `metadata.openclaw` frontmatter shape, sandbox-host bin check requirements
- [openclaw/mcporter — `docs/agent-skills.md`](https://github.com/openclaw/mcporter/blob/main/docs/agent-skills.md) — "one MCP server per skill" rule
- [openclaw/mcporter — `mcporter.schema.json`](https://github.com/openclaw/mcporter/blob/main/mcporter.schema.json) — authoritative JSON Schema for `mcporter.json` (auth fields, transport fields, `imports`)
