UNPKG

@withpica/mcp-server

Version:

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

99 lines • 3.49 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 via the SEP-2640 * bridge pattern: each skill is reachable as both a `skill://` Resource and an * MCP Tool (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 (s.name in SKILLS) 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) { const builtin = SKILLS[name]; if (builtin) return builtin; const org = this.extra.get(name); if (org) return org; throw new SkillNotFoundError(name); } /** "builtin" | "org" | "user" | null (null = not registered). */ originOf(name) { if (name in SKILLS) return "builtin"; const extra = this.extra.get(name); if (extra) return extra.origin ?? "org"; return null; } has(name) { return name in SKILLS || 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 }; //# sourceMappingURL=index.js.map