UNPKG

@withpica/mcp-server

Version:

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

168 lines • 7.28 kB
// Copyright (c) 2025-2026 Withpica Ltd. All rights reserved. import { SkillsRegistry, SkillNotFoundError, } from "../skills/index.js"; export class SkillsTools { registry; constructor(extraSkills = []) { this.registry = new SkillsRegistry(extraSkills); } getTools() { return [ { definition: { name: "pica_skill_list", scope: "always", audiences: ["stdio", "http"], tier: "read", description: "Use when the user asks: 'what catalog workflows are available?', 'what can you help me do with my songs?', 'show me my workflow options'. " + "Returns the list of downloadable PICA skill methodologies — name, one-line description, and trigger phrases. " + "Cheap to call. Use pica_skill_get(name) to load a specific skill's full methodology body when you're ready to follow it.", workflows: "infrastructure", vernacular_kind: "creator-entry", inputSchema: { type: "object", properties: {}, additionalProperties: false, }, annotations: { readOnlyHint: true, }, }, executor: this.listSkills.bind(this), }, { definition: { name: "pica_skill_get", scope: "always", audiences: ["stdio", "http"], tier: "read", description: "Use when the user asks: 'audit my catalog', 'register my songs', 'prepare for sync', 'set up my workspace', 'close the loop on this song'. " + "Returns the full methodology body for one skill — markdown with step-by-step instructions, tool chains, and offer scripts. " + "Call pica_skill_list first to see available skill names, then pica_skill_get to load one. Read once at the start of the workflow; do not re-fetch on every step.", workflows: "infrastructure", vernacular_kind: "creator-entry", inputSchema: { type: "object", properties: { name: { type: "string", description: "Kebab-case skill name from pica_skill_list (e.g. 'catalog-audit', 'register-my-works', 'prepare-for-sync').", }, }, required: ["name"], additionalProperties: false, }, annotations: { readOnlyHint: true, }, }, executor: this.getSkill.bind(this), }, ]; } async listSkills(_args) { const skills = this.registry.listSkills(); return { content: [ { type: "text", text: skills.length ? `Available skills (${skills.length}):\n\n` + skills .map((s) => `- ${s.name} — ${s.description}${s.origin === "org" ? " (your org's skill)" : ""}\n triggers: ${s.triggers.join(", ")}\n audience: ${s.audience}`) .join("\n\n") + "\n\nCall pica_skill_get(name) to load a skill's full methodology." : "No skills available.", }, ], structuredContent: { skills }, }; } async getSkill(args) { const name = String(args?.name ?? "").trim(); if (!name) { return { isError: true, content: [ { type: "text", text: "Missing required argument: name. Call pica_skill_list to see available skill names.", }, ], }; } let skill; try { skill = this.registry.getSkill(name); } catch (err) { if (err instanceof SkillNotFoundError) { return { isError: true, content: [ { type: "text", text: `Skill not found: ${name}. Call pica_skill_list to see available skill names.`, }, ], }; } throw err; } const origin = this.registry.originOf(name) ?? "builtin"; const provenance = origin === "user" ? "> This is a skill you authored — not PICA-provided guidance.\n\n" : origin === "org" ? "> This is a skill your organisation authored — not PICA-provided guidance.\n\n" : ""; const frontmatterSummary = [ `# ${skill.name}`, "", `**Description:** ${skill.description}`, `**Audience:** ${skill.audience}`, `**Triggers:** ${skill.triggers.join(", ")}`, `**Tools used:** ${skill.tools_required.join(", ")}`, `**Output:** ${skill.output}`, "", "---", "", ].join("\n"); return { content: [ { type: "text", text: provenance + frontmatterSummary + skill.body, }, { type: "resource_link", uri: `skill://withpica/${skill.name}/SKILL.md`, name: `Skill — ${skill.name}`, description: skill.description, mimeType: "text/markdown", }, ], structuredContent: { name: skill.name, description: skill.description, // The methodology itself. Clients that read structuredContent and // ignore the text blocks previously got the frontmatter and no steps. // Raw body — no provenance banner, no frontmatter summary; both of // those are prose we add for the text channel. Pinned by // "structuredContent.body is the registry body verbatim" in // src/__tests__/skills/skills-tools.test.ts. body: skill.body, triggers: skill.triggers, audience: skill.audience, tools_required: skill.tools_required, output: skill.output, origin, ...(skill.definition_of_done !== undefined ? { definition_of_done: skill.definition_of_done } : {}), ...(skill.when_not_to_use !== undefined ? { when_not_to_use: skill.when_not_to_use } : {}), }, }; } } //# sourceMappingURL=skills.js.map