@withpica/mcp-sdk 4.0.0 → 4.2.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/dist/index.js CHANGED
@@ -147,6 +147,35 @@ export class EntityNotFoundError extends ApiError {
147
147
  function isRetryableStatus(status) {
148
148
  return status === 429 || status === 502 || status === 503 || status === 504;
149
149
  }
150
+ /**
151
+ * A write (POST, PATCH) is resent only when the server said it did nothing:
152
+ * 429 and 503 are refusals before the route runs. A timeout, a dropped
153
+ * connection, a 502 or a 504 can each follow a write that DID happen, and
154
+ * resending it repeats the write. On prod 2026-10-29 a 94 s works create
155
+ * timed out at 30 s; the resend found the song the first attempt had just
156
+ * made and answered WORK_ALREADY_EXISTS naming it, and the agent then made by
157
+ * hand the recording the first attempt was still about to make (ops 198deea1).
158
+ * GET, PUT and DELETE are idempotent and keep every retry.
159
+ */
160
+ function isWriteMethod(method) {
161
+ const m = (method ?? "GET").toUpperCase();
162
+ return m === "POST" || m === "PATCH";
163
+ }
164
+ function isRefusedBeforeRunning(status) {
165
+ return status === 429 || status === 503;
166
+ }
167
+ /**
168
+ * A write whose outcome is unknown: it timed out, lost its connection, or got
169
+ * a 502/504, and was not resent because it may have been applied. Look before
170
+ * trying again.
171
+ */
172
+ export class WriteOutcomeUnknownError extends ApiError {
173
+ code = "WRITE_OUTCOME_UNKNOWN";
174
+ constructor(method, path, cause, status = 0) {
175
+ super(`${method} ${path} did not answer (${cause}). It may still have been applied, so it was not sent again: check whether it took effect (for example, query for what it creates) before retrying.`, status, false);
176
+ this.name = "WriteOutcomeUnknownError";
177
+ }
178
+ }
150
179
  function getTimeoutForPath(path) {
151
180
  return LONG_TIMEOUT_PATTERNS.some((p) => path.includes(p))
152
181
  ? LONG_TIMEOUT_MS
@@ -255,6 +284,15 @@ class BaseResource {
255
284
  }
256
285
  async fetchWithRetry(url, init, timeoutMs) {
257
286
  let lastError;
287
+ const write = isWriteMethod(init.method);
288
+ const pathOf = () => {
289
+ try {
290
+ return new URL(url).pathname;
291
+ }
292
+ catch {
293
+ return url;
294
+ }
295
+ };
258
296
  for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
259
297
  try {
260
298
  const response = await this.fetchWithTimeout(url, init, timeoutMs);
@@ -266,6 +304,12 @@ class BaseResource {
266
304
  if (response.status === 401 || response.status === 403) {
267
305
  throw new ApiError(`Authentication failed: ${response.status} ${errorText}`, response.status, false);
268
306
  }
307
+ // A write that may have run is never resent (isWriteMethod).
308
+ if (write &&
309
+ isRetryableStatus(response.status) &&
310
+ !isRefusedBeforeRunning(response.status)) {
311
+ throw new WriteOutcomeUnknownError(init.method ?? "POST", pathOf(), `${response.status} ${errorText.slice(0, 120)}`, response.status);
312
+ }
269
313
  // Retryable errors — retry with backoff
270
314
  if (isRetryableStatus(response.status) && attempt < MAX_RETRIES) {
271
315
  const retryAfter = response.headers.get("retry-after");
@@ -291,6 +335,10 @@ class BaseResource {
291
335
  catch (err) {
292
336
  if (err instanceof ApiError && !err.retryable)
293
337
  throw err;
338
+ // Timed out or lost the connection: a write may have run.
339
+ if (write && !(err instanceof CatalogueNotHonouredError)) {
340
+ throw new WriteOutcomeUnknownError(init.method ?? "POST", pathOf(), err.message);
341
+ }
294
342
  lastError = err;
295
343
  if (attempt < MAX_RETRIES) {
296
344
  const delayMs = RETRY_BASE_MS * Math.pow(2, attempt);
@@ -1143,6 +1191,8 @@ class AudioFilesResource extends BaseResource {
1143
1191
  queryParams.set("limit", String(params.limit));
1144
1192
  if (params?.offset)
1145
1193
  queryParams.set("offset", String(params.offset));
1194
+ if (params?.cursor)
1195
+ queryParams.set("cursor", params.cursor);
1146
1196
  const query = queryParams.toString();
1147
1197
  return this.request("GET", `/admin/audio-files${query ? `?${query}` : ""}`);
1148
1198
  }
@@ -3504,20 +3554,30 @@ class UsersResource extends BaseResource {
3504
3554
  async getMyProfile() {
3505
3555
  return this.request("GET", "/admin/users/profile");
3506
3556
  }
3557
+ /**
3558
+ * Write your bio: the biography on your own person record (one bio per
3559
+ * person). Refused 409 NO_LINKED_PERSON when your account has no person.
3560
+ */
3507
3561
  async setBio(bio) {
3508
- return this.request("POST", "/admin/users/bio", {
3509
- bio,
3510
- });
3562
+ return this.request("POST", "/admin/users/bio", { bio });
3563
+ }
3564
+ /**
3565
+ * Settle a waiting `bio_choice`: "current" keeps your bio, "earlier" uses
3566
+ * the earlier one. The text not kept goes to `set_aside_bios`, never
3567
+ * deleted. Refused 409 NO_CHOICE_WAITING when nothing is waiting.
3568
+ */
3569
+ async chooseBio(keep) {
3570
+ return this.request("POST", "/admin/users/bio", { choose: keep });
3511
3571
  }
3512
3572
  async setAvatarUrl(avatarUrl) {
3513
3573
  return this.request("POST", "/admin/users/avatar", { avatar_url: avatarUrl });
3514
3574
  }
3515
3575
  /**
3516
- * Copy `people.biography` into `user_profiles.bio`. Returns the new
3517
- * bio on success. On `would_overwrite` (existing bio + overwrite
3518
- * false), surfaces both the existing and proposed bios so the agent
3519
- * can present a yes/no to the user before re-calling with
3520
- * `overwrite: true`.
3576
+ * @deprecated Retired: there is one bio now. Your profile bio IS the
3577
+ * biography on your own person record, so there is nothing to import, and
3578
+ * the server answers 410 RETIRED. Read the bio with `getMyProfile` and
3579
+ * change it with `setBio`. Kept so an existing caller gets that message
3580
+ * rather than a missing method; removed in the next major.
3521
3581
  */
3522
3582
  async importBioFromPerson(params) {
3523
3583
  return this.request("POST", "/admin/users/import-bio-from-person", params ?? {});
@@ -3746,6 +3806,19 @@ class ShareTraceResource extends BaseResource {
3746
3806
  return this.request("GET", `/admin/share-trace?${params.toString()}`);
3747
3807
  }
3748
3808
  }
3809
+ class SongAccessResource extends BaseResource {
3810
+ /** GET /admin/works/{id}/access — who can see one of your songs. */
3811
+ async list(workId) {
3812
+ return this.request("GET", `/admin/works/${encodeURIComponent(workId)}/access`);
3813
+ }
3814
+ /**
3815
+ * POST /admin/works/{id}/access — end one line. A removal that took
3816
+ * nothing off throws (500 ACCESS_NOT_REMOVED).
3817
+ */
3818
+ async remove(workId, key) {
3819
+ return this.request("POST", `/admin/works/${encodeURIComponent(workId)}/access`, { key });
3820
+ }
3821
+ }
3749
3822
  class RecordingSplitsResource extends BaseResource {
3750
3823
  async list(recordingId) {
3751
3824
  return this.request("GET", `/admin/recordings/${recordingId}/splits`);
@@ -4476,6 +4549,14 @@ class StatementsResource extends BaseResource {
4476
4549
  async getImport(id) {
4477
4550
  return this.request("GET", `/admin/statements/imports/${encodeURIComponent(id)}`);
4478
4551
  }
4552
+ /**
4553
+ * Remove an imported statement: its royalty lines and payment total go
4554
+ * (one transaction); an uploaded statement document it came from stays.
4555
+ * Refused (409 IMPORT_RUNNING) while it is still importing.
4556
+ */
4557
+ async removeImport(importBatchId) {
4558
+ return this.request("DELETE", `/admin/statements/${encodeURIComponent(importBatchId)}`);
4559
+ }
4479
4560
  }
4480
4561
  class SelectionsResource extends BaseResource {
4481
4562
  async list(params) {
@@ -4848,6 +4929,7 @@ export class PicaClient {
4848
4929
  // ADR-232 — share trace aggregator + count explain registry +
4849
4930
  // audio pipeline status read.
4850
4931
  shareTrace;
4932
+ songAccess;
4851
4933
  countExplain;
4852
4934
  audioPipelineStatus;
4853
4935
  publishers;
@@ -4965,6 +5047,7 @@ export class PicaClient {
4965
5047
  this.recordingSplits = new RecordingSplitsResource(baseUrl, config.apiKey, debug);
4966
5048
  this.recordingCredits = new RecordingCreditsResource(baseUrl, config.apiKey, debug);
4967
5049
  this.shareTrace = new ShareTraceResource(baseUrl, config.apiKey, debug);
5050
+ this.songAccess = new SongAccessResource(baseUrl, config.apiKey, debug);
4968
5051
  this.countExplain = new CountExplainResource(baseUrl, config.apiKey, debug);
4969
5052
  this.audioPipelineStatus = new AudioPipelineStatusResource(baseUrl, config.apiKey, debug);
4970
5053
  this.publishers = new PublishersResource(baseUrl, config.apiKey, debug);