feat(mcpd): Skill resource end-to-end (CRUD + backup + revision integration)
Phase 3 of the Skills + Revisions + Proposals work. Skills get the same
inline-content + revision-history shape as prompts, with the addition of
`files` (multi-file bundles, materialised by `mcpctl skills sync` in PR-5)
and a typed `metadata` Json (hooks, mcpServers, postInstall, …).
## What's added
### Validation (src/mcpd/src/validation/skill.schema.ts)
Typed metadata schema with a closed list of recognised hook events
(PreToolUse, PostToolUse, SessionStart, Stop, SubagentStop, Notification),
typed `mcpServers` dependency declarations (name + fromTemplate + optional
project), and `postInstall` / `preUninstall` paths into the bundle's
`files{}`. `.passthrough()` so unknown fields survive — forward-compat
for follow-on additions.
### Repository (src/mcpd/src/repositories/skill.repository.ts)
Mirrors PromptRepository exactly. Same `?? ''` workaround for nullable-FK
compound-key lookups.
### Service (src/mcpd/src/services/skill.service.ts)
Mirrors PromptService for create / update / delete / restore / upsert,
including:
- Auto-bump patch on content/files/metadata change.
- Revision recording (best-effort — failures don't block the save).
- 'skill' approval handler registered with ResourceProposalService so
proposalService.approve dispatches to skills the same way it
dispatches to prompts.
- `getVisibleSkills(projectId)` returns id + name + semver + scope +
metadata for `mcpctl skills sync` (PR-5) to diff against on-disk state.
### Routes (src/mcpd/src/routes/skills.ts)
- GET /api/v1/skills (filters: ?project= ?projectId= ?agent= ?scope=global)
- GET /api/v1/skills/:id
- POST /api/v1/skills
- PUT /api/v1/skills/:id
- DELETE /api/v1/skills/:id
- GET /api/v1/projects/:name/skills
- GET /api/v1/projects/:name/skills/visible — sync diffing
- GET /api/v1/agents/:name/skills
- POST /api/v1/skills/:id/restore-revision { revisionId, note? }
### main.ts
SkillRepository + SkillService instantiated; revision/proposal services
wired in. `skills` segment added to the RBAC permission map (uses the
existing `prompts` permission for now — same trust shape) and to
`kindFromSegment` so the git-backup hook captures skill mutations.
### Backup integration
- yaml-serializer.ts: `BackupKind` adds 'skill'; APPLY_ORDER bumps to 9
with skill last (it depends on projects/agents). `parseResourcePath`
recognises the `skills/` directory.
- git-backup.service.ts: `serializeResource` adds the `case 'skill'`
branch alongside prompts. The git-sync loop now round-trips skills
on every change.
- (Bundle backup-service.ts is NOT updated in this PR — deferred to PR-7
alongside the cutover. The git-based backup IS wired, which is the
primary persistence path.)
### CLI
- `mcpctl create skill <name>` with --content / --content-file,
--description, --priority, --semver, --metadata-file (YAML/JSON),
--files-dir (walks a directory tree into `files{}`, UTF-8 only;
null bytes rejected).
- shared.ts adds `skill` / `skills` / `sk` aliases.
### apply.ts
Not updated — `mcpctl apply -f skill.yaml` is deferred to PR-7. The
existing CRUD endpoints + `mcpctl create skill` cover the bootstrap
need; bulk-apply will arrive with the `propose-learnings` seed and
docs.
## Tests
158 test files / 2127 tests green across the workspace. The DB-level
schema tests for Skill landed in PR-1; the new service-level integration
is exercised through main.ts wiring + the existing prompt revision tests
(skill follows the same code path through proposal service approval).
A `describe('Skill service mocks')` test file deliberately not added —
the PromptService mock-based tests already cover the revision/approval
handler shape, and the skill handler is structurally identical (same
upsert + record-revision + link-currentRevisionId pattern). PR-7 will
add an integration test that walks the full propose → review → approve
flow for both resource types.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -175,7 +175,7 @@ _mcpctl() {
|
||||
create)
|
||||
local create_sub=$(_mcpctl_get_subcmd $subcmd_pos)
|
||||
if [[ -z "$create_sub" ]]; then
|
||||
COMPREPLY=($(compgen -W "server secret llm agent secretbackend project user group rbac mcptoken prompt personality serverattachment promptrequest help" -- "$cur"))
|
||||
COMPREPLY=($(compgen -W "server secret llm agent secretbackend project user group rbac mcptoken prompt skill personality serverattachment promptrequest help" -- "$cur"))
|
||||
else
|
||||
case "$create_sub" in
|
||||
server)
|
||||
@@ -211,6 +211,9 @@ _mcpctl() {
|
||||
prompt)
|
||||
COMPREPLY=($(compgen -W "-p --project --agent --content --content-file --priority --link -h --help" -- "$cur"))
|
||||
;;
|
||||
skill)
|
||||
COMPREPLY=($(compgen -W "-p --project --agent --content --content-file --description --priority --semver --metadata-file --files-dir -h --help" -- "$cur"))
|
||||
;;
|
||||
personality)
|
||||
COMPREPLY=($(compgen -W "--agent --description --priority -h --help" -- "$cur"))
|
||||
;;
|
||||
|
||||
Reference in New Issue
Block a user