@withpica/mcp-server
Version:
MCP Server for withPICA — enables AI assistants to interact with your withPICA music catalog
131 lines • 5.96 kB
TypeScript
/**
* Recordings Tools — MCP tools for managing recordings.
*
* ADR-149 Phase 5b: Consolidated to query+inspect pattern.
* - pica_recordings_query replaces pica_recordings_list + pica_recordings_by_work
* - pica_recordings_inspect replaces pica_recordings_get (with sections support)
*
* ADR-174 Phase 2 item 3 reconciliation: the write surface covers only
* real columns on `public.recordings`. Phantom fields previously
* accepted via `additionalProperties: true` are gone; agents that
* tried to set them silently dropped data on the way to Postgres.
*
* Two pre-flight surprises documented inline below:
*
* 1. `title` is a legacy alias for `recording_title`. The HTTP route
* translates before the service sees the payload (POST since the
* route was written, PATCH only since ADR-174). The tool keeps
* `title` as the input field name for caller ergonomics; the
* description marks the rename explicitly.
*
* 2. Duration lives in `duration_ms` on recordings (works carry
* `duration_seconds`). Agents conflating the two get a 400 with
* a redirect from the route allow-list.
*
* Add-candidates surfaced per the audit at
* docs/audits/2026-04-14-mcp-db-reconciliation.md §3:
* duration_ms, preview_url, album_art_url, spotify_url,
* spotify_track_uri, spotify_track_id, youtube_video_id, youtube_url,
* apple_music_url, deezer_track_id.
*
* ADR-282: per-society fields (ppl_registered, ppl_recording_id,
* mlc_recording_id, mlc_song_code) have been replaced by the generic
* society_registrations array so no single society is privileged.
*
* Most of these are populated by the enrichment cascade on streaming
* import; exposing them lets agents seed or override that data
* deliberately rather than discovering — surprised — that the field
* they thought they set was silently absent.
*
* `isrc_prefix` is intentionally NOT in this set — column is GENERATED
* ALWAYS in `public.recordings` (left(replace(isrc,'-',''),5)). Caught
* by ADR-198 Phase 1 contract — every write attempt 500'd with PG
* 428C9 "cannot insert non-DEFAULT value into generated column".
*/
import { PicaClient } from "@withpica/mcp-sdk";
import { ToolDefinition, ToolExecutor } from "./index.js";
/**
* Canonical write-schema for `recordings`. Every property must
* correspond to a real column on `public.recordings` (or be the
* documented `title` legacy alias) — verified against
* `lib/db/database.types.ts`. The HTTP route at
* `app/api/admin/recordings/route.ts` carries the same allow-list
* (plus the `recording_type` legacy alias for non-MCP callers); this
* list is the narrower MCP-agent contract.
*
* `version_type` is enum-restricted by the
* `recordings_version_type_check` CHECK constraint (ADR-166 Phase 1).
* Reflecting the enum here means agents see invalid values as schema
* errors at the tool surface, not as 500s round-tripped from Postgres.
*/
/**
* The `recordings_version_type_check` CHECK vocabulary (ADR-166 Phase 1).
*
* Exported and referenced rather than re-typed, so the create/update schema
* and the `pica_recordings_query` filter cannot drift apart — the filter
* carried no enum at all until A10 (2026-08-24), so an agent had no way to
* know `live` was rejected and `live_performance` was not.
* Pinned by src/__tests__/tools/filter-param-enums.test.ts.
*/
export declare const RECORDING_VERSION_TYPES: readonly ["demo", "master", "alternate_master", "music_video", "lyric_video", "live_performance", "acoustic", "remix", "cover", "alternate", "instrumental", "backing_track", "edit"];
declare const RECORDING_WRITE_PROPERTIES: Record<string, Record<string, unknown>>;
export declare class RecordingsTools {
private pica;
constructor(pica: PicaClient);
/**
* Get all recordings tools
*/
getTools(): Array<{
definition: ToolDefinition;
executor: ToolExecutor;
}>;
/**
* Query recordings — list all, search by text, or filter by work
*/
private queryRecordings;
/**
* Inspect a recording — get details with optional section filtering
*/
/**
* Resolve an ISRC to a recording id. recordings.search filters by ISRC
* client-side, so we take the first EXACT (case-insensitive, dash-stripped)
* match rather than a substring hit. Throws EntityNotFoundError (→ NOT_FOUND)
* when nothing matches, so inspect surfaces a hard not-found instead of a
* confusing empty payload (ADR-198).
*/
private resolveRecordingIdByIsrc;
/**
* Resolve the primary recording for a section that runs WITHOUT "basic"
* (e.g. sections: ["credits"]). A 404 (EntityNotFoundError) or 400
* (invalid id) on this fetch is the same hard-error class as the
* basic-section fetch — the id the caller asked about cannot be
* resolved — so it propagates (2026-09-01 sibling sweep of the
* works_inspect 2026-07-18 fail-quiet fix; previously it degraded into
* credits_error/audio_error inside a success envelope). Any other
* failure is handed to onSoftError and the section records it exactly
* as before — only the primary fetch gets the hard treatment, never
* the section's own calls.
*/
private primaryForSection;
private inspectRecording;
/**
* Create recording.
*
* `version_type` defaults to 'master' (the DB-level default and the
* canonical ADR-166 value). Pre-ADR-174 the executor defaulted to
* 'original', which is not a valid version_type — every call without
* an explicit type tripped the recording-type CHECK constraint and
* surfaced as a 500 from the service.
*/
private createRecording;
/**
* Update recording
*/
private updateRecording;
/**
* Delete recording
*/
private deleteRecording;
}
export { RECORDING_WRITE_PROPERTIES };
//# sourceMappingURL=recordings.d.ts.map