Files
mcpctl/docs/skills.md
Michal 56735a5290 docs: skills + revisions + proposals reference, plus cheatsheet update
Phase 7 of the Skills + Revisions + Proposals work — documentation
pass for the surface added in PR-1 through PR-6. Reference material
only; no code changes.

## What's added

- `docs/skills.md` — skill model, scoping rules, CLI surface, the
  `mcpctl config claude --project` setup flow, metadata schema (with
  the deferred-execution note for hooks/mcpServers/postInstall), the
  on-disk state file shape, atomic install mechanics, failure
  semantics, and what's deferred.
- `docs/revisions.md` — ResourceRevision model, semver auto-bump
  rules, contentHash diff key (cross-resource sync), CLI for history
  / diff / restore, RBAC, audit emission, storage growth note.
- `docs/proposals.md` — ResourceProposal model, the reviewer flow
  (CLI + web UI), atomic-approval mechanics, the propose_prompt /
  propose_skill MCP tools, the propose-learnings global skill that
  steers Claude toward engaging with them, and the deferred legacy
  PromptRequest cutover.

## What's edited

- Top-level `CLAUDE.md` — resource cheatsheet adds `skill`, `proposal`,
  `revision` with cross-references to the new docs. The legacy
  `promptrequest` entry stays (still on the legacy code path) but
  notes that new work should use `proposal`.

## What's NOT in this PR

- The PromptRequest → ResourceProposal cutover migration. Both run
  side-by-side today; the focused cutover PR will rename + backfill +
  drop. Keeping that out of PR-7 means review can stay on docs.
- Bundle-backup / `mcpctl apply -f` skill support (deferred from PR-3).
- `metadata.hooks` / `metadata.mcpServers` / `metadata.postInstall`
  execution (deferred from PR-5).
- Existing-page UI migration to Tailwind (deferred from PR-6 — old
  inline-styled pages coexist fine inside the new Layout).

These are tracked as future PRs; each is its own focused change.

## Verification

`pnpm test:run` whole monorepo: 162 test files / 2157 tests green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 17:58:04 +01:00

7.9 KiB
Raw Blame History

Skills

Skills are Claude Code skill bundles distributed by mcpctl. Each skill is a named bundle of files — at minimum a SKILL.md explaining the skill's purpose and triggers, optionally with auxiliary scripts, templates, or data files. The mcpctl daemon (mcpd) is the source of truth; mcpctl skills sync materialises the skills onto each dev machine under ~/.claude/skills/<name>/, where Claude Code reads them natively.

┌─ mcpd (Postgres) ──────────────────────────────┐
│  Skill rows (content + files{} + metadata)     │
└────────────────┬───────────────────────────────┘
                 │ HTTP, hash-pinned diff
                 ▼
┌─ ~/.claude/skills/<name>/ ─────────────────────┐
│  SKILL.md                                      │
│  scripts/setup.sh                              │
│  …                                             │
└────────────────────────────────────────────────┘

Trust model

Skills are added by senior admins together with a security reviewer at publish time on mcpd. Once content is in mcpd, clients trust what mcpd serves — no client-side sandboxing, no signature checks, no consent prompts. The rigor lives on the publishing side (RBAC, audit, the reviewer queue). See proposals.md for the review→approve flow.

If you're publishing skills to clients you don't trust (e.g. an open- source distribution), the design is wrong for that — the skill format itself is fine, but the unguarded client trust assumption isn't.

Scoping

A skill attaches to one of:

  • GlobalprojectId and agentId both null. Synced onto every dev machine when its sync runs (with or without a project context).
  • Project-scopedprojectId set. Synced onto machines whose .mcpctl-project marker matches.
  • Agent-scopedagentId set. Surfaced administratively via the API; not currently materialised onto disk by mcpctl skills sync (see "Future" below).

The same <name> can exist at multiple scopes simultaneously. The two unique constraints are (name, projectId) and (name, agentId).

CLI

Create

mcpctl create skill <name> \
  [--project <name> | --agent <name>] \
  --content / --content-file <path> \
  [--description "<text>"] \
  [--priority <1-10>] \
  [--semver <X.Y.Z>] \
  [--metadata-file <path>] \
  [--files-dir <path>]

--content-file provides the SKILL.md body. --metadata-file accepts YAML or JSON; see "Metadata" below for the schema. --files-dir walks a directory tree into the files{} map (UTF-8 only; non-text files rejected — extend later if needed).

Edit

# Edit content in $EDITOR
mcpctl edit skill <name>

# Edit + bump semver
mcpctl edit skill <name> --bump major|minor|patch --note "<message>"

# Edit + set explicit semver
mcpctl edit skill <name> --semver 1.2.3

Each save records a ResourceRevision automatically. See revisions.md.

Sync to disk

# In a project directory (with .mcpctl-project marker):
mcpctl skills sync

# Override project:
mcpctl skills sync --project <name>

# Globals only (no project context, no marker):
cd / && mcpctl skills sync

# Used by the SessionStart hook — fail-open on network errors:
mcpctl skills sync --quiet

Useful flags:

Flag Purpose
--dry-run Print what would change, don't write anything.
--force Overwrite locally-modified skills.
--quiet Suppress output unless something changed; fail-open.
--keep-orphans Don't remove skills no longer in the server set.
--skip-postinstall Reserved for the postInstall executor (deferred).

Project setup

mcpctl config claude --project <name> does the full pickup chain:

  1. Writes .mcp.json so Claude Code routes MCP traffic through mcplocal.
  2. Writes .mcpctl-project (single line, project name) so skills sync knows which project's skills to pull when run from anywhere under that directory.
  3. Runs an initial skills sync synchronously.
  4. Installs a SessionStart hook in ~/.claude/settings.json that runs mcpctl skills sync --quiet before every Claude session. Tagged with _mcpctl_managed: true so subsequent runs find and update it instead of duplicating it.

Pass --skip-skills to opt out of steps 24 (useful in CI).

Metadata

The metadata field is a typed JSON blob:

hooks:
  PreToolUse:
    - type: command
      command: "echo before-tool"
  PostToolUse:
    - type: command
      command: "echo after-tool"
  SessionStart:
    - type: command
      command: "echo session-started"
mcpServers:
  - name: my-grafana
    fromTemplate: grafana
    project: monitoring
postInstall: scripts/install.sh
preUninstall: scripts/cleanup.sh
postInstallTimeoutSec: 60

v1 sync executes none of these — they're stored verbatim and materialisation is deferred to a follow-up. Once enabled:

  • hooks will be written into ~/.claude/settings.json with _mcpctl_managed: true markers (see Project Setup above for how the SessionStart hook works today).
  • mcpServers will be auto-attached via the mcpd attach API.
  • postInstall will run as the user with a curated env, hard timeout, and an audit event emitted back to mcpd. Hash-pinned: re-syncs of unchanged scripts won't re-execute.

State

~/.mcpctl/skills-state.json tracks the last-synced state:

  • per-skill: id, semver, contentHash (matches mcpd's hash), installDir, per-file sha256 + size, postInstallHash, lastSyncedAt.
  • top-level: lastSync, lastSyncProject, schemaVersion.

The state file is written atomically (temp + rename). Per-file SHA-256 detects local edits — sync warns and skips modified files unless you pass --force.

State lives outside ~/.claude/skills/ deliberately so Claude Code doesn't see our bookkeeping in its tree.

Atomic install

Each skill is staged under <targetDir>.mcpctl-staging-<pid>/, then the existing directory (if any) is renamed to <targetDir>.mcpctl-trash-<pid>, the staging dir is moved into place, and the trash is rmtree'd. A concurrent reader (Claude Code starting up) never sees a partial tree.

Symmetric atomic delete for orphan removal: rename to trash, rmtree. Locally-modified skills are preserved (warned + skipped) unless --force.

Failure semantics

Situation Exit code Behaviour
Network/timeout in --quiet 0 Skip silently. SessionStart hook never blocks Claude.
Auth failure 1 "run mcpctl login" message.
Disk full / state save failure 2 Loud error.
Per-skill error 0 Logged in result errors[]; sync continues.

The fail-open behaviour in --quiet is non-negotiable — a hung mcpd must never block Claude Code starting up.

Future

The following are deferred to follow-up PRs:

  • metadata.hooks materialisation into ~/.claude/settings.json
  • metadata.mcpServers auto-attach
  • metadata.postInstall execution with curated env + audit emission
  • Agent-scoped skills synced to disk (would need an agent-identity-on- disk concept that doesn't exist yet)
  • Bundle backup support for skills (bundle-backup is one path; git-backup is the other and is wired today)
  • mcpctl apply -f skill.yaml declarative skill apply