@withpica/mcp-sdk 3.28.0 → 4.1.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 CHANGED
@@ -11,6 +11,84 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
11
11
 
12
12
  ## [Unreleased]
13
13
 
14
+ ## [4.1.0] - 2026-10-29
15
+
16
+ ### Changed
17
+
18
+ - **A write that may have run is never sent twice** (#3391). A POST or PATCH is resent only on 429
19
+ or 503, refusals before the route runs. A timeout, a dropped connection, a 502 or a 504 now throws
20
+ `WriteOutcomeUnknownError` (`code: "WRITE_OUTCOME_UNKNOWN"`) instead of resending: a 94 s works
21
+ create on prod was resent at the 30 s timeout and answered `WORK_ALREADY_EXISTS` naming the song
22
+ its first attempt had made. GET, PUT and DELETE keep every retry.
23
+
24
+ - `credits.listCollaborators(workId)`: `verified` and `split_status` are each collaborator's own
25
+ confirmation (`split_status` is `attested`, `pending`, `disputed` or `declined`; `verified` is
26
+ true only when every one of their credits on the work is attested). Both used to say "verified"
27
+ for everyone.
28
+
29
+ ### Deprecated
30
+
31
+ - `is_owner` on `credits.listCollaborators(workId)` entries. The server no longer sends it, so a
32
+ reader gets `undefined`. It was `true` for every entry and did not reflect the credit (the same
33
+ field `pica_credits_list` dropped in mcp-server 2.107.0). The type keeps it optional, so 4.x code
34
+ that reads it still compiles; it is removed in the next major.
35
+
36
+ - `isrc_source` on `recordings.create` / `recordings.update` (and works writes): ignored by the
37
+ server, which records where an ISRC came from; the response's `warnings` says so.
38
+
39
+ ### Added
40
+
41
+ - `shareSend.send` takes `share_link_id` (email an existing link).
42
+ - `recordingCredits.bulkUpdate`: each credit takes `instrument`, applied on every target recording.
43
+ - `audioFiles.list({ cursor })` (#3392): start after a previous page's `pagination.next_cursor`
44
+ instead of at `offset`; a file uploaded during the walk no longer shifts the pages.
45
+
46
+ ### One bio per person
47
+
48
+ - `users.getMyProfile()` adds `bio_short`, `bio_confirmed`, `bio_choice: MyProfileBioChoice | null` and `set_aside_bios: MyProfileSetAsideBio[]`. `bio` is now the biography on the caller's own person record.
49
+ - `users.setBio()` returns `{ bio, person_id }`. New `users.chooseBio("current" | "earlier")`.
50
+ - `users.importBioFromPerson()` is **deprecated**. The server answers 410 RETIRED, and the method will be removed in the next major.
51
+
52
+ ## [4.0.0] - 2026-10-23
53
+
54
+ > **Major** for one breaking change, below. Publish this first, then `@withpica/mcp-utils` 2.0.1,
55
+ > then `@withpica/mcp-server` 2.110.0 (which calls `people.restore` and `removed.list`, so it needs
56
+ > this version), then `@withpica/mcp-server-directory` 1.9.0.
57
+
58
+ ### Changed
59
+
60
+ - **Breaking:** `AccessSimulateShareLinkConfig.access_type` is now
61
+ `"anyone" | "specific_people" | "collaborators_only"`, the share_links words,
62
+ instead of `"private" | "limited" | "public"`. Until this release the server
63
+ accepted the old words (`pica_access_simulate` listed them); it now refuses
64
+ them as an input error, so a caller still sending them must switch.
65
+
66
+ ### Added
67
+
68
+ - `CatalogHealthVerdict.recordings`: each kind of recording present (`CatalogHealthRecordingKind`:
69
+ `released_master`, `unreleased_master`, `demo`, `alternate`, `video`) with `label`, `total` and
70
+ `missingFields`, or `null` when the check could not run. `CatalogHealthItem.recordingKind` on
71
+ `missing_recording_fields` items (one item per kind). Type only: `health.catalogHealth()`
72
+ already returns what the server sends.
73
+ - `ShareTraceReason.audience` may be `"song_collaborators"` (a
74
+ collaborators-only link: a signed-in account credited on the song).
75
+ - `ShareTraceReason.audience` may be `"named_recipients"` (a specific-people
76
+ link), with `recipients: [{ recipient_id, email, person_id, view_count,
77
+ last_viewed_at }]`.
78
+ - `ShareTraceReason` share-link fields: `audience: "anyone_with_the_link"`,
79
+ `view_count`, `max_views`, `password_protected`. Every live link to a song is
80
+ now reported for any viewer.
81
+ - `works.restore(id)` and `people.restore(id)`: bring back a removed song or person
82
+ (`POST /admin/works/{id}/restore`, `/admin/people/{id}/restore`). Removing a song or a person
83
+ never deletes it. 409 when a live song now holds its title and artist or tunecode, or when the
84
+ record was merged into another.
85
+ - `removed.list({ kind?, limit?, offset? })` → `RemovedList` (`kind`, `total`, `totals`,
86
+ `items: RemovedItem[]`, `next_offset`): one page of the songs (default) or people removed from
87
+ the catalogue, with when, by whom and why (`GET /admin/removed`).
88
+ - `RemovedItem.holds`: for a removed person, the song credits, song artist credits, recording
89
+ credits, recording splits in force, agreement parties and main-artist songs/recordings that
90
+ still sit with them; `null` for a song.
91
+
14
92
  ## [3.28.0] - 2026-10-19
15
93
 
16
94
  > **Publish order:** this package first, then `@withpica/mcp-server` 2.109.0, which calls
package/dist/index.d.ts CHANGED
@@ -398,8 +398,15 @@ interface WorkCredit {
398
398
  splits: number;
399
399
  person_name?: string;
400
400
  ipi?: string;
401
+ /** True only when every one of the person's credits on the work is attested. */
401
402
  verified?: boolean;
403
+ /** attested | pending | disputed | declined (work_collaborators.attestation_status). */
402
404
  split_status?: string;
405
+ /**
406
+ * @deprecated No longer sent: it was `true` for every entry and did not
407
+ * reflect the credit. Kept optional so 4.x readers still compile; removed in
408
+ * the next major.
409
+ */
403
410
  is_owner?: boolean;
404
411
  }
405
412
  /** One credit row on a work, from GET /admin/works/:id/credits?view=rows. */
@@ -987,6 +994,15 @@ export interface DeletionResult {
987
994
  export declare class EntityNotFoundError extends ApiError {
988
995
  constructor(message: string);
989
996
  }
997
+ /**
998
+ * A write whose outcome is unknown: it timed out, lost its connection, or got
999
+ * a 502/504, and was not resent because it may have been applied. Look before
1000
+ * trying again.
1001
+ */
1002
+ export declare class WriteOutcomeUnknownError extends ApiError {
1003
+ readonly code = "WRITE_OUTCOME_UNKNOWN";
1004
+ constructor(method: string, path: string, cause: string, status?: number);
1005
+ }
990
1006
  declare class BaseResource {
991
1007
  protected baseUrl: string;
992
1008
  protected apiKey: string;
@@ -1132,6 +1148,14 @@ declare class WorksResource extends BaseResource {
1132
1148
  }): Promise<Work>;
1133
1149
  update(id: string, updates: Partial<Work>): Promise<Work>;
1134
1150
  delete(id: string): Promise<DeletionResult>;
1151
+ /**
1152
+ * Bring back a removed song (removing never deletes one). 409 when a live
1153
+ * song now holds its title and artist or its tunecode, or when it was
1154
+ * merged into another song.
1155
+ */
1156
+ restore(id: string): Promise<{
1157
+ success: boolean;
1158
+ }>;
1135
1159
  /**
1136
1160
  * @deprecated `works` has no `is_verified` column, so PATCH /admin/works/[id]
1137
1161
  * refuses this body (VALIDATION_ERROR "unknown field 'is_verified'") on every
@@ -1339,7 +1363,23 @@ declare class PeopleResource extends BaseResource {
1339
1363
  get(id: string): Promise<Person>;
1340
1364
  create(data: Partial<Person>): Promise<Person>;
1341
1365
  update(id: string, updates: Partial<Person>): Promise<Person>;
1342
- delete(id: string): Promise<void>;
1366
+ /**
1367
+ * Remove a person. Someone who still has credits is refused (409
1368
+ * PERSON_HOLDS_CREDITS, with what they hold) unless `moveTo` names the live
1369
+ * person their credits move to; the removal is then a person merge.
1370
+ */
1371
+ delete(id: string, opts?: {
1372
+ moveTo?: string;
1373
+ }): Promise<{
1374
+ moved_to?: {
1375
+ id: string;
1376
+ name: string;
1377
+ };
1378
+ }>;
1379
+ /** Bring back a removed person. 409 when they were merged into another. */
1380
+ restore(id: string): Promise<{
1381
+ success: boolean;
1382
+ }>;
1343
1383
  enrichFromISNI(id: string, isni: string): Promise<Person>;
1344
1384
  enrichFromMusicBrainz(id: string, musicbrainz_id: string): Promise<Person>;
1345
1385
  bulkDelete(ids: string[]): Promise<unknown>;
@@ -1856,6 +1896,8 @@ declare class AudioFilesResource extends BaseResource {
1856
1896
  query?: string;
1857
1897
  limit?: number;
1858
1898
  offset?: number;
1899
+ /** Start after a previous page's `pagination.next_cursor`, instead of at offset. */
1900
+ cursor?: string;
1859
1901
  }): Promise<AudioFile[]>;
1860
1902
  get(id: string): Promise<AudioFile>;
1861
1903
  /**
@@ -2026,6 +2068,14 @@ declare class AgreementsResource extends BaseResource {
2026
2068
  agreement: Agreement;
2027
2069
  signatureStatus: unknown;
2028
2070
  linkedWorks: unknown[];
2071
+ /** Documents linked to the agreement; null when the server could not read them. */
2072
+ linkedDocuments?: Array<{
2073
+ id: string;
2074
+ title: string | null;
2075
+ filename: string | null;
2076
+ category: string | null;
2077
+ created_at: string | null;
2078
+ }> | null;
2029
2079
  }>;
2030
2080
  create(data: Partial<Agreement>): Promise<Agreement>;
2031
2081
  /**
@@ -2176,6 +2226,58 @@ declare class WorkspaceResource extends BaseResource {
2176
2226
  }>;
2177
2227
  }>;
2178
2228
  }
2229
+ /** One removed song or person, as GET /admin/removed answers. */
2230
+ export interface RemovedItem {
2231
+ kind: "song" | "person";
2232
+ id: string;
2233
+ name: string;
2234
+ removed_at: string | null;
2235
+ /** "you" is never said here: a person's or agent's name, or null. */
2236
+ removed_by: string | null;
2237
+ /** Set when a song was moved to performances or linked to a shared song. */
2238
+ why: {
2239
+ kind: "moved_to_performance" | "linked_to_shared";
2240
+ title: string;
2241
+ } | null;
2242
+ /**
2243
+ * For a person: what they still hold in the live catalogue, which stays
2244
+ * with them while removed. Null for a song.
2245
+ */
2246
+ holds: {
2247
+ song_credits: number;
2248
+ song_artist_credits: number;
2249
+ recording_credits: number;
2250
+ /** Recording splits of every type still in force. */
2251
+ recording_splits: number;
2252
+ /** Agreements they are a party to. */
2253
+ agreements: number;
2254
+ main_artist_songs: number;
2255
+ main_artist_recordings: number;
2256
+ total: number;
2257
+ } | null;
2258
+ }
2259
+ export interface RemovedList {
2260
+ kind: "song" | "person";
2261
+ /** How many of this kind are removed. */
2262
+ total: number;
2263
+ /** How many of each kind are removed. */
2264
+ totals: {
2265
+ songs: number;
2266
+ people: number;
2267
+ };
2268
+ items: RemovedItem[];
2269
+ /** The offset of the next page, or null on the last. */
2270
+ next_offset: number | null;
2271
+ }
2272
+ /** What has been removed from the catalogue and can be brought back. */
2273
+ declare class RemovedResource extends BaseResource {
2274
+ /** One page of removed songs (the default) or people, newest changed first. */
2275
+ list(params?: {
2276
+ kind?: "song" | "person";
2277
+ limit?: number;
2278
+ offset?: number;
2279
+ }): Promise<RemovedList>;
2280
+ }
2179
2281
  declare class ShareSendResource extends BaseResource {
2180
2282
  send(args: {
2181
2283
  entity_type: "recording" | "work" | "audio_file" | "document";
@@ -2194,8 +2296,12 @@ declare class ShareSendResource extends BaseResource {
2194
2296
  scope?: "view" | "view+download";
2195
2297
  confirm_first_external_send?: boolean;
2196
2298
  included_file_ids?: string[];
2299
+ /** email this existing share link instead of making one (its settings stand) */
2300
+ share_link_id?: string;
2197
2301
  }): Promise<{
2198
2302
  send_id: string;
2303
+ /** "failed" means Postmark took no email (the share_sends row says why) */
2304
+ delivery: "sent" | "failed";
2199
2305
  share_link_id: string;
2200
2306
  share_url: string;
2201
2307
  work_ids: string[];
@@ -2696,6 +2802,17 @@ export interface CatalogHealthItem {
2696
2802
  truncated: boolean;
2697
2803
  needsScope: string | null;
2698
2804
  actionableByCaller: boolean;
2805
+ /** On missing_recording_fields: which kind of recording the item counts (one item per kind). */
2806
+ recordingKind?: CatalogHealthRecordingKind;
2807
+ }
2808
+ /** Mirrors lib/services/recording-kind.ts — a master is "released" only when it is on a release. */
2809
+ export type CatalogHealthRecordingKind = "released_master" | "unreleased_master" | "demo" | "alternate" | "video";
2810
+ export interface CatalogHealthRecordingKindLine {
2811
+ kind: CatalogHealthRecordingKind;
2812
+ /** Say this: "demos", "released masters". */
2813
+ label: string;
2814
+ total: number;
2815
+ missingFields: number;
2699
2816
  }
2700
2817
  export interface CatalogHealthVerdict {
2701
2818
  score: number | null;
@@ -2711,6 +2828,8 @@ export interface CatalogHealthVerdict {
2711
2828
  assessed: string[];
2712
2829
  notAssessed: string[];
2713
2830
  items: CatalogHealthItem[];
2831
+ /** Recordings by kind (only kinds present); null when the check could not run. */
2832
+ recordings: CatalogHealthRecordingKindLine[] | null;
2714
2833
  generatedAt: string;
2715
2834
  }
2716
2835
  export interface CatalogHealthPlanAction {
@@ -4551,20 +4670,38 @@ declare class UsersResource extends BaseResource {
4551
4670
  handle_accepts_invites: boolean;
4552
4671
  avatar_url: string | null;
4553
4672
  bio: string | null;
4673
+ bio_short: string | null;
4674
+ bio_confirmed: boolean;
4675
+ bio_choice: MyProfileBioChoice | null;
4676
+ set_aside_bios: MyProfileSetAsideBio[];
4554
4677
  linked_person_id: string | null;
4555
4678
  }>;
4679
+ /**
4680
+ * Write your bio: the biography on your own person record (one bio per
4681
+ * person). Refused 409 NO_LINKED_PERSON when your account has no person.
4682
+ */
4556
4683
  setBio(bio: string | null): Promise<{
4557
4684
  bio: string | null;
4685
+ person_id: string;
4686
+ }>;
4687
+ /**
4688
+ * Settle a waiting `bio_choice`: "current" keeps your bio, "earlier" uses
4689
+ * the earlier one. The text not kept goes to `set_aside_bios`, never
4690
+ * deleted. Refused 409 NO_CHOICE_WAITING when nothing is waiting.
4691
+ */
4692
+ chooseBio(keep: "current" | "earlier"): Promise<{
4693
+ bio: string | null;
4694
+ person_id: string;
4558
4695
  }>;
4559
4696
  setAvatarUrl(avatarUrl: string | null): Promise<{
4560
4697
  avatar_url: string | null;
4561
4698
  }>;
4562
4699
  /**
4563
- * Copy `people.biography` into `user_profiles.bio`. Returns the new
4564
- * bio on success. On `would_overwrite` (existing bio + overwrite
4565
- * false), surfaces both the existing and proposed bios so the agent
4566
- * can present a yes/no to the user before re-calling with
4567
- * `overwrite: true`.
4700
+ * @deprecated Retired: there is one bio now. Your profile bio IS the
4701
+ * biography on your own person record, so there is nothing to import, and
4702
+ * the server answers 410 RETIRED. Read the bio with `getMyProfile` and
4703
+ * change it with `setBio`. Kept so an existing caller gets that message
4704
+ * rather than a missing method; removed in the next major.
4568
4705
  */
4569
4706
  importBioFromPerson(params?: {
4570
4707
  overwrite?: boolean;
@@ -4573,6 +4710,17 @@ declare class UsersResource extends BaseResource {
4573
4710
  bio?: string;
4574
4711
  }>;
4575
4712
  }
4713
+ /** An earlier bio waiting for you to choose (see `UsersResource.chooseBio`). */
4714
+ export interface MyProfileBioChoice {
4715
+ id: string;
4716
+ earlier_bio: string;
4717
+ }
4718
+ /** An earlier bio kept after a choice. */
4719
+ export interface MyProfileSetAsideBio {
4720
+ id: string;
4721
+ bio: string;
4722
+ created_at: string;
4723
+ }
4576
4724
  interface DirectorySettings {
4577
4725
  organisation_id: string;
4578
4726
  opted_in: boolean;
@@ -4699,6 +4847,8 @@ export interface RecordingCreditsBulkCreditInput {
4699
4847
  split_type?: string | null;
4700
4848
  display_order?: number;
4701
4849
  notes?: string | null;
4850
+ /** Instrument(s) for this credit, applied on every target recording. */
4851
+ instrument?: string | null;
4702
4852
  }
4703
4853
  export interface RecordingCreditsBulkUpdateInput {
4704
4854
  /** Target every recording on this release. Exactly one of release_id / recording_ids. */
@@ -4774,7 +4924,21 @@ export interface ShareTraceReason {
4774
4924
  territory: string;
4775
4925
  }>;
4776
4926
  share_link_id?: string;
4927
+ /** 'anyone' links open for anyone with the URL; specific-people links
4928
+ * only through their named recipients' own URLs; collaborators-only
4929
+ * links only for a signed-in account credited on the song */
4930
+ audience?: "anyone_with_the_link" | "named_recipients" | "song_collaborators";
4931
+ recipients?: Array<{
4932
+ recipient_id: string;
4933
+ email: string | null;
4934
+ person_id: string | null;
4935
+ view_count: number;
4936
+ last_viewed_at: string | null;
4937
+ }>;
4777
4938
  expires_at?: string | null;
4939
+ view_count?: number;
4940
+ max_views?: number | null;
4941
+ password_protected?: boolean;
4778
4942
  grant_id?: string;
4779
4943
  scope?: string[];
4780
4944
  granted_at?: string | null;
@@ -4887,6 +5051,40 @@ declare class ShareTraceResource extends BaseResource {
4887
5051
  */
4888
5052
  trace(query: ShareTraceQuery): Promise<ShareTraceResult>;
4889
5053
  }
5054
+ /** One line of "who has access" to a song (ADR-346 part 5b). */
5055
+ export interface SongAccessLine {
5056
+ /** Opaque; pass back to remove. */
5057
+ key: string;
5058
+ granteeOrgName: string | null;
5059
+ /** The address a share waits on, when nobody has accepted yet. */
5060
+ pendingEmail: string | null;
5061
+ /** "both" is a legacy share written with no side. */
5062
+ side: "composition" | "master" | "both";
5063
+ /** For a master line: the one recording it covers. */
5064
+ recordingTitle: string | null;
5065
+ status: "active" | "pending";
5066
+ /** "look_after_master": they can also edit this master (2b-1). */
5067
+ canDo: "view" | "look_after_master";
5068
+ /** Part of a wider arrangement: change it from people & access, not here. */
5069
+ widerArrangement: boolean;
5070
+ }
5071
+ export interface SongAccessRemoval {
5072
+ removed: number;
5073
+ failed: number;
5074
+ /** True when they can still see the song: say so and offer a retry. */
5075
+ still_shared: boolean;
5076
+ /** Set when a wider arrangement was kept, so they can still edit. */
5077
+ kept_reason: string | null;
5078
+ }
5079
+ declare class SongAccessResource extends BaseResource {
5080
+ /** GET /admin/works/{id}/access — who can see one of your songs. */
5081
+ list(workId: string): Promise<SongAccessLine[]>;
5082
+ /**
5083
+ * POST /admin/works/{id}/access — end one line. A removal that took
5084
+ * nothing off throws (500 ACCESS_NOT_REMOVED).
5085
+ */
5086
+ remove(workId: string, key: string): Promise<SongAccessRemoval>;
5087
+ }
4890
5088
  /** Siblings of `data` on the recording split edit / end responses. */
4891
5089
  export interface RecordingSplitEditEnvelope {
4892
5090
  previous?: Record<string, any>;
@@ -5865,7 +6063,8 @@ declare class ShareLinksResource extends BaseResource {
5865
6063
  get(id: string): Promise<any>;
5866
6064
  /**
5867
6065
  * Update a narrow subset of share-link fields (name, description,
5868
- * personal_message, expires_at, max_views, is_active, password).
6066
+ * personal_message, expires_at, max_views, is_active, password,
6067
+ * revoke_recipient_ids).
5869
6068
  * Permission-bearing fields are not updatable post-create — revoke
5870
6069
  * and recreate to change permissions.
5871
6070
  */
@@ -6058,7 +6257,11 @@ export type AccessSimulateEntityType = "work" | "recording" | "person" | "releas
6058
6257
  export type AccessSimulateAction = "view" | "share_link_preview" | "claim" | "grant";
6059
6258
  export type AccessSimulateRecipientType = "user" | "anonymous" | "share_link";
6060
6259
  export interface AccessSimulateShareLinkConfig {
6061
- access_type: "private" | "limited" | "public";
6260
+ /** The share_links vocabulary. 'anyone': anyone with its URL.
6261
+ * 'specific_people': only the recipients in the two lists below, each
6262
+ * through their own URL. 'collaborators_only': a signed-in account
6263
+ * credited on the link's one song. */
6264
+ access_type: "anyone" | "specific_people" | "collaborators_only";
6062
6265
  allow_download?: boolean;
6063
6266
  allow_streaming?: boolean;
6064
6267
  allow_comments?: boolean;
@@ -6190,6 +6393,7 @@ export declare class PicaClient {
6190
6393
  recordingSplits: RecordingSplitsResource;
6191
6394
  recordingCredits: RecordingCreditsResource;
6192
6395
  shareTrace: ShareTraceResource;
6396
+ songAccess: SongAccessResource;
6193
6397
  countExplain: CountExplainResource;
6194
6398
  audioPipelineStatus: AudioPipelineStatusResource;
6195
6399
  publishers: PublishersResource;
@@ -6234,6 +6438,7 @@ export declare class PicaClient {
6234
6438
  groups: GroupsResource;
6235
6439
  assistantInteractions: AssistantInteractionsResource;
6236
6440
  shareSend: ShareSendResource;
6441
+ removed: RemovedResource;
6237
6442
  accessSimulate: AccessSimulateResource;
6238
6443
  approvals: ApprovalsResource;
6239
6444
  integrity: IntegrityResource;