UNPKG

@withpica/mcp-server

Version:

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

109 lines • 7.36 kB
// Copyright (c) 2025-2026 Withpica Ltd. All rights reserved. import { PUBLIC_FORBIDDEN_KEYWORDS_FULL } from "./forbidden-keywords.js"; // ── Class B: include name, sanitized description ── // The source-of-truth descriptions in the per-domain tool files name third-party // services (MLC / ISNI / MusicBrainz / Spotify / YouTube / Discogs / ACRCloud / // Librosa / Groq) or internal architecture (cascade engine / Tier A/B / identity // graph / CAR scoring). The strings here are rewrites that preserve capability // signal without leaking the algorithm or vendor binding. const CLASS_B_SANITIZED = { // Reclassified from Class C → B per 2026-04-27 sign-off pica_graph_traverse: "Traverse identifier-based connections from any external identifier. Returns connected works, recordings, and people across the catalog.", pica_run_work_cascade: "Re-run metadata enrichment for a work to fill gaps from external sources.", pica_run_person_cascade: "Re-run metadata enrichment for a person to fill gaps from external identity sources.", pica_cascade_health: "Snapshot of recent metadata-enrichment activity — outcome counts and recent failures.", pica_export_catalog_asset_report: "Generate a catalog asset report — a structured summary of catalog completeness and provenance.", pica_car_preview: "Preview a catalog asset report inline before generating the full export.", // Original Class B pica_resolve_work: "Resolve a work against external metadata sources. Returns structured proposals and applied updates.", pica_resolve_person: "Resolve a person against external identity sources. Returns structured proposals and applied updates.", pica_resolve_recording: "Resolve a recording against external metadata sources. Returns structured proposals and applied updates.", pica_people_triangulate: "Check whether an external artist name already matches an existing person in the catalog. Returns a confidence score, the matched person (if any), and alternative candidates.", pica_enrichment_candidates: "Find catalog entries that are good candidates for metadata enrichment.", pica_enrichment_compare: "Compare what external sources say about a catalog entry, side by side.", pica_registrations_compare: "Compare a work's registration status across society and platform records.", pica_enrichment_propose: "Propose a metadata enrichment from agent research; goes to a review queue.", pica_enrichment_proposals_list: "List pending metadata-enrichment proposals awaiting review.", pica_enrichment_proposal_apply: "Apply a previously proposed metadata enrichment.", pica_enrichment_proposal_reject: "Reject a previously proposed metadata enrichment.", pica_audio_analyze: "Analyze an audio file for tempo, key, energy, and lyric transcription.", pica_audio_identify: "Identify a track via audio fingerprinting. Returns matched metadata with confidence score.", pica_documents_analyse: "Analyse a document with AI. Extracts structured fields from invoices, statements, contracts.", // Tier 2 consolidation (ADR-149): pica_documents_query/_inspect are the // canonical names that replaced pica_documents_list/_read (those two were // removed in 2.99.0) — sanitized so the public discovery surface doesn't // leak financial_documents_hidden / finance-admin access-control wording. pica_documents_query: "List catalog documents: PICA's own split sheets first (kind 'split_sheet', the record; read with pica_split_sheet_get), then uploaded documents (registration exports, works lists, supporting split sheets) awaiting mapping.", pica_documents_inspect: "Read an uploaded document's extracted text to map it into the catalog.", pica_documents_link: "Link a source document to the catalog entity created from it (provenance).", pica_documents_delete: "Delete an uploaded document and its stored file (e.g. to clear a failed-to-parse document and re-upload a clean copy).", pica_import_streaming_link: "Import a work or recording from a public streaming-service link.", pica_import_youtube_link: "Import a recording from a YouTube link.", pica_multimedia_import_url: "Import an image or video from a public URL; optionally set it as a person's profile photo.", pica_multimedia_link_youtube: "Link a YouTube video to a work.", pica_ingest_statement: "Ingest a royalty statement document into the catalog.", // ADR-289 Wave C2 — raw description names MusicBrainz explicitly. pica_enrichment_clear: "Undo a metadata-enrichment match on a work — clears the ISWC and/or PRO IDs a prior automatic match wrote, but only the fields that actually came from that source. Cannot be undone.", }; // ── Forbidden keywords (AC-4 in ADR-202) ── // Source-of-truth list lives at `./forbidden-keywords.ts` and is shared // with the `scripts/lint-mcp-tools.ts` description-hygiene check // (ADR-180 Rule 9, 2026-05-02 amendment). // // The lobby surface uses the full union (PUBLIC ∪ AUTHENTICATED) — every // vendor / data-source / infra / library / concept term that's banned for // any audience is also banned at the unauthenticated boundary. const FORBIDDEN_KEYWORD_FRAGMENTS = PUBLIC_FORBIDDEN_KEYWORDS_FULL; /** * Apply ADR-202 filtering to a list of ToolDefinitions. * * ADR-248: membership on the public surface is now a declared property of * the tool — `audiences ∋ "public"` — instead of the former Class A allowlist * / Class C exclude / meta-exclude lists. The description SANITISATION stays * exactly as it was (ADR-202): a Class B tool ships its rewritten description, * everything else ships its own. The `readOnlyOnly` Phase-1 risk gate stays. * * Order of operations: * 1. Drop anything not declaring `audiences ∋ "public"` (default-exclude) * 2. If readOnlyOnly: drop tools whose risk metadata is not "safe" * 3. Replace description for CLASS_B_SANITIZED entries * 4. Strip executor reference (return PublicToolDefinition only) */ export function filterToolsForPublicDiscovery(tools, options) { const readOnlyOnly = options.readOnlyOnly ?? true; const result = []; for (const tool of tools) { if (!tool.audiences?.includes("public")) continue; // declared membership if (readOnlyOnly) { const meta = options.metadataLookup(tool.name); if (meta?.risk !== "safe") continue; } const isB = tool.name in CLASS_B_SANITIZED; const description = isB ? CLASS_B_SANITIZED[tool.name] : tool.description; result.push({ name: tool.name, description, inputSchema: tool.inputSchema, annotations: tool.annotations, }); } return result; } /** Returns the first forbidden keyword found in a string, or null. */ export function findForbiddenKeyword(text) { const lower = text.toLowerCase(); for (const fragment of FORBIDDEN_KEYWORD_FRAGMENTS) { if (lower.includes(fragment.toLowerCase())) return fragment; } return null; } // Internal exports for test introspection only — consumers use // filterToolsForPublicDiscovery + findForbiddenKeyword. export const _internal = { CLASS_B_SANITIZED, FORBIDDEN_KEYWORD_FRAGMENTS, }; //# sourceMappingURL=public-filter.js.map