UNPKG

@withpica/mcp-server

Version:

MCP Server for withPICA — enables AI assistants to interact with your withPICA music catalog

101 lines • 3.69 kB
// Copyright (c) 2025-2026 Withpica Ltd. All rights reserved. /** * Skills Registry — ADR-140 Phase 2b * * Exposes PICA's named methodologies as MCP-loadable skills. Each skill is * reachable as a `skill://` Resource, through the SEP-2640 skills extension * (skills/list, skills/get — ./sep2640.ts, ADR-328), and as MCP tools * (pica_skill_list + pica_skill_get). * * Bodies are bundled at build time by `scripts/build-skills.ts`. The registry * is intentionally thin — list returns a metadata-only summary (tiny handshake * cost), get returns the full body on demand (lazy slicing, mirrors the * _inspect sections pattern from ADR-199). */ import { SKILLS, SKILL_NAMES } from "./skills.generated.js"; export class SkillsRegistry { extra = new Map(); /** * @param extraSkills Customer-owned skills to merge with built-ins. * Built-in WINS on name collision; first entry wins on duplicate extras. * Default is empty — keeps the published package byte-identical for * stdio/external callers that pass no extras (hard invariant, ADR-271). * Pass `origin: "user"` on an entry to tag it as user-authored. */ constructor(extraSkills = []) { for (const s of extraSkills) { if (!s?.name) continue; if (Object.hasOwn(SKILLS, s.name)) continue; // built-in wins if (this.extra.has(s.name)) continue; // first wins on dup this.extra.set(s.name, s); } } /** Stable list of available skill names (built-ins first, then extras). */ listNames() { return [...SKILL_NAMES, ...this.extra.keys()]; } /** * Lightweight list for `pica_skill_list` — no body bytes. * Total size scales with skill count, not body size. */ listSkills() { const builtins = SKILL_NAMES.map((name) => { const s = SKILLS[name]; return { name: s.name, description: s.description, triggers: s.triggers, audience: s.audience, origin: "builtin", }; }); const extras = [...this.extra.values()].map((s) => ({ name: s.name, description: s.description, triggers: s.triggers, audience: s.audience, origin: (s.origin ?? "org"), })); return [...builtins, ...extras]; } /** * Full skill — bodies are loaded only when an agent invokes get. * Resolves built-ins first, then extras. * Throws when name is unknown so callers can return a RecoveryHint. */ getSkill(name) { if (Object.hasOwn(SKILLS, name)) return SKILLS[name]; const org = this.extra.get(name); if (org) return org; throw new SkillNotFoundError(name); } /** "builtin" | "org" | "user" | null (null = not registered). */ originOf(name) { if (Object.hasOwn(SKILLS, name)) return "builtin"; const extra = this.extra.get(name); if (extra) return extra.origin ?? "org"; return null; } has(name) { return Object.hasOwn(SKILLS, name) || this.extra.has(name); } } export class SkillNotFoundError extends Error { skillName; constructor(skillName) { super(`Skill not found: ${skillName}`); this.skillName = skillName; this.name = "SkillNotFoundError"; } } export { SKILLS, SKILL_NAMES }; // ADR-328 — SEP-2640 skills extension (skills/list, skills/get, served SKILL.md). export * from "./sep2640.js"; //# sourceMappingURL=index.js.map