UNPKG

@withpica/mcp-server

Version:

MCP Server for withPICA — enables AI assistants to interact with your withPICA music catalog

207 lines • 10.4 kB
// Copyright (c) 2025-2026 Withpica Ltd. All rights reserved. import { formatSuccess, formatStructuredList } from "@withpica/mcp-utils"; const ROLE_ENUM = [ "member", "founder", "lead", "touring_member", "session", "guest", "producer_in_residence", ]; export class GroupsTools { pica; constructor(pica) { this.pica = pica; } getTools() { return [ { definition: { name: "pica_group_add_member", scope: "write:people", audiences: ["stdio", "http"], tier: "write", description: "Add a person as a member of a group person row. The group must already have is_group=true on its people record. " + "Defaults: role='member', receive_notifications=true. " + "→ before: pica_people_query (find both people) → then: pica_group_list_members (verify roster)", 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: { group_person_id: { type: "string", description: "ID of the group person row (must have is_group=true)", }, member_person_id: { type: "string", description: "ID of the member person row", }, role: { type: "string", enum: [...ROLE_ENUM], description: "Constrained role within the group. Default 'member'.", }, role_label: { type: "string", description: "Free-text label for variants outside the enum (e.g. 'lead vocalist for tour 2025'). Optional.", }, joined_at: { type: "string", description: "When the member joined (ISO 8601). Omit for unknown.", }, receive_notifications: { type: "boolean", description: "Whether the member's linked user account (if any) gets notifications about group activity. Default true.", }, notification_filter: { type: "array", items: { type: "string" }, description: "Optional subset of original event types to be notified about (e.g. ['work_credit_added','agreement_party_linked']). Omit for all.", }, }, required: ["group_person_id", "member_person_id"], }, }, executor: this.addMember.bind(this), }, { definition: { name: "pica_group_remove_member", scope: "write:people", audiences: ["stdio", "http"], tier: "write", description: "Remove a member from a group by setting left_at on the active row. The row is preserved — never deleted — so member history survives. " + "→ then: pica_group_list_members (with include_inactive=true to see history)", 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: { group_person_id: { type: "string", description: "Group person ID", }, member_person_id: { type: "string", description: "Member person ID", }, left_at: { type: "string", description: "When they left (ISO 8601). Defaults to now.", }, }, required: ["group_person_id", "member_person_id"], }, }, executor: this.removeMember.bind(this), }, { definition: { name: "pica_group_list_members", scope: "read:people", audiences: ["stdio", "http"], tier: "read", description: "List members of a group person. Active members only by default; pass include_inactive=true for history. " + "To change the roster use pica_group_add_member or pica_group_remove_member; this only reads it. " + "→ then: pica_people_inspect (get details for a specific member)", 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: { group_person_id: { type: "string", description: "Group person ID (must have is_group=true)", }, include_inactive: { type: "boolean", description: "Include rows with left_at set (past members). Default false.", }, }, required: ["group_person_id"], }, }, executor: this.listMembers.bind(this), }, { definition: { name: "pica_person_list_groups", scope: "read:people", audiences: ["stdio", "http"], tier: "read", description: "List the groups a person is (or was) a member of. Active memberships only by default. " + "→ then: pica_people_inspect (get group details)", 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: { person_id: { type: "string", description: "Person ID", }, include_inactive: { type: "boolean", description: "Include rows with left_at set (past memberships). Default false.", }, }, required: ["person_id"], }, }, executor: this.listGroups.bind(this), }, ]; } // eslint-disable-next-line @typescript-eslint/no-explicit-any -- args bag from MCP transport async addMember(args) { const result = await this.pica.groups.addMember({ group_person_id: args.group_person_id, member_person_id: args.member_person_id, role: args.role, role_label: args.role_label, joined_at: args.joined_at, receive_notifications: args.receive_notifications, notification_filter: args.notification_filter, }); const data = result?.data ?? result; const role = data?.role ?? "member"; return formatSuccess(`Member added to group as ${role}${data?.role_label ? ` (${data.role_label})` : ""}.`, data); } // eslint-disable-next-line @typescript-eslint/no-explicit-any -- args bag from MCP transport async removeMember(args) { const result = await this.pica.groups.removeMember({ group_person_id: args.group_person_id, member_person_id: args.member_person_id, left_at: args.left_at, }); const data = result?.data ?? result; return formatSuccess(`Member removed from group (left_at=${data?.left_at ?? "now"}). Row preserved for history.`, data); } // eslint-disable-next-line @typescript-eslint/no-explicit-any -- args bag from MCP transport async listMembers(args) { const result = await this.pica.groups.listMembers(args.group_person_id, { include_inactive: !!args.include_inactive, }); const items = (result?.data ?? result ?? []); return formatStructuredList(items, "group_membership", { total: items.length, }); } // eslint-disable-next-line @typescript-eslint/no-explicit-any -- args bag from MCP transport async listGroups(args) { const result = await this.pica.groups.listGroupsForPerson(args.person_id, { include_inactive: !!args.include_inactive, }); const items = (result?.data ?? result ?? []); return formatStructuredList(items, "group_membership", { total: items.length, }); } } //# sourceMappingURL=groups.js.map