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>
7.9 KiB
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:
- Global —
projectIdandagentIdboth null. Synced onto every dev machine when its sync runs (with or without a project context). - Project-scoped —
projectIdset. Synced onto machines whose.mcpctl-projectmarker matches. - Agent-scoped —
agentIdset. Surfaced administratively via the API; not currently materialised onto disk bymcpctl 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:
- Writes
.mcp.jsonso Claude Code routes MCP traffic through mcplocal. - Writes
.mcpctl-project(single line, project name) soskills syncknows which project's skills to pull when run from anywhere under that directory. - Runs an initial
skills syncsynchronously. - Installs a SessionStart hook in
~/.claude/settings.jsonthat runsmcpctl skills sync --quietbefore every Claude session. Tagged with_mcpctl_managed: trueso subsequent runs find and update it instead of duplicating it.
Pass --skip-skills to opt out of steps 2–4 (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:
hookswill be written into~/.claude/settings.jsonwith_mcpctl_managed: truemarkers (see Project Setup above for how the SessionStart hook works today).mcpServerswill be auto-attached via the mcpd attach API.postInstallwill 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-filesha256+ 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.hooksmaterialisation into~/.claude/settings.jsonmetadata.mcpServersauto-attachmetadata.postInstallexecution 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.yamldeclarative skill apply