@withpica/mcp-server
Version:
MCP Server for withPICA — enables AI assistants to interact with your withPICA music catalog
180 lines • 9.92 kB
JavaScript
// Copyright (c) 2025-2026 Withpica Ltd. All rights reserved.
import { formatStructuredList, formatSuccess } from "@withpica/mcp-utils";
export class LabelsTools {
pica;
constructor(pica) {
this.pica = pica;
}
getTools() {
return [
{
definition: {
name: "pica_labels_query",
scope: "read:catalog",
audiences: ["stdio", "http", "public"],
tier: "read",
description: "Find label and distributor organisations by name. Returns organisation IDs " +
"suitable for releases.label_organization_id / distributor_organization_id " +
"(via pica_releases_create or pica_releases_update). " +
"Labels and distributors in PICA are organisations with org_type='label' or " +
"org_type='distributor' — only parties already known to PICA appear here. " +
"→ then: pica_releases_create / pica_releases_update (set label_organization_id / distributor_organization_id)",
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: {
query: {
type: "string",
description: "Partial label name (substring match, case-insensitive). Omit to list all labels.",
},
limit: {
type: "number",
minimum: 1,
maximum: 100,
description: "Max results (default: 50, max: 100)",
},
offset: {
type: "number",
minimum: 0,
description: "Skip this many results — page with offset while has_more is true.",
},
org_type: {
type: "string",
enum: ["label", "distributor"],
description: "Narrow to one party type. Omit to search both labels and distributors.",
},
},
},
annotations: {
readOnlyHint: true,
},
},
executor: this.queryLabels.bind(this),
},
{
definition: {
name: "pica_labels_create",
scope: "write:catalog",
audiences: ["stdio", "http", "public"],
tier: "write",
description: "Create a label or distributor organisation when pica_labels_query " +
"finds no match. Creates an unclaimed organisation row " +
"(verification pending) whose ID can be passed to " +
"pica_releases_create / pica_releases_update as " +
"label_organization_id or distributor_organization_id. " +
"Dedup is server-side: external identifiers (when supplied) resolve " +
"the label's global identity and an organisation already bridged to " +
"it is returned even if the name differs; otherwise an existing " +
"case-insensitive name match is returned instead of creating a duplicate. " +
"→ first: pica_labels_query (covers labels and distributors; the server also dedups across both types) " +
"→ then: pica_releases_update (link the returned id)",
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: {
name: {
type: "string",
description: "Label or distributor name (e.g. 'Wings Music Group')",
},
org_type: {
type: "string",
enum: ["label", "distributor"],
description: "'label' for the releasing label, 'distributor' for the distribution company. An organisation wearing both hats is one row — link the same id into both release fields.",
},
country: {
type: "string",
description: "ISO country code (e.g. 'GB'), if known",
},
website: {
type: "string",
description: "Official website URL, if known",
},
contact_email: {
type: "string",
description: "Public contact email, if known — used later to invite the organisation to claim itself",
},
identifiers: {
type: "object",
description: "External label identifiers, if known. Supplying any " +
"of these makes dedup identifier-based instead of name-based: " +
"two creates resolving to the same global label identity return " +
"the same organisation, whatever the names say.",
properties: {
musicbrainz_label_id: {
type: "string",
description: "MusicBrainz label MBID",
},
isni: {
type: "string",
description: "ISNI of the label",
},
label_code: {
type: "string",
description: "GVL/IFPI label code (LC number, digits only)",
},
discogs_label_id: {
type: "string",
description: "Discogs label id",
},
wikidata_id: {
type: "string",
description: "Wikidata entity id (e.g. 'Q38903')",
},
deezer_label_id: {
type: "string",
description: "Deezer label id",
},
},
},
},
required: ["name", "org_type"],
},
},
executor: this.createLabel.bind(this),
},
];
}
async createLabel(args) {
const label = await this.pica.labels.create({
name: args.name,
org_type: args.org_type,
...(args.country ? { country: args.country } : {}),
...(args.website ? { website: args.website } : {}),
...(args.contact_email
? { contact_email: args.contact_email }
: {}),
...(args.identifiers && typeof args.identifiers === "object"
? { identifiers: args.identifiers }
: {}),
});
if (!label) {
return formatSuccess("Label create completed but the server returned no organisation data — query pica_labels_query to confirm state before retrying.", { label: null });
}
if (label.already_existed) {
return formatSuccess(`"${label.name}" already exists as a ${label.org_type ?? "party"} organisation (id ${label.id}) — link it via pica_releases_update instead of creating a duplicate.`, label);
}
return formatSuccess(`Created ${args.org_type} organisation "${label.name}" (id ${label.id}, verification pending). Link it to the release via pica_releases_update (label_organization_id / distributor_organization_id).`, label);
}
async queryLabels(args) {
// listPage: an honest has_more and offset. The total is null — the
// route does not count platform-wide organisations — never the page
// length (row-cap T3).
const offset = args.offset || 0;
const page = await this.pica.labels.listPage({
query: args.query,
limit: args.limit,
offset,
org_type: args.org_type,
});
return formatStructuredList(page.data, "label", {
total: page.total,
has_more: page.hasMore,
offset,
});
}
}
//# sourceMappingURL=labels.js.map