UNPKG

@withpica/mcp-server

Version:

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

130 lines • 6.69 kB
// Copyright (c) 2025-2026 Withpica Ltd. All rights reserved. import { formatSuccess } from "@withpica/mcp-utils"; import { typedError } from "./error-envelope.js"; export class DirectoryTools { pica; constructor(pica) { this.pica = pica; } getTools() { return [ { definition: { name: "pica_directory_settings", scope: "admin", audiences: ["stdio", "http", "public"], tier: "read", description: "Get or update your account's directory settings. " + "Listing on the PICA public directory is per song; this reports how " + "many songs are listed, can list every ready song at once, and controls " + "what is shown (audio previews, splits, publishers, etc.).", workflows: "infrastructure", vernacular_kind: "specialist", vernacular_reason: "Specialist context; not yet covered by the Creator Question Atlas — pending telemetry-driven addition per ADR-226 Phase 4 substrate writes.", inputSchema: { type: "object", properties: { action: { type: "string", enum: ["get", "update"], description: "Action to perform. 'get' returns current settings. 'update' changes settings.", }, opted_in: { type: "boolean", description: "Listing is per song. true = list every song that is ready " + "(has an artist, an industry code, a credit and a recording) " + "and not hidden. false = record that the catalogue owner " + "declined; songs already listed stay listed until hidden " + "from their own page.", }, show_audio_preview: { type: "boolean", description: "Allow a short preview clip on directory listings. " + "A clip is cut when a song's audio is analysed; a song " + "without one shows no player. The full uploaded file is " + "never served.", }, show_splits: { type: "boolean", description: "Show credit split percentages in directory.", }, show_publishers: { type: "boolean", description: "Show publisher information in directory.", }, show_agreements: { type: "boolean", description: "Show agreement details in directory.", }, show_stems: { type: "boolean", description: "Show stems/instrumentals in directory.", }, contact_enabled: { type: "boolean", description: "Allow licensing enquiries from the directory. On by default; " + "set false to switch enquiries off for every listed work.", }, }, required: ["action"], }, }, executor: this.directorySettings.bind(this), }, ]; } async directorySettings(args) { if (args.action === "get") { const result = await this.pica.directory.getSettings(); const s = result.settings; // Listing is per song (2026-10-05): report songs, not the old flag. const listing = result.listing; const summary = [ listing ? `Songs listed: ${listing.listed} · ready but not listed: ${listing.ready} · hidden: ${listing.hidden} · not ready: ${listing.notReady}` : "Songs listed: couldn't check", s.show_audio_preview ? "Audio preview: on (30-second clips)" : "Audio preview: off", s.contact_enabled ? "Enquiries: on" : "Enquiries: off", s.show_splits ? "Splits: visible" : "Splits: hidden", s.show_publishers ? "Publishers: visible" : "Publishers: hidden", ].join("\n"); return { content: [{ type: "text", text: summary }], structuredContent: result, }; } // Update const updates = {}; const boolFields = [ "opted_in", "show_audio_preview", "show_splits", "show_publishers", "show_agreements", "show_stems", "contact_enabled", ]; for (const field of boolFields) { if (args[field] !== undefined) updates[field] = Boolean(args[field]); } if (Object.keys(updates).length === 0) { // typedError, not formatAsText({error}): the latter has no `isError`, so // the dispatcher and the client read a refusal as a SUCCESS whose payload // happens to contain the word "error", and mcp_audit_log.error_code is // NULL. See ./error-envelope.ts. return typedError("VALIDATION_ERROR", "No settings to update. Provide at least one setting field."); } const result = await this.pica.directory.updateSettings(updates); const listing = result.listing; const message = updates.opted_in === true && listing ? `Listed ${listing.listed} ready songs on the directory. ${listing.notReady} aren't ready yet; each song's page says what it needs.` : updates.opted_in === false ? "Recorded. Songs already listed stay listed — hide a song from its page to take it off the directory." : "Directory settings updated."; return formatSuccess(message, result); } } //# sourceMappingURL=directory.js.map