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

215 lines
7.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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](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** — `projectId` and `agentId` both null. Synced onto every dev
machine when its sync runs (with or without a project context).
- **Project-scoped** — `projectId` set. Synced onto machines whose
`.mcpctl-project` marker matches.
- **Agent-scoped** — `agentId` 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
```bash
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
```bash
# 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](revisions.md).
### Sync to disk
```bash
# 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:
```yaml
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