@withpica/mcp-server
Version:
MCP Server for withPICA — enables AI assistants to interact with your withPICA music catalog
130 lines • 6.69 kB
JavaScript
// 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