UNPKG

@withpica/mcp-server

Version:

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

245 lines • 9.73 kB
// Copyright (c) 2025-2026 Withpica Ltd. All rights reserved. /** * Read MCP_GAP_HINTS_ENABLED at call time. Not cached so tests can flip * it per-case. Returns true only for the explicit "1" value, mirroring * the MCP_LAYERED_DISCOVERY pattern at mcp-server/src/config.ts. */ export function gapHintsEnabled() { return process.env.MCP_GAP_HINTS_ENABLED === "1"; } // --------------------------------------------------------------------------- // pica_works_inspect // --------------------------------------------------------------------------- /** * A work is missing an executed agreement when: * - the `agreements` section is empty, OR * - none of its agreements have a fully-executed status * * We stay conservative — only fire when there's a clear gap, not when * the caller just didn't request the agreements section. */ export function computeWorkGapHints(workResult) { if (!gapHintsEnabled()) return []; const hints = []; const work = (workResult.work ?? {}); const workId = work.id ?? null; const agreements = Array.isArray(workResult.agreements) ? workResult.agreements : null; const hasExecutedAgreement = agreements !== null && agreements.some((a) => { const status = a.status || ""; return status === "fully_executed" || status === "active"; }); if (agreements !== null && !hasExecutedAgreement && workId) { hints.push({ gap: "missing_executed_agreement", capability: "email_search", rationale: "find the signed co-writer or collaborator agreement so the work has provenance", query_shape: "from:{writer_emails} subject:(agreement OR contract OR split) has:attachment", then_call: "pica_enrichment_propose", then_args_shape: { entity_type: "agreement", proposal_action: "create", parent_entity_type: "work", parent_entity_id: workId, proposed_fields: { title: "<from attachment>", agreement_type: "<from content>", other_party_name: "<counterparty>", fully_executed_date: "<ISO date from signatures>", }, sources: [ { url: "gmail://thread-id/<id>", fields: [ "title", "agreement_type", "other_party_name", "fully_executed_date", ], accessed_at: "<iso>", }, ], }, }); } return hints; } // --------------------------------------------------------------------------- // pica_agreements_inspect // --------------------------------------------------------------------------- export function computeAgreementGapHints(agreementResult) { if (!gapHintsEnabled()) return []; const hints = []; const agreement = (agreementResult.agreement ?? {}); const agreementId = agreement.id ?? null; const status = agreement.status ?? ""; const executedDate = agreement.fully_executed_date; const documentUrl = agreement.document_url; if (!agreementId) return []; const isExecuted = status === "fully_executed" || status === "active"; if (!executedDate && !isExecuted) { hints.push({ gap: "missing_execution_date", capability: "email_search", rationale: "find the countersigned copy of this agreement to confirm the execution date", query_shape: `subject:("${agreement.title || "agreement"}" OR countersigned) has:attachment`, then_call: "pica_enrichment_propose", then_args_shape: { entity_type: "agreement", entity_id: agreementId, proposal_action: "update", proposed_fields: { fully_executed_date: "<ISO date from attachment>", status: "fully_executed", }, sources: [ { url: "gmail://thread-id/<id>", fields: ["fully_executed_date", "status"], accessed_at: "<iso>", }, ], }, }); } if (!documentUrl) { hints.push({ gap: "missing_document", capability: "drive_search", rationale: "the agreement has no stored document — locate the signed copy in shared storage", query_shape: `name:("${agreement.title || "agreement"}" OR contract) type:pdf`, then_call: "pica_enrichment_propose", then_args_shape: { entity_type: "agreement", entity_id: agreementId, proposal_action: "update", proposed_fields: { document_url: "<gdrive:// or https:// url>", }, sources: [ { url: "gdrive://file-id/<id>", fields: ["document_url"], accessed_at: "<iso>", }, ], }, }); } return hints; } // --------------------------------------------------------------------------- // pica_people_inspect // --------------------------------------------------------------------------- export function computePersonGapHints(personResult) { if (!gapHintsEnabled()) return []; const hints = []; const person = (personResult.person ?? {}); const personId = person.id ?? null; const email = person.email; const name = person.name || "this person"; const identifiers = Array.isArray(personResult.identifiers) ? personResult.identifiers : []; const identifierTypes = new Set(identifiers.map((i) => i.type || "")); if (!personId) return []; if (!email) { hints.push({ gap: "missing_email", capability: "email_search", rationale: `find an email thread that mentions ${name} so their contact can be captured with provenance`, query_shape: `"${name}" in:anywhere`, then_call: "pica_enrichment_propose", then_args_shape: { entity_type: "person", entity_id: personId, proposal_action: "update", proposed_fields: { email: "<extracted address>", }, sources: [ { url: "gmail://message-id/<id>", fields: ["email"], accessed_at: "<iso>", }, ], }, }); } const hasAnyIndustryId = identifierTypes.has("ipi") || identifierTypes.has("isni") || identifierTypes.has("ipn"); if (!hasAnyIndustryId) { hints.push({ gap: "missing_industry_identifier", capability: "web_search", rationale: `find ${name}'s IPI, ISNI, or IPN in a public registry so downstream registration can complete`, query_shape: `"${name}" (IPI OR ISNI) site:(isni.org OR musicbrainz.org OR themlc.com)`, then_call: "pica_enrichment_propose", then_args_shape: { entity_type: "person", entity_id: personId, proposal_action: "update", proposed_fields: { ipi: "<11-digit IPI>", }, sources: [ { url: "web://https://isni.org/isni/<id>", fields: ["ipi"], accessed_at: "<iso>", }, ], }, }); } return hints; } // --------------------------------------------------------------------------- // pica_enrichment_proposals_list // --------------------------------------------------------------------------- /** * When the pending-proposals list is empty, surface a hint reminding * the agent that peer MCPs may hold evidence that would produce * proposals. This is the weakest of the four surfaces — the list tool * doesn't know which entity's proposals are missing — so we only fire * on the empty case and cite the generic flow. */ export function computeProposalListGapHints(listResult) { if (!gapHintsEnabled()) return []; if (!Array.isArray(listResult) || listResult.length > 0) return []; return [ { gap: "no_pending_proposals", capability: "email_search", rationale: "no proposals are queued — if the user mentioned missing splits or contracts, check their mailbox for correspondence and file evidence via pica_enrichment_propose", query_shape: "subject:(split OR agreement OR royalty) newer_than:90d", then_call: "pica_enrichment_propose", then_args_shape: { entity_type: "<work|recording|person|agreement>", entity_id: "<target id>", proposed_fields: { /* from the email */ }, sources: [ { url: "gmail://thread-id/<id>", fields: ["..."], accessed_at: "<iso>", }, ], }, }, ]; } //# sourceMappingURL=gap-hints.js.map