UNPKG

@withpica/mcp-server

Version:

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

167 lines • 11 kB
// Copyright (c) 2025-2026 Withpica Ltd. All rights reserved. import { formatSuccess, formatValidationError } from "@withpica/mcp-utils"; const ENTITY_TYPES = ["work", "recording", "person", "release"]; const ACTIONS = ["view", "share_link_preview", "claim", "grant"]; const RECIPIENT_TYPES = ["user", "anonymous", "share_link"]; export class AccessSimulateTools { pica; constructor(pica) { this.pica = pica; } getTools() { return [ { definition: { name: "pica_access_simulate", scope: "admin", audiences: ["stdio"], tier: "read", description: "Simulate who would see what — for any work, recording, person, or " + "release, given a hypothetical action (view, share-link preview, " + "claim, grant) and a recipient configuration, returns the visible " + "and hidden fields plus the chain of rules that produced the answer. " + "Read-only — never mutates state. Use BEFORE creating a share link " + "to verify the recipient sees what you intend, or BEFORE claiming " + "custody to see what changes. v1 ships real share_link_preview " + "logic for works; other paths return a stable scaffolded shape.", workflows: "infrastructure", vernacular_kind: "specialist", vernacular_reason: "Specialist read-only simulator — agents invoke this BEFORE a share link / claim / grant action to preview what the recipient would see. Not a natural-language entry surface; invoked operationally as a safety check, not from a 'Use when the user asks' question.", inputSchema: { type: "object", properties: { entity_type: { type: "string", enum: [...ENTITY_TYPES], description: "Entity to simulate access for. v1 implements 'work' fully; the others return a not_yet_supported scaffold with the same shape so agents can iterate against the API.", }, entity_id: { type: "string", description: "UUID of the entity. Must belong to the caller's organisation — cross-org IDs return entity_not_found rather than leaking existence.", }, action: { type: "string", enum: [...ACTIONS], description: "Access action to simulate. 'share_link_preview' is the v1 real path: pass recipient.share_config and get back the field-level visibility matrix the share link would expose. The other actions return scaffolded responses.", }, recipient: { type: "object", description: "Hypothetical recipient. For share_link_preview pass type='share_link' and a share_config; for view/claim/grant pass type='user'/'org' as appropriate.", properties: { type: { type: "string", enum: [...RECIPIENT_TYPES], description: "Recipient type — 'user' (authenticated user_id), 'anonymous' (no auth), 'share_link' (hypothetical share-link config).", }, user_id: { type: "string", description: "Recipient user UUID, when type='user'. Optional otherwise.", }, org_id: { type: "string", description: "Recipient organisation UUID, when simulating an action that targets an org (e.g. 'grant'). Optional otherwise.", }, share_config: { type: "object", description: "Hypothetical share-link configuration to simulate. Fields mirror the share-links write surface — access_type, allow_*, allowed_person_ids, sent_to_emails, watermark_audio, expires_at, max_views.", properties: { access_type: { type: "string", enum: ["private", "limited", "public"], description: "Recipient scope — 'private' (allowed_person_ids only), 'limited' (sent_to_emails only), 'public' (anyone with link).", }, allow_download: { type: "boolean", description: "When true, the recipient can download original audio files. Pair with watermark_audio=true for pre-release pitches.", }, allow_streaming: { type: "boolean", description: "When true, the recipient can stream audio inline.", }, allow_comments: { type: "boolean", description: "When true, the recipient sees the discussion thread and can post comments.", }, allow_view_collaborators: { type: "boolean", description: "When true, the recipient sees the credit lines, splits, and attestation status.", }, allow_view_financials: { type: "boolean", description: "When true, the recipient sees earnings, advances, and royalty statements. The most common reason a recipient sees less than expected.", }, watermark_audio: { type: "boolean", description: "When true, audio surfaces are mixed with an inaudible watermark identifier.", }, notify_on_access: { type: "boolean", description: "When true, the link's creator gets notified each time the recipient accesses it.", }, allowed_person_ids: { type: "array", items: { type: "string" }, description: "Person UUIDs allowed to access when access_type='private'.", }, sent_to_emails: { type: "array", items: { type: "string" }, description: "Email addresses the link was sent to when access_type='limited'.", }, max_views: { type: "number", description: "Cap on total views; null/omitted for unlimited. <=0 makes the link unusable.", }, expires_at: { type: "string", description: "ISO timestamp; recipients see an expired page after this time.", }, }, required: ["access_type"], }, }, required: ["type"], }, }, required: ["entity_type", "entity_id", "action", "recipient"], }, annotations: { readOnlyHint: true, idempotentHint: true, }, }, executor: this.simulate.bind(this), }, ]; } async simulate(args) { if (!args.entity_type || !ENTITY_TYPES.includes(args.entity_type)) { return formatValidationError({ entity_type: `must be one of: ${ENTITY_TYPES.join(", ")}`, }); } if (!args.entity_id || typeof args.entity_id !== "string") { return formatValidationError({ entity_id: "is required" }); } if (!args.action || !ACTIONS.includes(args.action)) { return formatValidationError({ action: `must be one of: ${ACTIONS.join(", ")}`, }); } if (!args.recipient || typeof args.recipient !== "object" || !RECIPIENT_TYPES.includes(args.recipient.type)) { return formatValidationError({ recipient: `recipient.type must be one of: ${RECIPIENT_TYPES.join(", ")}`, }); } const result = await this.pica.accessSimulate.simulate(args); const summary = result.not_yet_supported ? `Access simulation scaffolded (v1 stub) — ${result.reasons[0]?.detail ?? "see reasons"}` : result.visible ? `Access simulated — ${result.visible_fields.length} field(s) visible, ${result.hidden_fields.length} hidden, ${result.warnings.length} warning(s)` : `Access denied — ${result.reasons[0]?.detail ?? "see reasons"}`; return formatSuccess(summary, result); } } //# sourceMappingURL=access-simulate.js.map