@withpica/mcp-server
Version:
MCP Server for withPICA — enables AI assistants to interact with your withPICA music catalog
144 lines • 7.93 kB
JavaScript
// Copyright (c) 2025-2026 Withpica Ltd. All rights reserved.
import { formatStructuredList, formatSuccess } from "@withpica/mcp-utils";
export class PartyClaimsTools {
pica;
constructor(pica) {
this.pica = pica;
}
getTools() {
return [
{
definition: {
name: "pica_party_claim_invite",
scope: "write:catalog",
audiences: ["stdio", "http", "public"],
tier: "write",
description: "Invite a label, distributor, or publisher to claim its unclaimed " +
"organisation on PICA. The user supplies the recipient email — that " +
"address receives a single-use claim link, and on redemption the " +
"claimant becomes the owner of the organisation and an active " +
"org-to-org connection is created with this catalog (never " +
"membership). Only unclaimed party organisations (created via " +
"pica_labels_create, verification pending, no members) can be " +
"invited. If another claim is already open on the organisation, " +
"both claims freeze as disputed for manual review. " +
"→ first: pica_labels_query (find the organisation id) " +
"→ then: pica_party_claim_status (track redemption)",
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: {
target_organisation_id: {
type: "string",
description: "id of the unclaimed party organisation to invite (from pica_labels_query / pica_labels_create)",
},
invited_email: {
type: "string",
description: "Recipient email supplied by the user — the consent and identity anchor. The claim link goes to exactly this address and only an account with this email can redeem it.",
},
release_id: {
type: "string",
description: "Optional release (in this catalog) that names the party — gives the invite email its context",
},
recording_id: {
type: "string",
description: "Optional recording (in this catalog) that names the party — alternative context to release_id",
},
},
required: ["target_organisation_id", "invited_email"],
},
},
executor: this.sendInvite.bind(this),
},
{
definition: {
name: "pica_party_claim_status",
scope: "read:catalog",
audiences: ["stdio", "http", "public"],
tier: "read",
description: "List the party claim invites this organisation has sent and their " +
"lifecycle status (pending / claimed / disputed / expired / revoked " +
"/ under_review / approved / rejected). A 'claimed' row means the " +
"party completed the claim: their organisation is verified and an " +
"active org-to-org connection exists. " +
"→ first: pica_party_claim_invite (send a claim invite)",
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: {
target_organisation_id: {
type: "string",
description: "Narrow to claims against one party organisation id",
},
status: {
type: "string",
enum: [
"pending",
"under_review",
"approved",
"claimed",
"rejected",
"disputed",
"expired",
"revoked",
],
description: "Narrow to one lifecycle status",
},
limit: {
type: "number",
minimum: 1,
maximum: 100,
description: "Max results (default: 50, max: 100)",
},
offset: {
type: "number",
minimum: 0,
description: "Skip this many claims (default 0). Page while has_more is true.",
},
},
},
annotations: {
readOnlyHint: true,
},
},
executor: this.claimStatus.bind(this),
},
];
}
async sendInvite(args) {
const result = await this.pica.partyClaims.invite({
target_organisation_id: args.target_organisation_id,
invited_email: args.invited_email,
...(args.release_id ? { release_id: args.release_id } : {}),
...(args.recording_id
? { recording_id: args.recording_id }
: {}),
});
if (!result || !result.invite) {
return formatSuccess("Claim invite call completed but the server returned no invite data — call pica_party_claim_status to confirm state before retrying.", { invite: null });
}
if (result.disputed) {
return formatSuccess("Another claim is already open on this organisation — BOTH claims are now frozen as disputed and no email was sent. The PICA team will adjudicate; track the outcome via pica_party_claim_status.", result);
}
return formatSuccess(`Claim invite sent to ${result.invite.invited_email}. When they redeem it they become the owner of the claimed organisation and an active org-to-org connection is created with this catalog. Track it via pica_party_claim_status.`, result);
}
async claimStatus(args) {
// listPage carries the route's real total (list() returned only the
// page, so "total" was the page length — row-cap T3).
const page = await this.pica.partyClaims.listPage({
target_organisation_id: args.target_organisation_id,
status: args.status,
limit: args.limit,
offset: args.offset,
});
return formatStructuredList(page.data, "party claim invite", {
total: page.total,
has_more: page.hasMore,
});
}
}
//# sourceMappingURL=party-claims.js.map