@withpica/mcp-server 2.98.0 → 2.100.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/CHANGELOG.md +254 -0
- package/README.md +8 -8
- package/dist/lib/changelog.generated.d.ts +2 -2
- package/dist/lib/changelog.generated.d.ts.map +1 -1
- package/dist/lib/changelog.generated.js +2 -2
- package/dist/lib/changelog.generated.js.map +1 -1
- package/dist/prompts/creator-question-atlas.d.ts.map +1 -1
- package/dist/prompts/creator-question-atlas.js +25 -2
- package/dist/prompts/creator-question-atlas.js.map +1 -1
- package/dist/prompts/index.js +2 -2
- package/dist/resources/agent-guide.d.ts.map +1 -1
- package/dist/resources/agent-guide.js +38 -7
- package/dist/resources/agent-guide.js.map +1 -1
- package/dist/resources/index.d.ts.map +1 -1
- package/dist/resources/index.js +0 -3
- package/dist/resources/index.js.map +1 -1
- package/dist/resources/required-schemas.generated.d.ts +17 -11
- package/dist/resources/required-schemas.generated.d.ts.map +1 -1
- package/dist/resources/required-schemas.generated.js +30 -15
- package/dist/resources/required-schemas.generated.js.map +1 -1
- package/dist/resources/required-schemas.source.d.ts.map +1 -1
- package/dist/resources/required-schemas.source.js +15 -11
- package/dist/resources/required-schemas.source.js.map +1 -1
- package/dist/server-instructions.d.ts +2 -2
- package/dist/server-instructions.d.ts.map +1 -1
- package/dist/server-instructions.js +2 -2
- package/dist/server-instructions.js.map +1 -1
- package/dist/skills/skills.generated.js +8 -8
- package/dist/skills/skills.generated.js.map +1 -1
- package/dist/tools/api-error-body.d.ts +16 -4
- package/dist/tools/api-error-body.d.ts.map +1 -1
- package/dist/tools/api-error-body.js +40 -1
- package/dist/tools/api-error-body.js.map +1 -1
- package/dist/tools/approvals.d.ts.map +1 -1
- package/dist/tools/approvals.js +12 -1
- package/dist/tools/approvals.js.map +1 -1
- package/dist/tools/assets.d.ts.map +1 -1
- package/dist/tools/assets.js +15 -3
- package/dist/tools/assets.js.map +1 -1
- package/dist/tools/collaborators.d.ts.map +1 -1
- package/dist/tools/collaborators.js +27 -3
- package/dist/tools/collaborators.js.map +1 -1
- package/dist/tools/credits.d.ts +4 -1
- package/dist/tools/credits.d.ts.map +1 -1
- package/dist/tools/credits.js +204 -137
- package/dist/tools/credits.js.map +1 -1
- package/dist/tools/deprecations.d.ts +65 -7
- package/dist/tools/deprecations.d.ts.map +1 -1
- package/dist/tools/deprecations.js +96 -96
- package/dist/tools/deprecations.js.map +1 -1
- package/dist/tools/discovery.d.ts +28 -2
- package/dist/tools/discovery.d.ts.map +1 -1
- package/dist/tools/discovery.js +74 -29
- package/dist/tools/discovery.js.map +1 -1
- package/dist/tools/documents.d.ts +2 -2
- package/dist/tools/documents.d.ts.map +1 -1
- package/dist/tools/documents.js +8 -92
- package/dist/tools/documents.js.map +1 -1
- package/dist/tools/duplicates.d.ts +15 -0
- package/dist/tools/duplicates.d.ts.map +1 -1
- package/dist/tools/duplicates.js +285 -1
- package/dist/tools/duplicates.js.map +1 -1
- package/dist/tools/import-documents.d.ts +1 -0
- package/dist/tools/import-documents.d.ts.map +1 -1
- package/dist/tools/import-documents.js +67 -0
- package/dist/tools/import-documents.js.map +1 -1
- package/dist/tools/import.d.ts.map +1 -1
- package/dist/tools/import.js +14 -0
- package/dist/tools/import.js.map +1 -1
- package/dist/tools/index.d.ts +2 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +38 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/integrity.d.ts.map +1 -1
- package/dist/tools/integrity.js +65 -8
- package/dist/tools/integrity.js.map +1 -1
- package/dist/tools/memory.d.ts.map +1 -1
- package/dist/tools/memory.js +24 -6
- package/dist/tools/memory.js.map +1 -1
- package/dist/tools/metadata.d.ts.map +1 -1
- package/dist/tools/metadata.js +83 -121
- package/dist/tools/metadata.js.map +1 -1
- package/dist/tools/notes.d.ts +3 -4
- package/dist/tools/notes.d.ts.map +1 -1
- package/dist/tools/notes.js +3 -39
- package/dist/tools/notes.js.map +1 -1
- package/dist/tools/people.d.ts +54 -22
- package/dist/tools/people.d.ts.map +1 -1
- package/dist/tools/people.js +236 -27
- package/dist/tools/people.js.map +1 -1
- package/dist/tools/profile-claims.d.ts +31 -0
- package/dist/tools/profile-claims.d.ts.map +1 -0
- package/dist/tools/profile-claims.js +188 -0
- package/dist/tools/profile-claims.js.map +1 -0
- package/dist/tools/projects.d.ts +3 -4
- package/dist/tools/projects.d.ts.map +1 -1
- package/dist/tools/projects.js +3 -27
- package/dist/tools/projects.js.map +1 -1
- package/dist/tools/public-filter.d.ts.map +1 -1
- package/dist/tools/public-filter.js +4 -7
- package/dist/tools/public-filter.js.map +1 -1
- package/dist/tools/recovery-hints.d.ts.map +1 -1
- package/dist/tools/recovery-hints.js +164 -30
- package/dist/tools/recovery-hints.js.map +1 -1
- package/dist/tools/releases.d.ts +2 -4
- package/dist/tools/releases.d.ts.map +1 -1
- package/dist/tools/releases.js +6 -210
- package/dist/tools/releases.js.map +1 -1
- package/dist/tools/rights.d.ts +9 -0
- package/dist/tools/rights.d.ts.map +1 -1
- package/dist/tools/rights.js +199 -3
- package/dist/tools/rights.js.map +1 -1
- package/dist/tools/royalties.d.ts +5 -5
- package/dist/tools/royalties.d.ts.map +1 -1
- package/dist/tools/royalties.js +2 -53
- package/dist/tools/royalties.js.map +1 -1
- package/dist/tools/shows.d.ts.map +1 -1
- package/dist/tools/shows.js +25 -2
- package/dist/tools/shows.js.map +1 -1
- package/dist/tools/split-sheets.d.ts +8 -0
- package/dist/tools/split-sheets.d.ts.map +1 -1
- package/dist/tools/split-sheets.js +178 -1
- package/dist/tools/split-sheets.js.map +1 -1
- package/dist/tools/statements.js +1 -1
- package/dist/tools/statements.js.map +1 -1
- package/dist/tools/version-guard.d.ts +16 -4
- package/dist/tools/version-guard.d.ts.map +1 -1
- package/dist/tools/version-guard.js +22 -8
- package/dist/tools/version-guard.js.map +1 -1
- package/dist/tools/works.d.ts.map +1 -1
- package/dist/tools/works.js +24 -11
- package/dist/tools/works.js.map +1 -1
- package/package.json +2 -2
- package/server.json +2 -2
package/dist/tools/credits.js
CHANGED
|
@@ -73,6 +73,55 @@ function recordingCreditExists(err) {
|
|
|
73
73
|
return typedError("RECORDING_CREDIT_EXISTS", body.message ??
|
|
74
74
|
`This person is already credited in this role on this recording${creditId ? ` (credit ${creditId})` : ""} with different details. Nothing was changed — one person holds one credit per role. Edit that credit with pica_credit_update (e.g. instrument: "drums, guitar") instead of adding a second.`, creditId ? { existing_credit_id: creditId } : undefined);
|
|
75
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* The columns that record a master credit's attestation. Mirrors
|
|
78
|
+
* RECORDING_CREDIT_ATTESTATION_FIELDS in
|
|
79
|
+
* lib/constants/recording-credit-attestation.ts, which
|
|
80
|
+
* POST /api/admin/recordings/[id]/credits and the bulk route refuse with the
|
|
81
|
+
* same code; this package cannot import the app.
|
|
82
|
+
*/
|
|
83
|
+
const RECORDING_ATTESTATION_FIELDS = [
|
|
84
|
+
"attestation_status",
|
|
85
|
+
"attested_at",
|
|
86
|
+
"attested_by",
|
|
87
|
+
"attestation_source_type",
|
|
88
|
+
"attestation_source_id",
|
|
89
|
+
];
|
|
90
|
+
/**
|
|
91
|
+
* Refuse a pica_recording_credits_update call that carries any attestation
|
|
92
|
+
* column — at the top level (single form) or on any credits[] entry (batch
|
|
93
|
+
* form). Presence is what counts: "pending" is refused too, so no caller is
|
|
94
|
+
* left believing it controls the field. Returns null when the call is clean.
|
|
95
|
+
*/
|
|
96
|
+
function refuseRecordingAttestation(args) {
|
|
97
|
+
const has = (o) => {
|
|
98
|
+
if (!o || typeof o !== "object")
|
|
99
|
+
return null;
|
|
100
|
+
for (const f of RECORDING_ATTESTATION_FIELDS) {
|
|
101
|
+
if (Object.hasOwn(o, f))
|
|
102
|
+
return f;
|
|
103
|
+
}
|
|
104
|
+
return null;
|
|
105
|
+
};
|
|
106
|
+
const guidance = "Every credit starts as pending and only the credited person can " +
|
|
107
|
+
"confirm it. Remove it and retry (put any source evidence in notes), " +
|
|
108
|
+
"then ask them to confirm with pica_collaborators_invite (recording_id) " +
|
|
109
|
+
"or pica_credits_send_for_attestation on the recording's song.";
|
|
110
|
+
const topField = has(args);
|
|
111
|
+
if (topField) {
|
|
112
|
+
return typedError("ATTESTATION_NOT_SETTABLE", `${topField} can't be set by this tool — nothing was saved. ${guidance}`, { field: topField });
|
|
113
|
+
}
|
|
114
|
+
if (Array.isArray(args.credits)) {
|
|
115
|
+
for (let i = 0; i < args.credits.length; i++) {
|
|
116
|
+
const field = has(args.credits[i]);
|
|
117
|
+
if (field) {
|
|
118
|
+
return typedError("ATTESTATION_NOT_SETTABLE", `credits[${i}] carries ${field}, which this tool can't set — ` +
|
|
119
|
+
`nothing was saved. ${guidance}`, { field, index: i });
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
76
125
|
export class CreditsTools {
|
|
77
126
|
pica;
|
|
78
127
|
constructor(pica) {
|
|
@@ -154,8 +203,10 @@ export class CreditsTools {
|
|
|
154
203
|
"'publisher' (their publishing share is that entry's split) — " +
|
|
155
204
|
"there is no separate publisher field. is_primary marks the " +
|
|
156
205
|
"primary attribution and applies to composition credits only " +
|
|
157
|
-
"(recording credits have no primary flag). You can also set notes " +
|
|
158
|
-
"
|
|
206
|
+
"(recording credits have no primary flag). You can also set notes. " +
|
|
207
|
+
"Attestation is not settable here: every credit this saves starts " +
|
|
208
|
+
"as pending, and only the credited person confirms it — send it " +
|
|
209
|
+
"with pica_credits_send_for_attestation. Once it's saved, it's worth asking the " +
|
|
159
210
|
"user: send these to the collaborators to verify, or generate a " +
|
|
160
211
|
"split sheet for sign-off? Is anyone here on a work-for-hire or " +
|
|
161
212
|
"producer agreement (a producer takes points off the top of " +
|
|
@@ -221,11 +272,6 @@ export class CreditsTools {
|
|
|
221
272
|
type: "string",
|
|
222
273
|
description: "Free-text notes about this credit (e.g., recording-session context, contribution specifics).",
|
|
223
274
|
},
|
|
224
|
-
attestation_status: {
|
|
225
|
-
type: "string",
|
|
226
|
-
enum: ["pending", "attested", "disputed", "declined"],
|
|
227
|
-
description: "Attestation status. Default: pending. Service-managed fields attested_at / attested_by are set by the attestation workflow, not this tool.",
|
|
228
|
-
},
|
|
229
275
|
},
|
|
230
276
|
required: ["person_id", "role", "splits"],
|
|
231
277
|
},
|
|
@@ -237,7 +283,7 @@ export class CreditsTools {
|
|
|
237
283
|
nextSteps: [
|
|
238
284
|
{
|
|
239
285
|
tool: "pica_credits_list",
|
|
240
|
-
reason: "Read the credits back to confirm every advertised field (is_primary, notes
|
|
286
|
+
reason: "Read the credits back to confirm every advertised field (is_primary, notes) actually persisted. Silent persistence drops are real — never trust the write response alone.",
|
|
241
287
|
when: "on_success",
|
|
242
288
|
},
|
|
243
289
|
{
|
|
@@ -286,6 +332,10 @@ export class CreditsTools {
|
|
|
286
332
|
"Arranger, Programmer, Other. Look up the person first with " +
|
|
287
333
|
"pica_people_query; if they're not in the catalog, leave " +
|
|
288
334
|
"person_id empty and their name still shows via credited_name. " +
|
|
335
|
+
"Attestation is not settable here: every credit starts as " +
|
|
336
|
+
"pending and only the credited person confirms it — ask them " +
|
|
337
|
+
"with pica_collaborators_invite (recording_id) or " +
|
|
338
|
+
"pica_credits_send_for_attestation on the recording's song. " +
|
|
289
339
|
"After saving, confirm it stuck with pica_recordings_inspect " +
|
|
290
340
|
"(sections: ['recording_credits']). → then: " +
|
|
291
341
|
"pica_recordings_inspect (verify saved), pica_people_query " +
|
|
@@ -329,11 +379,6 @@ export class CreditsTools {
|
|
|
329
379
|
type: "string",
|
|
330
380
|
description: "Optional free-text notes about this credit (recording-session context, role specifics).",
|
|
331
381
|
},
|
|
332
|
-
attestation_status: {
|
|
333
|
-
type: "string",
|
|
334
|
-
enum: ["pending", "attested", "disputed", "declined"],
|
|
335
|
-
description: "Optional starting attestation status — master credits are attestable just like composition credits. Defaults to 'pending'. Use pica_credits_send_for_attestation to ask the credited person to confirm; attested_at / attested_by are set by the attestation workflow, not this tool.",
|
|
336
|
-
},
|
|
337
382
|
instrument: {
|
|
338
383
|
type: "string",
|
|
339
384
|
description: "Optional instrument(s) for this credit, e.g. 'drums' or 'drums, bass' (one credit per person — list multiple instruments on the same row). Stored on recording_credits.instrument and rendered in /inspect's contributors table.",
|
|
@@ -408,96 +453,6 @@ export class CreditsTools {
|
|
|
408
453
|
},
|
|
409
454
|
executor: withBillingGate(this.pica, "recording credit update", this.updateRecordingCredits.bind(this)),
|
|
410
455
|
},
|
|
411
|
-
// ADR-264 WS4 prerequisite — bulk recording credits. One album's
|
|
412
|
-
// production credits stops being 30 sequential per-recording calls.
|
|
413
|
-
{
|
|
414
|
-
definition: {
|
|
415
|
-
name: "pica_recording_credits_bulk_update",
|
|
416
|
-
scope: "write:catalog",
|
|
417
|
-
audiences: ["stdio", "http"],
|
|
418
|
-
tier: "write",
|
|
419
|
-
description: "Apply one set of recording credits across many recordings in a " +
|
|
420
|
-
"single call. Use when the user asks: 'credit the producer on " +
|
|
421
|
-
"the whole album', 'add these credits to every track', 'backfill " +
|
|
422
|
-
"the engineer across these recordings'. Target either a release " +
|
|
423
|
-
"(release_id — every recording on its tracklist) or an explicit " +
|
|
424
|
-
"recording_ids list, never both. The same credit set is applied " +
|
|
425
|
-
"to EVERY target recording; credits that already exist on a " +
|
|
426
|
-
"recording (same credited name + role) are skipped, never " +
|
|
427
|
-
"overwritten, so re-running is safe. Set dry_run: true first to " +
|
|
428
|
-
"preview exactly which rows would be created vs skipped. Caps: " +
|
|
429
|
-
"100 recordings, 50 credits, 500 total rows per call. Per-track " +
|
|
430
|
-
"differences (a featured artist on track 3 only) go via " +
|
|
431
|
-
"pica_recording_credits_update on that recording. → then: " +
|
|
432
|
-
"pica_recordings_inspect (verify saved)",
|
|
433
|
-
workflows: ["recording-credits-required"],
|
|
434
|
-
vernacular_kind: "specialist",
|
|
435
|
-
vernacular_reason: "Specialist context; not yet covered by the Creator Question Atlas — pending telemetry-driven addition per ADR-226 Phase 4 substrate writes.",
|
|
436
|
-
previewMode: "dry_run",
|
|
437
|
-
inputSchema: {
|
|
438
|
-
type: "object",
|
|
439
|
-
properties: {
|
|
440
|
-
release_id: {
|
|
441
|
-
type: "string",
|
|
442
|
-
description: "UUID of a release — targets every recording on its tracklist. Provide exactly one of release_id / recording_ids.",
|
|
443
|
-
},
|
|
444
|
-
recording_ids: {
|
|
445
|
-
type: "array",
|
|
446
|
-
items: { type: "string" },
|
|
447
|
-
description: "Explicit recording UUIDs to credit (max 100). Provide exactly one of release_id / recording_ids.",
|
|
448
|
-
},
|
|
449
|
-
credits: {
|
|
450
|
-
type: "array",
|
|
451
|
-
description: "The credit set applied to EVERY target recording (max 50). Each entry needs credited_name + role; person_id links an existing person record (resolve via pica_people_query first).",
|
|
452
|
-
items: {
|
|
453
|
-
type: "object",
|
|
454
|
-
properties: {
|
|
455
|
-
credited_name: {
|
|
456
|
-
type: "string",
|
|
457
|
-
description: "The displayed credit name — the contributor's professional name as it should appear on the recording.",
|
|
458
|
-
},
|
|
459
|
-
role: {
|
|
460
|
-
type: "string",
|
|
461
|
-
enum: [...RECORDING_CREDIT_ROLES],
|
|
462
|
-
description: "Recording credit role from the 16-entry RECORDING_CREDIT_ROLES enum. Songwriting roles (Writer, Composer, Lyricist) belong on the work via pica_credits_update.",
|
|
463
|
-
},
|
|
464
|
-
person_id: {
|
|
465
|
-
type: "string",
|
|
466
|
-
description: "Optional UUID of an existing person record in the catalog. Leave empty if the contributor has no person record — credited_name still shows.",
|
|
467
|
-
},
|
|
468
|
-
split_percentage: {
|
|
469
|
-
type: "number",
|
|
470
|
-
description: "Optional percentage share for this credit (0-100), applied identically on every target recording.",
|
|
471
|
-
},
|
|
472
|
-
split_type: {
|
|
473
|
-
type: "string",
|
|
474
|
-
description: "Optional free-form split type label (e.g. 'master', 'producer_points').",
|
|
475
|
-
},
|
|
476
|
-
notes: {
|
|
477
|
-
type: "string",
|
|
478
|
-
description: "Optional free-text notes attached to each created credit row.",
|
|
479
|
-
},
|
|
480
|
-
},
|
|
481
|
-
required: ["credited_name", "role"],
|
|
482
|
-
},
|
|
483
|
-
},
|
|
484
|
-
dry_run: {
|
|
485
|
-
type: "boolean",
|
|
486
|
-
description: "When true, returns the per-row would_create / would_skip_existing decision set without persisting anything. Preview before committing.",
|
|
487
|
-
},
|
|
488
|
-
},
|
|
489
|
-
required: ["credits"],
|
|
490
|
-
},
|
|
491
|
-
nextSteps: [
|
|
492
|
-
{
|
|
493
|
-
tool: "pica_recordings_inspect",
|
|
494
|
-
reason: "Read recording_credits back via sections:['recording_credits'] on a sample recording to confirm the rows persisted.",
|
|
495
|
-
when: "on_success",
|
|
496
|
-
},
|
|
497
|
-
],
|
|
498
|
-
},
|
|
499
|
-
executor: withBillingGate(this.pica, "bulk recording credit update", this.bulkUpdateRecordingCredits.bind(this)),
|
|
500
|
-
},
|
|
501
456
|
// ADR-232 — atomic credit primitives. Composable verbs over the same
|
|
502
457
|
// substrate as pica_credits_update / pica_recording_credits_update,
|
|
503
458
|
// but discrete: add one row, remove one row, no replace-semantics.
|
|
@@ -631,8 +586,8 @@ export class CreditsTools {
|
|
|
631
586
|
description: "(recordings only) Displayed credit name (NOT NULL on recording_credits). Required for recordings.",
|
|
632
587
|
},
|
|
633
588
|
share_pct: {
|
|
634
|
-
type: "number",
|
|
635
|
-
description: "Percentage share (0-100). Maps to split_percentage on work_collaborators (composition) / recording_credits (master). Omit when unknown — it is stored as NULL (unknown), never as 0; the health plan then reports the work as incomplete rather than rescaling the others. REQUIRED for credit_type 'owner' (master ownership is recorded as given, never assumed 100).",
|
|
589
|
+
type: ["number", "null"],
|
|
590
|
+
description: "Percentage share (0-100). Maps to split_percentage on work_collaborators (composition) / recording_credits (master). Omit it (or send null) when unknown — it is stored as NULL (unknown), never as 0; the health plan then reports the work as incomplete rather than rescaling the others. REQUIRED for credit_type 'owner' (master ownership is recorded as given, never assumed 100).",
|
|
636
591
|
},
|
|
637
592
|
notes: {
|
|
638
593
|
type: "string",
|
|
@@ -668,14 +623,20 @@ export class CreditsTools {
|
|
|
668
623
|
audiences: ["stdio", "http"],
|
|
669
624
|
tier: "write",
|
|
670
625
|
description: "Edit ONE existing credit in place — change a writer's share, an " +
|
|
671
|
-
"instrumentalist's instrument, a role, the displayed name
|
|
672
|
-
"notes
|
|
673
|
-
"
|
|
674
|
-
"to
|
|
675
|
-
"
|
|
676
|
-
"
|
|
677
|
-
"
|
|
678
|
-
"
|
|
626
|
+
"instrumentalist's instrument, a role, the displayed name, the " +
|
|
627
|
+
"notes, or WHO the credit is for (person_id) — leaving every " +
|
|
628
|
+
"other credit exactly as it is. Use when the user asks: 'change " +
|
|
629
|
+
"Mary's split to 40', 'set Charlie's instrument to bass', 'fix " +
|
|
630
|
+
"the spelling of this credit', 'link the Brian Lucey credit to " +
|
|
631
|
+
"his new person record'. This is the verb between pica_credit_add " +
|
|
632
|
+
"and pica_credit_remove: it never replaces the set (that is " +
|
|
633
|
+
"pica_credits_update) and never deletes-and-recreates, so the " +
|
|
634
|
+
"credit keeps its id. Its attestation state is kept too, except " +
|
|
635
|
+
"when person_id changes WHO it is for: a confirmed, disputed or " +
|
|
636
|
+
"declined credit then goes back to pending, because the newly " +
|
|
637
|
+
"linked person gave none of those answers (the response's " +
|
|
638
|
+
"person_change says so). " +
|
|
639
|
+
"Find the credit_id with pica_credits_list. " +
|
|
679
640
|
"Writer-split closure is NOT enforced on a single edit — re-split " +
|
|
680
641
|
"a song one writer at a time; the response's `allocation.status` " +
|
|
681
642
|
"(closed / open / incomplete) tells you whether the set still " +
|
|
@@ -715,6 +676,14 @@ export class CreditsTools {
|
|
|
715
676
|
type: ["string", "null"],
|
|
716
677
|
description: "Free-text notes. Pass null to clear.",
|
|
717
678
|
},
|
|
679
|
+
person_id: {
|
|
680
|
+
type: "string",
|
|
681
|
+
description: "Change who this credit is for (UUID from pica_people_query) — link a name-only credit to a person record, or move a credit linked to the wrong person. The person must be in this organisation and not deleted; if they already hold this role on the song or recording, it is refused (CREDIT_PERSON_CONFLICT) rather than making a second credit. A confirmed, disputed or declined credit goes back to pending.",
|
|
682
|
+
},
|
|
683
|
+
confirm_name_mismatch: {
|
|
684
|
+
type: "boolean",
|
|
685
|
+
description: "Only with person_id on a name-only credit: set true after the user confirms the person is the one credited even though the names differ (e.g. a stage name). Without it a mismatch is refused (PERSON_NAME_MISMATCH).",
|
|
686
|
+
},
|
|
718
687
|
},
|
|
719
688
|
required: ["work_id", "credit_id"],
|
|
720
689
|
},
|
|
@@ -799,19 +768,28 @@ export class CreditsTools {
|
|
|
799
768
|
credit_ids: creditIds,
|
|
800
769
|
reminder_type: reminderType,
|
|
801
770
|
});
|
|
802
|
-
const
|
|
803
|
-
const
|
|
804
|
-
const
|
|
771
|
+
const rows = (result.results || []);
|
|
772
|
+
const name = (r) => r.person_name || r.person_id;
|
|
773
|
+
const of = (o) => rows.filter((r) => r.outcome === o);
|
|
774
|
+
const sent = of("sent");
|
|
775
|
+
const held = of("held");
|
|
776
|
+
const notPicaUsers = rows.filter((r) => r.outcome === "no_channel" && r.reason === "not_a_pica_user");
|
|
777
|
+
const noEmail = rows.filter((r) => r.outcome === "no_channel" && r.reason !== "not_a_pica_user");
|
|
778
|
+
const failed = of("failed");
|
|
805
779
|
const summary = [
|
|
806
|
-
`
|
|
807
|
-
|
|
808
|
-
|
|
780
|
+
`Asked ${sent.length + held.length}/${rows.length} credited people`,
|
|
781
|
+
sent.length > 0 ? `Sent to: ${sent.map(name).join(", ")}` : null,
|
|
782
|
+
held.length > 0
|
|
783
|
+
? `Email held for approval (not delivered yet): ${held.map(name).join(", ")}`
|
|
809
784
|
: null,
|
|
810
785
|
notPicaUsers.length > 0
|
|
811
|
-
? `Not PICA users (use pica_collaborators_invite): ${notPicaUsers.map(
|
|
786
|
+
? `Not PICA users (use pica_collaborators_invite): ${notPicaUsers.map(name).join(", ")}`
|
|
787
|
+
: null,
|
|
788
|
+
noEmail.length > 0
|
|
789
|
+
? `No email on file (add one to their person record): ${noEmail.map(name).join(", ")}`
|
|
812
790
|
: null,
|
|
813
791
|
failed.length > 0
|
|
814
|
-
? `
|
|
792
|
+
? `Send failed: ${failed.map(name).join(", ")}`
|
|
815
793
|
: null,
|
|
816
794
|
]
|
|
817
795
|
.filter(Boolean)
|
|
@@ -819,6 +797,14 @@ export class CreditsTools {
|
|
|
819
797
|
return formatSuccess(summary, result);
|
|
820
798
|
}
|
|
821
799
|
catch (err) {
|
|
800
|
+
// Nobody on the request could be reached: the route refuses with 422
|
|
801
|
+
// and names who and why. Not a transport failure.
|
|
802
|
+
const body = parseApiErrorBody(err);
|
|
803
|
+
if (body.code === "NO_REACHABLE_RECIPIENT") {
|
|
804
|
+
return typedError("NO_REACHABLE_RECIPIENT", body.message ?? "Nobody on this request could be reached.", body.details && typeof body.details === "object"
|
|
805
|
+
? body.details
|
|
806
|
+
: undefined);
|
|
807
|
+
}
|
|
822
808
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
823
809
|
return typedError("CREDITS_ATTESTATION_SEND_FAILED", `Failed to send credits for attestation: ${errorMessage}`);
|
|
824
810
|
}
|
|
@@ -883,6 +869,26 @@ export class CreditsTools {
|
|
|
883
869
|
isError: true,
|
|
884
870
|
};
|
|
885
871
|
}
|
|
872
|
+
// attestation_status is not caller-settable. Since 2026-09-29 the
|
|
873
|
+
// collaborators route rejects it (UNKNOWN_FIELD) and
|
|
874
|
+
// replace_work_collaborators forces every inserted row to 'pending'
|
|
875
|
+
// (migration 20260929190000) — a caller could otherwise fabricate
|
|
876
|
+
// "attested" with no attestation ever happening. This tool kept
|
|
877
|
+
// advertising and forwarding it, so agents sent it and the whole write
|
|
878
|
+
// failed (ops 00d9e736, 9a1cb927). Refuse it here, before anything is
|
|
879
|
+
// written, with the way to actually get a credit confirmed — never
|
|
880
|
+
// silently drop it, or the caller believes the credit is attested.
|
|
881
|
+
const attestationIdx = args.credits
|
|
882
|
+
.map((c) => c ?? {})
|
|
883
|
+
.findIndex((c) => Object.hasOwn(c, "attestation_status"));
|
|
884
|
+
if (attestationIdx !== -1) {
|
|
885
|
+
return typedError("ATTESTATION_NOT_SETTABLE", `credits[${attestationIdx}] carries attestation_status, which this ` +
|
|
886
|
+
`tool can't set — nothing was saved. Every credit starts as ` +
|
|
887
|
+
`pending and only the credited person can confirm it. Remove ` +
|
|
888
|
+
`attestation_status from every entry and retry (put any source ` +
|
|
889
|
+
`evidence in notes), then call pica_credits_send_for_attestation ` +
|
|
890
|
+
`so each person can confirm their own credit.`, { field: "attestation_status", index: attestationIdx });
|
|
891
|
+
}
|
|
886
892
|
// ADR-252: the 100% rule is a COMPOSITION concept (writer/publisher shares).
|
|
887
893
|
// Master/recording credits carry independent, unconstrained splits (a
|
|
888
894
|
// producer takes points; a vocalist may have 0%). Validate the total over
|
|
@@ -912,9 +918,9 @@ export class CreditsTools {
|
|
|
912
918
|
isError: true,
|
|
913
919
|
};
|
|
914
920
|
}
|
|
915
|
-
// ADR-252: only fields with a canonical home survive. is_primary
|
|
916
|
-
// notes
|
|
917
|
-
//
|
|
921
|
+
// ADR-252: only fields with a canonical home survive. is_primary and
|
|
922
|
+
// notes live on work_collaborators (composition); notes is also on
|
|
923
|
+
// recording_credits (master). attestation_status is refused above.
|
|
918
924
|
// territory / rights_type / start_date / end_date / publisher_id /
|
|
919
925
|
// publisher_split_percentage had no home after the work_credits drop and
|
|
920
926
|
// are no longer accepted — see write-constants.ts.
|
|
@@ -933,8 +939,6 @@ export class CreditsTools {
|
|
|
933
939
|
credit.is_primary = c.is_primary;
|
|
934
940
|
if (c.notes !== undefined)
|
|
935
941
|
credit.notes = c.notes;
|
|
936
|
-
if (c.attestation_status)
|
|
937
|
-
credit.attestation_status = c.attestation_status;
|
|
938
942
|
return credit;
|
|
939
943
|
});
|
|
940
944
|
const publishingCredits = allCredits.filter((c) => PUBLISHING_ROLES.has(String(c.role || "writer")
|
|
@@ -1049,9 +1053,21 @@ export class CreditsTools {
|
|
|
1049
1053
|
* is set, mirroring the work-side updateCredits notification flow.
|
|
1050
1054
|
*/
|
|
1051
1055
|
async updateRecordingCredits(args) {
|
|
1056
|
+
// attestation_status is not caller-settable. An attestation is the
|
|
1057
|
+
// credited person confirming their credit; until 2026-10-03 this tool
|
|
1058
|
+
// advertised it and the single form forwarded it, so any caller could
|
|
1059
|
+
// create a master credit already "attested". The route now refuses it
|
|
1060
|
+
// too, and session-role writes are refused in the database (migration
|
|
1061
|
+
// 20261003072610). Refuse here, before anything is written, with the way
|
|
1062
|
+
// to actually get a credit confirmed — never silently drop it, or the
|
|
1063
|
+
// caller believes the credit is attested. Same code and shape as the
|
|
1064
|
+
// pica_credits_update refusal in open PR #2622.
|
|
1065
|
+
const attestationRefusal = refuseRecordingAttestation(args);
|
|
1066
|
+
if (attestationRefusal)
|
|
1067
|
+
return attestationRefusal;
|
|
1052
1068
|
// Superset dispatch (write-merge fold, mirrors ReleasesTools.attachTrack)
|
|
1053
|
-
// —
|
|
1054
|
-
// self-validate their own args
|
|
1069
|
+
// — subsumed pica_recording_credits_bulk_update (that name was removed in
|
|
1070
|
+
// mcp-server 2.99.0). Both executors fully self-validate their own args.
|
|
1055
1071
|
if (Array.isArray(args.credits)) {
|
|
1056
1072
|
return this.bulkUpdateRecordingCredits(args);
|
|
1057
1073
|
}
|
|
@@ -1104,11 +1120,6 @@ export class CreditsTools {
|
|
|
1104
1120
|
display_order: args.display_order ?? 0,
|
|
1105
1121
|
notes: args.notes ?? null,
|
|
1106
1122
|
instrument: args.instrument ?? null,
|
|
1107
|
-
// ADR-252: master credits are attestable. The SDK input type predates
|
|
1108
|
-
// the column; the route validates + persists it.
|
|
1109
|
-
...(args.attestation_status
|
|
1110
|
-
? { attestation_status: args.attestation_status }
|
|
1111
|
-
: {}),
|
|
1112
1123
|
});
|
|
1113
1124
|
}
|
|
1114
1125
|
catch (err) {
|
|
@@ -1176,7 +1187,10 @@ export class CreditsTools {
|
|
|
1176
1187
|
return formatSuccess(msg, credit);
|
|
1177
1188
|
}
|
|
1178
1189
|
/**
|
|
1179
|
-
* ADR-264 WS4 prerequisite —
|
|
1190
|
+
* ADR-264 WS4 prerequisite — the bulk path of pica_recording_credits_update
|
|
1191
|
+
* (reached when args.credits is an array). It was also the executor of the
|
|
1192
|
+
* standalone pica_recording_credits_bulk_update tool until mcp-server 2.99.0
|
|
1193
|
+
* removed that name.
|
|
1180
1194
|
*
|
|
1181
1195
|
* Thin wrapper over POST /admin/recording-credits/bulk via
|
|
1182
1196
|
* RecordingCreditsResource.bulkUpdate. The route + service own validation
|
|
@@ -1275,12 +1289,21 @@ export class CreditsTools {
|
|
|
1275
1289
|
}
|
|
1276
1290
|
patch.share_pct = v;
|
|
1277
1291
|
}
|
|
1278
|
-
for (const key of [
|
|
1292
|
+
for (const key of [
|
|
1293
|
+
"credit_type",
|
|
1294
|
+
"credited_name",
|
|
1295
|
+
"notes",
|
|
1296
|
+
"instrument",
|
|
1297
|
+
"person_id",
|
|
1298
|
+
]) {
|
|
1279
1299
|
if (args[key] !== undefined)
|
|
1280
1300
|
patch[key] = args[key];
|
|
1281
1301
|
}
|
|
1282
1302
|
if (Object.keys(patch).length === 0) {
|
|
1283
|
-
return typedError("VALIDATION_ERROR", "nothing to update — pass at least one of share_pct, credit_type, credited_name, notes, instrument.");
|
|
1303
|
+
return typedError("VALIDATION_ERROR", "nothing to update — pass at least one of share_pct, credit_type, credited_name, notes, instrument, person_id.");
|
|
1304
|
+
}
|
|
1305
|
+
if (args.confirm_name_mismatch === true) {
|
|
1306
|
+
patch.confirm_name_mismatch = true;
|
|
1284
1307
|
}
|
|
1285
1308
|
try {
|
|
1286
1309
|
const result = await this.pica.credits.atomicUpdate(workId, creditId, patch);
|
|
@@ -1300,11 +1323,35 @@ export class CreditsTools {
|
|
|
1300
1323
|
severity: "important",
|
|
1301
1324
|
});
|
|
1302
1325
|
}
|
|
1326
|
+
const personChange = result?.person_change;
|
|
1327
|
+
if (personChange?.attestation_reset) {
|
|
1328
|
+
// person_change.message is the server's own sentence
|
|
1329
|
+
// (describeCreditPersonChange in lib/services/credit-person-link.ts),
|
|
1330
|
+
// so the agent hears the same account the routes give.
|
|
1331
|
+
const what = personChange.message ??
|
|
1332
|
+
`This credit's ${personChange.previous_attestation_status ?? "previous"} status was cleared; it is now ${personChange.attestation_status ?? "pending"}.`;
|
|
1333
|
+
hints.push({
|
|
1334
|
+
gap: "credit_confirmation_reset",
|
|
1335
|
+
suggestion: `${what} The person now linked gave no confirmation of their own. Tell the user, and offer to ask the newly linked person to confirm (pica_credits_send_for_attestation on this work).`,
|
|
1336
|
+
severity: "important",
|
|
1337
|
+
});
|
|
1338
|
+
}
|
|
1303
1339
|
return formatSuccess("Credit updated", result, hints);
|
|
1304
1340
|
}
|
|
1305
1341
|
catch (err) {
|
|
1306
1342
|
const message = err instanceof Error ? err.message : String(err);
|
|
1307
1343
|
const body = parseApiErrorBody(err);
|
|
1344
|
+
// Typed refusals of a change of person (lib/services/credit-person-link.ts).
|
|
1345
|
+
// Checked before the generic status branches: PERSON_NOT_FOUND is a
|
|
1346
|
+
// 404 and CREDIT_PERSON_CONFLICT a 409, and each needs its own next step.
|
|
1347
|
+
if (body.code === "PERSON_NOT_FOUND" ||
|
|
1348
|
+
body.code === "PERSON_DELETED" ||
|
|
1349
|
+
body.code === "PERSON_REQUIRED" ||
|
|
1350
|
+
body.code === "PERSON_NAME_MISMATCH" ||
|
|
1351
|
+
body.code === "CREDIT_PERSON_CONFLICT" ||
|
|
1352
|
+
body.code === "PERSON_LINK_IS_THE_CATALOGUES") {
|
|
1353
|
+
return typedError(body.code, body.message ?? message, body.details ?? undefined);
|
|
1354
|
+
}
|
|
1308
1355
|
if (body.code === "INSUFFICIENT_SCOPE" || body.status === 403) {
|
|
1309
1356
|
return typedError("INSUFFICIENT_SCOPE", `credit_id '${creditId}' is not a credit on work '${workId}' in this organisation. List the work's credits with pica_credits_list to find a valid id. Underlying error: ${message}`);
|
|
1310
1357
|
}
|
|
@@ -1359,7 +1406,7 @@ export class CreditsTools {
|
|
|
1359
1406
|
content: [
|
|
1360
1407
|
{
|
|
1361
1408
|
type: "text",
|
|
1362
|
-
text: "share_pct must be a number between 0 and 100.",
|
|
1409
|
+
text: "share_pct must be a number between 0 and 100, or null (unknown).",
|
|
1363
1410
|
},
|
|
1364
1411
|
],
|
|
1365
1412
|
isError: true,
|
|
@@ -1439,6 +1486,26 @@ export class CreditsTools {
|
|
|
1439
1486
|
const existing = recordingCreditExists(err);
|
|
1440
1487
|
if (existing)
|
|
1441
1488
|
return existing;
|
|
1489
|
+
// A 4xx carrying a machine code is the route REFUSING this call for a
|
|
1490
|
+
// reason it names (VALIDATION_ERROR, PERSON_ORG_MISMATCH,
|
|
1491
|
+
// RECORDING_TARGET_REQUIRED, SHARES_ARE_SUGGESTIONS, …). Relay that
|
|
1492
|
+
// code and message instead of folding it into CREDIT_ADD_FAILED, which
|
|
1493
|
+
// reads as a transport failure and invites a blind retry — ops
|
|
1494
|
+
// 704708a1 was a 400 VALIDATION_ERROR reported as CREDIT_ADD_FAILED.
|
|
1495
|
+
const refusal = parseApiErrorBody(err);
|
|
1496
|
+
if (refusal.code &&
|
|
1497
|
+
refusal.status !== undefined &&
|
|
1498
|
+
refusal.status >= 400 &&
|
|
1499
|
+
refusal.status < 500) {
|
|
1500
|
+
// Siblings first, so a route key can never overwrite the envelope's
|
|
1501
|
+
// own `error` / `message` (typedError writes those last).
|
|
1502
|
+
return typedError(refusal.code, refusal.message ?? errorMessage, {
|
|
1503
|
+
...refusal.extra,
|
|
1504
|
+
...(refusal.details !== undefined
|
|
1505
|
+
? { details: refusal.details }
|
|
1506
|
+
: {}),
|
|
1507
|
+
});
|
|
1508
|
+
}
|
|
1442
1509
|
return typedError("CREDIT_ADD_FAILED", `Failed to add credit: ${errorMessage}`);
|
|
1443
1510
|
}
|
|
1444
1511
|
}
|