@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 +78 -0
- package/dist/index.d.ts +213 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +122 -14
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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);
|
|
@@ -577,6 +625,14 @@ class WorksResource extends BaseResource {
|
|
|
577
625
|
async delete(id) {
|
|
578
626
|
return this.request("DELETE", `/admin/works/${id}`);
|
|
579
627
|
}
|
|
628
|
+
/**
|
|
629
|
+
* Bring back a removed song (removing never deletes one). 409 when a live
|
|
630
|
+
* song now holds its title and artist or its tunecode, or when it was
|
|
631
|
+
* merged into another song.
|
|
632
|
+
*/
|
|
633
|
+
async restore(id) {
|
|
634
|
+
return this.request("POST", `/admin/works/${id}/restore`);
|
|
635
|
+
}
|
|
580
636
|
/**
|
|
581
637
|
* @deprecated `works` has no `is_verified` column, so PATCH /admin/works/[id]
|
|
582
638
|
* refuses this body (VALIDATION_ERROR "unknown field 'is_verified'") on every
|
|
@@ -718,8 +774,18 @@ class PeopleResource extends BaseResource {
|
|
|
718
774
|
async update(id, updates) {
|
|
719
775
|
return this.request("PATCH", `/admin/people/${id}`, updates);
|
|
720
776
|
}
|
|
721
|
-
|
|
722
|
-
|
|
777
|
+
/**
|
|
778
|
+
* Remove a person. Someone who still has credits is refused (409
|
|
779
|
+
* PERSON_HOLDS_CREDITS, with what they hold) unless `moveTo` names the live
|
|
780
|
+
* person their credits move to; the removal is then a person merge.
|
|
781
|
+
*/
|
|
782
|
+
async delete(id, opts = {}) {
|
|
783
|
+
const qs = opts.moveTo ? `?move_to=${encodeURIComponent(opts.moveTo)}` : "";
|
|
784
|
+
return this.request("DELETE", `/admin/people/${id}${qs}`);
|
|
785
|
+
}
|
|
786
|
+
/** Bring back a removed person. 409 when they were merged into another. */
|
|
787
|
+
async restore(id) {
|
|
788
|
+
return this.request("POST", `/admin/people/${id}/restore`);
|
|
723
789
|
}
|
|
724
790
|
async enrichFromISNI(id, isni) {
|
|
725
791
|
return this.request("POST", `/admin/people/${id}/enrich/isni`, {
|
|
@@ -1125,6 +1191,8 @@ class AudioFilesResource extends BaseResource {
|
|
|
1125
1191
|
queryParams.set("limit", String(params.limit));
|
|
1126
1192
|
if (params?.offset)
|
|
1127
1193
|
queryParams.set("offset", String(params.offset));
|
|
1194
|
+
if (params?.cursor)
|
|
1195
|
+
queryParams.set("cursor", params.cursor);
|
|
1128
1196
|
const query = queryParams.toString();
|
|
1129
1197
|
return this.request("GET", `/admin/audio-files${query ? `?${query}` : ""}`);
|
|
1130
1198
|
}
|
|
@@ -1471,9 +1539,21 @@ class WorkspaceResource extends BaseResource {
|
|
|
1471
1539
|
return this.request("GET", `/admin/events?limit=${limit || 20}`);
|
|
1472
1540
|
}
|
|
1473
1541
|
}
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1542
|
+
/** What has been removed from the catalogue and can be brought back. */
|
|
1543
|
+
class RemovedResource extends BaseResource {
|
|
1544
|
+
/** One page of removed songs (the default) or people, newest changed first. */
|
|
1545
|
+
async list(params = {}) {
|
|
1546
|
+
const qp = new URLSearchParams();
|
|
1547
|
+
if (params.kind)
|
|
1548
|
+
qp.set("kind", params.kind);
|
|
1549
|
+
if (params.limit !== undefined)
|
|
1550
|
+
qp.set("limit", String(params.limit));
|
|
1551
|
+
if (params.offset !== undefined)
|
|
1552
|
+
qp.set("offset", String(params.offset));
|
|
1553
|
+
const q = qp.toString();
|
|
1554
|
+
return this.request("GET", `/admin/removed${q ? `?${q}` : ""}`);
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1477
1557
|
class ShareSendResource extends BaseResource {
|
|
1478
1558
|
async send(args) {
|
|
1479
1559
|
return this.request("POST", "/mcp/share-send", args);
|
|
@@ -3474,20 +3554,30 @@ class UsersResource extends BaseResource {
|
|
|
3474
3554
|
async getMyProfile() {
|
|
3475
3555
|
return this.request("GET", "/admin/users/profile");
|
|
3476
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
|
+
*/
|
|
3477
3561
|
async setBio(bio) {
|
|
3478
|
-
return this.request("POST", "/admin/users/bio", {
|
|
3479
|
-
|
|
3480
|
-
|
|
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 });
|
|
3481
3571
|
}
|
|
3482
3572
|
async setAvatarUrl(avatarUrl) {
|
|
3483
3573
|
return this.request("POST", "/admin/users/avatar", { avatar_url: avatarUrl });
|
|
3484
3574
|
}
|
|
3485
3575
|
/**
|
|
3486
|
-
*
|
|
3487
|
-
*
|
|
3488
|
-
*
|
|
3489
|
-
*
|
|
3490
|
-
*
|
|
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.
|
|
3491
3581
|
*/
|
|
3492
3582
|
async importBioFromPerson(params) {
|
|
3493
3583
|
return this.request("POST", "/admin/users/import-bio-from-person", params ?? {});
|
|
@@ -3716,6 +3806,19 @@ class ShareTraceResource extends BaseResource {
|
|
|
3716
3806
|
return this.request("GET", `/admin/share-trace?${params.toString()}`);
|
|
3717
3807
|
}
|
|
3718
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
|
+
}
|
|
3719
3822
|
class RecordingSplitsResource extends BaseResource {
|
|
3720
3823
|
async list(recordingId) {
|
|
3721
3824
|
return this.request("GET", `/admin/recordings/${recordingId}/splits`);
|
|
@@ -4508,7 +4611,8 @@ class ShareLinksResource extends BaseResource {
|
|
|
4508
4611
|
}
|
|
4509
4612
|
/**
|
|
4510
4613
|
* Update a narrow subset of share-link fields (name, description,
|
|
4511
|
-
* personal_message, expires_at, max_views, is_active, password
|
|
4614
|
+
* personal_message, expires_at, max_views, is_active, password,
|
|
4615
|
+
* revoke_recipient_ids).
|
|
4512
4616
|
* Permission-bearing fields are not updatable post-create — revoke
|
|
4513
4617
|
* and recreate to change permissions.
|
|
4514
4618
|
*/
|
|
@@ -4817,6 +4921,7 @@ export class PicaClient {
|
|
|
4817
4921
|
// ADR-232 — share trace aggregator + count explain registry +
|
|
4818
4922
|
// audio pipeline status read.
|
|
4819
4923
|
shareTrace;
|
|
4924
|
+
songAccess;
|
|
4820
4925
|
countExplain;
|
|
4821
4926
|
audioPipelineStatus;
|
|
4822
4927
|
publishers;
|
|
@@ -4861,6 +4966,7 @@ export class PicaClient {
|
|
|
4861
4966
|
groups;
|
|
4862
4967
|
assistantInteractions;
|
|
4863
4968
|
shareSend;
|
|
4969
|
+
removed;
|
|
4864
4970
|
accessSimulate;
|
|
4865
4971
|
approvals;
|
|
4866
4972
|
// Registration Integrity Floor (spec 2026-07-09, Task 8).
|
|
@@ -4933,6 +5039,7 @@ export class PicaClient {
|
|
|
4933
5039
|
this.recordingSplits = new RecordingSplitsResource(baseUrl, config.apiKey, debug);
|
|
4934
5040
|
this.recordingCredits = new RecordingCreditsResource(baseUrl, config.apiKey, debug);
|
|
4935
5041
|
this.shareTrace = new ShareTraceResource(baseUrl, config.apiKey, debug);
|
|
5042
|
+
this.songAccess = new SongAccessResource(baseUrl, config.apiKey, debug);
|
|
4936
5043
|
this.countExplain = new CountExplainResource(baseUrl, config.apiKey, debug);
|
|
4937
5044
|
this.audioPipelineStatus = new AudioPipelineStatusResource(baseUrl, config.apiKey, debug);
|
|
4938
5045
|
this.publishers = new PublishersResource(baseUrl, config.apiKey, debug);
|
|
@@ -4972,6 +5079,7 @@ export class PicaClient {
|
|
|
4972
5079
|
this.groups = new GroupsResource(baseUrl, config.apiKey, debug);
|
|
4973
5080
|
this.assistantInteractions = new AssistantInteractionsResource(baseUrl, config.apiKey, debug);
|
|
4974
5081
|
this.shareSend = new ShareSendResource(baseUrl, config.apiKey, debug);
|
|
5082
|
+
this.removed = new RemovedResource(baseUrl, config.apiKey, debug);
|
|
4975
5083
|
this.accessSimulate = new AccessSimulateResource(baseUrl, config.apiKey, debug);
|
|
4976
5084
|
this.approvals = new ApprovalsResource(baseUrl, config.apiKey, debug);
|
|
4977
5085
|
this.integrity = new IntegrityResource(baseUrl, config.apiKey, debug);
|