@withpica/mcp-sdk 1.4.0 → 1.8.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
@@ -10,6 +10,7 @@ const LONG_TIMEOUT_PATTERNS = [
10
10
  "/exports/",
11
11
  "/enrichment/",
12
12
  "/enrich",
13
+ "/resolve", // ADR-179 resolver fan-out (up to 5 sources per call)
13
14
  "/industry-ready",
14
15
  "/catalog-asset-report",
15
16
  "/analyze",
@@ -51,6 +52,26 @@ class BaseResource {
51
52
  this.apiKey = apiKey;
52
53
  this.debug = debug;
53
54
  }
55
+ /**
56
+ * Vercel Deployment Protection bypass header for preview self-fetches.
57
+ *
58
+ * When the SDK is used server-side on a Vercel preview (e.g. the MCP
59
+ * HTTP route calling back into its own admin resolve routes),
60
+ * Vercel's edge wraps the internal call in deployment protection and
61
+ * 401s before the request reaches the route handler. "Protection
62
+ * Bypass for Automation" auto-injects VERCEL_AUTOMATION_BYPASS_SECRET
63
+ * at runtime; forward it as the documented header so self-fetches
64
+ * reach the actual route. Empty outside Vercel — safe no-op.
65
+ */
66
+ getBypassHeaders() {
67
+ if (typeof process !== "undefined" &&
68
+ process.env?.VERCEL_AUTOMATION_BYPASS_SECRET) {
69
+ return {
70
+ "x-vercel-protection-bypass": process.env.VERCEL_AUTOMATION_BYPASS_SECRET,
71
+ };
72
+ }
73
+ return {};
74
+ }
54
75
  async fetchWithTimeout(url, init, timeoutMs) {
55
76
  const controller = new AbortController();
56
77
  const timer = setTimeout(() => controller.abort(), timeoutMs);
@@ -122,6 +143,7 @@ class BaseResource {
122
143
  headers: {
123
144
  Authorization: `Bearer ${this.apiKey}`,
124
145
  "Content-Type": "application/json",
146
+ ...this.getBypassHeaders(),
125
147
  },
126
148
  body: body ? JSON.stringify(body) : undefined,
127
149
  }, timeoutMs);
@@ -142,6 +164,7 @@ class BaseResource {
142
164
  headers: {
143
165
  Authorization: `Bearer ${this.apiKey}`,
144
166
  "Content-Type": "application/json",
167
+ ...this.getBypassHeaders(),
145
168
  },
146
169
  body: body ? JSON.stringify(body) : undefined,
147
170
  }, timeoutMs);
@@ -202,6 +225,15 @@ class WorksResource extends BaseResource {
202
225
  const result = await this.request("GET", `/admin/works/${workId}/production-assets`);
203
226
  return result?.assets || [];
204
227
  }
228
+ /**
229
+ * ADR-173 Decision 2: list every release this work appears on, with
230
+ * its track position. Distinct from listProductionAssets — this is
231
+ * release-context, not physical-asset provenance.
232
+ */
233
+ async listReleases(workId) {
234
+ const result = await this.request("GET", `/admin/works/${workId}/releases`);
235
+ return result?.data || [];
236
+ }
205
237
  }
206
238
  class PeopleResource extends BaseResource {
207
239
  async list(params) {
@@ -615,6 +647,15 @@ class RecordingsResource extends BaseResource {
615
647
  const result = await this.request("GET", `/admin/recordings/${recordingId}/production-assets`);
616
648
  return result?.assets || [];
617
649
  }
650
+ /**
651
+ * ADR-173 Decision 2: list every release this recording appears on,
652
+ * with its track position. Backs the `releases` section on
653
+ * pica_recordings_inspect.
654
+ */
655
+ async listReleases(recordingId) {
656
+ const result = await this.request("GET", `/admin/recordings/${recordingId}/releases`);
657
+ return result?.data || [];
658
+ }
618
659
  }
619
660
  // --- Enrichment Resource ---
620
661
  class EnrichmentResource extends BaseResource {
@@ -645,6 +686,78 @@ class EnrichmentResource extends BaseResource {
645
686
  async enrichWorkYouTube(workId) {
646
687
  return this.request("POST", `/admin/youtube/enrich/${workId}`);
647
688
  }
689
+ /**
690
+ * ADR-179 outcome resolver — fan a work out across every eligible
691
+ * enrichment source and return a structured receipt.
692
+ *
693
+ * The orchestration lives server-side (see
694
+ * `lib/services/enrichment-cascade/resolver.ts`); this SDK method is
695
+ * the thin wrapper the MCP tool layer calls. Future Tasks-primitive
696
+ * conversion (ADR-171) will turn the request into a Task so agents
697
+ * receive per-source status events as each provider resolves. Today
698
+ * the shape is the final receipt only — callers treat the object
699
+ * returned here as that single terminal event.
700
+ *
701
+ * sources: optional whitelist. Omit to fan out to every
702
+ * eligible source (mlc, spotify, youtube,
703
+ * musicbrainz, discogs).
704
+ * includeFuzzy: defaults true. When false, Tier B cascade rules
705
+ * (fuzzy matches that would file review proposals)
706
+ * are skipped entirely.
707
+ */
708
+ async resolveWork(workId, options) {
709
+ const body = {};
710
+ if (options?.sources)
711
+ body.sources = options.sources;
712
+ if (options?.includeFuzzy !== undefined) {
713
+ body.include_fuzzy = options.includeFuzzy;
714
+ }
715
+ return this.request("POST", `/admin/works/${workId}/resolve`, body);
716
+ }
717
+ /**
718
+ * ADR-179 Phase 2 — person resolver. Fans a person across the two
719
+ * eligible identity-graph sources (ISNI + MusicBrainz) and returns
720
+ * a structured receipt in the same shape as `resolveWork`. Replaces
721
+ * the legacy `PeopleResource.enrichFromISNI` / `enrichFromMusicBrainz`
722
+ * calls at the MCP tool layer — those SDK methods stay available this
723
+ * release but are deprecated via `pica_people_enrich_*` returning
724
+ * `TOOL_DEPRECATED`.
725
+ *
726
+ * sources: optional whitelist. Omit to run both sources.
727
+ * Allowed: `isni` | `musicbrainz`.
728
+ * includeFuzzy: defaults true. When false, Tier B cascade rules
729
+ * (fuzzy matches that would file review proposals)
730
+ * are skipped entirely.
731
+ */
732
+ async resolvePerson(personId, options) {
733
+ const body = {};
734
+ if (options?.sources)
735
+ body.sources = options.sources;
736
+ if (options?.includeFuzzy !== undefined) {
737
+ body.include_fuzzy = options.includeFuzzy;
738
+ }
739
+ return this.request("POST", `/admin/people/${personId}/resolve`, body);
740
+ }
741
+ /**
742
+ * ADR-179 Phase 3 — recording resolver. Fans a recording across eligible
743
+ * master-side sources (Spotify, YouTube, MusicBrainz, Discogs) and returns
744
+ * a structured receipt in the same shape as `resolveWork` / `resolvePerson`.
745
+ *
746
+ * sources: optional whitelist. Omit to run every eligible source.
747
+ * Allowed: `spotify` | `youtube` | `musicbrainz` | `discogs`.
748
+ * includeFuzzy: defaults true. When false, Tier B cascade rules
749
+ * (fuzzy matches that would file review proposals)
750
+ * are skipped entirely.
751
+ */
752
+ async resolveRecording(recordingId, options) {
753
+ const body = {};
754
+ if (options?.sources)
755
+ body.sources = options.sources;
756
+ if (options?.includeFuzzy !== undefined) {
757
+ body.include_fuzzy = options.includeFuzzy;
758
+ }
759
+ return this.request("POST", `/admin/recordings/${recordingId}/resolve`, body);
760
+ }
648
761
  /**
649
762
  * ADR-164: manually re-evaluate a work against every cascade rule.
650
763
  * Idempotent — rules whose preconditions are already satisfied skip.
@@ -717,6 +830,27 @@ class EnrichmentResource extends BaseResource {
717
830
  async rejectEnrichmentProposal(proposalId, options) {
718
831
  return this.request("POST", `/admin/enrichment-proposals/${proposalId}/reject`, options ?? {});
719
832
  }
833
+ /**
834
+ * ADR-178: File a proposal sourced from open-web agent research.
835
+ *
836
+ * Distinct from cascade-sourced proposals. Every `proposed_fields`
837
+ * key must appear in at least one `sources[].fields` array —
838
+ * unsourced claims return a 400 with `code: "MISSING_SOURCE"` and
839
+ * the proposal is not persisted. The server hard-codes
840
+ * `source = 'agent_research'` and `rule_id = 'agent_research'`;
841
+ * callers omit both.
842
+ *
843
+ * Response codes carried through to the caller:
844
+ * - 201: `{ proposal_id, source, rule_id, status }` (pending)
845
+ * - 409 `DUPLICATE_SUPPRESSED`: identical content already filed
846
+ * - 400 `MISSING_SOURCE`: uncited proposed field or empty sources
847
+ * - 400 `MISSING_FIELDS`: proposed_fields was empty
848
+ * - 400 `INVALID_SOURCE_SHAPE`: a source entry lacked url/fields/accessed_at
849
+ * - 404 `ENTITY_NOT_FOUND`: entity_id not in caller's org
850
+ */
851
+ async proposeAgentResearch(input) {
852
+ return this.request("POST", "/admin/enrichment-proposals", input);
853
+ }
720
854
  /** Preview what a Spotify URL would do — delegates to streaming-link */
721
855
  async spotifyUrlPreview(url) {
722
856
  return this.request("POST", "/admin/import/streaming-link", { url });
@@ -830,6 +964,132 @@ class SettingsResource extends BaseResource {
830
964
  async userProfile() {
831
965
  return this.request("GET", "/admin/settings/user-profile");
832
966
  }
967
+ /**
968
+ * Update the authenticated user's privacy / marketing-preference flags.
969
+ * Parity with ADR-183's settings-tab toggles — exposes the same fields
970
+ * the web UI writes, so an agent can unsubscribe on the user's behalf
971
+ * without leaving the conversation. ADR-184 slice 5.
972
+ */
973
+ async updatePrivacySettings(params) {
974
+ return this.request("PATCH", "/admin/settings/user-profile", params);
975
+ }
976
+ /**
977
+ * Read the authenticated user's privacy / marketing-preference flags.
978
+ * Pulls from the same user-profile row the settings UI reads (ADR-184 slice
979
+ * 5 read-side sibling). Reuses the existing GET endpoint — no dedicated
980
+ * route needed; the MCP tool picks only the two privacy flags from the
981
+ * response so unrelated profile fields are not surfaced through this tool.
982
+ */
983
+ async getPrivacySettings() {
984
+ return this.request("GET", "/admin/settings/user-profile");
985
+ }
986
+ /**
987
+ * Capture or update the authenticated user's own identifiers
988
+ * (stage_name, IPI, ISNI, IPN, PRO). Subject is always derived from
989
+ * the auth context — `user_id` is never accepted (ADR-184 rule 2).
990
+ *
991
+ * The route lazy-creates the backing `people` row if the user's
992
+ * `user_profiles.person_id` is null, then fires
993
+ * `crossLinkOnIdentifierUpdate` so newly-visible cross-org credits
994
+ * appear on the next `pica_discoveries_query`. Response includes the
995
+ * count of new discoveries in each of the 3 typed tables so the
996
+ * agent can surface "found N more…" in the same conversation.
997
+ * ADR-189 Phase 3.
998
+ */
999
+ async updateMyIdentity(params) {
1000
+ return this.request("POST", "/admin/my-identity", params);
1001
+ }
1002
+ /**
1003
+ * Update the authenticated caller's organisation profile
1004
+ * (org_type, display_name, tagline, ipi_number). Scope bounded by
1005
+ * auth context — the org id is derived server-side, never accepted
1006
+ * as a parameter. Distinct from the settings-UI PUT at
1007
+ * `/admin/settings/organisation-profile`: this path validates
1008
+ * org_type against the CHECK enum, rate-limits, and stamps via
1009
+ * withAgentActionStamp for grant-auth callers (ADR-185 Part 3).
1010
+ * ADR-189 Phase 3.
1011
+ */
1012
+ async updateOrganisationProfile(params) {
1013
+ return this.request("POST", "/admin/organisation-profile", params);
1014
+ }
1015
+ }
1016
+ // --- GDPR Resource (user self-service delete + export) ---
1017
+ class GdprResource extends BaseResource {
1018
+ /**
1019
+ * Create a soft-delete request for the authenticated user's account.
1020
+ * Never accepts a user_id — the subject is derived from the bearer token
1021
+ * (ADR-184 rule 2). Returns the deletion_request_id immediately; actual
1022
+ * erasure runs async through the gdpr-deletion processing path. Matches
1023
+ * the web UI's 30-day reversal window.
1024
+ */
1025
+ async deleteMyAccount(params) {
1026
+ return this.request("POST", "/admin/gdpr/delete-my-account", params ?? {});
1027
+ }
1028
+ /**
1029
+ * Queue a GDPR Article 15 / 20 data export for the authenticated user.
1030
+ * Returns a job_id immediately; the signed download link is emailed when
1031
+ * the job completes. Format defaults to JSON (ADR-184 defers format
1032
+ * negotiation to a future parameter).
1033
+ */
1034
+ async exportMyData() {
1035
+ return this.request("POST", "/admin/gdpr/export-my-data", {});
1036
+ }
1037
+ }
1038
+ // --- Feedback Resource (user feedback submissions) ---
1039
+ class FeedbackResource extends BaseResource {
1040
+ /**
1041
+ * Submit user feedback through the existing /api/feedback pipeline.
1042
+ * ADR-184 slice 8 — actionType is always of form `general_<category>`
1043
+ * so downstream teamTasksService.createFromGeneralFeedback routes
1044
+ * it to the correct team role.
1045
+ */
1046
+ async submit(params) {
1047
+ return this.request("POST", "/feedback", params);
1048
+ }
1049
+ }
1050
+ // --- Agent Identity Resource (ADR-185 Part 1) ---
1051
+ /**
1052
+ * Session-auth-only surface: the three HTTP routes backing these
1053
+ * methods refuse `pica_grant_` callers with 403 `session_auth_required`
1054
+ * regardless of scope. When invoked via the stdio MCP server the caller
1055
+ * is an API key owned by the user, which passes; when invoked via HTTP
1056
+ * MCP behind a grant token the API rejects before any DB work.
1057
+ */
1058
+ class AgentIdentityResource extends BaseResource {
1059
+ async createIdentity(params) {
1060
+ return this.request("POST", "/admin/agent-identities", {
1061
+ display_name: params.displayName,
1062
+ client_fingerprint: params.clientFingerprint ?? null,
1063
+ });
1064
+ }
1065
+ async listIdentities() {
1066
+ return this.request("GET", "/admin/agent-identities");
1067
+ }
1068
+ async issueGrant(params) {
1069
+ return this.request("POST", `/admin/agent-identities/${params.agentIdentityId}/grants`, {
1070
+ scopes: params.scopes,
1071
+ expires_at: params.expiresAt ?? null,
1072
+ });
1073
+ }
1074
+ async listMyGrants() {
1075
+ return this.request("GET", "/admin/agent-grants");
1076
+ }
1077
+ async revokeGrant(params) {
1078
+ return this.request("DELETE", `/admin/agent-grants/${params.grantId}`);
1079
+ }
1080
+ async getActivity(params) {
1081
+ const query = new URLSearchParams();
1082
+ if (params.agentIdentityId)
1083
+ query.set("agent_identity_id", params.agentIdentityId);
1084
+ if (params.agentGrantId)
1085
+ query.set("agent_grant_id", params.agentGrantId);
1086
+ if (typeof params.limit === "number")
1087
+ query.set("limit", String(params.limit));
1088
+ if (typeof params.offset === "number")
1089
+ query.set("offset", String(params.offset));
1090
+ const qs = query.toString();
1091
+ return this.request("GET", qs ? `/admin/agent-activity?${qs}` : "/admin/agent-activity");
1092
+ }
833
1093
  }
834
1094
  // --- Calendar Resource ---
835
1095
  class CalendarResource extends BaseResource {
@@ -1220,6 +1480,9 @@ class ProjectsResource extends BaseResource {
1220
1480
  async delete(id) {
1221
1481
  return this.request("DELETE", `/admin/projects/${id}`);
1222
1482
  }
1483
+ async attachWork(projectId, data) {
1484
+ return this.request("POST", `/admin/projects/${projectId}/works`, data);
1485
+ }
1223
1486
  }
1224
1487
  class SplitSheetsResource extends BaseResource {
1225
1488
  async listForWork(workId) {
@@ -1263,6 +1526,18 @@ class PublishersResource extends BaseResource {
1263
1526
  return this.request("POST", "/admin/publishers", data);
1264
1527
  }
1265
1528
  }
1529
+ class LabelsResource extends BaseResource {
1530
+ async list(params) {
1531
+ const queryParams = new URLSearchParams();
1532
+ if (params?.query)
1533
+ queryParams.set("query", params.query);
1534
+ if (params?.limit)
1535
+ queryParams.set("limit", String(params.limit));
1536
+ const qs = queryParams.toString();
1537
+ const res = await this.request("GET", `/admin/labels${qs ? `?${qs}` : ""}`);
1538
+ return (res?.data ?? []);
1539
+ }
1540
+ }
1266
1541
  class ReleasesResource extends BaseResource {
1267
1542
  async list(params) {
1268
1543
  const qs = params?.limit ? `?limit=${params.limit}` : "";
@@ -1280,6 +1555,88 @@ class ReleasesResource extends BaseResource {
1280
1555
  async delete(id) {
1281
1556
  return this.request("DELETE", `/admin/releases/${id}`);
1282
1557
  }
1558
+ /**
1559
+ * ADR-173: attach a recording and/or work to a release at a
1560
+ * specific (disc, track) position. Idempotent on position.
1561
+ */
1562
+ async attachTrack(releaseId, data) {
1563
+ return this.request("POST", `/admin/releases/${releaseId}/tracks`, data);
1564
+ }
1565
+ /**
1566
+ * ADR-173: list tracks on a release in (disc, track) order with
1567
+ * inlined work + recording summary fields.
1568
+ */
1569
+ async listTracks(releaseId) {
1570
+ return this.request("GET", `/admin/releases/${releaseId}/tracks`);
1571
+ }
1572
+ /**
1573
+ * ADR-173: soft-confirmation detach by position. Call without
1574
+ * confirm:true to get a preview; call with confirm:true to execute.
1575
+ */
1576
+ async detachTrack(releaseId, data) {
1577
+ return this.request("POST", `/admin/releases/${releaseId}/tracks/detach`, data);
1578
+ }
1579
+ /**
1580
+ * ADR-173: transactional positional reorder. Input is the full
1581
+ * new order as [{ track_id, track_number, disc_number }].
1582
+ */
1583
+ async reorderTracks(releaseId, newOrder) {
1584
+ return this.request("POST", `/admin/releases/${releaseId}/tracks/reorder`, {
1585
+ new_order: newOrder,
1586
+ });
1587
+ }
1588
+ /**
1589
+ * ADR-173 Decision 4: compound attach that resolves the
1590
+ * recording's work_id server-side before creating the track row.
1591
+ */
1592
+ async attachRecordingWithWork(releaseId, data) {
1593
+ return this.request("POST", `/admin/releases/${releaseId}/tracks/attach-recording-with-work`, data);
1594
+ }
1595
+ /**
1596
+ * ADR-175 Decision 2: attach many tracks to a release atomically.
1597
+ * Duplicate (disc_number, track_number) pairs in the payload cause
1598
+ * the call to fail atomically — no rows are inserted.
1599
+ */
1600
+ async bulkAttachTracks(releaseId, tracks) {
1601
+ return this.request("POST", `/admin/releases/${releaseId}/tracks/bulk-attach`, { tracks });
1602
+ }
1603
+ /**
1604
+ * ADR-175 Decision 3: swap two track positions on a release.
1605
+ */
1606
+ async moveTrack(releaseId, from, to) {
1607
+ return this.request("POST", `/admin/releases/${releaseId}/tracks/move`, {
1608
+ from,
1609
+ to,
1610
+ });
1611
+ }
1612
+ /**
1613
+ * ADR-175 Decision 4: attach a track by external identifier. The SDK
1614
+ * resolves to an internal recording id server-side; on miss or
1615
+ * collision the route returns a 400 with a recovery hint and no row
1616
+ * is created.
1617
+ */
1618
+ async attachTrackByIdentifier(releaseId, data) {
1619
+ return this.request("POST", `/admin/releases/${releaseId}/tracks/attach-by-identifier`, data);
1620
+ }
1621
+ /**
1622
+ * ADR-175 Decision 5: release-scoped completeness report. Pass a
1623
+ * release_id for a single-release report, or omit for a scan of every
1624
+ * release in the caller's organisation.
1625
+ */
1626
+ async completenessCheck(params) {
1627
+ const qs = params?.release_id
1628
+ ? `?release_id=${encodeURIComponent(params.release_id)}`
1629
+ : "";
1630
+ return this.request("GET", `/admin/releases/completeness-check${qs}`);
1631
+ }
1632
+ /**
1633
+ * ADR-175 Decision 1: create a release and its tracks in a single
1634
+ * atomic transaction. Track entries accept recording_id, work_id,
1635
+ * or identifier_lookup — whichever shape the agent has.
1636
+ */
1637
+ async createWithTracks(data) {
1638
+ return this.request("POST", "/admin/releases/create-with-tracks", data);
1639
+ }
1283
1640
  }
1284
1641
  class SessionsResource extends BaseResource {
1285
1642
  async list(params) {
@@ -1594,6 +1951,7 @@ export class PicaClient {
1594
1951
  splitSheets;
1595
1952
  recordingSplits;
1596
1953
  publishers;
1954
+ labels;
1597
1955
  agreementTemplates;
1598
1956
  producerAgreements;
1599
1957
  workForHire;
@@ -1604,6 +1962,9 @@ export class PicaClient {
1604
1962
  disputes;
1605
1963
  chain;
1606
1964
  telegram;
1965
+ gdpr;
1966
+ feedback;
1967
+ agentIdentity;
1607
1968
  /**
1608
1969
  * Get accurate catalog stats via SQL counts (no pagination limits)
1609
1970
  */
@@ -1670,6 +2031,7 @@ export class PicaClient {
1670
2031
  this.splitSheets = new SplitSheetsResource(baseUrl, config.apiKey, debug);
1671
2032
  this.recordingSplits = new RecordingSplitsResource(baseUrl, config.apiKey, debug);
1672
2033
  this.publishers = new PublishersResource(baseUrl, config.apiKey, debug);
2034
+ this.labels = new LabelsResource(baseUrl, config.apiKey, debug);
1673
2035
  this.agreementTemplates = new AgreementTemplatesResource(baseUrl, config.apiKey, debug);
1674
2036
  this.producerAgreements = new ProducerAgreementsResource(baseUrl, config.apiKey, debug);
1675
2037
  this.workForHire = new WorkForHireResource(baseUrl, config.apiKey, debug);
@@ -1680,6 +2042,9 @@ export class PicaClient {
1680
2042
  this.disputes = new DisputesResource(baseUrl, config.apiKey, debug);
1681
2043
  this.chain = new ChainResource(baseUrl, config.apiKey, debug);
1682
2044
  this.telegram = new TelegramResource(baseUrl, config.apiKey, debug);
2045
+ this.gdpr = new GdprResource(baseUrl, config.apiKey, debug);
2046
+ this.feedback = new FeedbackResource(baseUrl, config.apiKey, debug);
2047
+ this.agentIdentity = new AgentIdentityResource(baseUrl, config.apiKey, debug);
1683
2048
  }
1684
2049
  }
1685
2050
  //# sourceMappingURL=index.js.map