@withpica/mcp-server
Version:
MCP Server for withPICA — enables AI assistants to interact with your withPICA music catalog
245 lines • 9.73 kB
JavaScript
// 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