@withpica/mcp-server
Version:
MCP Server for withPICA — enables AI assistants to interact with your withPICA music catalog
341 lines • 18.6 kB
JavaScript
// Copyright (c) 2025-2026 Withpica Ltd. All rights reserved.
import { formatAsText, formatList, formatSuccess } from "@withpica/mcp-utils";
import { notifyParties, summarizeNotifications, withBillingGate, } from "@withpica/mcp-utils";
import { AGREEMENT_TYPES_WRITE_PROPERTIES, AGREEMENT_TYPES_CREATE_REQUIRED, AGREEMENT_TYPES_UPDATE_REQUIRED, } from "@withpica/mcp-utils";
export class AgreementTypesTools {
pica;
constructor(pica) {
this.pica = pica;
}
getResource(type) {
switch (type) {
case "template":
return this.pica.agreementTemplates;
case "producer":
return this.pica.producerAgreements;
case "work_for_hire":
return this.pica.workForHire;
default:
throw new Error(`Unknown agreement type: ${type}`);
}
}
labelFor(type) {
switch (type) {
case "template":
return "Agreement template";
case "producer":
return "Producer agreement";
case "work_for_hire":
return "Work-for-hire agreement";
}
}
getTools() {
return [
// ── Unified CRUD ────────────────────────────────────────────────
{
definition: {
name: "pica_agreement_types_query",
scope: "read:agreements",
audiences: ["stdio", "http", "public"],
tier: "read",
description: "List agreements by type — templates, producer agreements, or work-for-hire contracts. " +
"For one agreement in full, use pica_agreement_types_inspect; " +
"this one lists by type.",
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: {
type: {
type: "string",
enum: ["template", "producer", "work_for_hire"],
description: "Agreement sub-type to query",
},
limit: {
type: "number",
description: "Max results",
},
},
required: ["type"],
},
},
executor: this.query.bind(this),
},
{
definition: {
name: "pica_agreement_types_inspect",
scope: "read:agreements",
audiences: ["stdio", "http", "public"],
tier: "read",
description: "Get a specific agreement by type and ID — full details including body, terms, and metadata. " +
"Producer and work-for-hire agreements are money: without the org's finance access the body, fee, advance and points are withheld and the row carries finance_access_required: true. " +
"To browse every agreement of a type, use " +
"pica_agreement_types_query; this one needs the 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: {
type: {
type: "string",
enum: ["template", "producer", "work_for_hire"],
description: "Agreement sub-type",
},
id: {
type: "string",
description: "Agreement ID",
},
},
required: ["type", "id"],
},
},
executor: this.inspect.bind(this),
},
{
definition: {
name: "pica_agreement_types_create",
scope: "write:agreements",
audiences: ["stdio", "http", "public"],
tier: "write",
description: "Create a new agreement of the specified type. Three contract shapes: " +
"split sheet — informal, all collaborators agree to publishing splits pre-registration (prefer pica_split_sheet_generate for that flow); " +
"work-for-hire — contractor waives royalty share, takes a flat fee + services description; " +
"producer agreement — producer takes points off the top of master royalties + deliverables. " +
"Pick the shape that matches the deal. Three branches dispatched via 'type': " +
"template (needs name + category + templateText), " +
"producer (needs producer_id + royalty_points + deliverables + work_id|recording_id), " +
"work_for_hire (needs contractor_id + contractor_role + fee_amount + services_description + work_id|recording_id). " +
"Creating a producer or work-for-hire agreement needs the org's finance access (refused with FINANCE_ACCESS_REQUIRED otherwise). " +
"Prefer pica_agreement_templates_duplicate when the user wants a copy of an existing template.",
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: AGREEMENT_TYPES_WRITE_PROPERTIES,
required: [...AGREEMENT_TYPES_CREATE_REQUIRED],
},
},
executor: withBillingGate(this.pica, "agreement type creation", this.create.bind(this)),
},
{
definition: {
name: "pica_agreement_types_update",
scope: "write:agreements",
audiences: ["stdio", "http", "public"],
tier: "write",
description: "Update an existing agreement by type and ID. " +
"Note: PATCH on producer_agreements and work_for_hire_agreements is action-only " +
"(send_for_signature / countersign / mark_advance_paid / mark_paid / record_recoupment / " +
"calculate_royalty), not generic field-level; every action except send_for_signature needs the org's finance access. Template branch accepts field updates. " +
"Prefer pica_agreement_templates_set_default when the change is " +
"just making a template the default for its type.",
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: AGREEMENT_TYPES_WRITE_PROPERTIES,
required: [...AGREEMENT_TYPES_UPDATE_REQUIRED],
},
},
executor: withBillingGate(this.pica, "agreement type update", this.update.bind(this)),
},
{
definition: {
name: "pica_agreement_types_delete",
scope: "write:agreements",
audiences: ["stdio", "http", "public"],
tier: "destructive",
description: "Delete a template / producer agreement / work-for-hire record by type + id — the " +
"agreement_types family. Standard agreements (master_recording, publishing_admin, etc.) " +
"use pica_agreements_delete instead. No undo — use " +
"pica_agreement_types_inspect to check the record first.",
workflows: "infrastructure",
vernacular_kind: "specialist",
vernacular_reason: "Destructive operation; invoked by exact name after explicit user confirmation in a specialist flow.",
inputSchema: {
type: "object",
properties: {
type: {
type: "string",
enum: ["template", "producer", "work_for_hire"],
description: "Agreement sub-type",
},
id: {
type: "string",
description: "Agreement UUID from pica_agreement_types_query.",
},
},
required: ["type", "id"],
},
},
executor: withBillingGate(this.pica, "agreement type deletion", this.delete.bind(this)),
},
// ── Template-specific operations ────────────────────────────────
{
definition: {
name: "pica_agreement_templates_render",
scope: "read:agreements",
audiences: ["stdio", "http", "public"],
tier: "read",
description: "Fill an agreement template with work and people data — generates a ready-to-send agreement. Pass the template ID plus work_id and/or people_ids to populate the placeholders with real catalog data. " +
"Find the template id with pica_agreement_types_query type:template; " +
"this one only fills it in and returns the text.",
workflows: ["agreement-required"],
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: {
id: {
type: "string",
description: "Template ID to render",
},
work_id: {
type: "string",
description: "Work ID to pull title, credits, and metadata from",
},
people_ids: {
type: "array",
items: { type: "string" },
description: "People IDs to include as parties in the agreement",
},
variables: {
type: "object",
description: "Additional variables to substitute — keys match placeholder names in the template body",
},
},
required: ["id"],
},
},
executor: this.renderTemplate.bind(this),
},
{
definition: {
name: "pica_agreement_templates_duplicate",
scope: "write:agreements",
audiences: ["stdio", "http", "public"],
tier: "write",
description: "Duplicate an agreement template — creates a copy you can customise without changing the original. " +
"Prefer pica_agreement_types_update to edit the original in place.",
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: {
id: {
type: "string",
description: "Template ID to duplicate",
},
},
required: ["id"],
},
},
executor: withBillingGate(this.pica, "template duplication", this.duplicateTemplate.bind(this)),
},
{
definition: {
name: "pica_agreement_templates_set_default",
scope: "write:agreements",
audiences: ["stdio", "http", "public"],
tier: "write",
description: "Set a template as the default for its type — when creating agreements of that type, this template will be suggested first. " +
"Use pica_agreement_types_query type:template to list the " +
"templates you can choose from.",
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: {
id: {
type: "string",
description: "Template ID to set as default",
},
},
required: ["id"],
},
},
executor: withBillingGate(this.pica, "template default", this.setDefaultTemplate.bind(this)),
},
];
}
// ── Unified CRUD executors ──────────────────────────────────────────
async query(args) {
const resource = this.getResource(args.type);
const items = await resource.list();
return formatList(items, { total: items.length });
}
async inspect(args) {
const resource = this.getResource(args.type);
const item = await resource.get(args.id);
return formatAsText(item);
}
async create(args) {
const { type, ...fields } = args;
const resource = this.getResource(type);
const item = await resource.create(fields);
// Notification for producer agreements
if (type === "producer" && fields.producer_id) {
const results = await notifyParties(this.pica, [
{
person_id: fields.producer_id,
message: `a producer agreement has been created for "${fields.title || "untitled"}" — please review the terms`,
work_id: fields.work_id,
notification_type: "agreement_created",
},
]);
const notifySummary = summarizeNotifications(results);
if (notifySummary) {
return formatSuccess(`Producer agreement created. ${notifySummary}`, item);
}
}
// Notification for work-for-hire
if (type === "work_for_hire" && fields.contractor_id) {
const results = await notifyParties(this.pica, [
{
person_id: fields.contractor_id,
message: `a work-for-hire agreement has been created: "${fields.title || "untitled"}" — please review`,
work_id: fields.work_id,
notification_type: "agreement_created",
},
]);
const notifySummary = summarizeNotifications(results);
if (notifySummary) {
return formatSuccess(`Work-for-hire agreement created. ${notifySummary}`, item);
}
}
return formatSuccess(`${this.labelFor(type)} created`, item);
}
async update(args) {
const { type, id, ...updates } = args;
const resource = this.getResource(type);
const item = await resource.update(id, updates);
return formatSuccess(`${this.labelFor(type)} updated`, item);
}
async delete(args) {
const { type, id } = args;
const resource = this.getResource(type);
await resource.delete(id);
return formatSuccess(`${this.labelFor(type)} ${id} deleted`);
}
// ── Template-specific executors ─────────────────────────────────────
async renderTemplate(args) {
const { id, ...renderData } = args;
const rendered = await this.pica.agreementTemplates.render(id, renderData);
return formatSuccess("Template rendered with catalog data", rendered);
}
async duplicateTemplate(args) {
const duplicate = await this.pica.agreementTemplates.duplicate(args.id);
return formatSuccess("Template duplicated", duplicate);
}
async setDefaultTemplate(args) {
const result = await this.pica.agreementTemplates.setDefault(args.id);
return formatSuccess("Template set as default for its type", result);
}
}
//# sourceMappingURL=agreement-types.js.map