UNPKG

@withpica/mcp-server

Version:

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

118 lines • 6.64 kB
// Copyright (c) 2025-2026 Withpica Ltd. All rights reserved. import { formatStructured } from "@withpica/mcp-utils"; const MAX_LIMIT = 100; export class SyncFitTools { pica; constructor(pica) { this.pica = pica; } getTools() { return [ { definition: { name: "pica_sync_fit_query", scope: "read:catalog", audiences: ["stdio", "http", "public"], tier: "read", description: "Where the user's songs sit against film and TV productions still " + "in progress: the same list as the sync page's 'fit' section. Use " + "when the user asks: 'which of my songs could fit a film?', 'what " + "productions suit this song?', 'which songs fit this show?'. Each " + "row is one song and one production, with a band (strong or " + "possible: how much they have in common, NOT a likelihood of a " + "placement) and the reasons PICA's matcher found (shared genre, " + "language and territory, production stage). Each production " + "carries its genres, themes, countries, language, release date, " + "stage, companies and networks as TMDB and Wikidata hold them. " + "PICA holds no synopsis or tone for a production, and themes are " + "rarely filled. With song_facts: true each row also carries the " + "song's own raw facts: its genres and lyric language, and a " + "`measured` entry per analysed master with tempo_bpm, key, mode " + "(major/minor) and duration_seconds, measured from the audio by " + "PICA's analyser, the same names the other PICA tools use. " + "Energy is left out until its measurement is fixed, and lyric " + "themes are not stored yet. Use the facts to judge the fit " + "yourself and explain your " + "reasoning to the user. Your judgement belongs in your answer: " + "never write it back to PICA as data (no notes, tags or fields " + "recording a fit). Nothing here is a confirmed opportunity. " + "Without work_id or screen_project_id the list covers the whole " + "catalogue, spread so each song's best three pairs come before " + "any song's fourth. Pages with limit (up to 100, default 40) and " + "offset; total says how many pairs there are in all. " + "→ first: pica_works_query (find a work_id) " + "→ then: pica_sync_placements_query (placements already made)", workflows: "infrastructure", vernacular_kind: "creator-entry", inputSchema: { type: "object", properties: { work_id: { type: "string", description: "One song's productions: a work id from pica_works_query.", }, screen_project_id: { type: "string", description: "One production's songs: a screen_project_id from an earlier result.", }, song_facts: { type: "boolean", description: "Also return each song's raw facts (genres, lyric language, and measured tempo_bpm, key, mode, duration_seconds per analysed master) so you can judge the fit yourself.", }, limit: { type: "number", minimum: 1, maximum: MAX_LIMIT, description: "Pairs per page (default 40, at most 100).", }, offset: { type: "number", minimum: 0, description: "Pairs to skip, for the next page.", }, }, }, }, executor: this.query.bind(this), }, ]; } async query(args) { const page = await this.pica.syncFit.query({ ...(typeof args.work_id === "string" ? { work_id: args.work_id } : {}), ...(typeof args.screen_project_id === "string" ? { screen_project_id: args.screen_project_id } : {}), ...(args.song_facts === true ? { song_facts: true } : {}), ...(typeof args.limit === "number" ? { limit: Math.min(Math.max(1, Math.floor(args.limit)), MAX_LIMIT) } : {}), ...(typeof args.offset === "number" ? { offset: Math.max(0, Math.floor(args.offset)) } : {}), }); const n = page.rows.length; const first = n === 0 ? 0 : page.offset + 1; const last = page.offset + n; const more = last < page.total; const summary = n === 0 && page.total > 0 ? `No pairs at offset ${page.offset}: there are ${page.total} in all. Start again at offset 0.` : page.total === 0 ? "No song–production pairs strong enough to show. Matching needs a genre or an audio analysis on a song, and an open production it shares something with." : `Showing pairs ${first}–${last} of ${page.total} (${page.songs} ${page.songs === 1 ? "song" : "songs"}).` + (more ? ` More remain: call again with offset ${last}. Do not treat this page as everything.` : "") + " Bands are how much a song and a production have in common, not how likely a placement is."; return formatStructured({ count: n, total: page.total, songs: page.songs, offset: page.offset, limit: page.limit, has_more: more, items: page.rows, }, summary); } } //# sourceMappingURL=sync-fit.js.map