@withpica/mcp-server 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +5 -0
- package/CHANGELOG.md +1850 -0
- package/README.md +304 -0
- package/assets/fonts/GeistSans-Light.woff2 +0 -0
- package/assets/fonts/InstrumentSerif-Italic.woff2 +0 -0
- package/assets/fonts/InstrumentSerif-Regular.woff2 +0 -0
- package/dist/apps/download.d.ts +2 -0
- package/dist/apps/download.d.ts.map +1 -0
- package/dist/apps/download.js +125 -0
- package/dist/apps/download.js.map +1 -0
- package/dist/apps/generated/shared-bundle.d.ts +5 -0
- package/dist/apps/generated/shared-bundle.d.ts.map +1 -0
- package/dist/apps/generated/shared-bundle.js +7 -0
- package/dist/apps/generated/shared-bundle.js.map +1 -0
- package/dist/apps/release.d.ts +2 -0
- package/dist/apps/release.d.ts.map +1 -0
- package/dist/apps/release.js +69 -0
- package/dist/apps/release.js.map +1 -0
- package/dist/apps/shared.d.ts +15 -0
- package/dist/apps/shared.d.ts.map +1 -0
- package/dist/apps/shared.js +480 -0
- package/dist/apps/shared.js.map +1 -0
- package/dist/apps/upload.d.ts +2 -0
- package/dist/apps/upload.d.ts.map +1 -0
- package/dist/apps/upload.js +280 -0
- package/dist/apps/upload.js.map +1 -0
- package/dist/config.d.ts +14 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +73 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +35 -0
- package/dist/index.js.map +1 -0
- package/dist/prompts/index.d.ts +86 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +645 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/resources/agent-guide.d.ts +15 -0
- package/dist/resources/agent-guide.d.ts.map +1 -0
- package/dist/resources/agent-guide.js +284 -0
- package/dist/resources/agent-guide.js.map +1 -0
- package/dist/resources/index.d.ts +66 -0
- package/dist/resources/index.d.ts.map +1 -0
- package/dist/resources/index.js +510 -0
- package/dist/resources/index.js.map +1 -0
- package/dist/resources/llms-primer.d.ts +2 -0
- package/dist/resources/llms-primer.d.ts.map +1 -0
- package/dist/resources/llms-primer.js +68 -0
- package/dist/resources/llms-primer.js.map +1 -0
- package/dist/resources/required-schemas.generated.d.ts +455 -0
- package/dist/resources/required-schemas.generated.d.ts.map +1 -0
- package/dist/resources/required-schemas.generated.js +1041 -0
- package/dist/resources/required-schemas.generated.js.map +1 -0
- package/dist/resources/required-schemas.source.d.ts +53 -0
- package/dist/resources/required-schemas.source.d.ts.map +1 -0
- package/dist/resources/required-schemas.source.js +493 -0
- package/dist/resources/required-schemas.source.js.map +1 -0
- package/dist/resources/welcome.d.ts +14 -0
- package/dist/resources/welcome.d.ts.map +1 -0
- package/dist/resources/welcome.js +26 -0
- package/dist/resources/welcome.js.map +1 -0
- package/dist/server-instructions.d.ts +60 -0
- package/dist/server-instructions.d.ts.map +1 -0
- package/dist/server-instructions.js +93 -0
- package/dist/server-instructions.js.map +1 -0
- package/dist/server.d.ts +52 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +334 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/access-simulate.d.ts +23 -0
- package/dist/tools/access-simulate.d.ts.map +1 -0
- package/dist/tools/access-simulate.js +162 -0
- package/dist/tools/access-simulate.js.map +1 -0
- package/dist/tools/agent-identity.d.ts +36 -0
- package/dist/tools/agent-identity.d.ts.map +1 -0
- package/dist/tools/agent-identity.js +274 -0
- package/dist/tools/agent-identity.js.map +1 -0
- package/dist/tools/agreement-types.d.ts +27 -0
- package/dist/tools/agreement-types.d.ts.map +1 -0
- package/dist/tools/agreement-types.js +281 -0
- package/dist/tools/agreement-types.js.map +1 -0
- package/dist/tools/agreements.d.ts +20 -0
- package/dist/tools/agreements.d.ts.map +1 -0
- package/dist/tools/agreements.js +282 -0
- package/dist/tools/agreements.js.map +1 -0
- package/dist/tools/analytics.d.ts +20 -0
- package/dist/tools/analytics.d.ts.map +1 -0
- package/dist/tools/analytics.js +130 -0
- package/dist/tools/analytics.js.map +1 -0
- package/dist/tools/app-tools.d.ts +15 -0
- package/dist/tools/app-tools.d.ts.map +1 -0
- package/dist/tools/app-tools.js +388 -0
- package/dist/tools/app-tools.js.map +1 -0
- package/dist/tools/assets.d.ts +25 -0
- package/dist/tools/assets.d.ts.map +1 -0
- package/dist/tools/assets.js +454 -0
- package/dist/tools/assets.js.map +1 -0
- package/dist/tools/audio-files.d.ts +20 -0
- package/dist/tools/audio-files.d.ts.map +1 -0
- package/dist/tools/audio-files.js +409 -0
- package/dist/tools/audio-files.js.map +1 -0
- package/dist/tools/audit.d.ts +19 -0
- package/dist/tools/audit.d.ts.map +1 -0
- package/dist/tools/audit.js +58 -0
- package/dist/tools/audit.js.map +1 -0
- package/dist/tools/auth.d.ts +22 -0
- package/dist/tools/auth.d.ts.map +1 -0
- package/dist/tools/auth.js +212 -0
- package/dist/tools/auth.js.map +1 -0
- package/dist/tools/bulk.d.ts +22 -0
- package/dist/tools/bulk.d.ts.map +1 -0
- package/dist/tools/bulk.js +164 -0
- package/dist/tools/bulk.js.map +1 -0
- package/dist/tools/calendar.d.ts +15 -0
- package/dist/tools/calendar.d.ts.map +1 -0
- package/dist/tools/calendar.js +68 -0
- package/dist/tools/calendar.js.map +1 -0
- package/dist/tools/collaborators.d.ts +21 -0
- package/dist/tools/collaborators.d.ts.map +1 -0
- package/dist/tools/collaborators.js +381 -0
- package/dist/tools/collaborators.js.map +1 -0
- package/dist/tools/comparisons.d.ts +22 -0
- package/dist/tools/comparisons.d.ts.map +1 -0
- package/dist/tools/comparisons.js +80 -0
- package/dist/tools/comparisons.js.map +1 -0
- package/dist/tools/credits.d.ts +39 -0
- package/dist/tools/credits.d.ts.map +1 -0
- package/dist/tools/credits.js +541 -0
- package/dist/tools/credits.js.map +1 -0
- package/dist/tools/custody-hints.d.ts +16 -0
- package/dist/tools/custody-hints.d.ts.map +1 -0
- package/dist/tools/custody-hints.js +27 -0
- package/dist/tools/custody-hints.js.map +1 -0
- package/dist/tools/custody.d.ts +38 -0
- package/dist/tools/custody.d.ts.map +1 -0
- package/dist/tools/custody.js +281 -0
- package/dist/tools/custody.js.map +1 -0
- package/dist/tools/dashboard.d.ts +22 -0
- package/dist/tools/dashboard.d.ts.map +1 -0
- package/dist/tools/dashboard.js +258 -0
- package/dist/tools/dashboard.js.map +1 -0
- package/dist/tools/directory.d.ts +15 -0
- package/dist/tools/directory.d.ts.map +1 -0
- package/dist/tools/directory.js +107 -0
- package/dist/tools/directory.js.map +1 -0
- package/dist/tools/discovery.d.ts +49 -0
- package/dist/tools/discovery.d.ts.map +1 -0
- package/dist/tools/discovery.js +851 -0
- package/dist/tools/discovery.js.map +1 -0
- package/dist/tools/disputes.d.ts +18 -0
- package/dist/tools/disputes.d.ts.map +1 -0
- package/dist/tools/disputes.js +62 -0
- package/dist/tools/disputes.js.map +1 -0
- package/dist/tools/documents.d.ts +15 -0
- package/dist/tools/documents.d.ts.map +1 -0
- package/dist/tools/documents.js +37 -0
- package/dist/tools/documents.js.map +1 -0
- package/dist/tools/duplicates.d.ts +25 -0
- package/dist/tools/duplicates.d.ts.map +1 -0
- package/dist/tools/duplicates.js +108 -0
- package/dist/tools/duplicates.js.map +1 -0
- package/dist/tools/enrichment.d.ts +56 -0
- package/dist/tools/enrichment.d.ts.map +1 -0
- package/dist/tools/enrichment.js +616 -0
- package/dist/tools/enrichment.js.map +1 -0
- package/dist/tools/exports.d.ts +19 -0
- package/dist/tools/exports.d.ts.map +1 -0
- package/dist/tools/exports.js +184 -0
- package/dist/tools/exports.js.map +1 -0
- package/dist/tools/feedback.d.ts +22 -0
- package/dist/tools/feedback.d.ts.map +1 -0
- package/dist/tools/feedback.js +100 -0
- package/dist/tools/feedback.js.map +1 -0
- package/dist/tools/forbidden-keywords.d.ts +62 -0
- package/dist/tools/forbidden-keywords.d.ts.map +1 -0
- package/dist/tools/forbidden-keywords.js +99 -0
- package/dist/tools/forbidden-keywords.js.map +1 -0
- package/dist/tools/gap-hints.d.ts +53 -0
- package/dist/tools/gap-hints.d.ts.map +1 -0
- package/dist/tools/gap-hints.js +245 -0
- package/dist/tools/gap-hints.js.map +1 -0
- package/dist/tools/groups.d.ts +29 -0
- package/dist/tools/groups.d.ts.map +1 -0
- package/dist/tools/groups.js +186 -0
- package/dist/tools/groups.js.map +1 -0
- package/dist/tools/import-documents.d.ts +21 -0
- package/dist/tools/import-documents.d.ts.map +1 -0
- package/dist/tools/import-documents.js +206 -0
- package/dist/tools/import-documents.js.map +1 -0
- package/dist/tools/import.d.ts +31 -0
- package/dist/tools/import.d.ts.map +1 -0
- package/dist/tools/import.js +610 -0
- package/dist/tools/import.js.map +1 -0
- package/dist/tools/index.d.ts +293 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +1182 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/integrations.d.ts +19 -0
- package/dist/tools/integrations.d.ts.map +1 -0
- package/dist/tools/integrations.js +120 -0
- package/dist/tools/integrations.js.map +1 -0
- package/dist/tools/labels.d.ts +20 -0
- package/dist/tools/labels.d.ts.map +1 -0
- package/dist/tools/labels.js +48 -0
- package/dist/tools/labels.js.map +1 -0
- package/dist/tools/licensing.d.ts +40 -0
- package/dist/tools/licensing.d.ts.map +1 -0
- package/dist/tools/licensing.js +436 -0
- package/dist/tools/licensing.js.map +1 -0
- package/dist/tools/memory.d.ts +21 -0
- package/dist/tools/memory.d.ts.map +1 -0
- package/dist/tools/memory.js +120 -0
- package/dist/tools/memory.js.map +1 -0
- package/dist/tools/metadata.d.ts +15 -0
- package/dist/tools/metadata.d.ts.map +1 -0
- package/dist/tools/metadata.js +1532 -0
- package/dist/tools/metadata.js.map +1 -0
- package/dist/tools/multimedia.d.ts +19 -0
- package/dist/tools/multimedia.d.ts.map +1 -0
- package/dist/tools/multimedia.js +371 -0
- package/dist/tools/multimedia.js.map +1 -0
- package/dist/tools/my-reported-issues.d.ts +32 -0
- package/dist/tools/my-reported-issues.d.ts.map +1 -0
- package/dist/tools/my-reported-issues.js +123 -0
- package/dist/tools/my-reported-issues.js.map +1 -0
- package/dist/tools/notes.d.ts +21 -0
- package/dist/tools/notes.d.ts.map +1 -0
- package/dist/tools/notes.js +115 -0
- package/dist/tools/notes.js.map +1 -0
- package/dist/tools/notifications.d.ts +28 -0
- package/dist/tools/notifications.d.ts.map +1 -0
- package/dist/tools/notifications.js +417 -0
- package/dist/tools/notifications.js.map +1 -0
- package/dist/tools/onboarding.d.ts +24 -0
- package/dist/tools/onboarding.d.ts.map +1 -0
- package/dist/tools/onboarding.js +81 -0
- package/dist/tools/onboarding.js.map +1 -0
- package/dist/tools/people.d.ts +254 -0
- package/dist/tools/people.d.ts.map +1 -0
- package/dist/tools/people.js +481 -0
- package/dist/tools/people.js.map +1 -0
- package/dist/tools/projects.d.ts +20 -0
- package/dist/tools/projects.d.ts.map +1 -0
- package/dist/tools/projects.js +316 -0
- package/dist/tools/projects.js.map +1 -0
- package/dist/tools/public-filter.d.ts +43 -0
- package/dist/tools/public-filter.d.ts.map +1 -0
- package/dist/tools/public-filter.js +356 -0
- package/dist/tools/public-filter.js.map +1 -0
- package/dist/tools/publishers.d.ts +30 -0
- package/dist/tools/publishers.d.ts.map +1 -0
- package/dist/tools/publishers.js +105 -0
- package/dist/tools/publishers.js.map +1 -0
- package/dist/tools/purchases.d.ts +27 -0
- package/dist/tools/purchases.d.ts.map +1 -0
- package/dist/tools/purchases.js +9 -0
- package/dist/tools/purchases.js.map +1 -0
- package/dist/tools/recording-attribution-hints.d.ts +24 -0
- package/dist/tools/recording-attribution-hints.d.ts.map +1 -0
- package/dist/tools/recording-attribution-hints.js +27 -0
- package/dist/tools/recording-attribution-hints.js.map +1 -0
- package/dist/tools/recordings.d.ts +96 -0
- package/dist/tools/recordings.d.ts.map +1 -0
- package/dist/tools/recordings.js +564 -0
- package/dist/tools/recordings.js.map +1 -0
- package/dist/tools/recovery-hints.d.ts +40 -0
- package/dist/tools/recovery-hints.d.ts.map +1 -0
- package/dist/tools/recovery-hints.js +827 -0
- package/dist/tools/recovery-hints.js.map +1 -0
- package/dist/tools/release-rich.d.ts +31 -0
- package/dist/tools/release-rich.d.ts.map +1 -0
- package/dist/tools/release-rich.js +245 -0
- package/dist/tools/release-rich.js.map +1 -0
- package/dist/tools/releases.d.ts +36 -0
- package/dist/tools/releases.d.ts.map +1 -0
- package/dist/tools/releases.js +649 -0
- package/dist/tools/releases.js.map +1 -0
- package/dist/tools/report-issue.d.ts +21 -0
- package/dist/tools/report-issue.d.ts.map +1 -0
- package/dist/tools/report-issue.js +101 -0
- package/dist/tools/report-issue.js.map +1 -0
- package/dist/tools/royalties.d.ts +23 -0
- package/dist/tools/royalties.d.ts.map +1 -0
- package/dist/tools/royalties.js +262 -0
- package/dist/tools/royalties.js.map +1 -0
- package/dist/tools/search.d.ts +30 -0
- package/dist/tools/search.d.ts.map +1 -0
- package/dist/tools/search.js +115 -0
- package/dist/tools/search.js.map +1 -0
- package/dist/tools/send.d.ts +17 -0
- package/dist/tools/send.d.ts.map +1 -0
- package/dist/tools/send.js +188 -0
- package/dist/tools/send.js.map +1 -0
- package/dist/tools/sessions.d.ts +18 -0
- package/dist/tools/sessions.d.ts.map +1 -0
- package/dist/tools/sessions.js +153 -0
- package/dist/tools/sessions.js.map +1 -0
- package/dist/tools/settings.d.ts +23 -0
- package/dist/tools/settings.d.ts.map +1 -0
- package/dist/tools/settings.js +365 -0
- package/dist/tools/settings.js.map +1 -0
- package/dist/tools/share-links.d.ts +22 -0
- package/dist/tools/share-links.d.ts.map +1 -0
- package/dist/tools/share-links.js +188 -0
- package/dist/tools/share-links.js.map +1 -0
- package/dist/tools/signup.d.ts +26 -0
- package/dist/tools/signup.d.ts.map +1 -0
- package/dist/tools/signup.js +266 -0
- package/dist/tools/signup.js.map +1 -0
- package/dist/tools/split-sheets.d.ts +25 -0
- package/dist/tools/split-sheets.d.ts.map +1 -0
- package/dist/tools/split-sheets.js +309 -0
- package/dist/tools/split-sheets.js.map +1 -0
- package/dist/tools/storage-config.d.ts +13 -0
- package/dist/tools/storage-config.d.ts.map +1 -0
- package/dist/tools/storage-config.js +245 -0
- package/dist/tools/storage-config.js.map +1 -0
- package/dist/tools/subscription.d.ts +60 -0
- package/dist/tools/subscription.d.ts.map +1 -0
- package/dist/tools/subscription.js +440 -0
- package/dist/tools/subscription.js.map +1 -0
- package/dist/tools/sync-placements.d.ts +31 -0
- package/dist/tools/sync-placements.d.ts.map +1 -0
- package/dist/tools/sync-placements.js +431 -0
- package/dist/tools/sync-placements.js.map +1 -0
- package/dist/tools/team.d.ts +22 -0
- package/dist/tools/team.d.ts.map +1 -0
- package/dist/tools/team.js +144 -0
- package/dist/tools/team.js.map +1 -0
- package/dist/tools/telegram.d.ts +21 -0
- package/dist/tools/telegram.d.ts.map +1 -0
- package/dist/tools/telegram.js +144 -0
- package/dist/tools/telegram.js.map +1 -0
- package/dist/tools/uploads.d.ts +17 -0
- package/dist/tools/uploads.d.ts.map +1 -0
- package/dist/tools/uploads.js +165 -0
- package/dist/tools/uploads.js.map +1 -0
- package/dist/tools/works.d.ts +71 -0
- package/dist/tools/works.d.ts.map +1 -0
- package/dist/tools/works.js +694 -0
- package/dist/tools/works.js.map +1 -0
- package/package.json +54 -0
- package/scripts/build-required-schemas.ts +233 -0
- package/scripts/bundle-apps.ts +61 -0
- package/scripts/refresh-schema-mirror.ts +182 -0
- package/server.json +20 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recordings Tools — MCP tools for managing recordings.
|
|
3
|
+
*
|
|
4
|
+
* ADR-149 Phase 5b: Consolidated to query+inspect pattern.
|
|
5
|
+
* - pica_recordings_query replaces pica_recordings_list + pica_recordings_by_work
|
|
6
|
+
* - pica_recordings_inspect replaces pica_recordings_get (with sections support)
|
|
7
|
+
*
|
|
8
|
+
* ADR-174 Phase 2 item 3 reconciliation: the write surface covers only
|
|
9
|
+
* real columns on `public.recordings`. Phantom fields previously
|
|
10
|
+
* accepted via `additionalProperties: true` are gone; agents that
|
|
11
|
+
* tried to set them silently dropped data on the way to Postgres.
|
|
12
|
+
*
|
|
13
|
+
* Two pre-flight surprises documented inline below:
|
|
14
|
+
*
|
|
15
|
+
* 1. `title` is a legacy alias for `recording_title`. The HTTP route
|
|
16
|
+
* translates before the service sees the payload (POST since the
|
|
17
|
+
* route was written, PATCH only since ADR-174). The tool keeps
|
|
18
|
+
* `title` as the input field name for caller ergonomics; the
|
|
19
|
+
* description marks the rename explicitly.
|
|
20
|
+
*
|
|
21
|
+
* 2. Duration lives in `duration_ms` on recordings (works carry
|
|
22
|
+
* `duration_seconds`). Agents conflating the two get a 400 with
|
|
23
|
+
* a redirect from the route allow-list.
|
|
24
|
+
*
|
|
25
|
+
* Add-candidates surfaced per the audit at
|
|
26
|
+
* docs/audits/2026-04-14-mcp-db-reconciliation.md §3:
|
|
27
|
+
* duration_ms, preview_url, album_art_url, spotify_url,
|
|
28
|
+
* spotify_track_uri, spotify_track_id, youtube_video_id, youtube_url,
|
|
29
|
+
* apple_music_url, deezer_track_id, mlc_recording_id, mlc_song_code.
|
|
30
|
+
*
|
|
31
|
+
* Most of these are populated by the enrichment cascade on streaming
|
|
32
|
+
* import; exposing them lets agents seed or override that data
|
|
33
|
+
* deliberately rather than discovering — surprised — that the field
|
|
34
|
+
* they thought they set was silently absent.
|
|
35
|
+
*
|
|
36
|
+
* `isrc_prefix` is intentionally NOT in this set — column is GENERATED
|
|
37
|
+
* ALWAYS in `public.recordings` (left(replace(isrc,'-',''),5)). Caught
|
|
38
|
+
* by ADR-198 Phase 1 contract — every write attempt 500'd with PG
|
|
39
|
+
* 428C9 "cannot insert non-DEFAULT value into generated column".
|
|
40
|
+
*/
|
|
41
|
+
import { PicaClient } from "@withpica/mcp-sdk";
|
|
42
|
+
import { ToolDefinition, ToolExecutor } from "./index.js";
|
|
43
|
+
/**
|
|
44
|
+
* Canonical write-schema for `recordings`. Every property must
|
|
45
|
+
* correspond to a real column on `public.recordings` (or be the
|
|
46
|
+
* documented `title` legacy alias) — verified against
|
|
47
|
+
* `lib/db/database.types.ts`. The HTTP route at
|
|
48
|
+
* `app/api/admin/recordings/route.ts` carries the same allow-list
|
|
49
|
+
* (plus the `recording_type` legacy alias for non-MCP callers); this
|
|
50
|
+
* list is the narrower MCP-agent contract.
|
|
51
|
+
*
|
|
52
|
+
* `version_type` is enum-restricted by the
|
|
53
|
+
* `recordings_version_type_check` CHECK constraint (ADR-166 Phase 1).
|
|
54
|
+
* Reflecting the enum here means agents see invalid values as schema
|
|
55
|
+
* errors at the tool surface, not as 500s round-tripped from Postgres.
|
|
56
|
+
*/
|
|
57
|
+
declare const RECORDING_WRITE_PROPERTIES: Record<string, Record<string, unknown>>;
|
|
58
|
+
export declare class RecordingsTools {
|
|
59
|
+
private pica;
|
|
60
|
+
constructor(pica: PicaClient);
|
|
61
|
+
/**
|
|
62
|
+
* Get all recordings tools
|
|
63
|
+
*/
|
|
64
|
+
getTools(): Array<{
|
|
65
|
+
definition: ToolDefinition;
|
|
66
|
+
executor: ToolExecutor;
|
|
67
|
+
}>;
|
|
68
|
+
/**
|
|
69
|
+
* Query recordings — list all, search by text, or filter by work
|
|
70
|
+
*/
|
|
71
|
+
private queryRecordings;
|
|
72
|
+
/**
|
|
73
|
+
* Inspect a recording — get details with optional section filtering
|
|
74
|
+
*/
|
|
75
|
+
private inspectRecording;
|
|
76
|
+
/**
|
|
77
|
+
* Create recording.
|
|
78
|
+
*
|
|
79
|
+
* `version_type` defaults to 'master' (the DB-level default and the
|
|
80
|
+
* canonical ADR-166 value). Pre-ADR-174 the executor defaulted to
|
|
81
|
+
* 'original', which is not a valid version_type — every call without
|
|
82
|
+
* an explicit type tripped the recording-type CHECK constraint and
|
|
83
|
+
* surfaced as a 500 from the service.
|
|
84
|
+
*/
|
|
85
|
+
private createRecording;
|
|
86
|
+
/**
|
|
87
|
+
* Update recording
|
|
88
|
+
*/
|
|
89
|
+
private updateRecording;
|
|
90
|
+
/**
|
|
91
|
+
* Delete recording
|
|
92
|
+
*/
|
|
93
|
+
private deleteRecording;
|
|
94
|
+
}
|
|
95
|
+
export { RECORDING_WRITE_PROPERTIES };
|
|
96
|
+
//# sourceMappingURL=recordings.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recordings.d.ts","sourceRoot":"","sources":["../../src/tools/recordings.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAEH,OAAO,EAAE,UAAU,EAAuB,MAAM,mBAAmB,CAAC;AACpE,OAAO,EAAE,cAAc,EAAE,YAAY,EAAc,MAAM,YAAY,CAAC;AAYtE;;;;;;;;;;;;;GAaG;AACH,QAAA,MAAM,0BAA0B,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA6IvE,CAAC;AAEF,qBAAa,eAAe;IAC1B,OAAO,CAAC,IAAI,CAAa;gBAEb,IAAI,EAAE,UAAU;IAI5B;;OAEG;IACH,QAAQ,IAAI,KAAK,CAAC;QAAE,UAAU,EAAE,cAAc,CAAC;QAAC,QAAQ,EAAE,YAAY,CAAA;KAAE,CAAC;IAiJzE;;OAEG;YACW,eAAe;IAkC7B;;OAEG;YACW,gBAAgB;IAiL9B;;;;;;;;OAQG;YACW,eAAe;IAgB7B;;OAEG;YACW,eAAe;IAW7B;;OAEG;YACW,eAAe;CAM9B;AAED,OAAO,EAAE,0BAA0B,EAAE,CAAC"}
|
|
@@ -0,0 +1,564 @@
|
|
|
1
|
+
// Copyright (c) 2024-2026 Withpica Ltd. All rights reserved.
|
|
2
|
+
/**
|
|
3
|
+
* Recordings Tools — MCP tools for managing recordings.
|
|
4
|
+
*
|
|
5
|
+
* ADR-149 Phase 5b: Consolidated to query+inspect pattern.
|
|
6
|
+
* - pica_recordings_query replaces pica_recordings_list + pica_recordings_by_work
|
|
7
|
+
* - pica_recordings_inspect replaces pica_recordings_get (with sections support)
|
|
8
|
+
*
|
|
9
|
+
* ADR-174 Phase 2 item 3 reconciliation: the write surface covers only
|
|
10
|
+
* real columns on `public.recordings`. Phantom fields previously
|
|
11
|
+
* accepted via `additionalProperties: true` are gone; agents that
|
|
12
|
+
* tried to set them silently dropped data on the way to Postgres.
|
|
13
|
+
*
|
|
14
|
+
* Two pre-flight surprises documented inline below:
|
|
15
|
+
*
|
|
16
|
+
* 1. `title` is a legacy alias for `recording_title`. The HTTP route
|
|
17
|
+
* translates before the service sees the payload (POST since the
|
|
18
|
+
* route was written, PATCH only since ADR-174). The tool keeps
|
|
19
|
+
* `title` as the input field name for caller ergonomics; the
|
|
20
|
+
* description marks the rename explicitly.
|
|
21
|
+
*
|
|
22
|
+
* 2. Duration lives in `duration_ms` on recordings (works carry
|
|
23
|
+
* `duration_seconds`). Agents conflating the two get a 400 with
|
|
24
|
+
* a redirect from the route allow-list.
|
|
25
|
+
*
|
|
26
|
+
* Add-candidates surfaced per the audit at
|
|
27
|
+
* docs/audits/2026-04-14-mcp-db-reconciliation.md §3:
|
|
28
|
+
* duration_ms, preview_url, album_art_url, spotify_url,
|
|
29
|
+
* spotify_track_uri, spotify_track_id, youtube_video_id, youtube_url,
|
|
30
|
+
* apple_music_url, deezer_track_id, mlc_recording_id, mlc_song_code.
|
|
31
|
+
*
|
|
32
|
+
* Most of these are populated by the enrichment cascade on streaming
|
|
33
|
+
* import; exposing them lets agents seed or override that data
|
|
34
|
+
* deliberately rather than discovering — surprised — that the field
|
|
35
|
+
* they thought they set was silently absent.
|
|
36
|
+
*
|
|
37
|
+
* `isrc_prefix` is intentionally NOT in this set — column is GENERATED
|
|
38
|
+
* ALWAYS in `public.recordings` (left(replace(isrc,'-',''),5)). Caught
|
|
39
|
+
* by ADR-198 Phase 1 contract — every write attempt 500'd with PG
|
|
40
|
+
* 428C9 "cannot insert non-DEFAULT value into generated column".
|
|
41
|
+
*/
|
|
42
|
+
import { EntityNotFoundError } from "@withpica/mcp-sdk";
|
|
43
|
+
import { formatSuccess, formatStructuredList, withBillingGate, pushSectionError, } from "@withpica/mcp-utils";
|
|
44
|
+
import { buildRecordingAttributionHints } from "./recording-attribution-hints.js";
|
|
45
|
+
/**
|
|
46
|
+
* Canonical write-schema for `recordings`. Every property must
|
|
47
|
+
* correspond to a real column on `public.recordings` (or be the
|
|
48
|
+
* documented `title` legacy alias) — verified against
|
|
49
|
+
* `lib/db/database.types.ts`. The HTTP route at
|
|
50
|
+
* `app/api/admin/recordings/route.ts` carries the same allow-list
|
|
51
|
+
* (plus the `recording_type` legacy alias for non-MCP callers); this
|
|
52
|
+
* list is the narrower MCP-agent contract.
|
|
53
|
+
*
|
|
54
|
+
* `version_type` is enum-restricted by the
|
|
55
|
+
* `recordings_version_type_check` CHECK constraint (ADR-166 Phase 1).
|
|
56
|
+
* Reflecting the enum here means agents see invalid values as schema
|
|
57
|
+
* errors at the tool surface, not as 500s round-tripped from Postgres.
|
|
58
|
+
*/
|
|
59
|
+
const RECORDING_WRITE_PROPERTIES = {
|
|
60
|
+
// Core (legacy `title` alias — translated to `recording_title` by the
|
|
61
|
+
// HTTP route; included here so agents don't have to know the rename).
|
|
62
|
+
title: {
|
|
63
|
+
type: "string",
|
|
64
|
+
description: "Recording title. Legacy alias — the HTTP route translates this to the canonical `recording_title` column. Either field is accepted; pass `title` for ergonomics.",
|
|
65
|
+
},
|
|
66
|
+
recording_title: {
|
|
67
|
+
type: "string",
|
|
68
|
+
description: "Recording title (canonical column name).",
|
|
69
|
+
},
|
|
70
|
+
artist_name: {
|
|
71
|
+
type: "string",
|
|
72
|
+
description: "Artist name for this recording",
|
|
73
|
+
},
|
|
74
|
+
version_type: {
|
|
75
|
+
type: "string",
|
|
76
|
+
description: "Recording version type. Defaults to 'master' on create. Enforced by DB CHECK constraint.",
|
|
77
|
+
enum: [
|
|
78
|
+
"master",
|
|
79
|
+
"alternate_master",
|
|
80
|
+
"music_video",
|
|
81
|
+
"lyric_video",
|
|
82
|
+
"live_performance",
|
|
83
|
+
"acoustic",
|
|
84
|
+
"remix",
|
|
85
|
+
"cover",
|
|
86
|
+
"alternate",
|
|
87
|
+
],
|
|
88
|
+
},
|
|
89
|
+
work_id: {
|
|
90
|
+
type: "string",
|
|
91
|
+
description: "ID of the work this recording is of (FK to works). A bad UUID returns 400 RECORDING_FK_VIOLATION — call pica_works_query first to resolve a title to an ID.",
|
|
92
|
+
},
|
|
93
|
+
ownership_percentage: {
|
|
94
|
+
type: "number",
|
|
95
|
+
description: "Ownership percentage (0–100, validated server-side)",
|
|
96
|
+
},
|
|
97
|
+
labels: {
|
|
98
|
+
type: "string",
|
|
99
|
+
description: "Record label(s) as freetext. Legacy column — the structured home is `global_recording_labels` (not yet exposed via MCP).",
|
|
100
|
+
},
|
|
101
|
+
// Identifiers
|
|
102
|
+
isrc: {
|
|
103
|
+
type: "string",
|
|
104
|
+
description: "International Standard Recording Code (e.g. GBUM71804829). Triggers global_recordings linking on create/update.",
|
|
105
|
+
},
|
|
106
|
+
isrc_source: {
|
|
107
|
+
type: "string",
|
|
108
|
+
description: "Where the ISRC came from (e.g. 'spotify', 'mlc', 'manual', 'ppl', 'apple_music', 'youtube_music', 'soundcloud', 'disco').",
|
|
109
|
+
},
|
|
110
|
+
musicbrainz_recording_id: {
|
|
111
|
+
type: "string",
|
|
112
|
+
description: "MusicBrainz recording UUID",
|
|
113
|
+
},
|
|
114
|
+
mlc_recording_id: {
|
|
115
|
+
type: "string",
|
|
116
|
+
description: "MLC recording ID",
|
|
117
|
+
},
|
|
118
|
+
mlc_song_code: {
|
|
119
|
+
type: "string",
|
|
120
|
+
description: "MLC song code (links recording to its work registration)",
|
|
121
|
+
},
|
|
122
|
+
// Master metadata (ADR-165 Phase 2 — populated by enrichment but
|
|
123
|
+
// settable from MCP for manual seeding/overrides).
|
|
124
|
+
duration_ms: {
|
|
125
|
+
type: "number",
|
|
126
|
+
description: "Duration in milliseconds. Note: `works` carries duration_seconds; `recordings` carries duration_ms.",
|
|
127
|
+
},
|
|
128
|
+
preview_url: {
|
|
129
|
+
type: "string",
|
|
130
|
+
description: "30-second preview URL (Spotify or equivalent)",
|
|
131
|
+
},
|
|
132
|
+
album_art_url: {
|
|
133
|
+
type: "string",
|
|
134
|
+
description: "Denormalised album artwork URL. Source of truth is releases.artwork_url; duplicated here for catalogue-card hot paths.",
|
|
135
|
+
},
|
|
136
|
+
spotify_url: {
|
|
137
|
+
type: "string",
|
|
138
|
+
description: "Per-track Spotify URL (e.g. https://open.spotify.com/track/...). Distinct from releases.spotify_url which is the album-level URL.",
|
|
139
|
+
},
|
|
140
|
+
spotify_track_uri: {
|
|
141
|
+
type: "string",
|
|
142
|
+
description: "spotify:track:{id} URI for the master. Distinct from release_tracks.spotify_track_uri (per-track-in-release).",
|
|
143
|
+
},
|
|
144
|
+
spotify_track_id: {
|
|
145
|
+
type: "string",
|
|
146
|
+
description: "Spotify external track id (for dedup + cascade matching)",
|
|
147
|
+
},
|
|
148
|
+
youtube_video_id: {
|
|
149
|
+
type: "string",
|
|
150
|
+
description: "YouTube video id. Non-master version types (music_video, lyric_video, etc.) get their own video id.",
|
|
151
|
+
},
|
|
152
|
+
youtube_url: {
|
|
153
|
+
type: "string",
|
|
154
|
+
description: "YouTube watch URL",
|
|
155
|
+
},
|
|
156
|
+
apple_music_url: {
|
|
157
|
+
type: "string",
|
|
158
|
+
description: "Apple Music track URL",
|
|
159
|
+
},
|
|
160
|
+
deezer_track_id: {
|
|
161
|
+
type: "string",
|
|
162
|
+
description: "Deezer track id",
|
|
163
|
+
},
|
|
164
|
+
// PPL
|
|
165
|
+
ppl_registered: {
|
|
166
|
+
type: "boolean",
|
|
167
|
+
description: "Whether registered with PPL",
|
|
168
|
+
},
|
|
169
|
+
ppl_recording_id: {
|
|
170
|
+
type: "string",
|
|
171
|
+
description: "PPL recording ID",
|
|
172
|
+
},
|
|
173
|
+
// Declared jsonb extension points
|
|
174
|
+
ai_disclosure: {
|
|
175
|
+
type: "object",
|
|
176
|
+
description: "AI disclosure object (UK AI Copyright compliance). Fields: composition, performance, production, mastering ('none'|'assisted'|'generated'), tools_used (string[]), disclosure_date, disclosed_by.",
|
|
177
|
+
},
|
|
178
|
+
provenance_attestation: {
|
|
179
|
+
type: "object",
|
|
180
|
+
description: "Human creation proof. Fields: human_created (bool), attested_by, attested_at, attestation_method, supporting_evidence, ip_address, signature_hash, certificate_id.",
|
|
181
|
+
},
|
|
182
|
+
training_rights: {
|
|
183
|
+
type: "object",
|
|
184
|
+
description: "B2B ML licensing compliance object. Fields: available_for_training (bool), training_terms, minimum_fee, excluded_uses, permitted_uses, geographic_restrictions, requires_attribution, requires_notification, license_duration, updated_at, updated_by.",
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
export class RecordingsTools {
|
|
188
|
+
pica;
|
|
189
|
+
constructor(pica) {
|
|
190
|
+
this.pica = pica;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Get all recordings tools
|
|
194
|
+
*/
|
|
195
|
+
getTools() {
|
|
196
|
+
return [
|
|
197
|
+
{
|
|
198
|
+
definition: {
|
|
199
|
+
name: "pica_recordings_query",
|
|
200
|
+
description: "Find recordings — list all, search by query, or filter by work. " +
|
|
201
|
+
"→ then: pica_recordings_inspect (details for a specific recording)",
|
|
202
|
+
workflows: "infrastructure",
|
|
203
|
+
inputSchema: {
|
|
204
|
+
type: "object",
|
|
205
|
+
properties: {
|
|
206
|
+
query: {
|
|
207
|
+
type: "string",
|
|
208
|
+
description: "Search by title, ISRC, or artist name",
|
|
209
|
+
},
|
|
210
|
+
work_id: {
|
|
211
|
+
type: "string",
|
|
212
|
+
description: "Filter recordings by work ID (replaces pica_recordings_by_work)",
|
|
213
|
+
},
|
|
214
|
+
limit: {
|
|
215
|
+
type: "number",
|
|
216
|
+
description: "Max results (default: 50)",
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
executor: this.queryRecordings.bind(this),
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
definition: {
|
|
225
|
+
name: "pica_recordings_inspect",
|
|
226
|
+
description: "Get details for a specific recording. Omit sections for everything. " +
|
|
227
|
+
"Sections: basic (recording row + ISRC prefix intel), credits (work-side credits via parent work), recording_credits (first-party operator-authored production credits on this recording), audio (audio files via parent work), production_assets, releases. " +
|
|
228
|
+
"Round-trip a pica_recording_credits_update write via sections:['recording_credits']. " +
|
|
229
|
+
"→ then: pica_audio_query (audio files), pica_credits_update (fix work-side credits), pica_recording_credits_update (add a recording-side credit)",
|
|
230
|
+
workflows: "infrastructure",
|
|
231
|
+
inputSchema: {
|
|
232
|
+
type: "object",
|
|
233
|
+
properties: {
|
|
234
|
+
id: {
|
|
235
|
+
type: "string",
|
|
236
|
+
description: "Recording ID",
|
|
237
|
+
},
|
|
238
|
+
sections: {
|
|
239
|
+
type: "array",
|
|
240
|
+
items: {
|
|
241
|
+
type: "string",
|
|
242
|
+
enum: [
|
|
243
|
+
"basic",
|
|
244
|
+
"credits",
|
|
245
|
+
"recording_credits",
|
|
246
|
+
"audio",
|
|
247
|
+
"production_assets",
|
|
248
|
+
"releases",
|
|
249
|
+
],
|
|
250
|
+
},
|
|
251
|
+
description: 'Which data sections to include. Omit for all sections. Use ["basic"] for lightweight lookup. Use ["recording_credits"] to round-trip a pica_recording_credits_update write.',
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
required: ["id"],
|
|
255
|
+
},
|
|
256
|
+
},
|
|
257
|
+
executor: this.inspectRecording.bind(this),
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
definition: {
|
|
261
|
+
name: "pica_recordings_create",
|
|
262
|
+
description: "Create a new recording. IMPORTANT: call pica_recordings_query with work_id first to check for existing recordings on the same work — duplicate masters cause ISRC conflicts. " +
|
|
263
|
+
"Album metadata (album_name, album_release_date, upc, track_number) lives on pica_releases_create/update, not here. " +
|
|
264
|
+
"Label-as-org belongs on the release (pica_releases_*.label_organization_id via pica_labels_query); the legacy `labels` freetext column is kept for backwards compat. " +
|
|
265
|
+
"ISWCs identify works, not recordings — set them via pica_works_create/update. " +
|
|
266
|
+
"Duration is in milliseconds (`duration_ms`); the `duration_seconds` works convention is rejected with a 400. " +
|
|
267
|
+
"After several mutations, surface a pica_dashboard_briefing to the user. " +
|
|
268
|
+
"→ before: pica_recordings_query (check duplicates) → then: pica_audio_presigned_upload (upload audio), pica_audio_analyze (extract features)",
|
|
269
|
+
workflows: ["recording-required"],
|
|
270
|
+
inputSchema: {
|
|
271
|
+
type: "object",
|
|
272
|
+
properties: RECORDING_WRITE_PROPERTIES,
|
|
273
|
+
required: ["title", "artist_name", "version_type"],
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
executor: withBillingGate(this.pica, "recording creation", this.createRecording.bind(this)),
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
definition: {
|
|
280
|
+
name: "pica_recordings_update",
|
|
281
|
+
description: "Update an existing recording. Only real `recordings` columns are accepted. " +
|
|
282
|
+
"Album metadata (album_name, upc, track_number) belongs on pica_releases_create/update; ISWC belongs on pica_works_create/update; duration is in `duration_ms` (not `duration_seconds`). " +
|
|
283
|
+
"Bad keys return a 400 with a redirect message naming the correct tool. " +
|
|
284
|
+
"→ then: pica_recordings_inspect (verify changes persisted)",
|
|
285
|
+
workflows: ["recording-required"],
|
|
286
|
+
inputSchema: {
|
|
287
|
+
type: "object",
|
|
288
|
+
properties: {
|
|
289
|
+
id: {
|
|
290
|
+
type: "string",
|
|
291
|
+
description: "Recording ID (required)",
|
|
292
|
+
},
|
|
293
|
+
...RECORDING_WRITE_PROPERTIES,
|
|
294
|
+
},
|
|
295
|
+
required: ["id"],
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
executor: withBillingGate(this.pica, "recording update", this.updateRecording.bind(this)),
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
definition: {
|
|
302
|
+
name: "pica_recordings_delete",
|
|
303
|
+
description: "Delete a recording (destructive — unlinks from release tracks and work).",
|
|
304
|
+
workflows: "infrastructure",
|
|
305
|
+
inputSchema: {
|
|
306
|
+
type: "object",
|
|
307
|
+
properties: {
|
|
308
|
+
id: {
|
|
309
|
+
type: "string",
|
|
310
|
+
description: "Recording ID",
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
required: ["id"],
|
|
314
|
+
},
|
|
315
|
+
},
|
|
316
|
+
executor: withBillingGate(this.pica, "recording deletion", this.deleteRecording.bind(this)),
|
|
317
|
+
},
|
|
318
|
+
];
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Query recordings — list all, search by text, or filter by work
|
|
322
|
+
*/
|
|
323
|
+
async queryRecordings(args) {
|
|
324
|
+
const limit = args.limit || 50;
|
|
325
|
+
const workId = args.work_id;
|
|
326
|
+
const query = args.query;
|
|
327
|
+
// Filter by work_id — delegate to getByWork
|
|
328
|
+
if (workId) {
|
|
329
|
+
const recordings = await this.pica.recordings.getByWork(workId);
|
|
330
|
+
return formatStructuredList(recordings, "recording", {
|
|
331
|
+
work_id: workId,
|
|
332
|
+
total: recordings.length,
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
// Search by text query — delegate to search method (client-side filtering)
|
|
336
|
+
if (query) {
|
|
337
|
+
const result = await this.pica.recordings.search({ query, limit });
|
|
338
|
+
return formatStructuredList(result.data, "recording", {
|
|
339
|
+
query,
|
|
340
|
+
total: result.total,
|
|
341
|
+
hasMore: result.hasMore,
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
// Default: list all
|
|
345
|
+
const recordings = await this.pica.recordings.list({ limit });
|
|
346
|
+
return formatStructuredList(recordings, "recording", {
|
|
347
|
+
total: recordings.length,
|
|
348
|
+
limit,
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Inspect a recording — get details with optional section filtering
|
|
353
|
+
*/
|
|
354
|
+
async inspectRecording(args) {
|
|
355
|
+
const id = args.id;
|
|
356
|
+
const sections = args.sections;
|
|
357
|
+
const allSections = !sections || sections.length === 0;
|
|
358
|
+
const result = {};
|
|
359
|
+
// ADR-224 — see works.ts for the doctrine. recordings_inspect's
|
|
360
|
+
// pre-conversion pattern was the sibling-field shape (`result.X = []`
|
|
361
|
+
// + `result.X_error = msg`), the medium-severity variant of the four
|
|
362
|
+
// ADR-224 found. Flag-off mode preserves the sibling-field shape;
|
|
363
|
+
// flag-on drops the sibling fields and routes failures through
|
|
364
|
+
// errors[]. Primary-entity EntityNotFoundError propagation (PR #110,
|
|
365
|
+
// 2026-04-26) is preserved unchanged in both modes — that's a hard
|
|
366
|
+
// error signal that doesn't go through the section-error path.
|
|
367
|
+
const useStructured = process.env.ADR_224_STRUCTURED_ERRORS === "1";
|
|
368
|
+
const errors = [];
|
|
369
|
+
const TOOL_NAME = "pica_recordings_inspect";
|
|
370
|
+
// Basic section: recording details + ISRC prefix intel
|
|
371
|
+
if (allSections || sections?.includes("basic")) {
|
|
372
|
+
try {
|
|
373
|
+
const recording = await this.pica.recordings.get(id);
|
|
374
|
+
result.recording = recording;
|
|
375
|
+
// Enrich with ISRC prefix intelligence if ISRC is present
|
|
376
|
+
if (recording.isrc) {
|
|
377
|
+
const prefixIntel = await this.pica.recordings.prefixLookup(recording.isrc);
|
|
378
|
+
if (prefixIntel) {
|
|
379
|
+
result.prefix_intel = prefixIntel;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
catch (error) {
|
|
384
|
+
// 404 on the primary entity is a hard error — propagate so the
|
|
385
|
+
// agent sees an explicit not-found signal instead of a 200 with
|
|
386
|
+
// a partial-empty payload (ADR-198 verification 2026-04-26 —
|
|
387
|
+
// pica_recordings_inspect was hallucinating into the void on
|
|
388
|
+
// missing route handlers / missing recordings). This propagation
|
|
389
|
+
// predates and is independent of ADR-224 — kept unchanged.
|
|
390
|
+
if (error instanceof EntityNotFoundError) {
|
|
391
|
+
throw error;
|
|
392
|
+
}
|
|
393
|
+
if (!useStructured) {
|
|
394
|
+
result.recording_error =
|
|
395
|
+
error instanceof Error
|
|
396
|
+
? error.message
|
|
397
|
+
: "Failed to fetch recording";
|
|
398
|
+
}
|
|
399
|
+
pushSectionError(errors, TOOL_NAME, "basic", error, id);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
// Credits section: work credits (via recording's parent work)
|
|
403
|
+
if (allSections || sections?.includes("credits")) {
|
|
404
|
+
try {
|
|
405
|
+
// We need the recording to get its work_id
|
|
406
|
+
const recording = result.recording ||
|
|
407
|
+
(await this.pica.recordings.get(id));
|
|
408
|
+
const workId = recording.work_id;
|
|
409
|
+
if (workId) {
|
|
410
|
+
const credits = await this.pica.credits.listForWork(workId);
|
|
411
|
+
result.credits = credits;
|
|
412
|
+
}
|
|
413
|
+
else {
|
|
414
|
+
result.credits = [];
|
|
415
|
+
result.credits_note = "No parent work linked — no credits available";
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
catch (error) {
|
|
419
|
+
if (!useStructured) {
|
|
420
|
+
result.credits_error =
|
|
421
|
+
error instanceof Error ? error.message : "Failed to fetch credits";
|
|
422
|
+
}
|
|
423
|
+
else {
|
|
424
|
+
result.credits = null;
|
|
425
|
+
}
|
|
426
|
+
pushSectionError(errors, TOOL_NAME, "credits", error, id);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
// ADR-213 Primitive A.1 — additive recording_credits section.
|
|
430
|
+
// Distinct from the `credits` section above (which reads work-side
|
|
431
|
+
// credits via the parent work). This reads first-party operator-
|
|
432
|
+
// authored production credits directly from the recording_credits
|
|
433
|
+
// table. Closes the round-trip path for pica_recording_credits_update
|
|
434
|
+
// per ADR-198. Trade-secret separation: this never touches the
|
|
435
|
+
// enrichment-cascade-fed `credits` table (per ip-protection.md).
|
|
436
|
+
if (allSections || sections?.includes("recording_credits")) {
|
|
437
|
+
try {
|
|
438
|
+
result.recording_credits = await this.pica.recordingCredits.list(id);
|
|
439
|
+
}
|
|
440
|
+
catch (error) {
|
|
441
|
+
if (!useStructured) {
|
|
442
|
+
result.recording_credits = [];
|
|
443
|
+
result.recording_credits_error =
|
|
444
|
+
error instanceof Error
|
|
445
|
+
? error.message
|
|
446
|
+
: "Failed to fetch recording_credits";
|
|
447
|
+
}
|
|
448
|
+
else {
|
|
449
|
+
result.recording_credits = null;
|
|
450
|
+
}
|
|
451
|
+
pushSectionError(errors, TOOL_NAME, "recording_credits", error, id);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
// Audio section: audio files linked to the parent work
|
|
455
|
+
if (allSections || sections?.includes("audio")) {
|
|
456
|
+
try {
|
|
457
|
+
const recording = result.recording ||
|
|
458
|
+
(await this.pica.recordings.get(id));
|
|
459
|
+
const workId = recording.work_id;
|
|
460
|
+
if (workId) {
|
|
461
|
+
const audioFiles = await this.pica.audioFiles.list({
|
|
462
|
+
work_id: workId,
|
|
463
|
+
});
|
|
464
|
+
result.audio_files = audioFiles;
|
|
465
|
+
}
|
|
466
|
+
else {
|
|
467
|
+
result.audio_files = [];
|
|
468
|
+
result.audio_note =
|
|
469
|
+
"No parent work linked — no audio files available";
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
catch (error) {
|
|
473
|
+
if (!useStructured) {
|
|
474
|
+
result.audio_error =
|
|
475
|
+
error instanceof Error
|
|
476
|
+
? error.message
|
|
477
|
+
: "Failed to fetch audio files";
|
|
478
|
+
}
|
|
479
|
+
else {
|
|
480
|
+
result.audio_files = null;
|
|
481
|
+
}
|
|
482
|
+
pushSectionError(errors, TOOL_NAME, "audio", error, id);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
// ADR-156 op #3 Session B Task 5 — surface linked physical assets
|
|
486
|
+
// so the inspect tool is discoverable from the recording side.
|
|
487
|
+
if (allSections || sections?.includes("production_assets")) {
|
|
488
|
+
try {
|
|
489
|
+
result.production_assets =
|
|
490
|
+
await this.pica.recordings.listProductionAssets(id);
|
|
491
|
+
}
|
|
492
|
+
catch (error) {
|
|
493
|
+
if (!useStructured) {
|
|
494
|
+
result.production_assets = [];
|
|
495
|
+
result.production_assets_error =
|
|
496
|
+
error instanceof Error
|
|
497
|
+
? error.message
|
|
498
|
+
: "Failed to fetch production assets";
|
|
499
|
+
}
|
|
500
|
+
else {
|
|
501
|
+
result.production_assets = null;
|
|
502
|
+
}
|
|
503
|
+
pushSectionError(errors, TOOL_NAME, "production_assets", error, id);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
// ADR-173 Decision 2 — every release this recording appears on,
|
|
507
|
+
// with its track position. Answers "which albums is this master
|
|
508
|
+
// on?" in one call.
|
|
509
|
+
if (allSections || sections?.includes("releases")) {
|
|
510
|
+
try {
|
|
511
|
+
result.releases = await this.pica.recordings.listReleases(id);
|
|
512
|
+
}
|
|
513
|
+
catch (error) {
|
|
514
|
+
if (!useStructured) {
|
|
515
|
+
result.releases = [];
|
|
516
|
+
result.releases_error =
|
|
517
|
+
error instanceof Error ? error.message : "Failed to fetch releases";
|
|
518
|
+
}
|
|
519
|
+
else {
|
|
520
|
+
result.releases = null;
|
|
521
|
+
}
|
|
522
|
+
pushSectionError(errors, TOOL_NAME, "releases", error, id);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
const enriched = useStructured ? { ...result, errors } : result;
|
|
526
|
+
return formatSuccess("Recording details", enriched);
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* Create recording.
|
|
530
|
+
*
|
|
531
|
+
* `version_type` defaults to 'master' (the DB-level default and the
|
|
532
|
+
* canonical ADR-166 value). Pre-ADR-174 the executor defaulted to
|
|
533
|
+
* 'original', which is not a valid version_type — every call without
|
|
534
|
+
* an explicit type tripped the recording-type CHECK constraint and
|
|
535
|
+
* surfaced as a 500 from the service.
|
|
536
|
+
*/
|
|
537
|
+
async createRecording(args) {
|
|
538
|
+
if (!args.version_type)
|
|
539
|
+
args.version_type = "master";
|
|
540
|
+
const recording = await this.pica.recordings.create(args);
|
|
541
|
+
// ADR-213 Primitive A — append attribution hints so every new recording
|
|
542
|
+
// prompts the ownership / credits / master-claim conversation by default.
|
|
543
|
+
// Mirror of buildCustodyHints (works.ts:723) for the recordings side.
|
|
544
|
+
const hints = buildRecordingAttributionHints(recording);
|
|
545
|
+
return formatSuccess("Recording created successfully", recording, hints);
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* Update recording
|
|
549
|
+
*/
|
|
550
|
+
async updateRecording(args) {
|
|
551
|
+
const { id, ...updates } = args;
|
|
552
|
+
const recording = await this.pica.recordings.update(id, updates);
|
|
553
|
+
return formatSuccess("Recording updated successfully", recording);
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* Delete recording
|
|
557
|
+
*/
|
|
558
|
+
async deleteRecording(args) {
|
|
559
|
+
await this.pica.recordings.delete(args.id);
|
|
560
|
+
return formatSuccess(`Recording ${args.id} deleted successfully`);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
export { RECORDING_WRITE_PROPERTIES };
|
|
564
|
+
//# sourceMappingURL=recordings.js.map
|