@withpica/mcp-server
Version:
MCP Server for withPICA — enables AI assistants to interact with your withPICA music catalog
130 lines • 6.45 kB
JavaScript
// Copyright (c) 2025-2026 Withpica Ltd. All rights reserved.
import { formatStructuredList, formatSuccess } from "@withpica/mcp-utils";
export class PublishersTools {
pica;
constructor(pica) {
this.pica = pica;
}
getTools() {
return [
{
definition: {
name: "pica_publishers_query",
scope: "read:catalog",
audiences: ["stdio", "http", "public"],
tier: "read",
description: "Find publishers by name or IPI. Use before setting publisher on credits. " +
"→ then: pica_credits_update (link publisher to a credit)",
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: "Search by publisher name or IPI",
},
limit: {
type: "number",
minimum: 1,
maximum: 200,
description: "Max results (default: 50)",
},
offset: {
type: "number",
minimum: 0,
description: "Skip this many results — page with offset while has_more is true.",
},
},
},
},
executor: this.queryPublishers.bind(this),
},
{
definition: {
name: "pica_publishers_create",
scope: "write:catalog",
audiences: ["stdio", "http", "public"],
tier: "write",
description: "Create (or fetch existing) publisher in the catalog-wide " +
"`global_publishers` table. Idempotent by ipi_number — if a " +
"publisher with the same IPI already exists, returns the existing " +
"row. Check `pica_publishers_query` first to avoid duplicates and " +
"to resolve parent_publisher_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: "Publisher name." },
ipi_number: {
type: "string",
description: "Publisher IPI number (9-11 digits; normalised to 11 with " +
"leading zero-padding). Required.",
},
legal_name: {
type: "string",
description: "Legal entity name (e.g. 'Example Publishing Ltd.') — optional.",
},
country: {
type: "string",
description: "ISO country code (e.g. 'GB', 'US').",
},
isni: {
type: "string",
description: "ISNI identifier (16-character alphanumeric) if the publisher has one.",
},
publisher_type: {
type: "string",
description: "Category label (e.g. 'major', 'indie', 'admin', 'self_published'). Freeform — no backend CHECK.",
},
wikidata_id: {
type: "string",
description: "Wikidata Q-number (e.g. 'Q123456').",
},
parent_publisher_id: {
type: "string",
description: "UUID of parent publisher for subsidiary/admin relationships. FK → global_publishers.id. Use `pica_publishers_query` to find the parent's ID before passing it here; the route returns FK_NOT_FOUND if the UUID does not exist.",
},
founded_date: {
type: "string",
description: "Founding date (YYYY-MM-DD).",
},
headquarters: {
type: "string",
description: "Freeform location (e.g. 'London, UK').",
},
},
required: ["name", "ipi_number"],
},
},
executor: this.createPublisher.bind(this),
},
];
}
async queryPublishers(args) {
// listPage: an honest has_more and offset. The total is null — the
// route does not count the platform-wide registry — never the page
// length (row-cap T3).
const limit = args.limit || 50;
const offset = args.offset || 0;
const page = await this.pica.publishers.listPage({
query: args.query,
limit,
offset,
});
return formatStructuredList(page.data, "publisher", {
total: page.total,
has_more: page.hasMore,
limit,
offset,
});
}
async createPublisher(args) {
const result = await this.pica.publishers.create(args);
return formatSuccess("Publisher created", result);
}
}
//# sourceMappingURL=publishers.js.map